如何将内容保留在方形轮廓内,同时删除其他形状的轮廓?
How to keep content within the square contour, while delete other shape of contours?
我的项目是从这张图片中扫描出一张纸
经过大量处理,图像质量下降。我 运行 一些代码来找到所有的轮廓
img = cv2.imread("scan1.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret,bw = cv2.threshold(gray,220,255,cv2.THRESH_BINARY_INV) contours,hierarchy = cv2.findContours(bw, cv2.RETR_CCOMP,1) cntLen = 10 ct = 0 #number of contours for cnt in contours:
if len(cnt) > cntLen: #eliminate the noises
ct += 1
newimg = img.copy()
cv2.drawContours(newimg,[cnt],0,(0,0,255),2)
cv2.imshow('Win', newimg)
cv2.waitKey(0) print('Total contours: ',ct)
当然有很多结果,但我只想整理所有复选框轮廓并检查是否有人勾选它。
我在 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() );
}
}
这是输出:
如您所见,过滤器遗漏了一些复选框。特别是,过滤规范可能过于严格,一些复选框似乎由其他字符连接。
让我们看看是否可以通过首先应用一些形态学来改善结果,以去除不属于复选框的轮廓。我将利用目标轮廓由 水平 和 垂直 线组成的事实。
让我们创建一个 “垂直线” 蒙版,仅包含二值图像中的垂直线。
//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) );
我只是应用了带有 8
垂直线的 morphological opening
,这是结果:
我将使用相同的操作来生成 “水平遮罩”。这次的结构元素如下:
cv::Mat horizontalStructure = cv::getStructuringElement( cv::MORPH_RECT, cv::Size(8, 1) );
相同的形态学操作产生这个掩码:
我们只是 OR
两个掩码来生成最终的二进制掩码:
注意所有的复选框是如何在形态学过滤器中幸存下来的。非常酷,现在,计算轮廓并相应地过滤它们。我已经更改了过滤器参数,让我们使用 blob area
看看我们得到什么样的结果。我将搜索特定区域范围上方和下方的斑点。
//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() );
}
}
这是您得到的最终输出:
可能有多种解决方案,其中一种方法是:
提取 contours/connected 个组件(正如您所做的那样)
对您可以定义的正方形掩码进行基于相关性的模板匹配,如下所示:
mask from the sample image
然后进行阈值处理以获取应用蒙版的峰的坐标(因此您可以计算框的 4 个坐标)
得到box的4个坐标后,可以根据检测到的box的aspectratio/moment添加一个额外的filter(参考eldesgraciado)
注:
- 您必须进行大量实验才能获得正确的值
阈值,并且必须具有阈值的 +/- 余量
- 您将获得比赛的多个高峰,在这种情况下,您必须
拿最强的
- 填写表格时,可能会出现笔划在框外继续的情况,所以在提取框的坐标后必须应用纵横比
- 这个方法很灵敏了。噪音 B.形式的倾斜 C.图像或框的比例变化等,建议根据您的示例图像进行一些预处理步骤,例如倾斜校正。
我的项目是从这张图片中扫描出一张纸
经过大量处理,图像质量下降。我 运行 一些代码来找到所有的轮廓
img = cv2.imread("scan1.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret,bw = cv2.threshold(gray,220,255,cv2.THRESH_BINARY_INV) contours,hierarchy = cv2.findContours(bw, cv2.RETR_CCOMP,1) cntLen = 10 ct = 0 #number of contours for cnt in contours:
if len(cnt) > cntLen: #eliminate the noises
ct += 1
newimg = img.copy()
cv2.drawContours(newimg,[cnt],0,(0,0,255),2)
cv2.imshow('Win', newimg)
cv2.waitKey(0) print('Total contours: ',ct)
当然有很多结果,但我只想整理所有复选框轮廓并检查是否有人勾选它。
我在 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() );
}
}
这是输出:
如您所见,过滤器遗漏了一些复选框。特别是,过滤规范可能过于严格,一些复选框似乎由其他字符连接。
让我们看看是否可以通过首先应用一些形态学来改善结果,以去除不属于复选框的轮廓。我将利用目标轮廓由 水平 和 垂直 线组成的事实。
让我们创建一个 “垂直线” 蒙版,仅包含二值图像中的垂直线。
//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) );
我只是应用了带有 8
垂直线的 morphological opening
,这是结果:
我将使用相同的操作来生成 “水平遮罩”。这次的结构元素如下:
cv::Mat horizontalStructure = cv::getStructuringElement( cv::MORPH_RECT, cv::Size(8, 1) );
相同的形态学操作产生这个掩码:
我们只是 OR
两个掩码来生成最终的二进制掩码:
注意所有的复选框是如何在形态学过滤器中幸存下来的。非常酷,现在,计算轮廓并相应地过滤它们。我已经更改了过滤器参数,让我们使用 blob area
看看我们得到什么样的结果。我将搜索特定区域范围上方和下方的斑点。
//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() );
}
}
这是您得到的最终输出:
可能有多种解决方案,其中一种方法是:
提取 contours/connected 个组件(正如您所做的那样)
对您可以定义的正方形掩码进行基于相关性的模板匹配,如下所示: mask from the sample image
然后进行阈值处理以获取应用蒙版的峰的坐标(因此您可以计算框的 4 个坐标)
得到box的4个坐标后,可以根据检测到的box的aspectratio/moment添加一个额外的filter(参考eldesgraciado)
注:
- 您必须进行大量实验才能获得正确的值 阈值,并且必须具有阈值的 +/- 余量
- 您将获得比赛的多个高峰,在这种情况下,您必须 拿最强的
- 填写表格时,可能会出现笔划在框外继续的情况,所以在提取框的坐标后必须应用纵横比
- 这个方法很灵敏了。噪音 B.形式的倾斜 C.图像或框的比例变化等,建议根据您的示例图像进行一些预处理步骤,例如倾斜校正。