【问题标题】:OpenCV/Python: cv2.minAreaRect won't return a rotated rectangleOpenCV/Python:cv2.minAreaRect 不会返回一个旋转的矩形
【发布时间】:2017-07-04 11:45:12
【问题描述】:

我想使用纠偏图像。为此,我编写了一个程序(诚然有很多帮助):

  1. 将图像转换为更易于计算(thresh、dilation 等)
  2. 围绕所有对象绘制轮廓
  3. 计算文本轮廓周围的四个极值点(忽略任何有边距的东西)
  4. 使用 cv2.minAreaRect 在该区域周围绘制一个矩形

这个想法是 cv2.minAreaRect 也返回角度,我可以用它来歪斜图像。但是,就我而言,它是 –90°。

您可以看到示例输入图像。 你可以看到我得到的结果

我在“干净”的图像上测试了程序(MS Word 屏幕截图在 Gimp 中旋转 ≈ 30°),得到了相同的结果。

我的代码:

import numpy as np
import cv2
import itertools

img = cv2.imread('zuo.png')
imgray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

ret,thresh = cv2.threshold(imgray,64,255,0)
############
kernel = np.ones((2,2),np.uint8)
img_e = cv2.dilate(thresh,kernel,iterations = 1)
# cv2.imwrite("out_eroded.png", img_e)
# http://docs.opencv.org/3.0-beta/doc/py_tutorials/py_imgproc/py_morphological_ops/py_morphological_ops.html
# img_e = thresh
############
imgbw, contours, hierarchy = cv2.findContours(img_e,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
# imgbw, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)

margin_distance = 25

def flatten(arr, n = 1):
    # print(arr)
    ret = list(itertools.chain.from_iterable(arr))
    # print(ret)
    if n != 1:
        return flatten(ret, n - 1)
    else:
        return ret

# print(list(flatten([[1,2,3],[4,5,6], [7], [8,9]])))

def get_min_max_values(cs, im_y, im_x):
    # print(flatten(cs), 1)
    # print(im_y, im_x)
    min_y = im_y - margin_distance
    min_x = im_x - margin_distance
    max_y = margin_distance
    max_x = margin_distance
    for lvl1 in cs:
        for lvl2 in lvl1:
            x, y = lvl2[0]
            # x = im_x - x
            # y = im_y - y
            max_y = max(y, max_y) if y + margin_distance < im_y else max_y
            max_x = max(x, max_x) if x + margin_distance < im_x else max_x
            min_y = min(y, min_y) if y > margin_distance else min_y
            min_x = min(x, min_x) if x > margin_distance else min_x

    return ((min_y, min_x), (min_y, max_x), (max_y, min_x), (max_y, max_x))

new_rect = get_min_max_values(contours, len(img), len(img[0]))
new_rect = list(map(lambda x: list(x)[::-1], list(new_rect)))
print(new_rect)
rect = cv2.minAreaRect(np.int0(new_rect))
# print(rect)
print(rect)
box = cv2.boxPoints(rect)
box = np.int0(box)

img_out = cv2.drawContours(img, [box], -1, (0,0,255), 5) # -1 = wszystkie kontury
img_out = cv2.drawContours(img, contours, -1, (0,255,0), 3)

cv2.imwrite("out.png", img_out)

为什么矩形没有倾斜以匹配文本?我没有看到任何可以证明这一点的人工制品。

编辑:添加了干净的、天生的数字文件:inputoutput

【问题讨论】:

  • 你能画出new_rect中包含的点(用红色圆圈或其他什么)吗?
  • 您也可以添加 GIMP 图像和结果吗?
  • 在 get_min_max_values 中返回一个轴对齐的矩形(角点),这就是 minAreaRect 优化的内容。
  • @Miki 你的意思是生成一个形状并使用这些坐标在原始文件上绘制它吗?
  • 我的意思是你最好画出这些点(在你的图像上使用“圆圈”),因为它们可能是错误的,正如 Micka 所说的

标签: python image python-3.x opencv image-processing


【解决方案1】:

TLDR:使用凸包而不是仅使用四个点!

第 1 部分:您当前方法中的错误。

您的函数 get_min_max_values 计算 axis-aligned bounding box of all contours 的角点。但是这里真正要计算的是所有轮廓的最左、最上、最右和最下点的坐标。

您不仅要“记住”最小的 y,还必须保留 y 最小的点(最高点)的两个坐标。这同样适用于所有其他点。

下面的代码显示了如何正确计算这些点。我决定保持代码 sn-p 简短易读,这就是为什么我在这里只展示如何计算最左边和最上面的点。无论如何,所有四个点都以相同的方式计算...

您会注意到,我不会将 (clamp) 点与循环的边距进行比较;相反,我只在循环结束时执行一次,因为这样做会产生相同的结果,但代码更简单。

def get_min_max_values(cs, im_height, im_width):

  min_y = im_height - margin_distance
  min_x = im_width - margin_distance

  left_point = (min_y, min_x)
  top_point = (min_y, min_x)

  for lvl1 in cs:
    for lvl2 in lvl1:
        x, y = lvl2[0]

        left_point = left_point if x > left_point[1] else (y, x) 
        top_point  = top_point if y > top_point[0]  else (y, x)

  left_point[0] = left_point[0] if left_point[0] > margin_distance else margin_distance + 1
  left_point[1] = left_point[1] if left_point[1] > margin_distance else margin_distance + 1

  top_point[0] = top_point[0] if top_point[0] > margin_distance else margin_distance + 1
  top_point[1] = top_point[1] if top_point[1] > margin_distance else margin_distance + 1


  return (top_point, left_point)

现在让我们看看结果:

您可以看到所有四个“极值”点确实都在旋转的矩形内,但由于“最小区域”约束,许多其他点仍然在外面。当您计算最小旋转边界矩形时,您需要考虑所有“边界”点以使其正常工作。

第 2 部分:行之有效的解决方案,只需对代码进行最少的更改

在使用 findContours 计算轮廓后,您必须将所有这些轮廓点复制到同一个数组,然后最后将该数组传递给 convexHull function。此函数计算convex hull 点。然后,您将这些点用作 minAreaRect 函数的输入,这就是您获得的:

进一步改进您的解决方案

如果您根本不计算轮廓,我很确定您的算法可以运行得更快。相反,只需使用阈值像素位置作为凸包函数的输入。

【讨论】:

  • 感谢您的建议和示例代码。我尝试推断和调整您建议的其余代码,但它一直返回非常奇怪的坐标。我对如何应用您的最终建议有一个想法,所以尽管我在运行此代码时遇到问题,但请让我接受您的回答。
  • 我现在才注意到您在我回答之前编辑了帖子(没有刷新)。非常感谢您的帮助-您获得的输出正是我想要的!但是,我无法重现您的结果 - 它只是不会绘制矩形。你能看看我的尝试并告诉我我错过了什么吗? pastebin.com/VEVHgtii
  • 您好,看来您只需确保将 all 找到的轮廓中的 all 点放入单个数组中,然后将其传递作为convexHull 的参数。顺便说一句,不要忘记我的建议,即避免使用轮廓并直接使用阈值点;这将使工作更容易,结果将是相同的。 :)
  • 啊,我明白了。我会尝试这样做。您的建议并没有被忽视,但我主要了解轮廓方法中的代码,而我还不知道如何直接发送像素。但是,一旦我使它以任何方式工作,我将很乐意尝试您提出的更好的解决方案。无论如何,再次感谢!这周你是我最喜欢的人。
  • 如何将所有阈值像素放入一个数组中(伪代码):pastebin.com/LdnAR3eH。然后将此数组用作convexHull 参数。
猜你喜欢
  • 2016-09-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-06
相关资源
最近更新 更多