C++ 基于颜色的 OpenCV 边缘/边界检测

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/29156091/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 21:09:09  来源:igfitidea点击:

OpenCV Edge/Border detection based on color

c++opencvedge-detection

提问by Jeff

I'm fairly new to OpenCV, and very excited to learn more. I've been toying with the idea of outlining edges, shapes.

我对 OpenCV 还很陌生,很高兴能学到更多。我一直在玩弄轮廓边缘、形状的想法。

I've come across this code (running on an iOS device), which uses Canny. I'd like to be able to render this in color, and circle each shape. Can someone point me in the right direction?

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

Thanks!

谢谢!

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

And example might be these burger patties. OpenCV would detect the patty, and outline it.enter image description here

例子可能是这些汉堡肉饼。OpenCV 会检测肉饼,并勾勒出它的轮廓。在此处输入图片说明

Original Image:

原图:

enter image description here

在此处输入图片说明

回答by Micka

Color information is often handled by conversion to HSV color space which handles "color" directly instead of dividing color into R/G/B components which makes it easier to handle same colors with different brightness etc.

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

if you convert your image to HSV you'll get this:

如果您将图像转换为 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 channel:

色调通道:

enter image description here

在此处输入图片说明

Saturation channel:

饱和通道:

enter image description here

在此处输入图片说明

Value channel:

价值渠道:

enter image description here

在此处输入图片说明

typically, the hue channel is the first one to look at if you are interested in segmenting "color" (e.g. all red objects). One problem is, that hue is a circular/angular value which means that the highest values are very similar to the lowest values, which results in the bright artifacts at the border of the patties. To overcome this for a particular value, you can shift the whole hue space. If shifted by 50° you'll get something like this instead:

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

在此处输入图片说明

now you can use a simple canny edge detection to find edges in the hue channel:

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

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

enter image description here

在此处输入图片说明

You can see that the regions are a little bigger than the real patties, that might be because of the tiny reflections on the ground around the patties, but I'm not sure about that. Maybe it's just because of jpeg compression artifacts ;)

你可以看到这些区域比真正的肉饼大一点,这可能是因为肉饼周围地面上的微小反射,但我不确定这一点。也许这只是因为 jpeg 压缩工件;)

If you instead use the saturation channel to extract edges, you'll end up with something like this:

如果您改为使用饱和度通道来提取边缘,您将得到如下结果:

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

enter image description here

在此处输入图片说明

where the contours aren't completely closed. Maybe you can combine hue and saturation within preprocessing to extract edges in the hue channel but only where saturation is high enough.

轮廓没有完全闭合的地方。也许您可以在预处理中结合色调和饱和度来提取色调通道中的边缘,但仅限于饱和度足够高的地方。

At this stage you have edges. Regard that edges aren't contours yet. If you directly extract contours from edges they might not be closed/separated etc:

在这个阶段,你有优势。请注意,边缘还不是轮廓。如果您直接从边缘提取轮廓,它们可能不会被关闭/分离等:

// 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

在此处输入图片说明

you can remove those small contours by checking cv::contourArea(contoursH[i]) > someThresholdbefore drawing. But you see the two patties on the left to be connected? Here comes the hardest part... use some heuristics to "improve" your result.

您可以通过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

在此处输入图片说明

if you extract contours from that it will look like this:

如果从中提取轮廓,它将如下所示:

enter image description here

在此处输入图片说明

If you instead choose only the "inner" contours it is exactly what you like:

如果您只选择“内部”轮廓,这正是您喜欢的:

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

在此处输入图片说明

mind that the dilation and inner contour stuff is a little fuzzy, so it might not work for different images and if the initial edges are placed better around the object border it might 1. not be necessary to do the dilate and inner contour thing and 2. if it is still necessary, the dilate will make the object smaller in this scenario (which luckily is great for the given sample image.).

请注意,扩张和内轮廓的东西有点模糊,所以它可能不适用于不同的图像,如果初始边缘更好地放置在对象边界周围,它可能 1. 没有必要做扩张和内轮廓的事情和 2 . 如果仍然有必要,在这种情况下,扩张将使对象变小(幸运的是,这对于给定的样本图像非常有用。)。

EDIT: Some important information about HSV: The hue channel will give every pixel a color of the spectrum, even if the saturation is very low ( = gray/white) or if the color is very low (value) so often it is desired to threshold the saturation and value channels to find some specific color! This might be much easier and much more stavle to handle than the dilation I've used in my code.

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