基于颜色的OpenCV边缘/边框检测

35

我对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会检测到肉饼并勾出它的轮廓。enter image description here

原始图像:

enter image description here


2
使用findContours确定所有形状的轮廓:http://docs.opencv.org/modules/imgproc/doc/structural_analysis_and_shape_descriptors.html#findcontours - 接下来,使用drawContours以您想要的颜色绘制每个轮廓:http://docs.opencv.org/modules/imgproc/doc/structural_analysis_and_shape_descriptors.html#drawcontours。在我看到您要处理的图像之前,我无法说更多。 - rayryeng
@rayryeng添加了一张参考图片。感谢您的帮助。 - Jeff
2
没问题!能否展示一下没有红色轮廓的原始图像?一旦我得到它,我有一些想法想尝试,等我有了结果,我会发布一些东西来帮助你! - rayryeng
刚刚添加了原始图像。 - Jeff
2
请不要使用C API(如cvCanny等),而是尽可能转向C++ API,这样会更少出错,并且更容易学习和使用! - Micka
@Micka 很好知道。谢谢! - Jeff
1个回答

98

颜色信息通常通过转换到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];

色调通道:

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);

在此输入图像描述

您可以看到这些区域比真正的饼要大一些,可能是因为饼周围的地面上存在微小的反射,但我对此不太确定。也许只是由于JPEG压缩产生的伪影;)

如果您改用饱和度通道提取边缘,您将得到类似于以下内容:

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

在这里输入图片描述

当轮廓没有完全闭合时,您可以尝试在预处理中结合色调和饱和度来提取色调通道中饱和度足够高的边缘。

现在,您已经获得了边缘。请注意,边缘并不是轮廓。如果直接从边缘中提取轮廓,则可能会出现未闭合或分离等问题:

// 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);
 }

输入图像描述

您可以通过检查 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);
 }

图片描述

请注意,膨胀和内轮廓的部分可能有些模糊,因此可能无法适用于不同的图像。如果最初的边缘在物体边界周围放置得更好,则可能不需要进行膨胀和内轮廓操作。如果仍然需要进行这些操作,则在这种情况下膨胀会使物体变小(幸运的是,对于给定的示例图像效果很好)。编辑:关于HSV的一些重要信息:色调通道将为每个像素赋予光谱颜色,即使饱和度非常低(=灰色/白色)或颜色非常低(值),因此通常希望对饱和度和值通道进行阈值处理以找到某些特定的颜色!这可能比我在代码中使用的膨胀要容易得多且更加稳定。


5
这个答案真是太棒了。-- 我只是在挑刺,但如何将轮廓叠加到彩色图像上呢? - Jeff
1
正是我要做的事情。昨晚没时间了。干得好!顺便说一句,你的 C API 评论让我笑了。另外,这是一个很棒的答案。让我想起了我的风格! - rayryeng
1
移位50的原因是什么?那只是因为你要寻找的颜色(和亮度伪影)恰好靠近0/360边界吗?50是一个很好的数字,可以使所有的红色连续吗?假设你正在寻找另一种颜色(比如蓝色,大约在240左右),那么移位就完全没有必要了,对吗? - James S
是的,如果您正在寻找不低于0或超过360(或在opencv中为180)的色调范围,则移位不会带来任何效果。如果范围低于0或超过360/180,您可以评估0区域以获得相同的结果,而不是进行移位。 - Micka

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接