OpenCVで画像内の輪郭抽出からその輪郭の隣接領域(四角形)を求めてその領域を切り出すという処理を作ってみました。前回までのプログラムとほぼ同じですが、前回までは輪郭抽出 → 直線近似 → 隣接領域 の流れでしたが、今回は、輪郭抽出 → 凸包の取得 → 隣接領域を試しました。以下の画像がその結果の例です。(※実画像サイズは大きめです。)
前回までの結果とほぼ同じですが結果の画像の緑の線が輪郭で、青い線が一定以上の面積の輪郭から取り出した凸包です。
「凸包」とはある図形を含む最小の凸図形のことです。以下の図形で青い線です。ここでは輪郭の凸包ですが点集合に対する凸包も定義出来ます。どうやら「輪ゴムで囲うようにぴったりと囲んだ線の図形」という表現をよく使うようです。今回のプログラムでは最終的にこの画面のように凸包に隣接する矩形を取得して切り出します。
今回のプログラム全体は以下からダウンロード出来ます。(※必要な場合は用途に限らずご利用頂いて問題ありませんが、一切無保証です。弊社は一切の責任を負いません。)
ソース一式(※OpenCVを含むため61M程度のサイズです。)
今回の処理は以下です。ボタンの「凸包取得」での処理です。
/*------------------------------------------------ 処理2 -------------------------------------------------*/ void func2(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); } //輪郭の座標リスト 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::convexHull(*contour, approx); //凸包の取得面積が一定以上なら取得 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::imshow("label" + std::to_string(roiCnt + 1), roi[roiCnt]); roiCnt++; //念のため矩形の数をチェック if (roiCnt == 99) { break; } } i++; } //全体を表示する場合 ///cv::imshow("coun", imgIn); //必要に応じて保存したい画像を設定する imgMatWrite = imgIn; }
今回のポイントの凸包の取得についてだけ書きます。その他は前回までを参照して下さい。
以下が凸包を取得する処理です。
//凸包の取得(頂点はデフォルトの反時計回り)
cv::convexHull(*contour, approx);
convexHullで凸包を取得しますが、ここでは省略してデフォルトになっている3番目のパラメータで頂点の順序を指定します。デフォルトが反時計回りで、3番目にtrueを指定すると時計回りになります。最終的にはこの凸包に隣接する矩形を取得して切り出しています。このあたりは前回と同様です。
このconvexHullでの頂点の回りですが、座標を取得する場合に便利な場合があります。例えば以下のように四角形という前提で座標を取り出す場合、以下のように取り出せました。この処理はプログラムの「凸包で四角形取得」ボタンでの処理です。四角形が認識できる画像で四角形が取得出来ます。
Mat roi= cv::Mat(imgIn, cv::Rect(approx[2].x, approx[2].y, approx[3].x – approx[2].x, approx[0].y – approx[3].y));
最後にまとめ的にですが、前回の輪郭抽出から直線近似、今回の輪郭抽出から凸包取得それぞれ場合によって使いこなしていければというところです。
今回でこのシリーズは終了です。OpenCVについては今後も書きたいと思います。
関連書籍
(※OpenCV 2 プログラミングブックは私も持っています。)
(※ここからはKindle版で英語ですが最新の情報もあるようです。また価格的にはいいかもしれないです。)