使用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部分识别角落页面失败的主要内容,如果未能解决你的问题,请参考以下文章

怎样使用OpenCV进行人脸识别

怎样使用OpenCV进行人脸识别

怎样使用OpenCV进行人脸识别

OpenCV(项目)车牌识别4 -- 总结篇

怎样使用OpenCV进行人脸识别

基于opencv3.0下的人脸识别和识别部分的高斯模糊处理