【问题标题】:Rotate an image without cropping in OpenCV in C++在 C++ 中的 OpenCV 中旋转图像而不进行裁剪
【发布时间】:2014-03-29 07:11:13
【问题描述】:

我想旋转一张图片,但是不裁剪就无法获得旋转后的图片

我的原图:

现在我使用这个代码:

#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;
}

并获得如下图片:

但我想得到这个:

【问题讨论】:

  • 毕达哥拉斯...找到矩形对角线,并将其用于高度和宽度。
  • 使用毕达哥拉斯我将获得高度和宽度,但图像定位不正确。
  • 如果在旋转之前把它放在中间会更好。
  • SHR,我该如何使用 warpAffine?
  • 您可以在以下Github存储库中获取接受答案的代码:Rotate an image matrix in 2D without cropping。随意分享和改进它!

标签: c++ opencv


【解决方案1】:

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

主要思想:

  • 通过向新图像中心添加平移来调整旋转矩阵
  • 使用cv::RotatedRect尽可能依赖已有的opencv功能

使用 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;
}

【讨论】:

  • 这段代码在翻转 90 度时仍然会产生一个小黑边。
  • 我知道这是一个老问题,执行时结果似乎绝对正确,无论如何,您能否澄清以下两行背后的数学? rot.at(0,2) += bbox.width/2.0 - center.x; rot.at(1,2) += bbox.height/2.0 - center.y;在这里,您将另一个平移添加到由 cv::getRotationMatrix2D 在获得的矩阵内计算的 Tx,Ty。我无法弄清楚你用来执行此操作的几何/数学规则。
  • 旋转矩阵rot 将目标映射到center。边界框bbox 具有不同的中心。这个想法是从仿射变换的平移分量中删除center。新的中心是添加的边界框bbox的中心。
  • 谢谢你的回答拉斯,无论如何,对不起,我还是不清楚:如果你分解从 getRotationMatriX2D 获得的旋转矩阵,你可以看到它是以下旋转/平移的结果,按以下顺序执行: 1) 将输入矩阵旋转中心(在本例中为原始图像中心)从 (center_x,center_y) 转换为 (0,0); 2)以给定的角度旋转输入矩阵(并对其进行缩放); 3) 翻译回输入矩阵反转点 1)。这 3 个变换矩阵的乘法给出了您在 opencv 文档中看到的 getRotationMatrix2D 公式。
  • 好的,抱歉,我没有看到这个链接的内容:john.freml.in/opencv-rotation;阐明了答案背后的所有数学。我留下上面的评论,可能对其他人在这个操作背后的几何推理有用。再次感谢@LarsSchillingmann
【解决方案2】:

试试下面的代码,思路很简单:

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

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

  3. warpAffine 之后,如果您需要裁剪精确图像,则使用旋转矩阵在放大图像中转换源图像的四个角,如here 所述

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

这是代码:

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;

}

裁剪图像

【讨论】:

  • 有效!谢谢!有没有更简单的解决方案?这个强大的库怎么没有像 Octave 这样的功能?在 Octave(或 Matlab)中只有 'imrotate(I, 45)'!!
  • 这个版本并不完美,因为它裁剪了一点。看看第一张图片的底角!!
  • 这可能是 1 或 2 个像素的错误,可能在舍入或将 double 类型转换为 int 时发生,您可以忽略它或在裁剪前将框向外扩展 1 或 2 个像素。
  • 我会再试一次,我必须用'cv::Mat targetMat(newWidth, newHeight, src.type(), cv::Scalar(0));'来更新你的代码。在我获得 NaN 值之前。
  • 我更新了您的代码,为控制错误添加了边界检查。有关详细信息,请参阅下面我自己的回复。
【解决方案3】:

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

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

【讨论】:

  • 当然!傻我,sin 和 cos 已经是旋转矩阵的一部分。感谢您完善我的答案。
  • 这很有帮助,而且开箱即用。谢谢!
【解决方案4】:

感谢@Haris!这是 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

【讨论】:

    【解决方案5】:

    在搜索了一个干净且易于理解的解决方案并阅读了上面的答案以试图理解它们之后,我最终想出了一个使用三角函数的解决方案。

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

    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
    

    编辑:请参考下面@Remi Cuingnet's的回答。

    【讨论】:

      【解决方案6】:

      增加图像画布(从中心开始,不改变图像大小),使其适合旋转后的图像,然后应用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());
      

      【讨论】:

      • 无论 'bounds' 的宽度或高度是否小于 'img' 的宽度或高度,此代码都会崩溃。例如,当角度为 +/-90 时就是这种情况。原因是 roi 中 offsetX 或 offsetY 的负值。
      【解决方案7】:

      旋转图像并控制填充黑色区域的python版本,您可以使用scipy.ndimage.rotate。这是一个例子:

      from skimage import io
      from scipy import ndimage
      
      image = io.imread('https://www.pyimagesearch.com/wp- 
      content/uploads/2019/12/tensorflow2_install_ubuntu_header.jpg')
      io.imshow(image)
      plt.show()
      

      rotated = ndimage.rotate(image, angle=234, mode='nearest')
      rotated = cv2.resize(rotated, (image.shape[:2]))
      # rotated = cv2.cvtColor(rotated, cv2.COLOR_BGR2RGB)
      # cv2.imwrite('rotated.jpg', rotated)
      io.imshow(rotated)
      plt.show()
      

      【讨论】:

        【解决方案8】:

        感谢大家的这篇文章,它非常有用。但是,当旋转 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
        

        【讨论】:

          【解决方案9】:

          @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
          }
          
          

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

          【讨论】:

            【解决方案10】:

            如果您对图像进行旋转和缩放:

            #include "opencv2/opencv.hpp"
            #include <functional>
            #include <vector>
            
            bool compareCoords(cv::Point2f p1, cv::Point2f p2, char coord)
            {
                assert(coord == 'x' || coord == 'y');
            
                if (coord == 'x')
                    return p1.x < p2.x;
            
                return p1.y < p2.y;
            }
            
            
            int main(int argc, char** argv)
            {
            
                cv::Mat image = cv::imread("lenna.png");
            
                float angle = 45.0;  // degrees
                float scale = 0.5;
                cv::Mat_<float> rot_mat = cv::getRotationMatrix2D( cv::Point2f( 0.0f, 0.0f ), angle, scale );
                
                // Image corners
                cv::Point2f pA = cv::Point2f(0.0f, 0.0f);
                cv::Point2f pB = cv::Point2f(image.cols, 0.0f);
                cv::Point2f pC = cv::Point2f(image.cols, image.rows);
                cv::Point2f pD = cv::Point2f(0.0f, image.rows);
            
                std::vector<cv::Point2f> pts = { pA, pB, pC, pD };
                std::vector<cv::Point2f> ptsTransf;
                cv::transform(pts, ptsTransf, rot_mat );
            
                using namespace std::placeholders;
                float minX = std::min_element(ptsTransf.begin(), ptsTransf.end(), std::bind(compareCoords, _1, _2, 'x'))->x;
                float maxX = std::max_element(ptsTransf.begin(), ptsTransf.end(), std::bind(compareCoords, _1, _2, 'x'))->x;
                float minY = std::min_element(ptsTransf.begin(), ptsTransf.end(), std::bind(compareCoords, _1, _2, 'y'))->y;
                float maxY = std::max_element(ptsTransf.begin(), ptsTransf.end(), std::bind(compareCoords, _1, _2, 'y'))->y;
            
                float newW = maxX - minX;
                float newH = maxY - minY;
            
                cv::Mat_<float> trans_mat = (cv::Mat_<float>(2,3) << 0, 0, -minX, 0, 0, -minY);
                cv::Mat_<float> M = rot_mat + trans_mat;
            
                cv::Mat warpedImage;
                cv::warpAffine( image, warpedImage, M, cv::Size(newW, newH) );
            
                cv::imshow("lenna", image);
                cv::imshow("Warped lenna", warpedImage);
            
                cv::waitKey();
                cv::destroyAllWindows();
                return 0;
            }
            

            【讨论】:

              【解决方案11】:

              如果只是旋转 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);
              

              希望有用。

              【讨论】:

              • 不是很有用,因为当图像旋转 90 度时不会发生任何剪切。
              【解决方案12】:

              顺便说一下,仅适用于 90º 旋转,这里有一个更高效 + 准确的函数:

              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
              

              【讨论】:

              • 本题使用c++语言,如标签所示
              猜你喜欢
              • 2015-08-23
              • 1970-01-01
              • 1970-01-01
              • 2017-10-09
              • 1970-01-01
              • 1970-01-01
              • 2017-08-04
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多