はじめに
エピポーラ幾何はなんぞや?という方向けに簡単に説明すると複数のカメラで撮影した物体の対応点からカメラの位置や画像での対応する箇所を求めることができる幾何です。
詳しくはネット上に丁寧に説明している記事がたくさんあるので、そちらを参照してください。
ここでは2つのカメラで撮影した画像の対応点から基礎行列Fを求めてエピポーラ線を出力するところまでを実装していきたいと思います。
あくまでOpenCVでこんな簡単にできるで!って記事です。
基礎行列を求めてくれる関数:findFundamentalMat
OpenCVは何でも揃ってます。
基礎行列の計算ロジックとか何も知らなくてもこの関数に対応点を入力値として渡すだけで計算してくれます。流石っすね。
とはいえ計算ロジック等は知っておいた方がいいと思いますけどね。
使い方
行列1と行列2は対応する点を同じ順序で入力します。
メソッド以降は引数を与えなくても実行されます。
cv2.findFundamentalMat(行列1, 行列2, メソッド, パラメータ1, パラメータ2)
メソッドは次の4種類。
- cv2.FM_7POINT
- cv2.FM_8POINT
- cv2.FM_RANSAC
- cv2.FM_LMEDS
パラメータ1はRANSACの場合にのみ利用されるパラメータで、外れ値を計算する際に使います。
パラメータ2はRANSACまたはLMEDSの場合に利用されるパラメータで正確度の要求値です。
出力は基礎行列Fとマスク(外れ値を0として返す)です。
お試しソースコード
今回お試しに利用した画像は次の2つです。
本格的に基礎行列を計算する場合には特徴量などを用いて正確に対応点を取得した方がよいと思いますが、今回はお試しなので対応点の座標を手で記述します。
対応点の座標についてはこちらの記事のようなプログラムを作ると、クリック操作を数回するだけで取得できるようになるので効率的です!
import cv2 import numpy as np #対応点の行列 ptsCAM1 = np.array([[157,343],[409,323],[515,383],[535,241],[697,57],[801,361],[803,163],[919,219]]) ptsCAM2 = np.array([[113,469],[381,467],[295,585],[709,377],[653,425],[143,773],[1015,455],[473,715]]) #基礎行列F,及びmaskの取得 F, mask = cv2.findFundamentalMat(ptsCAM1, ptsCAM2, cv2.FM_LMEDS) #基礎行列とmaskの出力 print(F,'\n\n',mask)
実行すると基礎行列とmaskが出力されます。
[[ 1.66352250e-06 1.50784946e-07 -2.70805858e-03] [-7.51360157e-07 -2.04163905e-06 -3.23795058e-03] [ 2.34717086e-03 2.34549283e-03 1.00000000e+00]] [[1] [1] [1] [1] [0] [1] [1] [1]]
エピポーラ線を求める関数:computeCorrespondEpilines
こちらも引数を渡すだけでエピポーラ線を求めてくれます。
使い方
入力点は画像内の座標点です。
画像インデックスは入力点を定めた画像のもの(1か2)を入力します。
基礎行列まですべて引数として渡します。
cv2.computeCorrespondEpilines(入力点, 画像インデックス, 基礎行列)
この関数ではエピポーラ線をax+by+c=0として(a,b,c)を出力します。
お試し
Colabで使うためにimshowだけ通常のOpenCVとは書き方が異なります。各自修正してください。
#googlecolabで実行するために利用 from google.colab.patches import cv2_imshow #画像の読み込み imgCAM1 = cv2.imread('/content/drive/My Drive/cam1.JPG',1) imgCAM2 = cv2.imread('/content/drive/My Drive/cam2.JPG',1) #サンプル点を描く for points in ptsCAM1: imgCAM1 = cv2.circle(imgCAM1, tuple(points), 5, (0, 0, 255), -1) #エピポーラ線を求める linesCAM1 = cv2.computeCorrespondEpilines(ptsCAM2, 2, F) linesCAM1 = linesCAM1.reshape(-1,3) #行列の変形 #エピポーラ線を描く widthCAM1 = imgCAM1.shape[1] #画像幅 for lines in linesCAM1: x0,y0 = map(int, [0,-lines[2]/lines[1]]) #左端 x1,y1 = map(int, [widthCAM1,-(lines[2]+lines[0]*widthCAM1)/lines[1]]) #右端 imgCAM1 = cv2.line(imgCAM1, (x0,y0), (x1,y1), (255, 255, 255), 1) #線の描画 cv2_imshow(imgCAM1) #表示 #サンプル点を描く for points in ptsCAM2: imgCAM2 = cv2.circle(imgCAM2, tuple(points), 5, (0, 0, 255), -1) #エピポーラ線を求める linesCAM2 = cv2.computeCorrespondEpilines(ptsCAM1, 1, F) linesCAM2 = linesCAM2.reshape(-1,3) #行列の変形 #エピポーラ線を描く widthCAM2 = imgCAM2.shape[1] #画像幅 for lines in linesCAM2: x0,y0 = map(int, [0,-lines[2]/lines[1]]) #左端 x1,y1 = map(int, [widthCAM2,-(lines[2]+lines[0]*widthCAM2)/lines[1]]) #右端 imgCAM2 = cv2.line(imgCAM2, (x0,y0), (x1,y1), (255, 255, 255), 1) #線の描画 cv2_imshow(imgCAM2) #表示
実行すると次のようにエピポーラ線が記述されます。
cam1.JPG cam2.JPG
OpenCVすごい!
最後に
対応点を見つけるには特徴量を用いたり、あらかじめ対応点が簡単に指定できるような画像を撮影しておくとよいと思います。
近いうちにカメラキャリブレーションについてもなんか書くかも!
コメント