OpenCV / Python:cv2.minAreaRect不会返回旋转的矩形

时间:2017-02-15 17:02:33

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

我想使用修改图像。要做到这一点,我写了(无可否认得到很多帮助)一个程序:

  1. 将图像转换为更容易计算(打谷,扩张等)
  2. 在所有对象周围绘制轮廓
  3. 计算文本轮廓周围的四个极值点(忽略任何边距)
  4. 使用cv2.minAreaRect
  5. 在该区域周围绘制一个矩形

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

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

    我在“干净”的图像上测试了程序(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

1 个答案:

答案 0 :(得分:3)

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

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

你的函数get_min_max_values计算axis-aligned bounding box of all contours的角点。但你真正想要在这里计算的是最左边,最上面,最右边的坐标和所有轮廓的最底点。

而不仅仅是&#34;记住&#34;最小y,你必须保留y最小点(最顶点)的两个坐标。这同样适用于所有其他要点。

下面的代码显示了如何正确计算这些点。我决定保持代码片段的简洁性和可读性,这就是为什么我只展示如何计算最左边和最高点的原因。无论如何,所有四个点都以相同的方式计算......

正如您将注意到的那样,我不会将(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)

现在让我们看一下结果:

enter image description here

你可以看到所有四个&#34;极端&#34;点确实位于旋转的矩形内部,但由于“最小区域”,很多其他点仍留在外面。约束。你需要采取所有&#34; border&#34;计算最小旋转边界矩形时要考虑到这一点,以使其正常工作。

第2部分:有效的解决方案,您的代码需要进行少量更改

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

enter image description here

进一步改善您的解决方案

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