web-dev-qa-db-ja.com

色に基づいたOpenCVエッジ/境界線検出

私はOpenCVを初めて使用しますが、詳細を知りたいと思っています。エッジ、シェイプの輪郭を描くというアイデアをいじっています。

Cannyを使用するこのコード(iOSデバイスで実行)に出会いました。これをカラーでレンダリングし、各図形を丸で囲みたいです。誰かが私を正しい方向に向けることができますか?

ありがとう!

IplImage *grayImage = cvCreateImage(cvGetSize(iplImage), IPL_DEPTH_8U, 1);
cvCvtColor(iplImage, grayImage, CV_BGRA2GRAY);
cvReleaseImage(&iplImage);

IplImage* img_blur = cvCreateImage( cvGetSize( grayImage ), grayImage->depth, 1);
cvSmooth(grayImage, img_blur, CV_BLUR, 3, 0, 0, 0);
cvReleaseImage(&grayImage);

IplImage* img_canny = cvCreateImage( cvGetSize( img_blur ), img_blur->depth, 1);
cvCanny( img_blur, img_canny, 10, 100, 3 );
cvReleaseImage(&img_blur);

cvNot(img_canny, img_canny);

そして例はこれらのハンバーガーのパテであるかもしれません。 OpenCVはパティを検出し、その概要を説明します。enter image description here

元の画像:

enter image description here

20
Jeff

色情報は、多くの場合、色をR/G/Bコンポーネントに分割する代わりに「色」を直接処理するHSV色空間への変換によって処理されます。

画像をHSVに変換すると、次のようになります。

_cv::Mat hsv;
cv::cvtColor(input,hsv,CV_BGR2HSV);

std::vector<cv::Mat> channels;
cv::split(hsv, channels);

cv::Mat H = channels[0];
cv::Mat S = channels[1];
cv::Mat V = channels[2];
_

色相チャンネル:

enter image description here

彩度チャンネル:

enter image description here

バリューチャネル:

enter image description here

通常、色相チャネルは、「色」(たとえば、すべての赤いオブジェクト)のセグメント化に関心がある場合に最初に調べるチャネルです。 1つの問題は、色相が円形/角度の値であるということです。これは、最高値が最低値に非常に似ていることを意味し、パティの境界に明るいアーチファクトが生じます。特定の値でこれを克服するために、色相空間全体をシフトできます。 50°シフトすると、代わりに次のような結果が得られます。

_cv::Mat shiftedH = H.clone();
int shift = 25; // in openCV hue values go from 0 to 180 (so have to be doubled to get to 0 .. 360) because of byte range from 0 to 255
for(int j=0; j<shiftedH.rows; ++j)
    for(int i=0; i<shiftedH.cols; ++i)
    {
        shiftedH.at<unsigned char>(j,i) = (shiftedH.at<unsigned char>(j,i) + shift)%180;
    }
_

enter image description here

これで、単純な単純なエッジ検出を使用して、色相チャネルのエッジを見つけることができます。

_cv::Mat cannyH;
cv::Canny(shiftedH, cannyH, 100, 50);
_

enter image description here

領域が実際のパテよりも少し大きいことがわかります。これは、パテの周りの地面に小さな反射があるためかもしれませんが、それについてはわかりません。たぶんそれは、jpeg圧縮のアーティファクトのためだけです;)

代わりに飽和チャンネルを使用してエッジを抽出すると、次のような結果になります。

_cv::Mat cannyS;
cv::Canny(S, cannyS, 200, 100);
_

enter image description here

輪郭が完全に閉じていない場合。前処理内で色相と彩度を組み合わせて、色相チャネルのエッジを抽出できますが、彩度が十分に高い場合のみです。

この段階でエッジができました。エッジはまだ輪郭ではありません。エッジから輪郭を直接抽出すると、輪郭が閉じたり分離されたりしない場合があります。

_// extract contours of the canny image:
std::vector<std::vector<cv::Point> > contoursH;
std::vector<cv::Vec4i> hierarchyH;
cv::findContours(cannyH,contoursH, hierarchyH, CV_RETR_TREE , CV_CHAIN_APPROX_SIMPLE);

// draw the contours to a copy of the input image:
cv::Mat outputH = input.clone();
for( int i = 0; i< contoursH.size(); i++ )
 {
   cv::drawContours( outputH, contoursH, i, cv::Scalar(0,0,255), 2, 8, hierarchyH, 0);
 }
_

enter image description here

描画する前にcv::contourArea(contoursH[i]) > someThresholdをチェックすることにより、これらの小さな輪郭を削除できます。しかし、左側の2つのパテが接続されているのがわかりますか?ここが最も難しい部分です...ヒューリスティックを使用して結果を「改善」します。

_cv::dilate(cannyH, cannyH, cv::Mat());
cv::dilate(cannyH, cannyH, cv::Mat());
cv::dilate(cannyH, cannyH, cv::Mat());

Dilation before contour extraction will "close" the gaps between different objects but increase the object size too.
_

enter image description here

輪郭を抽出すると、次のようになります。

enter image description here

代わりに「内側」の輪郭のみを選択した場合、それはまさにあなたが好きなものです:

_cv::Mat outputH = input.clone();
for( int i = 0; i< contoursH.size(); i++ )
 {
    if(cv::contourArea(contoursH[i]) < 20) continue; // ignore contours that are too small to be a patty
    if(hierarchyH[i][3] < 0) continue;  // ignore "outer" contours

    cv::drawContours( outputH, contoursH, i, cv::Scalar(0,0,255), 2, 8, hierarchyH, 0);
 }
_

enter image description here

拡張と内側の輪郭は少し曖昧なので、異なる画像では機能しない可能性があり、最初のエッジがオブジェクトの境界の周りにうまく配置されている場合、拡張と内側の輪郭を行う必要はありません2 。それでも必要な場合、膨張によりこのシナリオでオブジェクトが小さくなります(幸いなことに、これは与えられたサンプル画像に最適です)。

編集:HSVに関するいくつかの重要な情報:彩度が非常に低い(=グレー/ホワイト)場合や、色が非常に低い(値)場合でも、色相チャネルはすべてのピクセルにスペクトルの色を与えます。特定の色を見つけるために、彩度と値のチャネルのしきい値を設定します!これは、私がコードで使用した膨張よりもはるかに簡単で、はるかに手間がかかります。

67
Micka