简体   繁体   English

如何在 Python 中使用 OpenCV 拉直图像的旋转矩形区域?

[英]How to straighten a rotated rectangle area of an image using OpenCV in Python?

The following picture will tell you what I want.下图会告诉你我想要什么。

I have the information of the rectangles in the image (width, height, center point and rotation degree).我有图像中矩形的信息(宽度、高度、中心点和旋转度)。 Now, I want to write a script to cut them out and save them as an image, but straighten them as well.现在,我想写一个脚本来剪掉它们并将它们保存为图像,但也要拉直它们。 As in, I want to go from the rectangle shown inside the image to the rectangle that is shown outside.比如,我想从图像内部显示的矩形转到外部显示的矩形。

I am using OpenCV Python.我正在使用 OpenCV Python。 Please tell me a way to accomplish this.告诉我一个方法来完成这个。

Kindly show some code as examples of OpenCV Python are hard to find.展示一些代码,因为 OpenCV Python 的例子很难找到。

示例图片

You can use the warpAffine function to rotate the image around a defined center point.您可以使用warpAffine函数围绕定义的中心点旋转图像。 The suitable rotation matrix can be generated using getRotationMatrix2D (where theta is in degrees ).可以使用getRotationMatrix2D (其中theta为单位)生成合适的旋转矩阵。

起始图像找到所需的矩形后

You then can useNumpy slicing to cut the image.然后您可以使用Numpy 切片来切割图像。

旋转图像结果

import cv2
import numpy as np

def subimage(image, center, theta, width, height):

   ''' 
   Rotates OpenCV image around center with angle theta (in deg)
   then crops the image according to width and height.
   '''

   # Uncomment for theta in radians
   #theta *= 180/np.pi

   shape = ( image.shape[1], image.shape[0] ) # cv2.warpAffine expects shape in (length, height)

   matrix = cv2.getRotationMatrix2D( center=center, angle=theta, scale=1 )
   image = cv2.warpAffine( src=image, M=matrix, dsize=shape )

   x = int( center[0] - width/2  )
   y = int( center[1] - height/2 )

   image = image[ y:y+height, x:x+width ]

   return image

Keep in mind that dsize is the shape of the output image.请记住, dsize输出图像的形状。 If the patch/angle is sufficiently large, edges get cut off (compare image above) if using the original shape as--for means of simplicity--done above.如果补丁/角度足够大,如果使用原始形状(为了简单起见),边缘会被切断(比较上图)。 In this case, you could introduce a scaling factor to shape (to enlarge the output image) and the reference point for slicing (here center ).在这种情况下,您可以引入一个缩放因子来shape (放大输出图像)和切片的参考点(这里是center )。

The above function can be used as follows:上面的函数可以按如下方式使用:

image = cv2.imread('owl.jpg')
image = subimage(image, center=(110, 125), theta=30, width=100, height=200)
cv2.imwrite('patch.jpg', image)

I had problems with wrong offsets while using the solutions here and in similar questions.在此处和类似问题中使用解决方案时,我遇到了错误偏移的问题。

So I did the math and came up with the following solution that works:所以我做了数学计算并提出了以下有效的解决方案:

def subimage(self,image, center, theta, width, height):
    theta *= 3.14159 / 180 # convert to rad

    v_x = (cos(theta), sin(theta))
    v_y = (-sin(theta), cos(theta))
    s_x = center[0] - v_x[0] * ((width-1) / 2) - v_y[0] * ((height-1) / 2)
    s_y = center[1] - v_x[1] * ((width-1) / 2) - v_y[1] * ((height-1) / 2)

    mapping = np.array([[v_x[0],v_y[0], s_x],
                        [v_x[1],v_y[1], s_y]])

    return cv2.warpAffine(image,mapping,(width, height),flags=cv2.WARP_INVERSE_MAP,borderMode=cv2.BORDER_REPLICATE)

For reference here is an image that explains the math behind it:作为参考,这里有一张图片解释了它背后的数学原理:

Note that注意

w_dst = width-1
h_dst = height-1

This is because the last coordinate has the value width-1 and not width , or height .这是因为最后一个坐标的值为width-1而不是widthheight

The other methods will work only if the content of the rectangle is in the rotated image after rotation and will fail badly in other situations .其他方法只有当矩形的内容在旋转后的旋转图像中时才有效,在其他情况下会严重失败 What if some of the part are lost?如果部分零件丢失了怎么办? See an example below:请参见下面的示例:

在此处输入图像描述

If you are to crop the rotated rectangle text area using the above method,如果您要使用上述方法裁剪旋转的矩形文本区域,

import cv2
import numpy as np


def main():
    img = cv2.imread("big_vertical_text.jpg")
    cnt = np.array([
            [[64, 49]],
            [[122, 11]],
            [[391, 326]],
            [[308, 373]]
        ])
    print("shape of cnt: {}".format(cnt.shape))
    rect = cv2.minAreaRect(cnt)
    print("rect: {}".format(rect))

    box = cv2.boxPoints(rect)
    box = np.int0(box)

    print("bounding box: {}".format(box))
    cv2.drawContours(img, [box], 0, (0, 0, 255), 2)

    img_crop, img_rot = crop_rect(img, rect)

    print("size of original img: {}".format(img.shape))
    print("size of rotated img: {}".format(img_rot.shape))
    print("size of cropped img: {}".format(img_crop.shape))

    new_size = (int(img_rot.shape[1]/2), int(img_rot.shape[0]/2))
    img_rot_resized = cv2.resize(img_rot, new_size)
    new_size = (int(img.shape[1]/2)), int(img.shape[0]/2)
    img_resized = cv2.resize(img, new_size)

    cv2.imshow("original contour", img_resized)
    cv2.imshow("rotated image", img_rot_resized)
    cv2.imshow("cropped_box", img_crop)

    # cv2.imwrite("crop_img1.jpg", img_crop)
    cv2.waitKey(0)


def crop_rect(img, rect):
    # get the parameter of the small rectangle
    center = rect[0]
    size = rect[1]
    angle = rect[2]
    center, size = tuple(map(int, center)), tuple(map(int, size))

    # get row and col num in img
    height, width = img.shape[0], img.shape[1]
    print("width: {}, height: {}".format(width, height))

    M = cv2.getRotationMatrix2D(center, angle, 1)
    img_rot = cv2.warpAffine(img, M, (width, height))

    img_crop = cv2.getRectSubPix(img_rot, size, center)

    return img_crop, img_rot


if __name__ == "__main__":
    main()

This is what you will get:这就是你将得到的:

在此处输入图像描述

Apparently, some of the parts are cut out!显然,有些部分被切掉了! Why do not directly warp the rotated rectangle since we can get its four corner points with cv.boxPoints() method?既然我们可以用cv.boxPoints()方法得到它的四个角点,为什么不直接扭曲旋转的矩形呢?

import cv2
import numpy as np


def main():
    img = cv2.imread("big_vertical_text.jpg")
    cnt = np.array([
            [[64, 49]],
            [[122, 11]],
            [[391, 326]],
            [[308, 373]]
        ])
    print("shape of cnt: {}".format(cnt.shape))
    rect = cv2.minAreaRect(cnt)
    print("rect: {}".format(rect))

    box = cv2.boxPoints(rect)
    box = np.int0(box)
    width = int(rect[1][0])
    height = int(rect[1][1])

    src_pts = box.astype("float32")
    dst_pts = np.array([[0, height-1],
                        [0, 0],
                        [width-1, 0],
                        [width-1, height-1]], dtype="float32")
    M = cv2.getPerspectiveTransform(src_pts, dst_pts)
    warped = cv2.warpPerspective(img, M, (width, height))

Now the cropped image becomes现在裁剪后的图像变成

在此处输入图像描述

Much better, isn't it?好多了,不是吗? If you check carefully, you will notice that there are some black area in the cropped image.如果仔细检查,您会注意到裁剪后的图像中有一些黑色区域。 That is because a small part of the detected rectangle is out of the bound of the image.那是因为检测到的矩形的一小部分超出了图像的边界。 To remedy this, you may pad the image a little bit and do the crop after that.为了解决这个问题,您可以稍微填充图像,然后再进行裁剪。 There is an example illustrated in this answer .此答案中有一个示例。

Now, we compare the two methods to crop the rotated rectangle from the image.现在,我们比较两种从图像中裁剪旋转矩形的方法。 This method do not require rotating the image and can deal with this problem more elegantly with less code.这种方法不需要旋转图像,可以用更少的代码更优雅地处理这个问题。

Similar recipe for openCV version 3.4.0. openCV 版本 3.4.0 的类似配方。

from cv2 import cv
import numpy as np

def getSubImage(rect, src):
    # Get center, size, and angle from rect
    center, size, theta = rect
    # Convert to int 
    center, size = tuple(map(int, center)), tuple(map(int, size))
    # Get rotation matrix for rectangle
    M = cv2.getRotationMatrix2D( center, theta, 1)
    # Perform rotation on src image
    dst = cv2.warpAffine(src, M, src.shape[:2])
    out = cv2.getRectSubPix(dst, size, center)
    return out

img = cv2.imread('img.jpg')
# Find some contours
thresh2, contours, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# Get rotated bounding box
rect = cv2.minAreaRect(contours[0])
# Extract subregion
out = getSubImage(rect, img)
# Save image
cv2.imwrite('out.jpg', out)

This is my C++ version that performs the same task.这是我执行相同任务的 C++ 版本。 I have noticed it is a bit slow.我注意到它有点慢。 If anyone sees anything that would improve the performance of this function, then please let me know.如果有人看到任何可以提高此功能性能的内容,请告诉我。 :) :)

bool extractPatchFromOpenCVImage( cv::Mat& src, cv::Mat& dest, int x, int y, double angle, int width, int height) {

  // obtain the bounding box of the desired patch
  cv::RotatedRect patchROI(cv::Point2f(x,y), cv::Size2i(width,height), angle);
  cv::Rect boundingRect = patchROI.boundingRect();

  // check if the bounding box fits inside the image
  if ( boundingRect.x >= 0 && boundingRect.y >= 0 &&
       (boundingRect.x+boundingRect.width) < src.cols &&  
       (boundingRect.y+boundingRect.height) < src.rows ) { 

    // crop out the bounding rectangle from the source image
    cv::Mat preCropImg = src(boundingRect);

    // the rotational center relative tot he pre-cropped image
    int cropMidX, cropMidY;
    cropMidX = boundingRect.width/2;
    cropMidY = boundingRect.height/2;

    // obtain the affine transform that maps the patch ROI in the image to the
    // dest patch image. The dest image will be an upright version.
    cv::Mat map_mat = cv::getRotationMatrix2D(cv::Point2f(cropMidX, cropMidY), angle, 1.0f);
    map_mat.at<double>(0,2) += static_cast<double>(width/2 - cropMidX);
    map_mat.at<double>(1,2) += static_cast<double>(height/2 - cropMidY);

    // rotate the pre-cropped image. The destination image will be
    // allocated by warpAffine()
    cv::warpAffine(preCropImg, dest, map_mat, cv::Size2i(width,height)); 

    return true;
  } // if
  else {
    return false;
  } // else
} // extractPatch

This was a very frustrating endeavor, but finally I solved it based on rroowwllaanndd 's answer.这是一个非常令人沮丧的尝试,但最终我根据rroowwllaanndd的回答解决了它。 I just had to add the angle correction when the width < height .width < height时,我只需要添加角度校正 Without this I got very strange results for images which fulfilled this condition.没有这个,对于满足此条件的图像,我会得到非常奇怪的结果。

def crop_image(rect, image):
    shape = (image.shape[1], image.shape[0])  # cv2.warpAffine expects shape in (length, height)
    center, size, theta = rect
    width, height = tuple(map(int, size))
    center = tuple(map(int, center))
    if width < height:
        theta -= 90
        width, height = height, width

    matrix = cv.getRotationMatrix2D(center=center, angle=theta, scale=1.0)
    image = cv.warpAffine(src=image, M=matrix, dsize=shape)

    x = int(center[0] - width // 2)
    y = int(center[1] - height // 2)

    image = image[y : y + height, x : x + width]

    return image

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

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