About BBS

画像分割,領域結合,輪郭検出

作成者: 上田悦子, 最終変更者: 怡土順一, 最終変更リビジョン: 342, 最終変更日時: 2007-10-14 23:39:34 +0900 (日, 14 10月 2007)
This is a local copy taken at 2010/09/20. CLICK HERE to the original page at opencv.jp.

■ 画像の領域分割

画像による認識やトラッキングを行なうために,得られた画像を意味のある領域に分割する事は重要な技術である.OpenCVにはこのような領域分割を行なうための手法として,画像ピラミッドによる画像のセグメント化・平均値シフト法による画像のセグメント化・Watershedアルゴリズムによる領域分割が実装されている.

サンプル


画像ピラミッドを用いた画像の領域分割 cvPyrSegmentation

レベルを指定して画像ピラミッドを作成し,その情報を用いて画像のセグメント化を行なう.

サンプルコード

#include <cv.h> #include <highgui.h> int main (int argc, char **argv) { int level = 4; double threshold1, threshold2; IplImage *src_img = 0, *dst_img; CvMemStorage *storage = 0; CvSeq *comp = 0; CvRect roi; // (1)画像の読み込み if (argc >= 2) src_img = cvLoadImage (argv[1], CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_ANYCOLOR); if (src_img == 0) exit (-1); // (2)領域分割のためにROIをセットする roi.x = roi.y = 0; roi.width = src_img->width & -(1 << level); roi.height = src_img->height & -(1 << level); cvSetImageROI (src_img, roi); // (3)分割結果画像出力用の画像領域を確保し,領域分割を実行 dst_img = cvCloneImage (src_img); storage = cvCreateMemStorage (0); threshold1 = 255.0; threshold2 = 50.0; cvPyrSegmentation (src_img, dst_img, storage, &comp, level, threshold1, threshold2); // (4)入力画像と分割結果画像の表示 cvNamedWindow ("Source", CV_WINDOW_AUTOSIZE); cvNamedWindow ("Segmentation", CV_WINDOW_AUTOSIZE); cvShowImage ("Source", src_img); cvShowImage ("Segmentation", dst_img); cvWaitKey (0); cvDestroyWindow ("Source"); cvDestroyWindow ("Segmentation"); cvReleaseImage (&src_img); cvReleaseImage (&dst_img); cvReleaseMemStorage (&storage); return 0; }

// (1)画像の読み込み
コマンド引数で指定されたファイル名の画像(入力画像)をオープンし,関数 cvLoadImage()で読み込む.

// (2)領域分割のためにROIをセットする
cvPyrSegmentationは,2(ピラミッドのレベル)で割り切れる画像サイズしか 処理できない(エラーが発生する).そのため,指定したレベルに対応するROIサイズを 入力画像にセットする必要がある.サンプルプログラムでは,指定したレベル数だけ1を左に シフトし(2(指定したレベル)を計算),その後2の補数を取る事で ビットマスクを作成し,元画像のwidth, heightとのANDを取り,割り切れる画像サイズ を計算する.

// (3)分割結果画像出力用の画像領域を確保し,領域分割を実行
入力画像と同じサイズ,チャンネル数,デプスの出力用画像dst_imgを,cvCloneImage()で確保し, 2つの閾値をセットして,領域分割を実行する.

// (4)入力画像と分割結果画像の表示
ウィンドウを生成し,入力画像,分割結果画像を表示し,何かキーが押されるまで待つ.

実行結果例

入力画像と領域分割結果

[左から] 入力画像,領域分割結果(threshold2=25),領域分割結果(threshold2=50),領域分割結果(threshold2=75),領域分割結果(threshold2=100)


平均値シフト法による画像のセグメント化 cvPyrMeanShiftFiltering

平均値シフト法による画像のセグメント化を行う.

サンプルコード

#include <cv.h> #include <highgui.h> int main (int argc, char **argv) { int level = 2; IplImage *src_img = 0, *dst_img; CvRect roi; // (1)画像の読み込み if (argc >= 2) src_img = cvLoadImage (argv[1], CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_ANYCOLOR); if (src_img == 0) exit (-1); if (src_img->nChannels != 3) exit (-1); if (src_img->depth != IPL_DEPTH_8U) exit (-1); // (2)領域分割のためにROIをセットする roi.x = roi.y = 0; roi.width = src_img->width & -(1 << level); roi.height = src_img->height & -(1 << level); cvSetImageROI (src_img, roi); // (3)分割結果画像出力用の画像領域を確保し,領域分割を実行 dst_img = cvCloneImage (src_img); cvPyrMeanShiftFiltering (src_img, dst_img, 30.0, 30.0, level, cvTermCriteria (CV_TERMCRIT_ITER + CV_TERMCRIT_EPS, 5, 1)); // (4)入力画像と分割結果画像の表示 cvNamedWindow ("Source", CV_WINDOW_AUTOSIZE); cvNamedWindow ("MeanShift", CV_WINDOW_AUTOSIZE); cvShowImage ("Source", src_img); cvShowImage ("MeanShift", dst_img); cvWaitKey (0); cvDestroyWindow ("Source"); cvDestroyWindow ("MeanShift"); cvReleaseImage (&src_img); cvReleaseImage (&dst_img); return 0; }

// (1)画像の読み込み
コマンド引数で指定されたファイル名の画像(入力画像)をオープンし,関数 cvLoadImage()で読み込む.

// (2)領域分割のためにROIをセットする
cvPyrMeanShiftFilteringも上で示したcvPyrSegmentationと同様, 2(ピラミッドのレベル)で割り切れる画像サイズしか処理できない(エラーが発生する). そのため,指定したレベルに対応するROIサイズを入力画像にセットする必要がある. サンプルプログラムでは,指定したレベル数だけ1を左にシフトし(2(指定したレベル)を 計算),その後2の補数を取る事でビットマスクを作成し,元画像のwidth, heightとのANDを取り, 割り切れる画像サイズを計算する.

// (3)分割結果画像出力用の画像領域を確保し,領域分割を実行
入力画像と同じサイズ,チャンネル数,デプスの出力用画像dst_imgを,cvCloneImage()で確保し, 2つの閾値をセットして,領域分割を実行する.

// (4)入力画像と分割結果画像の表示
ウィンドウを生成し,入力画像,分割結果画像を表示し,何かキーが押されるまで待つ.

実行結果例

入力画像とセグメント化結果

[左から] 入力画像,セグメント化結果


Watershedアルゴリズムによる画像の領域分割 cvWatershed

マウスで円形のマーカー(シード領域)の中心を指定し,複数のマーカーを設定する. このマーカを画像のgradientに沿って広げて行き,gradientの高い部分に出来る境界を元に領域を分割する. 領域は,最初に指定したマーカーの数に分割される.
このサンプルコードは,マウスイベントを処理するための関数(on_mouse)を含んでいる.

サンプルコード

#include <cv.h> #include <highgui.h> IplImage *markers = 0, *dsp_img = 0; /* マウスイベント用コールバック関数 */ void on_mouse (int event, int x, int y, int flags, void *param) { int seed_rad = 20; static int seed_num = 0; CvPoint pt; // (1)クリックにより中心を指定し,円形のシード領域を設定する if (event == CV_EVENT_LBUTTONDOWN) { seed_num++; pt = cvPoint (x, y); cvCircle (markers, pt, seed_rad, cvScalarAll (seed_num), CV_FILLED, 8, 0); cvCircle (dsp_img, pt, seed_rad, cvScalarAll (255), 3, 8, 0); cvShowImage ("image", dsp_img); } } /* メインプログラム */ int main (int argc, char **argv) { int *idx, i, j; IplImage *src_img = 0, *dst_img = 0; // (2)画像の読み込み,マーカー画像の初期化,結果表示用画像領域の確保を行なう if (argc >= 2) src_img = cvLoadImage (argv[1], CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_ANYCOLOR); if (src_img == 0) exit (-1); dsp_img = cvCloneImage (src_img); dst_img = cvCloneImage (src_img); markers = cvCreateImage (cvGetSize (src_img), IPL_DEPTH_32S, 1); cvZero (markers); // (3)入力画像を表示しシードコンポーネント指定のためのマウスイベントを登録する cvNamedWindow ("image", CV_WINDOW_AUTOSIZE); cvShowImage ("image", src_img); cvSetMouseCallback ("image", on_mouse, 0); cvWaitKey (0); // (4)watershed分割を実行する cvWatershed (src_img, markers); // (5)実行結果の画像中のwatershed境界(ピクセル値=-1)を結果表示用画像上に表示する for (i = 0; i < markers->height; i++) { for (j = 0; j < markers->width; j++) { idx = (int *) cvPtr2D (markers, i, j, NULL); if (*idx == -1) cvSet2D (dst_img, i, j, cvScalarAll (255)); } } cvNamedWindow ("watershed transform", CV_WINDOW_AUTOSIZE); cvShowImage ("watershed transform", dst_img); cvWaitKey (0); cvDestroyWindow ("watershed transform"); cvReleaseImage (&markers); cvReleaseImage (&dsp_img); cvReleaseImage (&src_img); cvReleaseImage (&dst_img); return 1; }

// (1)クリックにより中心を指定し,円形のシード領域を設定する
入力画像を表示したウィンドウ上で,マウスクリックのイベントが発生すると,シードの数(seed_num)を 一つ増やし,マーカー画像(markers)上に,既定半径で色をseed_numである塗りつぶし円を描画する. この,markerがwatershed分割のシードとなる.
何かキー入力を行なうまで,シードを追加する事ができる.

// (2)画像の読み込み,マーカー画像の初期化,結果表示用画像領域の確保を行なう
コマンド引数で指定されたファイル名の画像(入力画像)をオープンし,関数 cvLoadImage()で読み込む.同時に表示用画像として入力画像をコピーする.
マーカー画像として,32ビットシングルチャンネルの画像領域(markers)を確保する. このマーカー画像では,watershed境界が-1,関係が未知の領域は0,それ以外では属する 領域番号が入っている.

// (3)入力画像を表示しシードコンポーネント指定のためのマウスイベントを登録する
入力画像を表示し,このウィンドウ上でのマウスイベントに対するコールバック関数(on_mouse)を指定する.

// (4)watershed分割を実行する
マーカー画像設定完了のキー入力を待ち,cvWatershed()を実行する.

// (5)実行結果の画像中のwatershed境界(ピクセル値=-1)を結果表示用画像上に表示する
分割結果を保持しているマーカー画像をスキャン(cvPtr2Dで指定した配列要素へのポインタが返ってくる)し, 値が-1のピクセルに対応する結果表示用画像(dst_img)のピクセル値を255(白)にcvSet2D()を用いて設定し,結果を表示する.

実行結果例

入力画像と領域分割結果

[左から] 入力画像,入力画像上に表示したマーカー領域,領域分割結果


■ 画像の輪郭検出

OpenCVでは,輪郭はさまざまなデータ構造により管理される. つまり,全ての輪郭が単なるリストで表現されることもあれば,親子関係を保持したツリー構造で表現されることもある. 輪郭を利用した処理(包含矩形,近似,比較)の解説ついては別の章に譲り, ここでは,輪郭の検出と走査について簡単に説明する.

サンプル


輪郭の検出と描画 cvFindContours

画像中から輪郭を検出し,-1〜+1までのレベルにある輪郭を描画する

サンプルコード

#include <cv.h> #include <highgui.h> int main (int argc, char *argv[]) { int i; IplImage *src_img = 0, **dst_img; IplImage *src_img_gray = 0; IplImage *tmp_img; CvMemStorage *storage = cvCreateMemStorage (0); CvSeq *contours = 0; int levels = 0; if (argc >= 2) src_img = cvLoadImage (argv[1], CV_LOAD_IMAGE_COLOR); if (src_img == 0) return -1; src_img_gray = cvCreateImage (cvGetSize (src_img), IPL_DEPTH_8U, 1); cvCvtColor (src_img, src_img_gray, CV_BGR2GRAY); tmp_img = cvCreateImage (cvGetSize (src_img), IPL_DEPTH_8U, 1); dst_img = (IplImage **) cvAlloc (sizeof (IplImage *) * 3); for (i = 0; i < 3; i++) { dst_img[i] = cvCloneImage (src_img); } // (1)画像の二値化 cvThreshold (src_img_gray, tmp_img, 120, 255, CV_THRESH_BINARY); // (2)輪郭の検出 cvFindContours (tmp_img, storage, &contours, sizeof (CvContour), CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE); // (3)輪郭の描画 cvDrawContours (dst_img[0], contours, CV_RGB (255, 0, 0), CV_RGB (0, 255, 0), levels - 1, 2, CV_AA, cvPoint (0, 0)); cvDrawContours (dst_img[1], contours, CV_RGB (255, 0, 0), CV_RGB (0, 255, 0), levels, 2, CV_AA, cvPoint (0, 0)); cvDrawContours (dst_img[2], contours, CV_RGB (255, 0, 0), CV_RGB (0, 255, 0), levels + 1, 2, CV_AA, cvPoint (0, 0)); // (4)画像の表示 cvNamedWindow ("Level:-1", CV_WINDOW_AUTOSIZE); cvShowImage ("Level:-1", dst_img[0]); cvNamedWindow ("Level:0", CV_WINDOW_AUTOSIZE); cvShowImage ("Level:0", dst_img[1]); cvNamedWindow ("Level:1", CV_WINDOW_AUTOSIZE); cvShowImage ("Level:1", dst_img[2]); cvWaitKey (0); cvDestroyWindow ("Level:-1"); cvDestroyWindow ("Level:0"); cvDestroyWindow ("Level:1"); cvReleaseImage (&src_img); cvReleaseImage (&src_img_gray); cvReleaseImage (&tmp_img); for (i = 0; i < 3; i++) { cvReleaseImage (&dst_img[i]); } cvFree (dst_img); cvReleaseMemStorage (&storage); return 0; }

// (1)画像の二値化
読み込んだ画像を二値化する.関数cvFindContours()は,処理対象の画像を二 値画像として扱う(値が0以外のピクセルは"1"、0のピクセルは"0"とする) ためである.

// (2)輪郭の検出
二値化された画像から,輪郭を検出する.5番目の引数では,輪郭の抽出モードを指定する. ここでは,CV_RETR_TREE を指定し, 全ての輪郭を抽出し,枝分かれした輪郭を完全に表現する階層構造を構成する. また,6番目の引数は,輪郭の近似手法を表しており, CV_CHAIN_APPROX_SIMPLEが指定された場合は, 水平・垂直・斜めの線分が圧縮され,それぞれの端点のみが点列として残る. その他のモード,近似手法については,リファレンス マニュアルを参照すること.

// (3)輪郭の描画
関数cvDrawContours()を用いて,輪郭を描画する.今回は,以下のように-1,0,1の三つのレベルを指定して輪郭を描画している.

  
-1:2番目の引数で指定した輪郭と,その子のレベル0までの輪郭(つまり,ひとつ下の階層の輪郭)が描画される
 0:2番目の引数で指定した輪郭のみが描画される
+1:2番目の引数で指定した輪郭と,同レベルの輪郭(つまり,同じ階層にある輪郭)が描画される

// (4)画像の表示
ウィンドウを生成し,3種類の輪郭抽出画像を表示して,何かキーが押されるまで待つ.

実行結果例

Level:-1 Level:0 Level:+1

OpenCV-1.0 リファレンス マニュアル
OpenCV-1.1pre リファレンス マニュアル
OpenCVサンプルコード


画素値の直接操作
部分画像のシャッフル
画像の連結
画像のコピー
画像形状の変形
タイリング
画像の反転
逆行列(擬似逆行列)の計算
色空間の写像
離散フーリエ変換
階層構造を持つ輪郭の座標取得
図形の描画
ポリゴンの描画
凸ポリゴンの描画
テキストの描画
IplImage構造体情報の保存
マップのシーケンスを保存
IplImage構造体情報の読み込み
マップのシーケンスを読み込む
K-means法によるクラスタリング
クラスタリングによる減色処理
エッジの検出
コーナーの検出
並進移動のためのピクセルサンプリング
回転移動のためのピクセルサンプリング
画像のサイズ変更
画像のアフィン変換(1)
画像のアフィン変換(2)
画像の透視投影変換
全方位画像の透視投影変換
モルフォロジー変換
平滑化
ユーザ定義フィルタ
境界線の作成
画像の二値化
画像の二値化(大津の手法)
画像ピラミッドの作成
画像ピラミッドを用いた画像の領域分割
平均値シフト法による画像のセグメント化
Watershedアルゴリズムによる画像の領域分割
輪郭の検出と描画
画像のモーメントを計算
ハフ変換による直線検出
ハフ変換による円検出
距離変換とその可視化
不要オブジェクトの除去
ヒストグラムの描画
ヒストグラム間の距離
二次元のヒストグラム
バックプロジェクションパッチ
ヒストグラムの均一化
テンプレートマッチング
形状のマッチング
点列を包含する矩形
輪郭領域の面積と輪郭の長さ
二つの矩形を包含する矩形
楕円のフィッティング
点列を包含する図形
動的背景更新による物体検出
snakeによる輪郭追跡(静止画)
オプティカルフロー1
オプティカルフロー2
オプティカルフロー3
Condensation
顔の検出
カメラキャリブレーション
歪み補正
マップを利用した歪み補正
サポートベクターマシン
画像の各ピクセル値を特徴ベクトルとしたSVMの学習
画像の各ピクセル値を特徴ベクトルとしたSVMによる物体検出
マウスイベントの取得
トラックバーの利用
カメラからの画像キャプチャ
動画としてファイルへ書き出す
ラベリング