技術メモブログ

技術ネタの備忘録的なブログ。技術ネタ以外にも気になったことをつらつら書きます

OpenCVSharpを用いた画像位置合わせ

概要

OpenCVSharpを用いて画像の位置補正をする情報が少なかったため、実装した点をメモ

環境

windows10
Visual Studio 2019

参考URL

画像位置合わせ:SIFTから深層学習まで - Qiita

OpenCvSharpでAKAZEを用いて特徴量を検出する - Qiita

実装前準備

  1. プロジェクト作成後、NegetからOpenCVSharp4.Windowsをインストール

2.画像を用意
位置合わせをするので、比較的似たような画像を用意してください。
こちらは、以前ワンフェスで撮影してきたスクルドさん。
左(Reference.jpg)が比較元、右(float.jpg)を比較先としてます。 f:id:mtkj:20200906205553j:plain

実装

後者の参考ブログ @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);

結果はこんな感じに f:id:mtkj:20200906205606j:plain

キーポイントのマッチング

            // BruteForceという総当たりマッチングを行う
            matcher = DescriptorMatcher.Create("BruteForce");
            matches = matcher.Match(descriptor1, descriptor2);

            // マッチングした特徴量同士を線でつなぐ
            Cv2.DrawMatches(mat, key_point1, temp, key_point2, matches, output3);
            Cv2.ImShow("output3", output3);

キーポイントが多くて、マッチングの線がとんでもないことに
f:id:mtkj:20200906210423j:plain

チューニング

            // チューニング
            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;
                }
            }

f:id:mtkj:20200906211640j:plain 注意点として、閾値数値が小さい と精度が高いと見なしている。
類似を 距離 で表現しているため、「似ている=距離が小さい」ということだと思われる。
※マッチングの情報に「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」画像が下に移動され、比較元画像と同じような位置となっています。
f:id:mtkj:20200906212609j:plain

最後に

OpenCVSharpのについての情報が少ないのが難点。
幸い、参考とさせていただく情報が見つかり大変助かりました。