使用openCV部分识别角落页面失败
Posted
技术标签:
【中文标题】使用openCV部分识别角落页面失败【英文标题】:Recognizing corner's page with openCV partialy fails 【发布时间】:2021-05-22 00:04:01 【问题描述】:我想获取页面的 4 个角, 我采取的步骤:
-
转换为灰度
图像应用阈值
应用 Canny 检测边缘
之后我使用了
findContours
为每个多边形绘制近似多边形,我的假设是相关的多边形必须有 4 个顶点。
但在此过程中我发现我的解决方案有时会丢失, 显然我的解决方案不够健壮(可能有点幼稚的解决方案)。
我认为那些纸角检测失败的一些原因是:
手动选择阈值以进行精确检测。approxPolyDP
的 epsilon 值相同
我的代码
import cv2
import numpy as np
image = cv2.imread('page1.jpg')
descalingFactor = 3
imgheight, imgwidth = image.shape[:2]
resizedImg = cv2.resize(image, (int(imgwidth / descalingFactor), int(imgheight / descalingFactor)),
interpolation=cv2.INTER_AREA)
cv2.imshow(winname="original", mat=resizedImg)
cv2.waitKey()
gray = cv2.cvtColor(resizedImg, cv2.COLOR_BGR2GRAY)
cv2.imshow(winname="gray", mat=gray)
cv2.waitKey()
img_blur = cv2.GaussianBlur(gray, (5, 5), 1)
cv2.imshow(winname="blur", mat=img_blur)
cv2.waitKey()
canny = cv2.Canny(gray,
threshold1=120,
threshold2=255,
edges=1)
cv2.imshow(winname="Canny", mat=canny)
cv2.waitKey()
contours, _ = cv2.findContours(image=canny, mode=cv2.RETR_EXTERNAL, method=cv2.CHAIN_APPROX_SIMPLE)
contours = sorted(contours, key=cv2.contourArea, reverse=True)
for idx, cnt in enumerate(contours):
# print("Contour #", idx)
# print("Contour #", idx, " len(cnt): ", len(cnt))
cv2.drawContours(image=resizedImg, contours=[cnt], contourIdx=0, color=(255, 0, 0), thickness=3)
cv2.imshow(winname="contour" + str(idx), mat=resizedImg)
conv = cv2.convexHull(cnt)
epsilon = 0.1 * cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, epsilon, True)
cv2.drawContours(resizedImg, [approx], 0, (0, 0, 255), 3)
cv2.waitKey(0)
if len(approx) == 4:
print("found the paper!!")
break
pts = np.squeeze(approx)
另一种方法
我想知道将具有 4 个顶点(四边形)的多边形拟合到轮廓,然后检查多边形与轮廓之间的面积差是否低于指定阈值,这不是更好的方法。
有人可以建议一个更强大的解决方案(用代码演示),谢谢。
图片:
image1:https://ibb.co/K2SqLwZ
图片2:https://ibb.co/mbGFsNp
图片3:https://ibb.co/m6QKkzw
image4:https://ibb.co/xh7W41V
【问题讨论】:
您需要更好的背景和照明。光反射使得很难从背景中确定页面。使用漫反射照明。带纹理的背景也很难处理。使用固定颜色的。 如果您一次拍摄多于一页(3 和 4 有两页),您很容易得到超过 4 个角。 您可以使用 cv2.goodFeaturesToTrack() 进行角点检测。它可以让你避免角落靠得太近。 参见例如***.com/questions/67434707/… 【参考方案1】:正如 fmw42 所建议的,您需要进一步限制问题。构建“在所有情况下都有效”解决方案的变量太多了。一个可能的、非常基本的解决方案是尝试获取页面的convex hull
。
另一种更稳健的方法是搜索角的四个顶点并外推线来近似纸张边缘。这样您就不需要完美、干净的边缘,因为您可以使用四个(甚至三个)角来重建它们。
要找到顶点,您可以在边缘上运行 霍夫线检测器 或 角检测器,并获得至少四个可识别的终点/起点集群。从中您可以对四个集群进行平均,以获得每个角的一对 (x, y)
点,并使用这些点推断线。
对于 Stack Overflow 问题,该解决方案是假设性的并且相当费力,所以让我尝试第一个建议 - 通过凸包检测。步骤如下:
-
阈值输入图像
从输入中获取边
使用最小面积过滤器获取边缘的外部轮廓
获取过滤后图像的凸包
获取凸包的角
让我们看看代码:
# imports:
import cv2
import numpy as np
# image path
path = "D://opencvImages//"
fileName = "img2.jpg"
# Reading an image in default mode:
inputImage = cv2.imread(path + fileName)
# Deep copy for results:
inputImageCopy = inputImage.copy()
# Convert BGR to grayscale:
grayInput = cv2.cvtColor(inputImageCopy, cv2.COLOR_BGR2GRAY)
# Threshold via Otsu:
_, binaryImage = cv2.threshold(grayInput, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
第一步是得到一个二值图像,很简单。这是通过 Otsu 阈值的结果:
尝试从纹理(或高频)背景中分割对象从来都不是一个好主意,但是,在这种情况下,它在图像直方图中是可辨别的,并且二值图像相当不错。让我们尝试检测此图像上的边缘,我正在应用Canny
,其参数与您的代码相同:
# Get edges:
cannyImage = cv2.Canny(binaryImage, threshold1=120, threshold2=255, edges=1)
产生这个:
似乎足够好,目标边缘大部分都存在。让我们检测轮廓。这个想法是设置一个区域过滤器,因为目标轮廓是最大的。我(启发式地)设置了100000
像素的最小区域。一旦找到目标轮廓,我就会得到它的convex hull,如下所示:
# Find the EXTERNAL contours on the binary image:
contours, hierarchy = cv2.findContours(cannyImage, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Store the corners:
cornerList = []
# Look for the outer bounding boxes (no children):
for i, c in enumerate(contours):
# Approximate the contour to a polygon:
contoursPoly = cv2.approxPolyDP(c, 3, True)
# Convert the polygon to a bounding rectangle:
boundRect = cv2.boundingRect(contoursPoly)
# Get the bounding rect's data:
rectX = boundRect[0]
rectY = boundRect[1]
rectWidth = boundRect[2]
rectHeight = boundRect[3]
# Estimate the bounding rect area:
rectArea = rectWidth * rectHeight
# Set a min area threshold
minArea = 100000
# Filter blobs by area:
if rectArea > minArea:
# Get the convex hull for the target contour:
hull = cv2.convexHull(c)
# (Optional) Draw the hull:
color = (0, 0, 255)
cv2.polylines(inputImageCopy, [hull], True, color, 2)
您会注意到我事先准备了一个列表 (cornerList
),我将在其中存储(希望)所有角落。上一个 sn-p 的最后两行是可选的,它们通过cv2.polylines 绘制凸包,这将是结果图像:
仍然在循环内部,在我们计算出凸包之后,我们将通过实现角检测器的cv2.goodFeaturesToTrack
获得角。该函数接收一个二值图像,因此我们需要准备一个黑色图像,其中的凸包点用白色绘制:
# Create image for good features to track:
(height, width) = cannyImage.shape[:2]
# Black image same size as original input:
hullImg = np.zeros((height, width), dtype =np.uint8)
# Draw the points:
cv2.drawContours(hullImg, [hull], 0, 255, 2)
cv2.imshow("hullImg", hullImg)
cv2.waitKey(0)
这是图片:
现在,我们必须设置角点检测器。它需要您正在寻找的拐角数量、一个最小“质量”参数,该参数丢弃检测为“拐角”的不良点以及拐角之间的最小距离。查看documentation 了解更多参数。让我们设置检测器,它将返回一个检测到角点的点数组。得到这个数组后,我们会将每个点存储在我们的cornerList
中,如下所示:
# Set the corner detection:
maxCorners = 4
qualityLevel = 0.01
minDistance = int(max(height, width) / maxCorners)
# Get the corners:
corners = cv2.goodFeaturesToTrack(hullImg, maxCorners, qualityLevel, minDistance)
corners = np.int0(corners)
# Loop through the corner array and store/draw the corners:
for c in corners:
# Flat the array of corner points:
(x, y) = c.ravel()
# Store the corner point in the list:
cornerList.append((x,y))
# (Optional) Draw the corner points:
cv2.circle(inputImageCopy, (x, y), 5, 255, 5)
cv2.imshow("Corners", inputImageCopy)
cv2.waitKey(0)
此外,您可以将角画成圆形,它会产生这样的图像:
这是在您的第三张图片上测试的相同算法:
【讨论】:
@stateMachine
很好的解决方案和很好的解释。
@stateMachine 非常感谢您为编写此答案所付出的努力和时间! :) 看起来令人印象深刻,现在我更好地掌握了 goodFeaturesToTrack 的用法。我已经彻底阅读了您的解决方案,非常好!照明条件有时会对角点检测造成很大的麻烦,例如:ibb.co/HPgRzV3 有什么建议,如何克服这种情况?以上是关于使用openCV部分识别角落页面失败的主要内容,如果未能解决你的问题,请参考以下文章