勉強しないとな~blog

ちゃんと勉強せねば…な電気設計エンジニアです。

OpenCVやってみる-21. 特徴点マッチングとHomography

今回もだいたいチュートリアル通りの内容です。
前々回前回で、違う視点から同じ対象を撮影した2つの画像での点の対応を見つけることができました。
この結果を使って、射影変換の行列を計算し、画像の視点変換をすることができます。これをやってみます。

OpenCV: Feature Matching + Homography to find Objects

特徴点のマッチングとHomographyによる物体検出 — OpenCV-Python Tutorials 1 documentation

使用画像

前々回、前回と同じシャルトル大聖堂の画像3つです。

f:id:nokixa:20210822102202p:plain:w200 f:id:nokixa:20210822102317p:plain:w200 f:id:nokixa:20210822102445p:plain:w200

特徴点マッチング

前々回にやったのと同じことをやります。
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オブジェクト)のqueryIdxtrainIdxBFMatcher.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)

f:id:nokixa:20210905023426p:plain

以下を参考に画像を重畳してみましたが、一致度が高すぎてなんだかよく分からず。

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)

f:id:nokixa:20210905023804p:plain:w400

両側画像を変換して右側画像の視点で見てみようとしましたが、さらに右側に拡張されるだけだったので、拡張領域は真っ黒に。

img_right_extended = cv2.warpPerspective(img_both, M_right, (750*2, 1000))
cv2.imshow('Image right extended', img_right_extended)

f:id:nokixa:20210905022642p:plain

なんとかできないかと考えてみましたが、変換行列を作るときに入力座標にオフセットをつければどうかな、と。

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)

f:id:nokixa:20210905025430p:plain

いけた!
左側は特に何もしなくてもよさそうかな。

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)

f:id:nokixa:20210905030217p:plain

img_left_extended = cv2.warpPerspective(img_both, M_left, (750*2, 1000))
cv2.imshow('Image left extended', img_left_extended)

f:id:nokixa:20210905031506p:plain

以上

今回もいい感じの結果が得られました。
カメラ画像の視点変換は自由自在です。

次回の内容は検討中…