Python 识别拐角';带有openCV的s页面部分失败
我想得到一页的四个角, 我采取的步骤:Python 识别拐角';带有openCV的s页面部分失败,python,opencv,image-processing,Python,Opencv,Image Processing,我想得到一页的四个角, 我采取的步骤: 转换为灰度 将阈值应用于图像 Canny在边缘检测中的应用 之后,我使用了findContours 为每个多边形绘制近似多边形,我的假设是相关多边形必须有4个顶点 但一路上我发现我的解决方案有时会出错, 显然,我的解决方案不够健壮(可能有点幼稚) 我认为纸张角点检测失败的一些原因是: 手动选取阈值进行canny检测 对于approxPolyDP 我的代码 import cv2 import numpy as np image = cv2.imread
findContours
- 手动选取阈值进行canny检测
- 对于
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)
另一种方法
我想知道,将一个具有4个顶点(四边形)的多边形拟合到轮廓上,然后检查多边形与轮廓之间的面积差是否低于指定的阈值,这不是更好的方法吗
有人能推荐一个更健壮的解决方案吗(用代码演示),谢谢
图像:
图1:
图2:
图3:
图4:正如fmw42所建议的,您需要更多地限制问题。有太多的变量来构建“在任何情况下都有效”的解决方案。一个可能的、非常基本的解决方案是尝试获取页面的
凸包
另一种更稳健的方法是搜索角点的四个顶点,并外推线条,以近似纸张边缘。这样,您就不需要完美、干净的边,因为您可以使用四个(甚至三个)角来重建它们
要查找顶点,可以在边上运行Hough线检测器或角检测器,并获得至少四个可识别的端点/起点簇。由此,您可以对四个簇进行平均,以获得每个角的一对(x,y)
点,并使用这些点外推直线
对于堆栈溢出问题,该解决方案是假设性的,而且相当费劲,所以让我尝试第一个方案——通过凸包检测。以下是步骤:
对输入图像设置阈值
从输入中获取边
使用最小面积过滤器获取边缘的外部轮廓
获取过滤图像的凸包
获取凸面外壳的角点
让我们看看代码:
# 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)
第一步是得到一个二进制图像,非常简单。这是通过大津设置阈值的结果:
尝试从纹理(或高频)背景中分割对象从来都不是一个好主意,但是,在这种情况下,在图像直方图中它是可以识别的,并且二值图像相当好。让我们尝试检测此图像上的边缘,我正在使用与代码相同的参数应用Canny
:
# Get edges:
cannyImage = cv2.Canny(binaryImage, threshold1=120, threshold2=255, edges=1)
这就产生了:
看起来已经足够好了,目标边缘大部分都存在。让我们检测轮廓。我们的想法是设置一个区域过滤器,因为目标轮廓是其他轮廓中最大的。I(启发式)将最小面积设置为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)
# 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)
你会注意到我事先准备了一个列表(cornerList
),我希望能在其中存储所有的角落。前面代码段的最后两行是可选的,它们通过绘制凸面外壳,这将是生成的图像:
仍然在循环内部,在我们计算凸包之后,我们将通过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)
这是图像:
现在,我们必须设置拐角检测器。它需要您正在寻找的角的数量、丢弃检测为“角”的不良点的最小“质量”参数以及角之间的最小距离。有关更多参数,请查看。让我们设置检测器,它将返回一个点阵列,在那里它检测到一个角点。获取此数组后,我们将在角点列表中存储每个点,如下所示:
# 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)
# 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)
此外,您还可以将角绘制为圆,这将产生以下图像:
这是在第三张图像上测试的相同算法:
你需要更好的背景和灯光。光线的反射使得很难从背景中确定页面。使用漫反射照明。此外,纹理背景也很难处理。使用恒定颜色。如果您一次拍摄多页照片(3页和4页有两页),则很容易获得超过4个角。您可以使用cv2.goodFeaturesToTrack()进行角点检测。它可以让您避免角落太近。例如,请参阅@stateMachine
很好的解决方案和很好的解释。@stateMachine非常感谢您为撰写此答案付出的努力和时间!!:)看起来令人印象深刻,现在我更好地掌握了跟踪使用情况的良好特性。我已经彻底阅读了你的解决方案,非常好!照明条件有时会给角点检测带来很大的麻烦,比如:有什么建议,如何克服这种情况?