基于颜色的OpenCV Edge / Border检测

时间:2015-03-19 22:23:15

标签: c++ opencv edge-detection

我对OpenCV很新,并且非常高兴能够了解更多信息。我一直在想着勾勒出边缘,形状的想法。

我遇到过这个代码(在iOS设备上运行),它使用了Canny。我希望能够以彩色渲染,并圈出每个形状。有人能指出我正确的方向吗?

谢谢!

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会检测到patty,并勾勒出它。enter image description here

原始图片:

enter image description here

1 个答案:

答案 0 :(得分:63)

颜色信息通常通过转换为HSV颜色空间来处理,该颜色空间直接处理“颜色”而不是将颜色划分为R / G / B组件,这使得更容易处理具有不同亮度的相同颜色等。

如果您将图片转换为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];

Hue频道:

enter image description here

饱和度渠道:

enter image description here

价值渠道:

enter image description here

通常,如果您对分割“颜色”(例如所有红色对象)感兴趣,则首先要查看色调通道。一个问题是,色调是圆形/角度值,这意​​味着最高值与最低值非常相似,这导致肉饼边缘处的明亮伪影。要针对特定​​值克服此问题,您可以移动整个色调空间。如果换了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

现在您可以使用简单的canny边缘检测来查找色调通道中的边缘:

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来删除这些小轮廓。但是你看到左边的两个小馅饼要连接了吗?这是最困难的部分......使用一些启发式方法来“改善”你的结果。

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

请注意,扩张和内部轮廓的东西有点模糊,因此它可能不适用于不同的图像,如果初始边缘在物体边界周围放置得更好,则可能1.不需要做扩张和内部轮廓事情和2.如果仍然需要,在这种情况下,扩张将使对象更小(幸运的是,对于给定的样本图像来说这很好。)。

编辑:关于HSV的一些重要信息:色调通道将为每个像素提供光谱的颜色,即使饱和度非常低(=灰色/白色)或者颜色非常低(值),所以通常它希望阈值饱和度和值通道找到一些特定的颜色!这可能比我在代码中使用的扩张更容易,也更容易处理。