简体   繁体   English

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

[英]OpenCV/Python: cv2.minAreaRect won't return a rotated rectangle

I want to deskew an image using. 我想使用图像去歪斜。 To do that I wrote (admittedly with lots of help) a program that: 为此,我编写了一个程序(必须有很多帮助):

  1. transforms image to be a easier to compute (thresh, dilation, etc.) 将图像转换为更易于计算(脱粒,膨胀等)
  2. draws contours around all objects 在所有对象周围绘制轮廓
  3. computes four extreme points around the text contours (ignoring anything with a margin) 计算文字轮廓周围的四个极限点(忽略任何带有边距的东西)
  4. draws a rectangle around that area using cv2.minAreaRect 使用cv2.minAreaRect在该区域周围绘制一个矩形

The idea was that cv2.minAreaRect returns the angle as well, which I could use to deskew the image. 这个想法是cv2.minAreaRect也返回角度,我可以使用它来校正图像。 However, in my case it's –90°. 但是,在我的情况下是–90°。

You can see a sample input image 您可以看到一个示例输入图像 这里 . You can see the result I get 你可以看到我得到的结果 这里 .

I tested the program on a “clean” image (MS Word Screenshot rotaten ≈ 30° in Gimp) and it gave an identical result. 我在“干净”的图像上测试了该程序(MS Word屏幕截图在Gimp中旋转了≈30°),并且给出了相同的结果。

My code: 我的代码:

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)

Why isn't the rectangle skewed to match the text? 为什么矩形不倾斜以匹配文本? I don't see any artifacts that would justify that. 我看不到任何可以证明这一点的文物。

EDIT: Added clean, born digital files: input and output . 编辑:添加了干净的出生数字文件: 输入输出

TLDR : Use convex hull instead of four points only! TLDR :仅使用凸包而不是四个点!

Part 1: The error(s) in your current approach. 第1部分:您当前方法中的错误。

Your function get_min_max_values computes the corner points of axis-aligned bounding box of all contours . 您的函数get_min_max_values计算所有轮廓线轴对齐边界框的角点。 But what you actually want to compute here are the coordinates of the leftmost, topmost, rightmost and the botommost point of all the contours. 但是,您实际要在此处计算的是所有轮廓的最左,最顶,最右和最底点的坐标。

Instead of only "remembering" the minimal y, you have to retain both coordinates of the point where y was minimal (topmost point). 不仅要“记住”最小的y,还必须保留y最小的点(最高点)的两个坐标。 The same applies for all other points. 所有其他要点也一样。

The code below shows how to compute those points properly. 下面的代码显示了如何正确计算这些点。 I decided to keep the code snippet short and readable, that's why I only show how to compute leftmost and topmost point here. 我决定使代码段简短易读,这就是为什么我仅在此处显示如何计算最左和最高点的原因。 All four points are computed in the same way anyway ... 无论如何,所有四个点都以相同的方式计算...

As you will notice, I do not compare ( clamp ) the points to the margin withing the loop; 如您所见,我不会随循环将这些点与边距进行比较( 钳位 ); instead, I do this only once at the end of the loop since doing this produces the same results but the code is simpler. 相反,我在循环结束时只执行一次,因为这样做会产生相同的结果,但是代码更简单。

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)

Now let us take look at the results: 现在让我们看一下结果:

在此处输入图片说明

You can see that all four "extremal" points are indeed inside the rotated rectangle but lots of other points remain outside because of the "minimal area" constraint. 您可以看到所有四个“极值”点确实在旋转的矩形内,但由于“最小面积”约束,许多其他点仍在外面。 You need to take all "border" points into account when you compute minumum rotated bounding rectangle to make this work right. 计算最小旋转边界矩形时,需要考虑所有“边界”点,以使其正常工作。

Part 2: The solution that works and requires minimal changes in your code 第2部分:该解决方案有效并且需要对代码进行最少的更改

After you compute the contours with findContours, you have to copy all those contour points to the same array and then finally you have pass that array to the convexHull function . 用findContours计算轮廓后,必须将所有轮廓点复制到同一数组中,然后最后将该数组传递给凸面函数 This function computes the convex hull points. 此函数计算凸包点。 You then use those points as input for minAreaRect function and this is what you obtain: 然后,将这些点用作minAreaRect函数的输入,这是您获得的结果:

在此处输入图片说明

Further improving your solution 进一步改善您的解决方案

I'm quite sure your algorithm can run much faster if you do not compute contours at all. 如果您完全不计算轮廓,我很确定您的算法可以运行得更快。 Instead, just use the thresholded pixel positions as inputs for convex hull function. 相反,仅将阈值像素位置用作凸包函数的输入。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM