OpenCVSharpを用いた画像位置合わせ
概要
OpenCVSharpを用いて画像の位置補正をする情報が少なかったため、実装した点をメモ
環境
windows10
Visual Studio 2019
参考URL
OpenCvSharpでAKAZEを用いて特徴量を検出する - Qiita
実装前準備
- プロジェクト作成後、NegetからOpenCVSharp4.Windowsをインストール
2.画像を用意
位置合わせをするので、比較的似たような画像を用意してください。
こちらは、以前ワンフェスで撮影してきたスクルドさん。
左(Reference.jpg)が比較元、右(float.jpg)を比較先としてます。
実装
後者の参考ブログ @miwazawa さんのをほぼ丸コピ状態・・・・
キーポイントの検出
Mat mat = new Mat(); //比較元画像 Mat temp = new Mat(); //比較先画像 Mat output1 = new Mat(); //比較元画像の特徴点出力先 Mat output2 = new Mat(); //比較先画像の特徴点出力先 Mat output3 = new Mat(); //DrawMatchesの出力先 AKAZE akaze = AKAZE.Create(); //AKAZEのセットアップ KeyPoint[] key_point1; //比較元画像の特徴点 KeyPoint[] key_point2; //比較先画像の特徴点 Mat descriptor1 = new Mat(); //比較元画像の特徴量 Mat descriptor2 = new Mat(); //比較先画像の特徴量 DescriptorMatcher matcher; //マッチング方法 DMatch[] matches; //特徴量ベクトル同士のマッチング結果を格納する配列 mat = Cv2.ImRead(@"img/Reference.jpg");//比較元画像 temp = Cv2.ImRead(@"img/float.jpg");//比較先画像 //特徴量の検出と特徴量ベクトルの計算 akaze.DetectAndCompute(mat, null, out key_point1, descriptor1); akaze.DetectAndCompute(temp, null, out key_point2, descriptor2); //画像1の特徴点をoutput1に出力 Cv2.DrawKeypoints(mat, key_point1, output1); Cv2.ImShow("output1", output1); //画像2の特徴点をoutput2に出力 Cv2.DrawKeypoints(temp, key_point2, output2); Cv2.ImShow("output2", output2);
結果はこんな感じに
キーポイントのマッチング
// BruteForceという総当たりマッチングを行う matcher = DescriptorMatcher.Create("BruteForce"); matches = matcher.Match(descriptor1, descriptor2); // マッチングした特徴量同士を線でつなぐ Cv2.DrawMatches(mat, key_point1, temp, key_point2, matches, output3); Cv2.ImShow("output3", output3);
キーポイントが多くて、マッチングの線がとんでもないことに
チューニング
// チューニング Mat output4 = new Mat(); int good_match_length = 0; // 特徴天の閾値(低い数値のほうが厳しくなる) int threshold = 250; for (int i = 0; i < key_point1.Length && i < key_point2.Length; ++i) { if (matches[i].Distance < threshold) { ++good_match_length; } } DMatch[] good_matches = new DMatch[good_match_length];//閾値以下の要素数で定義 //good_matchesに格納していく int j = 0; for (int i = 0; i < key_point1.Length && i < key_point2.Length; ++i) { if (matches[i].Distance < threshold) { good_matches[j] = matches[i]; ++j; } }
注意点として、閾値は 数値が小さい と精度が高いと見なしている。
類似を 距離 で表現しているため、「似ている=距離が小さい」ということだと思われる。
※マッチングの情報に「Distance」って変数があるので
画像の変換
キーポイント抽出が汚いですね・・・・
注意点としは、ホモグラフィ計算のコメントにもありますが
「FindHomography」メソッドの引数の説明を勘違いしてしまったこと
(自分だけ?)
// チューニングしたキーポイントを使って // 比較元のキーポイントを抽出 List<Point2d> refPoints = new List<Point2d>(); foreach (var v in good_matches) { Double.Parse(key_point1[v.QueryIdx].Pt.X.ToString()); refPoints.Add(new Point2d( Double.Parse(key_point1[v.QueryIdx].Pt.X.ToString()), Double.Parse(key_point1[v.QueryIdx].Pt.Y.ToString())) ); } // チューニングしたキーポイントを使って // 比較先のキーポイントを抽出 List<Point2d> floatPoints = new List<Point2d>(); foreach (var v in good_matches) { Double.Parse(key_point2[v.TrainIdx].Pt.X.ToString()); floatPoints.Add(new Point2d( Double.Parse(key_point2[v.TrainIdx].Pt.X.ToString()), Double.Parse(key_point2[v.TrainIdx].Pt.Y.ToString())) ); } // ホモグラフィを計算 // 引数説明の srcPointsとdstPoints で変換元画像と参照先画像が src:参照、dst:変換と認識しまいがちだが // 恐らく「srcのoriginal plane」が変換元座標を、「dstのtarget plane」が対象の画像となるように // srcの座標を、dstの座標に変換するといった説明なのかもしれない var homo = Cv2.FindHomography(InputArray.Create(floatPoints), InputArray.Create(refPoints), HomographyMethods.Ransac); // 画像の変換 Mat result = new Mat(); Cv2.WarpPerspective(temp, result, homo, new Size(874, 1311)); Cv2.ImShow("Last2", result);
変換結果です。
左は比較元とした「Reference.jpg」画像、右が変換された結果画像です。
比較先とした「float.jpg」画像が下に移動され、比較元画像と同じような位置となっています。
最後に
OpenCVSharpのについての情報が少ないのが難点。
幸い、参考とさせていただく情報が見つかり大変助かりました。