木構造(Trees)
■ 木構造
OpenCVでは,代表的ないくつかのデータ構造があらかじめ定義されている.木構造は,その一つである. ここでは,木構造を持つデータをどのように扱うかの例を示す.サンプル
輪郭座標の取得 cvInitTreeNodeIterator, cvNextTreeNode
輪郭の検出を行い,木構造を持つ輪郭データから座標を取り出す
サンプルコード
#include <cv.h> #include <highgui.h> int main (int argc, char *argv[]) { int i; IplImage *src_img = 0; IplImage *src_img_gray = 0; IplImage *tmp_img; CvMemStorage *storage = cvCreateMemStorage (0); CvSeq *contours = 0; CvPoint *point, *tmp; CvSeq *contour; CvTreeNodeIterator it; CvFileStorage *fs; 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); // (1)画像の二値化と輪郭の検出 cvThreshold (src_img_gray, tmp_img, 120, 255, CV_THRESH_BINARY); cvFindContours (tmp_img, storage, &contours, sizeof (CvContour), CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE); /* 輪郭シーケンスから座標を取得 */ fs = cvOpenFileStorage ("contours.yaml", 0, CV_STORAGE_WRITE); // (2)ツリーノードイテレータの初期化 cvInitTreeNodeIterator (&it, contours, 1); // (3)各ノード(輪郭)を走査 while ((contour = (CvSeq *) cvNextTreeNode (&it)) != NULL) { cvStartWriteStruct (fs, "contour", CV_NODE_SEQ); // (4)輪郭を構成する頂点座標を取得 tmp = CV_GET_SEQ_ELEM (CvPoint, contour, -1); for (i = 0; i < contour->total; i++) { point = CV_GET_SEQ_ELEM (CvPoint, contour, i); cvLine (src_img, *tmp, *point, CV_RGB (0, 0, 255), 2); cvStartWriteStruct (fs, NULL, CV_NODE_MAP | CV_NODE_FLOW); cvWriteInt (fs, "x", point->x); cvWriteInt (fs, "y", point->y); cvEndWriteStruct (fs); tmp = point; } cvEndWriteStruct (fs); } cvReleaseFileStorage (&fs); cvNamedWindow ("Contours", CV_WINDOW_AUTOSIZE); cvShowImage ("Contours", src_img); cvWaitKey (0); cvDestroyWindow ("Contours"); cvReleaseImage (&src_img); cvReleaseImage (&src_img_gray); cvReleaseImage (&tmp_img); cvReleaseMemStorage (&storage); return 0; }
// (1)画像の二値化と輪郭の検出
ここまでの処理は,
輪郭の検出と描画でのサンプルと同一なので,そちらを参照のこと.
// (2)ツリーノードイテレータの初期化
検出された輪郭のシーケンスから座標を取得する.
まず,関数cvInitTreeNodeIterator()により,ツリーノードイテレータを初期化する
(必ず一つの輪郭しか持たないような単純な場合は,ツリーノードイテレータを利用せずとも構わない).
ここで,最後の引数は,検出する輪郭の最大レベルを表している(ここではレベル2を指定している).
つまりここでは,関数cvFindContours()で輪郭を検出する際に,第5引数にCV_RETR_TREEが指定される場合を想定している.
関数cvFindContours()の引数が,CV_RETR_EXTERNALの場合(最も外側の輪郭しか検出されない)と,CV_RETR_LISTの場合(リスト構造)は,検出された輪郭シーケンスが階層構造を持たないので,レベル指定は意味をなさない.
CV_RETR_CCOMPの場合は,輪郭シーケンスが2階層の構造しかもたないので,2レベルまでですべての輪郭が取得できる.
また,関数cvFindContours()の第6引数で,CV_CHAIN_CODE以外が指定されていなければならない.
なぜなら,CV_CHAIN_CODEを指定した場合は,輪郭データが頂点のシーケンスではなくチェーンコードで表現されてしまうからである(もちろん,そこから座標を再計算することは可能である).
// (3)各ノード(輪郭)を走査
次に,関数cvNextTreeNode()により,順次ツリーノード(各輪郭シーケンス)へのポインタを得る.
返すのツリーノードが存在しなくなる,つまり最後のノードまでたどり着くと,この関数はNULLを返す.
// (4)輪郭を構成する頂点座標を取得
得られたノードの要素である座標データ(CvPoint)へのポインタを,マクロCV_GET_SEQ_ELEMにより取得する.
そして取得された座標同士を直線で結ぶ(cvLine)ことで輪郭を描画し,さらに,各座標をファイルにYAML形式で保存する.
また,この方法で保存されたファイルは,木構造を保持せず,すべての枝(輪郭)が並列に並べられる.