我在C++ 中给出答案,但Python 中提供相同的操作。
让我们研究两种可能的解决方案。第一个涉及将我建议的解决方案直接应用于您提供的输入图像。我正在根据aspect ratio 和minimum width/height 阈值过滤轮廓。
首先,读取输入图像并将其转换为灰度:
std::string imageName = "C://opencvImages//survey.jpg";
cv::Mat imageInput = cv::imread( imageName );
//compute gray scale image:
cv::cvtColor(imageInput, grayImage, cv::COLOR_RGB2GRAY );
接下来,通过Otsu thresholding 获取二进制图像。非常简单的东西:
//get binary image via Otsu:
cv::Mat binImage;
cv::threshold( grayImage, binImage, 0, 255, cv::THRESH_OTSU );
//Invert the image:
binImage = 255 - binImage;
现在,只需遍历二进制图像中的每个contour 并应用相应的“轮廓过滤器”。我将在0.9 和1.1 之间寻找具有minimum width/height 和aspect ratio 的轮廓。这些参数几乎都是手动设置的。我们来看代码:
//contour filter:
for( int i = 0; i< contours.size(); i++ ){
//get the bounding box for each parent countour found:
cv::Rect bBox = cv::boundingRect( contours[i] );
//compute aspect ratio:
float aspectRatio = bBox.height / bBox.width;
//set the aspect ratio thresholds:
float lowerAspectRatio = 0.9;
float upperAspectRatio = 1.1;
//set the width/height thresholds:
float minWidth = 8;
float minHeight = 8;
if ( (bBox.height > minHeight) && (bBox.width > minWidth) &&
(aspectRatio >= lowerAspectRatio) && (aspectRatio <= upperAspectRatio) ) {
cv::Scalar color = cv::Scalar( 0, 255, 0 );
cv::drawContours( imageInput, contours, i, color, 2, 8, hierarchy, 0, cv::Point() );
}
}
这是输出:
如您所见,过滤器遗漏了一些复选框。特别是过滤规范可能过于严格,一些复选框似乎被其他字符连接起来。
让我们看看我们是否可以通过首先应用一些形态来改善结果,以消除不属于复选框的轮廓。我将利用目标轮廓由 horizontal 和 vertical 线组成的事实。
让我们创建一个“垂直线”掩码,只包含二值图像中的垂直线。
//create a vertical structuring element of size 8:
cv::Mat verticalStructure = cv::getStructuringElement( cv::MORPH_RECT, cv::Size(1, 8) );
//apply the morphology operations to isolate the vertical lines:
cv::Mat verticalMask = binImage.clone();
cv::erode( verticalMask, verticalMask, verticalStructure, cv::Point(-1, -1) );
cv::dilate( verticalMask, verticalMask, verticalStructure, cv::Point(-1, -1) );
我只是应用morphological opening 与8 的垂直线,结果如下:
我将使用相同的操作来生成“水平蒙版”。这次的结构元素如下:
cv::Mat horizontalStructure = cv::getStructuringElement( cv::MORPH_RECT, cv::Size(8, 1) );
同样的形态学操作产生这个掩码:
我们只需OR 这两个掩码就可以产生最终的二进制掩码:
请注意所有复选框如何在形态过滤器中幸存下来。非常酷,现在,计算轮廓并相应地过滤它们。我已经更改了过滤器参数,让我们使用blob area,看看我们得到了什么样的结果。我将在特定区域范围上方和下方搜索 blob。
//contour filter:
for( int i = 0; i< contours.size(); i++ ){
//get the bounding box for each parent countour found:
cv::Rect bBox = cv::boundingRect( contours[i] );
//compute blob area:
float blobArea = bBox.area();
//set the area thresholds:
float minBlobArea = 25;
float maxBlobArea = 300;
//set the width/height thresholds:
float minWidth = 5;
float minHeight = 5;
if ( (bBox.height > minHeight) && (bBox.width > minWidth) &&
(blobArea > minBlobArea ) && (blobArea < maxBlobArea) ) {
cv::Scalar color = cv::Scalar( 0, 0, 255 );
cv::drawContours( imageInput, contours, i, color, 2, 8, hierarchy, 0, cv::Point() );
}
}
这是你得到的最终输出: