輪郭の抽出
2012年12月19日:OpenCV
OpenCVには輪郭を抽出する命令があります。 ここでは、画像から輪郭を抽出して表示するプログラムを作成します。 では、本ブログのロゴである
を画像処理により「青字のJ」の輪郭を抽出するプログラムを作成してみましょう。 プログラムはXCodeで作成しました。 ただGUIが異なるだけなので、Windowsの方にも参考になると思います。
GUIの設定
はじめにプロジェクトを立ちあげ、OpenCVの設定をします。 詳しくは以下の記事を参考にしてください。
次にボタンを設置して、画像を読み込めるようにします。 以下の記事を参考にしてプログラムを作ってください。
プログラム
#import <Cocoa/Cocoa.h> #include "opencv/cv.h" #include "opencv/highgui.h" @interface AppDelegate : NSObject { IplImage* image; } @property (assign) IBOutlet NSWindow *window; - (IBAction)Button:(NSButton *)sender; @end //AppDelegate.m #import "AppDelegate.h" @implementation AppDelegate - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { // Insert code here to initialize your application cvNamedWindow("window",1); } -(NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)app{ cvReleaseImage(&image); cvDestroyWindow("window"); return NSTerminateNow; } - (IBAction)Button:(NSButton *)sender { NSOpenPanel *openPanel=[NSOpenPanel openPanel]; NSArray *allowedFileTypes=[NSArray arrayWithObjects:@"png",@"PNG",nil]; [openPanel setAllowedFileTypes:allowedFileTypes]; NSInteger pressButton=[openPanel runModal]; if(pressButton==NSOKButton){ NSURL * filePath=[openPanel URL]; NSString* fileName=[filePath path]; const char* path=[fileName UTF8String]; image=cvLoadImage(path,CV_LOAD_IMAGE_ANYCOLOR); if(image==NULL){ NSLog(@"Not Read"); }else{ //ここからが本題です。 //グレースケール用のIplImageを用意する IplImage *img_gray; img_gray=cvCreateImage(cvGetSize(image),IPL_DEPTH_8U,1); //青色だけを取り出す for(int j=0;jheight;j++){ for(int i=0;iwidth;i++){ //青色の画素をグレースケールへ移す img_gray->imageData[img_gray->widthStep*j+i]=image->imageData[image->widthStep*j+i*3]; } } //二値化する(大津の方法) cvThreshold(img_gray, img_gray, 0, 255, CV_THRESH_BINARY|CV_THRESH_OTSU); //輪郭を抽出する //メモリストレージの確保 CvMemStorage* storage=cvCreateMemStorage(0); CvSeq* contours; int nCont=cvFindContours(img_gray,storage,&contours,sizeof(CvContour),CV_RETR_EXTERNAL,CV_CHAIN_APPROX_SIMPLE,cvPoint(0,0)); NSLog(@"輪郭数=%d",nCont); cvDrawContours(image,contours,CV_RGB(255,0,0),CV_RGB(0,255,0),1,2,CV_AA,cvPoint(0,0)); cvShowImage("window",image); } } } @end
解説
「//ここからが本題です」からが新しい部分です。 まず、グレースケールへ変換するためIplImage* img_grayを用意しています。 そして、imageを画素ごとに操作して、青色の部分だけをimg_grayへ写しています。 それを2つのfor文で行なっています。
for(int j=0;jheight;j++){ for(int i=0;iwidth;i++){ //青色の画素をグレースケールへ移す img_gray->imageData[img_gray->widthStep*j+i]=image->imageData[image->widthStep*j+i*3]; } }
最後に、img_grayを大津の方法で二値化しています。
cvThreshold(img_gray, img_gray, 0, 255, CV_THRESH_BINARY|CV_THRESH_OTSU);
これでJだけが白になった画像が得られます。 次に本題の輪郭の抽出を行なっています。 まずメモリストレージと呼ばれる構造体を定義して
CvMemStorage* storage=cvCreateMemStorage(0);
でメモリを確保します。 続いて、CvSeq構造体を定義しています。
CvSeq* contours;
そして、cvFindContoursで輪郭を取得しています。
int cvFindContours( cvArr* image, //処理対象の画像 CvMemStorage* storage, //輪郭情報を保存するメモリストレージ CvSeq* contour, //輪郭データへのポインタ int header_size, //ヘッダーサイズ int mode, //輪郭を検出する方法 int method, //輪郭を近似する方法 CvPoint offset //輪郭データをシフトする量 );
これにより、CvMemStorage構造体とCvSeq構造体に輪郭情報が保存されます。modeによって輪郭のとり方が変わります。モードに関しては以下の表にまとめます。
定数 | 意味 |
---|---|
CV_RETR_EXTERNAL | 外側の輪郭だけを抽出します。 |
CV_RETR_LIST | すべての輪郭を抽出します。 |
CV_RETR_CCOMP | すべての輪郭を抽出して、外側の輪郭と穴の輪郭で階層を管理します。 |
CV_RETR_TREE | すべての輪郭を抽出して、階層的に再構築します。 |
最後にcvDrawContoursで輪郭を描写します。
void cvDrawContours( CvArr* image, //輪郭を描写する画像 CvSeq* contour, //輪郭データへのポインタ CvScalar external_color, //外側の輪郭の色 CvScalar hole_color, //内部輪郭の色 int max_level, //描写する輪郭のレベル int thickness, //太さ int line_type, //線の描写法 CvPoint offset //輪郭を描写する位置をシフトする量 );
著者:安井 真人(やすい まさと)
@yasui_masatoさんをフォロー