##やりたいこと
トリミングされた二つの画像を、つなぎ目が自然になるように自動で結合させます。
##アイデア
上側画像と下側画像の端部を起点として、徐々に領域を狭めながら、比較をしていきます。
最も一致度の高かった領域を共通領域として画像結合させます。
##前提
- pillow形式で読み込み
- 垂直方向で結合
- 横方向位置はあらかじめ揃っている
- 横幅も揃っている
- python3.8
##流れ(勉強したこと)
1.画像のトリミング
2.画像を比較する
3.画像を連結する
4.計算量を減らすために
####1.画像のトリミング
今回はpillow形式のまま、Image.crop()
を使用してトリミングを行ないました。
Image.crop((トリミング左端のx座標, 上端のy座標, 右端のx座標, 下端のy座標))
引数はタプルで指定する必要があり、カッコ"("")"が二重になっているので、注意が必要です。計算量を少しでも少なくするため、上側と下側のうち、より小さいほうを比較開始領域とし、1pxずつ小さくしていっています。(例コードでは上側の画像が大きい場合、変数dif_hにその高さの差分を格納し、トリミング上端座標の指定に用いています)
####2.画像を比較する
トリミングした画像をopencv形式に変換し、cv2.matchTemplate()
を用いて画像の比較しています。
cv2.matchTemplate(比較する画像1, 比較する画像2, マッチングのモード)
マッチングのモードは色々あるようですが、今回の用途の場合何でもOK(例外あり)だと思うので、スタンダードっぽいcv2.TM_CCOEFF_NORMED
を用いました。
matchTemplateのアウトプットはマッチング度合いを明暗で示すグレースケール画像(らしい)ので、さらにcv2.minMaxLoc()
を用いて最大値=この画像のマッチング評価結果を取得しています。
cv2.minMaxLoc(評価するグレースケール画像)[1]
minmaxLocの返り値は、2つ目に最大値が格納されているので、[1]で最大値を取り出しています。(画像テンプレートマッチングはこちらのページ等で詳細に説明がされています)
上記を、領域を変化させながらfor文で繰り返し、マッチング評価結果が最大となる場所を探し出します。
####3.画像を連結する
こちらのページから引用させていただきました。
https://note.nkmk.me/python-pillow-concat-images/
def get_concat_v(im1, im2):
dst = Image.new('RGB', (im1.width, im1.height + im2.height))
dst.paste(im1, (0, 0))
dst.paste(im2, (0, im1.height))
return dst
pillow形式の画像において、Image.new()
で領域を作成し、Image.paste()
で連結させる画像を貼り付けています。
####4.計算量を減らすために
やってみると分かるのですが、画像サイズが大きければ大きいほど滅茶苦茶時間がかかります。
今回は、グレースケール化したり、上側画像と下側画像でより小さいほうを比較開始領域サイズとしたりしています。
今回採用していませんが、リサイズして画像比較したり、比較する領域を制限したり、一定の評価値を上回ったら比較を終了するなどして、計算量を減らせると思います。
##完成したコード
import numpy as np
from PIL import Image
import cv2
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def auto_img_concat_v(upper_img, under_img):
max_res_num = 0 # マッチング評価結果の最大値を保存
over_pixel = 0 # マッチング評価最大時の重なりピクセル数を保存
img_w = upper_img.width
upper_h = upper_img.height
under_h = under_img.height
compare_height = min(upper_h, under_h) # 比較するピクセル数(高さ)
# 上画像の高さが高い場合、差分を取得(そうでない場合は0)
dif_h = (upper_h - under_h) if upper_h > under_h else 0
for i in range(1, compare_height):
search_img = upper_img.crop((0, i + dif_h, img_w, upper_h)) # 上画像のトリミング
target_img = under_img.crop((0, 0, img_w, compare_height - i)) # 下画像のトリミング
search_np = np.array(search_img, dtype=np.uint8) # 画像を配列へ変換(上画像)
cv_search_img = cv2.cvtColor(search_np, cv2.COLOR_RGB2GRAY) #グレースケール化(上画像)
target_np = np.array(target_img, dtype=np.uint8) # 〃 (下画像)
cv_target_img = cv2.cvtColor(target_np, cv2.COLOR_RGB2GRAY) # 〃 (下画像)
res = cv2.matchTemplate(
cv_search_img, cv_target_img, cv2.TM_CCOEFF_NORMED) # マッチング評価(出力は類似度を表すグレースケール画像)
res_num = cv2.minMaxLoc(res)[1] # マッチング評価を数値で取得
print(res_num, "\n", i)
if max_res_num < res_num: # マッチング評価結果の最大値を取得
max_res_num = res_num
over_pixel = target_img.height # マッチング評価値最大時の重なりピクセル数を取得
print("\n", max_res_num, "\n", over_pixel)
if max_res_num > 0.98: # 評価値が一定以上なら結合処理
result_img = get_concat_v(upper_img.crop(
(0, 0, img_w, upper_h - over_pixel)), under_img) # 画像結合
return result_img
else:
print("画像結合に失敗しました")
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# 画像を連結させる関数(垂直方向)
def get_concat_v(im1, im2):
dst = Image.new('RGB', (im1.width, im1.height + im2.height))
dst.paste(im1, (0, 0))
dst.paste(im2, (0, im1.height))
return dst
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
if __name__ == "__main__":
upper = Image.open(r"C:\Users\aaa\Desktop\ダンボー_upper.jpg")
under = Image.open(r"C:\Users\aaa\Desktop\ダンボー_under.jpg")
concat_img = auto_img_concat_v(upper, under)
if concat_img:
concat_img.show()
##今後の予定
計算量が多いことや、横方向が一致していない画像に対応していない等の課題はありますが、
とりあえずやりたいことは出来ました。
今後は、pyautogと組み合わせて、スクロールした画面を自動連結するツールを作りたいです。