OpenCV Edge/Border 基于颜色的检测
OpenCV Edge/Border detection based on color
我对 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 会检测小馅饼,并勾勒出轮廓。
原图:
颜色信息通常通过转换为 HSV 颜色 space 来处理,它直接处理 "color" 而不是将颜色分成 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];
色调通道:
饱和通道:
价值渠道:
通常,如果您有兴趣分割 "color"(例如,所有红色对象),色调通道是第一个要查看的通道。一个问题是,色调是一个 circular/angular 值,这意味着最高值与最低值非常相似,这会导致馅饼边界出现明亮的伪像。要针对特定值克服此问题,您可以移动整个色调 space。如果移动 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;
}
现在您可以使用简单的 canny 边缘检测来查找色调通道中的边缘:
cv::Mat cannyH;
cv::Canny(shiftedH, cannyH, 100, 50);
你可以看到这些区域比真正的肉饼大一点,这可能是因为肉饼周围地面上的微小反光,但我不确定。可能只是因为 jpeg 压缩伪影 ;)
如果你改为使用饱和度通道来提取边缘,你最终会得到这样的结果:
cv::Mat cannyS;
cv::Canny(S, cannyS, 200, 100);
轮廓未完全闭合的地方。也许你可以在预处理中结合色调和饱和度来提取色调通道中的边缘,但仅限于饱和度足够高的地方。
在这个阶段你有优势。考虑到边缘还不是轮廓。如果您直接从边缘提取轮廓,它们可能不是 closed/separated 等:
// 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
来删除那些小轮廓。但是你看到左边的两个肉饼是连在一起的吗?最难的部分来了......使用一些启发式方法 "improve" 你的结果。
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.
如果从中提取轮廓,它将如下所示:
如果您只选择 "inner" 轮廓,这正是您喜欢的:
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);
}
请注意,膨胀和内部轮廓有些模糊,因此它可能不适用于不同的图像,如果初始边缘更好地放置在对象边界周围,则可能 1. 没有必要进行膨胀和内部轮廓和 2。如果仍然有必要,扩张将使对象在这种情况下变小(幸运的是,这对于给定的样本图像来说非常好。)。
编辑:关于 HSV 的一些重要信息:色调通道将为每个像素赋予光谱的颜色,即使饱和度非常低(= gray/white)或颜色非常低(值) 所以经常需要对饱和度和值通道设置阈值以找到一些特定的颜色!与我在代码中使用的膨胀相比,这可能更容易处理,也更容易处理。
我对 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 会检测小馅饼,并勾勒出轮廓。
原图:
颜色信息通常通过转换为 HSV 颜色 space 来处理,它直接处理 "color" 而不是将颜色分成 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];
色调通道:
饱和通道:
价值渠道:
通常,如果您有兴趣分割 "color"(例如,所有红色对象),色调通道是第一个要查看的通道。一个问题是,色调是一个 circular/angular 值,这意味着最高值与最低值非常相似,这会导致馅饼边界出现明亮的伪像。要针对特定值克服此问题,您可以移动整个色调 space。如果移动 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;
}
现在您可以使用简单的 canny 边缘检测来查找色调通道中的边缘:
cv::Mat cannyH;
cv::Canny(shiftedH, cannyH, 100, 50);
你可以看到这些区域比真正的肉饼大一点,这可能是因为肉饼周围地面上的微小反光,但我不确定。可能只是因为 jpeg 压缩伪影 ;)
如果你改为使用饱和度通道来提取边缘,你最终会得到这样的结果:
cv::Mat cannyS;
cv::Canny(S, cannyS, 200, 100);
轮廓未完全闭合的地方。也许你可以在预处理中结合色调和饱和度来提取色调通道中的边缘,但仅限于饱和度足够高的地方。
在这个阶段你有优势。考虑到边缘还不是轮廓。如果您直接从边缘提取轮廓,它们可能不是 closed/separated 等:
// 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
来删除那些小轮廓。但是你看到左边的两个肉饼是连在一起的吗?最难的部分来了......使用一些启发式方法 "improve" 你的结果。
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.
如果从中提取轮廓,它将如下所示:
如果您只选择 "inner" 轮廓,这正是您喜欢的:
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);
}
请注意,膨胀和内部轮廓有些模糊,因此它可能不适用于不同的图像,如果初始边缘更好地放置在对象边界周围,则可能 1. 没有必要进行膨胀和内部轮廓和 2。如果仍然有必要,扩张将使对象在这种情况下变小(幸运的是,这对于给定的样本图像来说非常好。)。
编辑:关于 HSV 的一些重要信息:色调通道将为每个像素赋予光谱的颜色,即使饱和度非常低(= gray/white)或颜色非常低(值) 所以经常需要对饱和度和值通道设置阈值以找到一些特定的颜色!与我在代码中使用的膨胀相比,这可能更容易处理,也更容易处理。