Как выпрямить область с вращающимся прямоугольником изображения, используя opencv в python? - программирование
Подтвердить что ты не робот

Как выпрямить область с вращающимся прямоугольником изображения, используя opencv в python?

На следующем рисунке вы узнаете, чего я хочу.

У меня есть информация о прямоугольниках в изображении, ширине, высоте, центральной точке и степени вращения. Теперь я хочу написать script, чтобы вырезать их и сохранить их как изображение, но выпрямить их. Как и в том, что я хочу перейти от прямоугольника, показанного внутри изображения, к прямоугольнику, который показан снаружи.

Я использую OpenCV python, please скажите мне, как это сделать.

Просьба показать код в качестве примеров OpenCV Python найти трудно.

Example Image

4b9b3361

Ответ 1

Вы можете использовать функцию 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 - это форма выходного изображения. Если участок /angular достаточно велики, края обрезаются (сравните изображение выше), если использовать исходную форму, как - для простоты - сделано выше. В этом случае вы можете ввести коэффициент масштабирования для shape (для увеличения выходного изображения) и опорную точку для нарезки (здесь center).

Вышеприведенную функцию можно использовать следующим образом:

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

Ответ 2

У меня были проблемы с неправильными смещениями с здесь и в аналогичных вопросах, размещенных на сайте. Поэтому я сделал математику и придумал следующее решение, которое работает:

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.

Если есть вопросы о математике, спросите их как комментарии, и я постараюсь ответить на них.

Ответ 3

Аналогичный рецепт для 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)

Ответ 4

Это моя версия на С++, которая выполняет ту же задачу. Я заметил, что это немного медленно. Если кто-нибудь увидит что-нибудь, что улучшит производительность этой функции, то, пожалуйста, дайте мне знать.:)

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

Ответ 5

Другие методы будут работать только в том случае, если содержимое прямоangularьника находится в повернутом изображении после поворота и будет плохо работать в других ситуациях. Что, если часть будет потеряна? Смотрите пример ниже:

enter image description here

Если вы хотите обрезать текстовую область повернутого прямоangularьника, используя описанный выше метод,

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

Видимо, некоторые части вырезаны! Почему нельзя деформировать повернутый прямоangularьник напрямую, поскольку мы можем получить его четыре angular точки методом 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

Намного лучше, не так ли? Если вы проверите внимательно, вы заметите, что на обрезанном изображении есть черная область. Это потому, что небольшая часть обнаруженного прямоangularьника выходит за границы изображения. Чтобы исправить это, вы можете немного дополнить изображение и выполнить обрезку после этого. В этом ответе есть пример.

Теперь мы сравним два метода, чтобы вырезать повернутый прямоangularьник из изображения. Этот метод не требует поворота изображения и может решить эту проблему более элегантно с меньшим количеством кода.