画像分割,領域結合,輪郭検出
■ 画像の領域分割
画像による認識やトラッキングを行なうために,得られた画像を意味のある領域に分割する事は重要な技術である.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 |
![]() |
![]() |
![]() |