在 C++ 中的 OpenCV 中旋转图像而不裁剪

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/22041699/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 23:54:37  来源:igfitidea点击:

Rotate an image without cropping in OpenCV in C++

c++opencv

提问by Manuel Ignacio López Quintero

I'd like to rotate an image, but I can't obtain the rotated image without cropping

我想旋转图像,但我无法在不裁剪的情况下获得旋转的图像

My original image:

我的原图:

enter image description here

在此处输入图片说明

Now I use this code:

现在我使用这个代码:

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

// Compile with g++ code.cpp -lopencv_core -lopencv_highgui -lopencv_imgproc

int main()
{
    cv::Mat src = cv::imread("im.png", CV_LOAD_IMAGE_UNCHANGED);
    cv::Mat dst;

    cv::Point2f pc(src.cols/2., src.rows/2.);
    cv::Mat r = cv::getRotationMatrix2D(pc, -45, 1.0);

    cv::warpAffine(src, dst, r, src.size()); // what size I should use?

    cv::imwrite("rotated_im.png", dst);

    return 0;
}

And obtain the following image:

并获得以下图像:

enter image description here

在此处输入图片说明

But I'd like to obtain this:

但我想得到这个:

enter image description here

在此处输入图片说明

Thank you very much for your help!

非常感谢您的帮助!

回答by Lars Schillingmann

My answer is inspired by the following posts / blog entries:

我的回答受到以下帖子/博客条目的启发:

Main ideas:

主要观点:

  • Adjusting the rotation matrix by adding a translation to the new image center
  • Using cv::RotatedRectto rely on existing opencv functionality as much as possible
  • 通过向新图像中心添加平移来调整旋转矩阵
  • 使用cv::RotatedRect尽可能依赖现有的opencv功能

Code tested with opencv 3.4.1:

使用 opencv 3.4.1 测试的代码:

#include "opencv2/opencv.hpp"

int main()
{
    cv::Mat src = cv::imread("im.png", CV_LOAD_IMAGE_UNCHANGED);
    double angle = -45;

    // get rotation matrix for rotating the image around its center in pixel coordinates
    cv::Point2f center((src.cols-1)/2.0, (src.rows-1)/2.0);
    cv::Mat rot = cv::getRotationMatrix2D(center, angle, 1.0);
    // determine bounding rectangle, center not relevant
    cv::Rect2f bbox = cv::RotatedRect(cv::Point2f(), src.size(), angle).boundingRect2f();
    // adjust transformation matrix
    rot.at<double>(0,2) += bbox.width/2.0 - src.cols/2.0;
    rot.at<double>(1,2) += bbox.height/2.0 - src.rows/2.0;

    cv::Mat dst;
    cv::warpAffine(src, dst, rot, bbox.size());
    cv::imwrite("rotated_im.png", dst);

    return 0;
}

回答by Haris

Just try the code below, the idea is simple:

只需尝试下面的代码,想法很简单:

  1. You need to create a blank image with the maximum size you're expecting while rotating at any angle. Here you should use Pythagoras as mentioned in the above comments.

  2. Now copy the source image to the newly created image and pass it to warpAffine. Here you should use the centre of newly created image for rotation.

  3. After warpAffineif you need to crop exact image for this translate four corners of source image in enlarged image using rotation matrix as described here

  4. Find minimum x and minimum y for top corner, and maximum x and maximum y for bottom corner from the above result to crop image.

  1. 您需要在以任何角度旋转时创建一个具有您期望的最大尺寸的空白图像。在这里你应该使用上面评论中提到的毕达哥拉斯。

  2. 现在将源图像复制到新创建的图像并将其传递给warpAffine. 在这里,您应该使用新创建的图像的中心进行旋转。

  3. warpAffine如果需要裁剪精确图像这放大图像使用旋转矩阵描述转换源图像的四角这里

  4. 从上面的结果中找到上角的最小 x 和最小 y,以及下角的最大 x 和最大 y 以裁剪图像。

This is the code:

这是代码:

int theta = 0;
Mat src,frame, frameRotated;
src = imread("rotate.png",1);
cout<<endl<<endl<<"Press '+' to rotate anti-clockwise and '-' for clockwise 's' to save" <<endl<<endl;

int diagonal = (int)sqrt(src.cols*src.cols+src.rows*src.rows);
int newWidth = diagonal;
int newHeight =diagonal;

int offsetX = (newWidth - src.cols) / 2;
int offsetY = (newHeight - src.rows) / 2;
Mat targetMat(newWidth, newHeight, src.type());
Point2f src_center(targetMat.cols/2.0F, targetMat.rows/2.0F);


while(1){
src.copyTo(frame);
double radians = theta * M_PI / 180.0;
double sin = abs(std::sin(radians));
double cos = abs(std::cos(radians));

frame.copyTo(targetMat.rowRange(offsetY, offsetY + frame.rows).colRange(offsetX, offsetX + frame.cols));
Mat rot_mat = getRotationMatrix2D(src_center, theta, 1.0);
warpAffine(targetMat, frameRotated, rot_mat, targetMat.size());
 //Calculate bounding rect and for exact image
 //Reference:- https://stackoverflow.com/questions/19830477/find-the-bounding-rectangle-of-rotated-rectangle/19830964?noredirect=1#19830964
    Rect bound_Rect(frame.cols,frame.rows,0,0);

    int x1 = offsetX;
    int x2 = offsetX+frame.cols;
    int x3 = offsetX;
    int x4 = offsetX+frame.cols;

    int y1 = offsetY;
    int y2 = offsetY;
    int y3 = offsetY+frame.rows;
    int y4 = offsetY+frame.rows;

    Mat co_Ordinate = (Mat_<double>(3,4) << x1, x2, x3, x4,
                                            y1, y2, y3, y4,
                                            1,  1,  1,  1 );
    Mat RotCo_Ordinate = rot_mat * co_Ordinate;

    for(int i=0;i<4;i++){
       if(RotCo_Ordinate.at<double>(0,i)<bound_Rect.x)
         bound_Rect.x=(int)RotCo_Ordinate.at<double>(0,i); //access smallest 
       if(RotCo_Ordinate.at<double>(1,i)<bound_Rect.y)
        bound_Rect.y=RotCo_Ordinate.at<double>(1,i); //access smallest y
     }

     for(int i=0;i<4;i++){
       if(RotCo_Ordinate.at<double>(0,i)>bound_Rect.width)
         bound_Rect.width=(int)RotCo_Ordinate.at<double>(0,i); //access largest x
       if(RotCo_Ordinate.at<double>(1,i)>bound_Rect.height)
        bound_Rect.height=RotCo_Ordinate.at<double>(1,i); //access largest y
     }

    bound_Rect.width=bound_Rect.width-bound_Rect.x;
    bound_Rect.height=bound_Rect.height-bound_Rect.y;

    Mat cropedResult;
    Mat ROI = frameRotated(bound_Rect);
    ROI.copyTo(cropedResult);

    imshow("Result", cropedResult);
    imshow("frame", frame);
    imshow("rotated frame", frameRotated);
    char k=waitKey();
    if(k=='+') theta+=10;
    if(k=='-') theta-=10;
    if(k=='s') imwrite("rotated.jpg",cropedResult);
    if(k==27) break;

}

enter image description here

在此处输入图片说明

Cropped Image

裁剪图像

enter image description hereenter image description here

在此处输入图片说明在此处输入图片说明

回答by Remi Cuingnet

Thanks Robula! Actually, you do not need to compute sine and cosine twice.

谢谢罗布拉!实际上,您不需要计算正弦和余弦两次。

import cv2

def rotate_image(mat, angle):
  # angle in degrees

  height, width = mat.shape[:2]
  image_center = (width/2, height/2)

  rotation_mat = cv2.getRotationMatrix2D(image_center, angle, 1.)

  abs_cos = abs(rotation_mat[0,0])
  abs_sin = abs(rotation_mat[0,1])

  bound_w = int(height * abs_sin + width * abs_cos)
  bound_h = int(height * abs_cos + width * abs_sin)

  rotation_mat[0, 2] += bound_w/2 - image_center[0]
  rotation_mat[1, 2] += bound_h/2 - image_center[1]

  rotated_mat = cv2.warpAffine(mat, rotation_mat, (bound_w, bound_h))
  return rotated_mat

回答by Rose Perrone

Thanks @Haris! Here's the Python version:

谢谢@哈里斯!这是 Python 版本:

def rotate_image(image, angle):
  '''Rotate image "angle" degrees.

  How it works:
    - Creates a blank image that fits any rotation of the image. To achieve
      this, set the height and width to be the image's diagonal.
    - Copy the original image to the center of this blank image
    - Rotate using warpAffine, using the newly created image's center
      (the enlarged blank image center)
    - Translate the four corners of the source image in the enlarged image
      using homogenous multiplication of the rotation matrix.
    - Crop the image according to these transformed corners
  '''

  diagonal = int(math.sqrt(pow(image.shape[0], 2) + pow(image.shape[1], 2)))
  offset_x = (diagonal - image.shape[0])/2
  offset_y = (diagonal - image.shape[1])/2
  dst_image = np.zeros((diagonal, diagonal, 3), dtype='uint8')
  image_center = (diagonal/2, diagonal/2)

  R = cv2.getRotationMatrix2D(image_center, angle, 1.0)
  dst_image[offset_x:(offset_x + image.shape[0]), \
            offset_y:(offset_y + image.shape[1]), \
            :] = image
  dst_image = cv2.warpAffine(dst_image, R, (diagonal, diagonal), flags=cv2.INTER_LINEAR)

  # Calculate the rotated bounding rect
  x0 = offset_x
  x1 = offset_x + image.shape[0]
  x2 = offset_x
  x3 = offset_x + image.shape[0]

  y0 = offset_y
  y1 = offset_y
  y2 = offset_y + image.shape[1]
  y3 = offset_y + image.shape[1]

  corners = np.zeros((3,4))
  corners[0,0] = x0
  corners[0,1] = x1
  corners[0,2] = x2
  corners[0,3] = x3
  corners[1,0] = y0
  corners[1,1] = y1
  corners[1,2] = y2
  corners[1,3] = y3
  corners[2:] = 1

  c = np.dot(R, corners)

  x = int(c[0,0])
  y = int(c[1,0])
  left = x
  right = x
  up = y
  down = y

  for i in range(4):
    x = int(c[0,i])
    y = int(c[1,i])
    if (x < left): left = x
    if (x > right): right = x
    if (y < up): up = y
    if (y > down): down = y
  h = down - up
  w = right - left

  cropped = np.zeros((w, h, 3), dtype='uint8')
  cropped[:, :, :] = dst_image[left:(left+w), up:(up+h), :]
  return cropped

回答by Robula

After searching around for a clean and easy to understand solution and reading through the answers above trying to understand them, I eventually came up with a solution using trigonometry.

在四处寻找一个干净且易于理解的解决方案并通读上面的答案试图理解它们之后,我最终想出了一个使用三角学的解决方案。

I hope this helps somebody :)

我希望这对某人有所帮助:)

import cv2
import math

def rotate_image(mat, angle):
    height, width = mat.shape[:2]
    image_center = (width / 2, height / 2)

    rotation_mat = cv2.getRotationMatrix2D(image_center, angle, 1)

    radians = math.radians(angle)
    sin = math.sin(radians)
    cos = math.cos(radians)
    bound_w = int((height * abs(sin)) + (width * abs(cos)))
    bound_h = int((height * abs(cos)) + (width * abs(sin)))

    rotation_mat[0, 2] += ((bound_w / 2) - image_center[0])
    rotation_mat[1, 2] += ((bound_h / 2) - image_center[1])

    rotated_mat = cv2.warpAffine(mat, rotation_mat, (bound_w, bound_h))
    return rotated_mat

EDIT:Please refer to @Remi Cuingnet'sanswer below.

编辑:请参阅下面@Remi Cuingnet 的回答。

回答by razz

Increase the image canvas (equally from the center without changing the image size) so that it can fit the image after rotation, then apply warpAffine:

增加图像画布(从中心平均,不改变图像大小),使其在旋转后适合图像,然后应用warpAffine

Mat img = imread ("/path/to/image", 1);
double offsetX, offsetY;
double angle = -45;
double width = img.size().width;
double height = img.size().height;
Point2d center = Point2d (width / 2, height / 2);
Rect bounds = RotatedRect (center, img.size(), angle).boundingRect();
Mat resized = Mat::zeros (bounds.size(), img.type());
offsetX = (bounds.width - width) / 2;
offsetY = (bounds.height - height) / 2;
Rect roi = Rect (offsetX, offsetY, width, height);
img.copyTo (resized (roi));
center += Point2d (offsetX, offsetY);
Mat M = getRotationMatrix2D (center, angle, 1.0);
warpAffine (resized, resized, M, resized.size());

enter image description here

在此处输入图片说明

回答by globalcaos

Thanks to everyone for this post, it has been super useful. However, I have found some black lines left and up (using Rose's python version) when rotating 90o. The problem seemed to be some int() roundings. In addition to that, I have changed the sign of the angle to make it grow clockwise.

感谢大家的这篇文章,它非常有用。但是,当旋转 90 度时,我发现左上和上一些黑线(使用 Rose 的 python 版本)。问题似乎是一些 int() 四舍五入。除此之外,我还改变了角度的符号,使其顺时针增长。

def rotate_image(image, angle):
    '''Rotate image "angle" degrees.

    How it works:
    - Creates a blank image that fits any rotation of the image. To achieve
      this, set the height and width to be the image's diagonal.
    - Copy the original image to the center of this blank image
    - Rotate using warpAffine, using the newly created image's center
      (the enlarged blank image center)
    - Translate the four corners of the source image in the enlarged image
      using homogenous multiplication of the rotation matrix.
    - Crop the image according to these transformed corners
    '''

    diagonal = int(math.ceil(math.sqrt(pow(image.shape[0], 2) + pow(image.shape[1], 2))))
    offset_x = (diagonal - image.shape[0])/2
    offset_y = (diagonal - image.shape[1])/2
    dst_image = np.zeros((diagonal, diagonal, 3), dtype='uint8')
    image_center = (float(diagonal-1)/2, float(diagonal-1)/2)

    R = cv2.getRotationMatrix2D(image_center, -angle, 1.0)
    dst_image[offset_x:(offset_x + image.shape[0]), offset_y:(offset_y + image.shape[1]), :] = image
    dst_image = cv2.warpAffine(dst_image, R, (diagonal, diagonal), flags=cv2.INTER_LINEAR)

    # Calculate the rotated bounding rect
    x0 = offset_x
    x1 = offset_x + image.shape[0]
    x2 = offset_x + image.shape[0]
    x3 = offset_x

    y0 = offset_y
    y1 = offset_y
    y2 = offset_y + image.shape[1]
    y3 = offset_y + image.shape[1]

    corners = np.zeros((3,4))
    corners[0,0] = x0
    corners[0,1] = x1
    corners[0,2] = x2
    corners[0,3] = x3
    corners[1,0] = y0
    corners[1,1] = y1
    corners[1,2] = y2
    corners[1,3] = y3
    corners[2:] = 1

    c = np.dot(R, corners)

    x = int(round(c[0,0]))
    y = int(round(c[1,0]))
    left = x
    right = x
    up = y
    down = y

    for i in range(4):
        x = c[0,i]
        y = c[1,i]
        if (x < left): left = x
        if (x > right): right = x
        if (y < up): up = y
        if (y > down): down = y
    h = int(round(down - up))
    w = int(round(right - left))
    left = int(round(left))
    up = int(round(up))

    cropped = np.zeros((w, h, 3), dtype='uint8')
    cropped[:, :, :] = dst_image[left:(left+w), up:(up+h), :]
    return cropped

回答by Jonathan Muller

Go version (using gocv) of @robula and @remi-cuingnet

@robula 和 @remi-cuingnet 的 Go 版本(使用 gocv)


func rotateImage(mat *gocv.Mat, angle float64) *gocv.Mat {
        height := mat.Rows()
        width := mat.Cols()

        imgCenter := image.Point{X: width/2, Y: height/2}

        rotationMat := gocv.GetRotationMatrix2D(imgCenter, -angle, 1.0)

        absCos := math.Abs(rotationMat.GetDoubleAt(0, 0))
        absSin := math.Abs(rotationMat.GetDoubleAt(0, 1))

        boundW := float64(height) * absSin + float64(width) * absCos
        boundH := float64(height) * absCos + float64(width) * absSin

        rotationMat.SetDoubleAt(0, 2, rotationMat.GetDoubleAt(0, 2) + (boundW / 2) - float64(imgCenter.X))
        rotationMat.SetDoubleAt(1, 2, rotationMat.GetDoubleAt(1, 2) + (boundH / 2) - float64(imgCenter.Y))

        gocv.WarpAffine(*mat, mat, rotationMat, image.Point{ X: int(boundW), Y: int(boundH) })

        return mat
}

I rotate in the same matrice in-memory, make a new matrice if you don't want to alter it

我在内存中的同一个矩阵中旋转,如果你不想改变它,请制作一个新矩阵

回答by Nandan IK

If it is just to rotate 90 degrees, maybe this code could be useful.

如果只是旋转 90 度,也许此代码可能有用。

    Mat img = imread("images.jpg");
    Mat rt(img.rows, img.rows, CV_8U);
    Point2f pc(img.cols / 2.0, img.rows / 2.0);
    Mat r = getRotationMatrix2D(pc, 90, 1);
    warpAffine(img, rt, r, rt.size());
    imshow("rotated", rt);

Hope it's useful.

希望它有用。

回答by globalcaos

By the way, for 90o rotations only, here is a more efficient + accurate function:

顺便说一句,仅对于 90o 旋转,这里有一个更高效 + 准确的函数:

def rotate_image_90(image, angle):
    angle = -angle
    rotated_image = image
    if angle == 0:
        pass
    elif angle == 90:
        rotated_image = np.rot90(rotated_image)
    elif angle == 180 or angle == -180:
        rotated_image = np.rot90(rotated_image)
        rotated_image = np.rot90(rotated_image)
    elif angle == -90:
        rotated_image = np.rot90(rotated_image)
        rotated_image = np.rot90(rotated_image)
        rotated_image = np.rot90(rotated_image)
    return rotated_image