#動機
タイル地図を使ってなんやかんやしようとしています。進行方向を上向きにしたり、特定の土地領域にフィットさせたり、特定の道路を垂直・水平に表示したりするために回転させてから矩形切り出しの処理があります。はじめは元画像(連結タイル)全体を回転させてから必要な矩形を切り出していたのですが、不要な部分まで回転計算しているのがマジで不要なのでなんとかしようとしたらなんとかなりました。地図に限らずこういう処理はあるでしょうし、ザっとググってみても同じようなことがズバリ書いてあるようなのは見つからなかったので紹介してみます。
元画像src_imgの座標(center[0],center[1])を中心とし、deg度回転させた画像からsize[0]×size[1]の大きさの矩形を切り出したい。centerとsizeはタプルに入っている等とする。
##全体を回転させてから切り出すコード
import cv2
#元画像全体を回転させてから切り出す
def cut_after_rot(src_img, deg, center, size):
rot_mat = cv2.getRotationMatrix2D(center, deg, 1.0)
src_h,src_w,_ = src_img.shape
rot_img = cv2.warpAffine(src_img, rot_mat, (src_w,src_h))
#スライスによって領域を切り出す
return rot_img[center[1]-size[1]//2:center[1]+size[1]//2, \
center[0]-size[0]//2:center[0]+size[0]//2, :]
##切り出しと回転が同時なコード
#欲しい領域のみ回転させる。切り出しと回転が同時なイメージ。
def rot_cut(src_img, deg, center, size):
rot_mat = cv2.getRotationMatrix2D(center, deg, 1.0)
rot_mat[0][2] += -center[0]+size[0]/2 # -(元画像内での中心位置)+(切り抜きたいサイズの中心)
rot_mat[1][2] += -center[1]+size[1]/2 # 同上
return cv2.warpAffine(src_img, rot_mat, size)
cv2.getRotationMatrix2D()で得られるrot_matは2x3の行列です。左の2x2部はふつうのdeg°の回転行列です。右の2x1部は平行移動量です。原点中心にdeg°回転させた画像を右の2x1部の値で平行移動すると、(center[0],center[1])を中心に回転したかのようになるようです。この平行移動量を操作して、切り出したいサイズの中心に来るようにしているのが上記関数内でコメントがついている箇所です。cv2.warpAffine()の第3引数でもsizeを指定してやって余計な部分の取り出し・回転をしないようにしています。説明不足感ありありですが、こんなもんで勘弁してください。
##性能比
試しに640x640のカラー画像を45°回転させて切り出す処理時間を、切り出す大きさを変えて計ってみました。
切り出しサイズ160x160 | 切り出しサイズ320x320 | |
---|---|---|
全体回転させた後で切り出す cut_after_rot() 1000回 | 3.41秒 | 3.61秒 |
切り出しと回転が同時 rot_cut() 1000回 | 0.34秒 | 0.72秒 |
cut_after_rot()では切り出しサイズによる影響が少ないです。おそらく全体回転にほとんどの処理時間がかかっています。
rot_cut()では切り出しサイズに応じた時間を要し、cut_after_rot()に比べて高速です。160x160→320x320で処理時間が4倍になりそうなところが2倍くらいで済んでいますが、そこのところはよく分かりません…
Qiita投稿前はこんな検証してなくて「処理が速い(たぶん)」くらいに思ってましたが、計ったみたら思ったより効果があってまあよかったです。