勾配,エッジ,コーナー
■ 勾配,エッジ
画像の勾配(濃度勾配)とは,各画素値の変化の度合を示すものである. これは,画素の位置を変数とした場合の微分値であるので"微分画像"と呼ばれる. どのような場合に画素値の変化が大きくなるのかを考えると, オブジェクトやテクスチャの"端"において画素値が急激に変化することが多い. そのため,微分画像はエッジらしい個所を表す画像と見なされ,オブジェクト の検出や認識,輪郭追跡の前処理などにしばしば利用される. OpenCVでは,Sobel, Laplacian, Cannyによるエッジ検出のための関数を用意している.サンプル
エッジの検出 cvSobel, cvLaplace, cvCanny
Sobel,Laplacian,Cannyによるエッジ検出
サンプルコード
#include <cv.h> #include <highgui.h> int main (int argc, char **argv) { IplImage *src_img, *dst_img1, *dst_img2, *dst_img3; IplImage *tmp_img; // (1)画像の読み込み if (argc != 2 || (src_img = cvLoadImage (argv[1], CV_LOAD_IMAGE_GRAYSCALE)) == 0) return -1; tmp_img = cvCreateImage (cvGetSize (src_img), IPL_DEPTH_16S, 1); dst_img1 = cvCreateImage (cvGetSize (src_img), IPL_DEPTH_8U, 1); dst_img2 = cvCreateImage (cvGetSize (src_img), IPL_DEPTH_8U, 1); dst_img3 = cvCreateImage (cvGetSize (src_img), IPL_DEPTH_8U, 1); // (2)Sobelフィルタによる微分画像の作成 cvSobel (src_img, tmp_img, 1, 0); cvConvertScaleAbs (tmp_img, dst_img1); // (3)画像のLaplacianの作成 cvLaplace (src_img, tmp_img); cvConvertScaleAbs (tmp_img, dst_img2); // (4)Cannyによるエッジ画像の作成 cvCanny (src_img, dst_img3, 50.0, 200.0); // (5)画像の表示 cvNamedWindow ("Original", CV_WINDOW_AUTOSIZE); cvShowImage ("Original", src_img); cvNamedWindow ("Sobel", CV_WINDOW_AUTOSIZE); cvShowImage ("Sobel", dst_img1); cvNamedWindow ("Laplace", CV_WINDOW_AUTOSIZE); cvShowImage ("Laplace", dst_img2); cvNamedWindow ("Canny", CV_WINDOW_AUTOSIZE); cvShowImage ("Canny", dst_img3); cvWaitKey (0); cvDestroyWindow ("Original"); cvDestroyWindow ("Sobel"); cvDestroyWindow ("Laplace"); cvDestroyWindow ("Canny"); cvReleaseImage (&src_img); cvReleaseImage (&dst_img1); cvReleaseImage (&dst_img2); cvReleaseImage (&dst_img3); cvReleaseImage (&tmp_img); return 0; }
// (1)画像の読み込み
コマンド引数で指定されたファイル名の画像(入力画像)をオープンし,関数
cvLoadImage()で読み込む.2番目の引数にCV_LOAD_IMAGE_GRAYSCALE
を指定することで,元画像をグレースケール画像として読み込む.
// (2)Sobelフィルタによる微分画像の作成
x方向に1次微分するための,3×3サイズのSobelフィルタを用いて微分画像を作成する.
ここでは,aperture_sizeを指定していないが,その場合はデフォルトのサイズ3が利用される.
また,ここでCV_SCHARR(=-1)を指定すると,Sharrフィルタを利用することができる.
この関数ではスケーリングは行われないので,出力画像の各ピクセル値は,
ほとんどの場合入力画像のそれよりも大きな値になる.オーバーフローを避
けるために,例えば入力画像が8ビットの場合,出力画像として16ビット画像を指定する必要がある.
// (3)画像のLaplacianの作成
以下のように,x-y両方向の二次微分の和を計算し,画像のLaplacianを作成する.
dst(x,y) = d2src/dx2 + d2src/dy2この関数でも,cvSobelの場合と同様に出力画像として16ビット画像を指定する.
// (4)Cannyによるエッジ画像の作成
関数cvCanny()が出力する結果は,前述の二つの関数とは異なり,勾配画像では無い.
ここで出力される画像は,エッジか,そうでないかの二値画像となる.
cvCanny()は,二種類の閾値を引数(3番目,4番目)にとり,
小さいほうがエッジ同士を接続するために用いられ,大きいほうが強いエッジ
の初期検出に用いられる.
エッジの接続では,注目ピクセルの8近傍(4方向)に対する勾配が求められ,
最も強い勾配を持つ方向に(閾値を下回らない限り)エッジが接続される.
// (5)画像の表示
入力画像と処理画像を実際に表示し,何かキーが押されるまで待つ.
実行結果例
入力画像 | 結果(Sobel) | 結果(Laplacian) | 結果(Canny) |
■ コーナー
サンプル
コーナーの検出 cvGoodFeaturesToTrack, cvFindCornerSubPix
画像中のコーナー(特徴点)検出
サンプルコード
#include <cv.h> #include <highgui.h> int main (int argc, char **argv) { int i, corner_count = 150; IplImage *dst_img1, *dst_img2, *src_img_gray; IplImage *eig_img, *temp_img; CvPoint2D32f *corners; if (argc != 2 || (dst_img1 = cvLoadImage (argv[1], CV_LOAD_IMAGE_ANYCOLOR | CV_LOAD_IMAGE_ANYDEPTH)) == 0) return -1; dst_img2 = cvCloneImage (dst_img1); src_img_gray = cvLoadImage (argv[1], CV_LOAD_IMAGE_GRAYSCALE); eig_img = cvCreateImage (cvGetSize (src_img_gray), IPL_DEPTH_32F, 1); temp_img = cvCreateImage (cvGetSize (src_img_gray), IPL_DEPTH_32F, 1); corners = (CvPoint2D32f *) cvAlloc (corner_count * sizeof (CvPoint2D32f)); // (1)cvCornerMinEigenValを利用したコーナー検出 cvGoodFeaturesToTrack (src_img_gray, eig_img, temp_img, corners, &corner_count, 0.1, 15); cvFindCornerSubPix (src_img_gray, corners, corner_count, cvSize (3, 3), cvSize (-1, -1), cvTermCriteria (CV_TERMCRIT_ITER | CV_TERMCRIT_EPS, 20, 0.03)); // (2)コーナーの描画 for (i = 0; i < corner_count; i++) cvCircle (dst_img1, cvPointFrom32f (corners[i]), 3, CV_RGB (255, 0, 0), 2); // (3)cvCornerHarrisを利用したコーナー検出 corner_count = 150; cvGoodFeaturesToTrack (src_img_gray, eig_img, temp_img, corners, &corner_count, 0.1, 15, NULL, 3, 1, 0.01); cvFindCornerSubPix (src_img_gray, corners, corner_count, cvSize (3, 3), cvSize (-1, -1), cvTermCriteria (CV_TERMCRIT_ITER | CV_TERMCRIT_EPS, 20, 0.03)); // (4)コーナーの描画 for (i = 0; i < corner_count; i++) cvCircle (dst_img2, cvPointFrom32f (corners[i]), 3, CV_RGB (0, 0, 255), 2); // (5)画像の表示 cvNamedWindow ("EigenVal", CV_WINDOW_AUTOSIZE); cvShowImage ("EigenVal", dst_img1); cvNamedWindow ("Harris", CV_WINDOW_AUTOSIZE); cvShowImage ("Harris", dst_img2); cvWaitKey (0); cvDestroyWindow ("EigenVal"); cvDestroyWindow ("Harris"); cvReleaseImage (&dst_img1); cvReleaseImage (&dst_img2); cvReleaseImage (&eig_img); cvReleaseImage (&temp_img); cvReleaseImage (&src_img_gray); return 0; }
// (1)cvCornerMinEigenValを利用したコーナー検出
関数 cvGoodFeaturesToTrack() は,画像中から大きな固有値を持つコーナーを検出する.
この関数は最初に,すべての入力画像のピクセルに対して,
cvCornerMinEigenVal を用いて最小の固有値を計算し,
結果を(2番目の引数で指定された) eig_image に保存する.
次に,"non-maxima suppression"を実行する(3×3の隣接領域内の極大のみが残る).
次のステップでは,(6番目の引数で指定された)quality_levelth と
max(eig_image(x,y)) の積よりも小さい最小固有値をもつコーナーを削除する.
最後に,この関数はコーナー点に着目し(最も強いコーナーが一番最初に対象となる),
新しく着目した特徴点とそれ以前に対象とした特徴点群との距離が
(7番目の引数で指定された) min_distance よりも大きいことをチェックすることで,
すべての検出されたコーナーそれぞれの距離が十分離れていることを保証する.
そのため,この関数は鮮明な特徴点との距離が近い特徴点を削除する.
さらに,関数 cvFindCornerSubPix()を利用して,4番目の引数で指定したサイズの倍の探索ウィンドウで,
コーナー(特徴点)のより正確な座標を探索する.
// (2)コーナーの描画
検出されたコーナーを赤色の円で描画する.
// (3)cvCornerHarrisを利用したコーナー検出
10番目の引数が0でない場合は,デフォルトの cvCornerMinEigenVal の代わりに,
Harrisオペレータ(cvCornerHarris)を用いる.
// (4)コーナーの描画
検出されたコーナーを青色の円で描画する.
// (5)画像の表示
実際に検出されたコーナーの画像を表示し,何かキーが押されるまで待つ.
実行結果例
最小固有値 | Harris |