はじめに
釣りタイトルっぽいな。
OpenCVでトリミング
OpenCVのタイプはnumpy.ndarray。だからスライスでその一部を取り出すことができる。
行列だから行・列の順。行列だから「どの行(列)からどの行(列)まで」を指定する。「左上座標と切り取るサイズ」ではない。
要はimg[r1: r2, c1: c2]だが、理屈を理解すれば img[r: r+h, c: c+w] という書き方を暗記するのは容易だ。
ところで、左上座標を x, y = …と定義したとき、サイズの書き方は h, w = … と w, h = … のどちらが適切なのだろうか。小学校算数のかけ算の順序問題が話題になっている昨今、必修化されるプログラミング教育の授業で本件は新たな火種となってしまう可能性がある。
なお、トリミングは当然PIL(Pillow)でもできるが、PILの仕様を書くと混乱してしまうのでここではいっさい触れない。
基本的なプログラム
import cv2
filename = "nurse.jpg"
img = cv2.imread(filename)
x, y = 380, 30
h, w = 160, 120
img_trim = img[y:y+h, x:x+w]
cv2.imshow("origin", img)
cv2.imshow("trim", img_trim)
cv2.imwrite("trimmed_image.jpg", img_trim)
cv2.waitKey(0)
cv2.destroyAllWindows()
元画像(スキマナース)から、
(x,y)=(380,30)から始まる高さ160×幅120のエリアを取り出すことができた。
もちろん、顔認識を入れれば位置やサイズを手打ちする必要はなくなる。
OpenCVで画像を保存 cv2.imwrite()
cv2.imwrite(filename, mat) という使い方をする。
filenameはファイル名。画像フォーマットはファイル名の拡張子で自動的に決まる。
matは画像データ。
応用例
これだけでは面白くないので、ループを回しながらトリミング位置を変えるというプログラムを書いてみた。
import cv2
from PIL import Image
filename = "nurse.jpg"
img = cv2.imread(filename)
imgH, imgW = img.shape[:2]
# ご自由にどうぞ
x, y = 50, 100
dx,dy = 2, 3
h, w = 160, 160
imgs=[]
while True:
if 0 <= x+dx <= imgW-w : #こんな書き方ができるんだねえ。
x = x+dx
else:
dx = -dx
if 0 <= y+dy <= imgH-h :
y = y+dy
else:
dy = -dy
window = img[y:y+h, x:x+w]
cv2.imshow("", window)
# この一文を有効にするとどうなるか君の目で確かめてくれ!
# cv2.moveWindow("", x+100, y+100)
imgPIL=Image.fromarray(window[:,:,::-1])
imgs.append(imgPIL)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cv2.destroyAllWindows()
imgs[0].save("peep.gif", save_all=True,
append_images=imgs[1:], optimize=False,
duration=20, loop=0)
このプログラムにより、こんなアニメーションGIFが作られる。
この手のゲームをピープホール(覗き穴)と呼ぶと昔ベーマガで読んだのだが、この言葉が使われているのを見たことがないぞ。
終わりに
Excelにはじめて触れたとき、R1C1形式を見て「RとC、どっちが行でどっちが列なんだ!」「Rが行でCが列なのはわかったけど、xy座標はx,yと書くのに行列がRCだと並びが逆じゃないか!」と憤ったことを思い出す。
その違和感とまた付き合うことになるとは思わなかった。