今回もだいたいチュートリアル通りの内容です。
前々回と前回で、違う視点から同じ対象を撮影した2つの画像での点の対応を見つけることができました。
この結果を使って、射影変換の行列を計算し、画像の視点変換をすることができます。これをやってみます。
OpenCV: Feature Matching + Homography to find Objects
特徴点のマッチングとHomographyによる物体検出 — OpenCV-Python Tutorials 1 documentation
使用画像
前々回、前回と同じシャルトル大聖堂の画像3つです。
特徴点マッチング
前々回にやったのと同じことをやります。
SIFTでのマッチングが結果が良かったので、SIFTで特徴点検出します。
import cv2 img_both = cv2.imread('Chartres_both.JPG') img_right = cv2.imread('Chartres_right.JPG') img_left = cv2.imread('Chartres_left.JPG') img_both = cv2.resize(img_both, None, fx=0.25, fy=0.25, interpolation=cv2.INTER_AREA) img_right = cv2.resize(img_right, None, fx=0.25, fy=0.25, interpolation=cv2.INTER_AREA) img_left = cv2.resize(img_left, None, fx=0.25, fy=0.25, interpolation=cv2.INTER_AREA) sift = cv2.SIFT_create() kp_both, des_both = sift.detectAndCompute(img_both, None) kp_right, des_right = sift.detectAndCompute(img_right, None) kp_left, des_left = sift.detectAndCompute(img_left, None) bf = cv2.BFMatcher() matches_right = bf.match(des_both, des_right) matches_right = sorted(matches_right, key = lambda x:x.distance) matches_left = bf.match(des_left, des_both) matches_left = sorted(matches_left, key = lambda x:x.distance)
射影変換行列計算
cv2.fincHomography()
関数で射影変換行列を計算することができます。
チュートリアルを読むと、以下のようなことが書いてあります。
- 射影変換行列を求めるには、最低4点のマッチング点が必要
- 誤ったマッチングペアがあると結果に悪影響を与える
- これに対処するため、
cv2.fincHomography()
関数ではRANSACかLEAST_MEDIANのアルゴリズムを使って、各入力のペアについて、良いマッチングかどうか判定します。その結果もこの関数の返り値として得られます。
良いマッチングのことをinlier、それ以外をoutlierと言うようです。
チュートリアルでは、10点以上のマッチングペアを使用するようにしていました。
シャルトル大聖堂の画像でいうと、前々回やった感じでは、マッチングペアを一致度の高い(特徴量の距離の小さい)順に並べて20個取ると、ほぼ正しいマッチング結果となっていました。
本来はratio testでマッチングペアをふるいにかけてからcv2.findHomography()
関数に投げたほうが良いようですが、今回はその必要なしかなと。
ということで、この20個を使って射影変換行列を探したいと思います。
src_pts_for_right = np.float32([kp_both[m.queryIdx].pt for m in matches_right[:20]]).reshape(-1,1,2) dst_pts_right = np.float32([kp_right[m.trainIdx].pt for m in matches_right[:20]]).reshape(-1,1,2) M_right, mask_right = cv2.findHomography(src_pts_for_right, dst_pts_right, cv2.RANSAC, 5.0)
マッチング結果のオブジェクト(DMatchオブジェクト)のqueryIdx
、trainIdx
はBFMatcher.match()
に与える2つの特徴量セットのうちそれぞれ1つ目、2つ目に対応します。
ちなみに、全ポイントinlierとなったようでした。
>>> print(mask_right) [[1] [1] [1] [1] [1] [1] [1] [1] [1] [1] [1] [1] [1] [1] [1] [1] [1] [1] [1] [1]]
画像の視点変換
チュートリアルでは、物体を探すというのを目標にしていますが、ここでは画像の視点変換をしようと思っているので、前に一度使ったcv2.warpPerspective()
を使ってみたいと思います。
OpenCVやってみる-4. 射影変換 - 勉強しないとな~blog
元の右側画像と、両側画像から変換した画像を比較しましたが、いい感じになっているかと。
img_both_converted = cv2.warpPerspective(img_both, M_right, (750, 1000)) img_right_comp = np.hstack((img_right, img_both_converted)) cv2.imshow('Image compare', img_right_comp)
以下を参考に画像を重畳してみましたが、一致度が高すぎてなんだかよく分からず。
Python OpenCV Overlaying or Blending Two Images
img_right_blended = cv2.addWeighted(img_right, 0.5, img_both_converted, 0.5, 0) cv2.imshow('Image compare(Blended)', img_right_blended)
両側画像を変換して右側画像の視点で見てみようとしましたが、さらに右側に拡張されるだけだったので、拡張領域は真っ黒に。
img_right_extended = cv2.warpPerspective(img_both, M_right, (750*2, 1000)) cv2.imshow('Image right extended', img_right_extended)
なんとかできないかと考えてみましたが、変換行列を作るときに入力座標にオフセットをつければどうかな、と。
dst_pts_right_offset = dst_pts_right for i in range(20): dst_pts_right_offset[i,0,:] += [750,0] M_right_offset, mask_right_offset = cv2.findHomography(src_pts_for_right, dst_pts_right_offset, cv2.RANSAC, 5.0) img_right_extended = cv2.warpPerspective(img_both, M_right_offset, (750*2, 1000)) cv2.imshow('Image right extended', img_right_extended)
いけた!
左側は特に何もしなくてもよさそうかな。
dst_pts_left = np.float32([kp_left[m.queryIdx].pt for m in matches_left[:20]]).reshape(-1,1,2) M_left, mask_left = cv2.findHomography(src_pts_for_left, dst_pts_left, cv2.RANSAC, 5.0)
img_left_extended = cv2.warpPerspective(img_both, M_left, (750*2, 1000)) cv2.imshow('Image left extended', img_left_extended)
以上
今回もいい感じの結果が得られました。
カメラ画像の視点変換は自由自在です。
次回の内容は検討中…