使用openCV识别角落页面部分失败

Jam*_*its 4 python opencv image-processing

我想获取页面的 4 个角,我采取的步骤:

  1. 转换为灰度
  2. 对图像应用阈值
  3. 应用Canny检测边缘
  4. 之后我用过findContours
  5. 为每个多边形绘制近似多边形,我的假设是相关多边形必须有 4 个顶点。

但一路上我发现我的解决方案有时会失败,显然我的解决方案不够健壮(可能有点幼稚的解决方案)。

在此输入图像描述

我认为纸角检测失败的一些原因是:

  • 手动选择阈值以进行精明检测。
  • epsilon 值相同approxPolyDP

我的代码

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)
Run Code Online (Sandbox Code Playgroud)

另一种方法

我想知道将具有 4 个顶点(四边形)的多边形拟合到轮廓,然后检查多边形与轮廓之间的面积差异是否低于指定阈值不是更好的方法。

有人可以建议一个更强大的解决方案(用代码演示),谢谢。

图像:

图片1:https: //ibb.co/K2SqLwZ

图片2:https: //ibb.co/mbGFsNp

图片3: https: //ibb.co/m6QKkzw

图片4: https: //ibb.co/xh7W41V

sta*_*ine 8

正如 fmw42 所建议的,您需要更多地限制问题。构建“在所有情况下都有效”的解决方案存在太多变量。一个可能的、非常基本的解决方案是尝试获取convex hull页面的。

另一种更稳健的方法是搜索角的四个顶点并推断线以近似纸张边缘。这样你就不需要完美、干净的边缘,因为你可以使用四个(甚至三个)角来重建它们。

要找到顶点,您可以在边缘上运行霍夫线检测器角点检测器,并获得至少四个可辨别的终点/起点簇。由此,您可以对四个簇进行平均,以获得(x, y)每个角的一对点,并使用这些点推断线。

对于 Stack Overflow 问题来说,这个解决方案是假设性的,而且相当费力,所以让我尝试第一个建议 -通过凸包检测。步骤如下:

  1. 输入图像的阈值
  2. 从输入中获取边缘
  3. 使用最小面积过滤器获取边缘的外部轮廓
  4. 获取滤波图像的凸包
  5. 获取凸包的角点

我们看一下代码:

# 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)
Run Code Online (Sandbox Code Playgroud)

第一步是获取二值图像,非常简单。这是如果您通过 Otsu 设定阈值的结果:

尝试从纹理(或高频)背景中分割对象从来都不是一个好主意,但是,在这种情况下,论文可以在图像直方图中辨别对象,并且二值图像相当不错。让我们尝试检测该图像上的边缘,我使用Canny与您的代码相同的参数:

# Get edges:
cannyImage = cv2.Canny(binaryImage, threshold1=120, threshold2=255, edges=1)
Run Code Online (Sandbox Code Playgroud)

产生这个:

看起来足够好,目标边缘大部分都存在。让我们检测轮廓。这个想法是设置一个区域过滤器,因为目标轮廓是其余轮廓中最大的。我(试探性地)设置了最小像素区域100000。一旦找到目标轮廓,我就会得到它的凸包,如下所示:

# 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)
Run Code Online (Sandbox Code Playgroud)

您会注意到我事先准备了一个列表(cornerList),我将(希望)存储所有角落。上一个片段的最后两行是可选的,它们通过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)
Run Code Online (Sandbox Code Playgroud)

这是图像:

现在,我们必须设置角点检测器。它需要您正在寻找的角点数量、丢弃检测为“角点”的不良点的最小“质量”参数以及角点之间的最小距离。查看文档了解更多参数。让我们设置检测器,它将返回检测到角点的点数组。得到这个数组后,我们将把每个点存储在我们的 中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)
        
Run Code Online (Sandbox Code Playgroud)

此外,您可以将角绘制为圆圈,它将产生以下图像:

这是在第三张图像上测试的相同算法: