首页 文章

如何在python中使用opencv来拉直图像的旋转矩形区域?

提问于
浏览
23

下图将告诉你我想要什么 .

我有图像中的矩形信息,宽度,高度,中心点和旋转度 . 现在,我想编写一个脚本来剪切它们并将它们保存为图像,但要理顺它们 . 就像在我想要从图像内部显示的矩形到外面显示的矩形一样 .

我正在使用OpenCV python, please 告诉我一种方法来实现这一目标 .

Kindly 显示一些代码作为OpenCV Python的例子很难找到 .

Example Image

5 回答

  • 39

    您可以使用warpAffine函数围绕定义的中心点旋转图像 . 可以使用getRotationMatrix2D(其中 theta 以度为单位)生成合适的旋转矩阵 .

    Start Image

    After finding the desired rectangle

    然后,您可以使用Numpy slicing来剪切图像 .

    Rotated Image

    Result

    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
    

    请记住 dsize 是输出图像的形状 . 如果贴片/角度足够大,如果使用原始形状,则边缘会被切断(比较上面的图像) - 为简单起见 - 在上面完成 . 在这种情况下,您可以将比例因子引入 shape (以放大输出图像)和切片的参考点(此处为 center ) .

    以上功能可以使用如下:

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

    我在这里遇到了错误的偏移问题,并在类似的问题中发布了解决方案 . 所以我做了数学计算并提出了以下有效的解决方案:

    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)
    

    这里参考的是一个解释它背后的数学的图像:

    注意

    w_dst = width-1
    h_dst = height-1
    

    那是因为最后一个坐标的值为 width-1 而不是 width ;或 height .

    如果有关于数学的问题,请将它们作为评论,我将尝试回答它们 .

  • 1

    这是我执行相同任务的C版本 . 我注意到它有点慢 . 如果有人看到任何可以改善此功能性能的东西,请告诉我 . :)

    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
    
  • 2

    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)
    
  • 12

    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 . 如果部分丢失会怎么样?请参阅以下示例:

    enter image description here

    如果要使用上述方法裁剪旋转的矩形文本区域,

    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()
    

    这就是你将得到的:

    enter image description here

    显然,有些部件被切掉了!为什么不直接扭曲旋转的矩形,因为我们可以用 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))
    

    现在裁剪的图像变成了

    enter image description here

    好多了,不是吗?如果仔细检查,您会注意到裁剪图像中有一些黑色区域 . 这是因为检测到的矩形的一小部分超出了图像的范围 . 为了解决这个问题,你可以稍微做一点,然后做一些裁剪 . this answer中有一个例子 .

    现在,我们比较两种方法从图像中裁剪旋转的矩形 . 此方法不需要旋转图像,并且可以使用更少的代码更优雅地处理此问题 .

相关问题