読者です 読者をやめる 読者になる 読者になる

vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

【Windows】OpenCVで画像比較 その1

前回の続きです。

特徴点のマッチング

以下を参考に画像を2枚読み込み、特徴点のマッチングを行ってみました。

コードは前回作成したものに、上記サイトの「// 特徴記述」以下をコピペすればとりあえず試すことができますが、一点ライブラリに「opencv_flann300.lib(opencv_flann300d.lib)」を追加する必要があります。
(「cv::Ptr<cv::DescriptorMatcher> matcher = cv::DescriptorMatcher::create("BruteForce");」の部分でエラーが発生します)

マッチングに使用した画像は以下です。

sample2.jpg

f:id:mslGt:20150909235855j:plain

※ファイルをjpgにしているのはなぜかこの記事を書くときに「sample2.png」がアップロードできなかったためです。

マッチングの結果このような感じになりました。中央下部の太枠で表示されているセルの位置が違っていますが、マッチングされていますね。 f:id:mslGt:20150909235346p:plain

画像の比較

さて、2枚の画像の共通点を探すならこれで良いかと思いますが、今回は違うところを見つけたいので、別の方法を使うことにしました。

なお、諸事情から比較画像には上記の画像ではなく、以下を使用しています。

sample1_2.png

f:id:mslGt:20150910000020p:plain

※前回使用した画像の一部に、緑の枠を加えて保存したものです。

cv::absdiff

2値化した2枚の画像に「cv::absdiff(比較画像1, 比較画像2, 結果画像)」を使うことで、画像同士の差分を取得できます。

収縮・膨張

2値化した画像を収縮・膨張することで、細かい点などのノイズを取り除きます。
今回は「cv::absdiff」の実行前に2値化しているため、「cv::absdiff」で得られた画像にそのまま処理を行います。

収縮は注目画素、その画素を含む特定範囲内に黒の画素があれば、その範囲内の画素をすべて黒に置き換え、膨張は逆に白に置き換えるそうです。

…すみません、「注目画素」って?など今ひとつよく理解できていない状態なので、もう少し調べてみる予定です。
現状の理解としては、画像内の輪郭などに含まれる点を中心として、特定の範囲内に黒の画素があった場合はそれらをすべて黒に置き換えるのが収縮、白に置き換えるのが膨張かなと考えています。

更に、cv::erode(cv::dilate)の引数は以下かな、と考えていますが、正しいことがわかれば訂正します。
1. 入力画像 2. 出力画像 3. 画素を調べる範囲(上記の「特定の範囲」。cv::Mat()とすると3✕3) 4. 画素を調べる範囲における、「画像内の輪郭などに含まれる点」の場所(デフォルトで範囲内の中央) 5. 収縮・膨張する回数

ソースコード

#include 

#ifdef _DEBUG        
#pragma comment(lib,"opencv_core300d.lib")
#pragma comment(lib,"opencv_features2d300d.lib")
#pragma comment(lib,"opencv_imgproc300d.lib")
#pragma comment(lib,"opencv_highgui300d.lib")
#pragma comment(lib,"opencv_hal300d.lib")
#pragma comment(lib,"opencv_imgcodecs300d.lib")
#pragma comment(lib, "opencv_video300d.lib")
#pragma comment(lib, "opencv_objdetect300d.lib")
#pragma comment(lib,"ippicvmt.lib")
#pragma comment(lib,"IlmImfd.lib")
#pragma comment(lib,"libjasperd.lib")
#pragma comment(lib,"libjpegd.lib")
#pragma comment(lib,"libpngd.lib")
#pragma comment(lib,"libtiffd.lib")
#pragma comment(lib,"libwebpd.lib")
#pragma comment(lib,"zlibd.lib")
#else    
#pragma comment(lib,"opencv_core300.lib")
#pragma comment(lib,"opencv_features2d300.lib")
#pragma comment(lib,"opencv_imgproc300.lib")
#pragma comment(lib,"opencv_highgui300.lib")
#pragma comment(lib,"opencv_hal300.lib")
#pragma comment(lib,"opencv_imgcodecs300.lib")
#pragma comment(lib,"ippicvmt.lib")
#pragma comment(lib,"IlmImf.lib")
#pragma comment(lib,"libjasper.lib")
#pragma comment(lib,"libjpeg.lib")
#pragma comment(lib,"libpng.lib")
#pragma comment(lib,"libtiff.lib")
#pragma comment(lib,"libwebp.lib")
#pragma comment(lib,"zlib.lib")  
#endif

int main(int argc, char* argv[])
{
    // 画像を読み込む.
    std::string strFileName1 = "img/sample1.png";
    cv::Mat matColor1 = cv::imread(strFileName1, -1);

    // グレー化・二値化.
    cv::Mat matGray1;
    cv::cvtColor(matColor1, matGray1, CV_RGB2GRAY);
    cv::Mat matThreshold1;
    cv::threshold(matGray1, matThreshold1, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);

    // 比較画像を読み込む.
    std::string strFileName2 = "img/sample1_2.png";
    cv::Mat matColor2 = cv::imread(strFileName2, -1);
    
    // グレー化・二値化.
    cv::Mat matGray2;
    cv::cvtColor(matColor2, matGray2, CV_RGB2GRAY);
    cv::Mat matThreshold2;
    cv::threshold(matGray2, matThreshold2, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);

    // 二枚の画像から差分を取得する.
    cv::Mat matDiff;
    cv::absdiff(matThreshold1, matThreshold2, matDiff);

    // 画像を収縮(erode)、膨張(dilate)してノイズを取り除く.
    cv::Mat matErode;
    cv::erode(matDiff, matErode, cv::Mat(), cv::Point(-1, -1), 1);
    cv::Mat matDilate;
    cv::dilate(matErode, matDilate, cv::Mat(), cv::Point(-1, -1), 1);

    std::vector vctContours;

    // 輪郭を抽出して座標を取得する.
    cv::findContours(matDilate, vctContours, CV_RETR_LIST, CV_CHAIN_APPROX_NONE);

    int i = 0;

    // 青色で輪郭を描画.
    for (auto contour = vctContours.begin(); contour != vctContours.end(); contour++){
        cv::drawContours(matColor1, vctContours, i, CV_RGB(0, 0, 255), 2);
        i++;
    }

    cv::imshow("diff", matColor1);
    cv::waitKey(0);
    cv::destroyAllWindows();
    return 0;
}
  • 以下によると、輪郭の抽出に使用する「cv::findContours」で、「std::vector<std::vector<cv::Point>> contours;」を使用するとエラーが発生するとのことですが、私の環境では再現しませんでした(バグ報告済みとのことなので、その後修正されたのかもしれません)。

OpenCV バグ cv::findContours() で std::vector<std::vector<cv::Point> > を使用してはいけない。 - Qiita

結果

結果は以下のように表示されます。sample1_2.pngで追加した緑の枠を囲むように輪郭の線が表示されていますね。

f:id:mslGt:20150910004116p:plain

色問題

前回は「cv::imread(strFileName1, 0)」としてグレースケールで画像を読み込んでいたのですが、今回は色付きで読み込んでいます。

というのは、グレー化した画像に色を指定して輪郭を描画しようとしても、輪郭にもグレー化が適用されてしまい、白黒になってしまうためです。

課題

今回は比較する画像内の要素の位置・サイズが同じものを使用しました。
ただ、これをマッチングで使用した、微妙に画像内の要素を移動させたり縮小・拡大すると、以下のように関係ない部分も差分として取得されます。

f:id:mslGt:20150910004148p:plain

以下ではこれを一度に比較するのではなく、特徴点から対応する箇所を取得して画像を分割し、一つずつ比較していっているようです。 * サイゼリヤの間違い探しが難しすぎたので大人の力で解決した - 河本の実験室

次回はこの辺りの方法を参考に、画像内の要素を移動・拡大・縮小させた画像も比較できるようにしたいと思います。

参考

画像の収縮・膨張