如何在Python中从模糊的图像中找到扭曲矩形的精确角落位置?

5
我正在寻找一种使用Python中的OpenCV准确检测扭曲矩形角落的方法。
通过谷歌搜索不同建议的解决方案,我尝试了正弦波叠加直线的方法(请参见阈值图像),但可能无法检测到角落。到目前为止,我已经尝试了findContours和HoughLines,但效果不佳。不幸的是,我不理解Xu Bin在how to find blur corner position with opencv?中的C代码。
这是我的初始图像:

enter image description here

调整大小和阈值后,我应用canny边缘检测来获得以下图像:

contours, hierarchy = cv2.findContours(g_mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
box = cv2.minAreaRect(contour)
box = cv2.cv.BoxPoints(box) if imutils.is_cv2() else cv2.boxPoints(box)
box = np.array(box, dtype="float")
box = perspective.order_points(box)

我只能得到以下结果,带有一些额外的绘画:

我认为线拟合是解决问题的好方法,但不幸的是我无法让HoughLines正常工作,在查看了OpenCV Python - How to implement RANSAC to detect straight lines?后,发现RANSAC似乎也难以应用于我的问题。
非常感谢您的帮助。

也许需要先进行一些预处理。将图像转换为灰度并进行阈值处理。如果需要,可以使用一些形态学方法来清理它。然后获取轮廓等。 - fmw42
2个回答

6
尽管这是旧的内容,但它至少可以帮助那些有同样问题的人。除了nathancy的答案外,以下步骤可以让您更准确地找到非常模糊的角落:
伪代码
  1. 如果需要,调整大小,但不是必需的
  2. 转换为灰度图像
  3. 应用模糊或双边滤波
  4. 应用Otsu阈值以获取二进制图像
  5. 查找组成矩形的轮廓
  6. 将轮廓近似为矩形
  7. 近似点是您矩形的角落!
代码
  1. 调整大小:
    该函数接受新的宽度和高度,因此我只是将图像放大5倍。
img = cv2.resize(img, (img.shape[0] * 5, img.shape[1] * 5))

resized

  1. 灰度转换:
    只需从OpenCV的默认BGR颜色空间转换为灰度。
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

gray

  1. 模糊/双边滤波:
    如果需要进一步柔化这张图片,您可以使用许多技术。也许是高斯模糊,或者像nathancy建议的双边滤波,但不需要两种都使用。
# choose one, or a different function
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
blurred = cv2.bilateralFilter(gray, 9, 75, 75)

blurred

3. 大津阈值法
使用阈值函数,将阈值和最大值的参数传递为0255。我们传递0是因为我们使用阈值技术cv2.THRESH_OTSU来确定阈值。这个值与阈值本身一起返回,但我只将其设置为_,因为我们不需要它。
_, thresh = cv2.threshold(blurred, 0, 255, cv2.THRESH_OTSU)

thresh

  1. 查找轮廓
    关于轮廓的内容非常丰富,我在这里不会进行详细解释,请随意查看文档。 对于我们来说,需要知道的重要事情是它返回一个轮廓列表和层次结构。我们不需要层次结构,因此将其设置为_,我们只需要找到的单个轮廓,所以我们设置contour = contours[0]
contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
contour = contours[0]

contour

  1. 将轮廓近似为矩形
    首先,我们计算轮廓的周长。然后,我们使用cv2.approxPolyDP函数近似它,并告诉它原始曲线与其近似之间的最大距离为0.05 * perimeter。您可能需要尝试不同的小数以获得更好的近似值。
    approx是一个numpy数组,形状为(num_points, 1, 2),在本例中为(4, 1, 2),因为它找到了矩形的4个角落。

欢迎在文档中阅读更多内容。

perimeter = cv2.arcLength(contour, True)
approx = cv2.approxPolyDP(contour, 0.05 * perimeter, True)
  1. 寻找你的斜矩形!
    你已经完成了!这是如何绘制这些点的方法。首先,我们通过循环覆盖它们来绘制圆,然后获取x和y坐标,然后绘制矩形本身。
# drawing points
for point in approx:
    x, y = point[0]
    cv2.circle(img, (x, y), 3, (0, 255, 0), -1)

# drawing skewed rectangle
cv2.drawContours(img, [approx], -1, (0, 255, 0))

finished product

完成的代码

import cv2

img = cv2.imread("rect.png")

img = cv2.resize(img, (img.shape[0] * 5, img.shape[1] * 5))
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blurred = cv2.bilateralFilter(gray, 9, 75, 75)
_, thresh = cv2.threshold(blurred, 0, 255, cv2.THRESH_OTSU)

contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
contour = contours[0]

perimeter = cv2.arcLength(contour, True)
approx = cv2.approxPolyDP(contour, 0.05 * perimeter, True)

for point in approx:
    x, y = point[0]
    cv2.circle(img, (x, y), 3, (0, 255, 0), -1)
cv2.drawContours(img, [approx], -1, (0, 255, 0))

2
为了检测角点,您可以使用 cv2.goodFeaturesToTrack()。该函数需要四个参数。
corners = cv2.goodFeaturesToTrack(image, maxCorners, qualityLevel, minDistance)
  • image - 输入8位或浮点32位灰度单通道图像
  • maxCorners - 返回的最大角点数
  • qualityLevel - 角点的最小接受质量水平在0-1之间。所有低于质量水平的角点都被拒绝
  • minDistance - 角点之间可能的最小欧几里得距离

现在我们知道如何找到角点,我们必须找到旋转矩形并应用该函数。以下是一种方法:


我们首先放大图像,将其转换为灰度图像,应用双边滤波器,然后使用Otsu阈值得到二进制图像。

接下来,我们通过使用cv2.findContours()找到包含畸变矩形的轮廓,然后获得用绿色突出显示的旋转边界框。我们将此边界框绘制在掩模上。

现在我们有了掩膜,只需使用cv2.goodFeaturesToTrack()在掩膜上查找角点。

这是原始输入图像的结果和每个角落的(x, y)坐标。

角点
(377.0, 375.0)
(81.0, 344.0)
(400.0, 158.0)
(104.0, 127.0)

代码

import cv2
import numpy as np
import imutils

# Resize image, blur, and Otsu's threshold
image = cv2.imread('1.png')
resize = imutils.resize(image, width=500)
mask = np.zeros(resize.shape, dtype=np.uint8)
gray = cv2.cvtColor(resize, cv2.COLOR_BGR2GRAY)
blur = cv2.bilateralFilter(gray,9,75,75)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]

# Find distorted rectangle contour and draw onto a mask
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
rect = cv2.minAreaRect(cnts[0])
box = cv2.boxPoints(rect)
box = np.int0(box)
cv2.drawContours(resize,[box],0,(36,255,12),2)
cv2.fillPoly(mask, [box], (255,255,255))

# Find corners on the mask
mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
corners = cv2.goodFeaturesToTrack(mask, maxCorners=4, qualityLevel=0.5, minDistance=150)

for corner in corners:
    x,y = corner.ravel()
    cv2.circle(resize,(x,y),8,(155,20,255),-1)
    print("({}, {})".format(x,y))

cv2.imshow('resize', resize)
cv2.imshow('thresh', thresh)
cv2.imshow('mask', mask)
cv2.waitKey()

嗨nathancy, 感谢您添加我的帖子和答案。 我尝试使用该方法,结果是最后一张图片。我遇到的问题是,矩形适配只对右上角和左下角给出合理的结果。这种方法并不能真正检测到左上角和右下角。 我想要的是像图像中的绿色矩形那样的东西: https://imgur.com/a/ukdzkKA - Robin2505
要得到所需的绿色矩形很困难,因为图像非常模糊,因此通过阈值处理或Canny边缘检测获得二进制图像时,物体和背景之间的分界线是模糊的。您可以尝试的潜在方法是应用双边滤波器并尝试使用Canny边缘检测进行实验。 - nathancy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接