颜色信息通常通过转换到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](https://istack.dev59.com/byIew.webp)
饱和度通道:
![enter image description here](https://istack.dev59.com/pLc83.webp)
明度通道:
![enter image description here](https://istack.dev59.com/eUtqa.webp)
通常,如果您想要分割“颜色”(例如所有红色物体),则首先查看色调通道。其中一个问题是,色调是一个循环/角度值,这意味着最高值非常类似于最低值,这导致了图案边界处的亮度伪影。为了克服这个问题,可以将整个色调空间移位。如果将其移动50°,则会得到以下结果:
cv::Mat shiftedH = H.clone();
int shift = 25;
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](https://istack.dev59.com/4gCxH.webp)
现在你可以使用简单的Canny边缘检测来在色调通道中查找边缘:
cv::Mat cannyH;
cv::Canny(shiftedH, cannyH, 100, 50);
![在此输入图像描述](https://istack.dev59.com/s4pP2.webp)
您可以看到这些区域比真正的饼要大一些,可能是因为饼周围的地面上存在微小的反射,但我对此不太确定。也许只是由于JPEG压缩产生的伪影;)
如果您改用饱和度通道提取边缘,您将得到类似于以下内容:
cv::Mat cannyS;
cv::Canny(S, cannyS, 200, 100);
![在这里输入图片描述](https://istack.dev59.com/t4eWf.webp)
当轮廓没有完全闭合时,您可以尝试在预处理中结合色调和饱和度来提取色调通道中饱和度足够高的边缘。
现在,您已经获得了边缘。请注意,边缘并不是轮廓。如果直接从边缘中提取轮廓,则可能会出现未闭合或分离等问题:
std::vector<std::vector<cv::Point> > contoursH;
std::vector<cv::Vec4i> hierarchyH;
cv::findContours(cannyH,contoursH, hierarchyH, CV_RETR_TREE , CV_CHAIN_APPROX_SIMPLE);
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);
}
![输入图像描述](https://istack.dev59.com/bPfrY.webp)
您可以通过检查 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](https://istack.dev59.com/gmap6.webp)
如果您从中提取轮廓,它会看起来像这样:
![enter image description here](https://istack.dev59.com/Y9rrp.webp)
如果您选择仅“内部”轮廓,则正好是您所喜欢的:
cv::Mat outputH = input.clone();
for( int i = 0; i< contoursH.size(); i++ )
{
if(cv::contourArea(contoursH[i]) < 20) continue;
if(hierarchyH[i][3] < 0) continue;
cv::drawContours( outputH, contoursH, i, cv::Scalar(0,0,255), 2, 8, hierarchyH, 0);
}
![图片描述](https://istack.dev59.com/6NJC5.webp)
请注意,膨胀和内轮廓的部分可能有些模糊,因此可能无法适用于不同的图像。如果最初的边缘在物体边界周围放置得更好,则可能不需要进行膨胀和内轮廓操作。如果仍然需要进行这些操作,则在这种情况下膨胀会使物体变小(幸运的是,对于给定的示例图像效果很好)。编辑:关于HSV的一些重要信息:色调通道将为每个像素赋予光谱颜色,即使饱和度非常低(=灰色/白色)或颜色非常低(值),因此通常希望对饱和度和值通道进行阈值处理以找到某些特定的颜色!这可能比我在代码中使用的膨胀要容易得多且更加稳定。
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