OpenCVで画像内の輪郭抽出からその輪郭の隣接領域(四角形)を求めてその領域を切り出すという処理を作ってみました。以下の画像がその結果の例です。(※実画像サイズは大きめです。)
今回はその2回目です。前回は輪郭抽出の処理まで書きました。今回は輪郭の直線近似から切り出しの最後まで書きます。
処理としては以下のような流れになります。
入力画像のグレースケール化
↓
グレースケールから2値可画像の取得
↓
2値化画像から輪郭取得
↓
輪郭を直線近似(※今回の説明はここからです。)
↓
直線近似した輪郭が一定以上の面積であれば輪郭に隣接する矩形を取得
↓
隣接した矩形を切り出して表示
ここから実際のプログラムの説明です。開発環境とベースになるプログラムは以下のページのプログラムを元にしています。
OpenCVをWin32ベースで利用する(その2)OpenCVの組み込み
今回のプログラム全体は以下からダウンロード出来ます。(※必要な場合は用途に限らずご利用頂いて問題ありませんが、一切無保証です。弊社は一切の責任を負いません。)
ソース一式(※OpenCVを含むため61M程度のサイズです。)
今回の処理は「輪郭取得」ボタンでの処理です。この部分のソースは以下です。
/*------------------------------------------------ 処理1 -------------------------------------------------*/ void func1(HWND hWnd) { ////MessageBox(hWnd, "func1", "debug", MB_OK); if (imgMatRead.rows == 0){ MessageBox(hWnd, "画像ファイルが無効です", "エラー", MB_OK); return; } //入力画像、ここでは毎回ファイルから読み込む cv::Mat imgIn = cv::imread((const std::string&)szOpenFileName, 1); //3チャンネルカラー画像で読み込む; //グレースケール cv::Mat grayImage, binImage; cv::cvtColor(imgIn, grayImage, CV_BGR2GRAY); //2値化(※反転で結果が変わる、基本は背景が黒で物体が白) BOOL bInv = checkInv(hWnd); if (bInv){ cv::threshold(grayImage, binImage, 0.0, 255.0, CV_THRESH_BINARY_INV | CV_THRESH_OTSU); } else{ cv::threshold(grayImage, binImage, 0.0, 255.0, CV_THRESH_BINARY | CV_THRESH_OTSU); } cv::imshow("bin", binImage); //輪郭の座標リスト std::vector< std::vector< cv::Point > > contours; //輪郭取得 ////cv::findContours(binImage, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); cv::findContours(binImage, contours, CV_RETR_LIST, CV_CHAIN_APPROX_NONE); // 検出された輪郭線を緑で描画 for (auto contour = contours.begin(); contour != contours.end(); contour++){ cv::polylines(imgIn, *contour, true, cv::Scalar(0, 255, 0), 2); } //輪郭の数 int roiCnt = 0; //輪郭のカウント int i = 0; for (auto contour = contours.begin(); contour != contours.end(); contour++){ std::vector< cv::Point > approx; //輪郭を直線近似する cv::approxPolyDP(cv::Mat(*contour), approx, 0.01 * cv::arcLength(*contour, true), true); // 近似の面積が一定以上なら取得 double area = cv::contourArea(approx); if (area > 1000.0){ //青で囲む場合 cv::polylines(imgIn, approx, true, cv::Scalar(255, 0, 0), 2); std::stringstream sst; sst << "area : " << area; cv::putText(imgIn, sst.str(), approx[0], CV_FONT_HERSHEY_PLAIN, 1.0, cv::Scalar(0, 128, 0)); //輪郭に隣接する矩形の取得 cv::Rect brect = cv::boundingRect(cv::Mat(approx).reshape(2)); roi[roiCnt] = cv::Mat(imgIn, brect); //入力画像に表示する場合 //cv::drawContours(imgIn, contours, i, CV_RGB(0, 0, 255), 4); //表示 cv::imshow("label" + std::to_string(roiCnt+1), roi[roiCnt]); roiCnt++; //念のため輪郭をカウント if (roiCnt == 99) { break; } } i++; } //全体を表示する場合 //cv::imshow("coun", imgIn); imgMatWrite = imgIn; }
輪郭直線近似処理
それでは、今回の説明の輪郭の直線近似処理についてです。cv::findContours()で取得した各輪郭について、cv::approxPolyDP()で直線近似を取得します。ここでのポイントは、3番目のパラメータです。
cv::approxPolyDP(cv::Mat(*contour), approx, 0.01 * cv::arcLength(*contour, true), true);
この3番目のパラメータでは「近似精度」を指定します。これは元のカーブと近似カーブの最大距離です。ここでは、輪郭線の周囲長を取得してその割合で指定しています。この値が小さい程、元の輪郭に近い直線で近似します。
cv::approxPolyDP(cv::Mat(*contour), approx, 0.01 * cv::arcLength(*contour, true), true);
これで実行した場合です。
cv::approxPolyDP(cv::Mat(*contour), approx, 0.1 * cv::arcLength(*contour, true), true);
これで実行した場合です。
青い線が直線近似です。0.1の場合は三角で近似する結果になりました。今回のプログラムでは輪郭に近くなるように0.01を使っています。
隣接矩形の取得
最後に輪郭に隣接する矩形の取得についてです。以下で取得した輪郭の直線近似に隣接する矩形を取得しています。
cv::Rect brect = cv::boundingRect(cv::Mat(approx).reshape(2));
reshapeで近似直線を2チャンネルに変更してそれから隣接する矩形を取得しています。この結果をMatの配列に格納して表示しています。これで処理結果の表示としています。ページ始めで表示している画像です。
今回はここまでです。次回は今回までの処理と同様の処理ですが、凸包を使ってみます。
関連書籍
(※OpenCV 2 プログラミングブックは私も持っています。)
(※ここからはKindle版で英語ですが最新の情報もあるようです。また価格的にはいいかもしれないです。)