• 基于c++和opencv底层的图像旋转


    图像旋转:本质上是对旋转后的图片中的每个像素计算在原图的位置。

    在opencv包里有自带的旋转函数,当你知道倾斜角度theta时:

    getRotationMatrix2D可得2X3的旋转变换矩阵 M,在用warpaffine函数可得倾斜后的图像dst。

    很方便啊,为什么还要自己实现底层的图像旋转呢?因为有些地方你用这两个函数就会出现问题,比如说:


    原图的size是MXN,且图像是完全填充的(因为如果有留白可能还不能将问题完全反映出来),现在你需要将它90°变换(为了形象说明),可是用前面两个API会产生什么结果呢?如下:

      

         

    噢?有一部分看不见了…我们来看一下上面两个函数:

    C++: Mat getRotationMatrix2D(Point2f center, double angle, double scale)

    C++: void warpAffine(InputArray src, OutputArray dst, InputArray M, Size dsize, int flags=INTER_LINEAR, int borderMode=BORDER_CONSTANT, const Scalar& borderValue=Scalar())

    一开始我以为是这两个参数的设置问题。一般来说,center是设置在源图像中心点进行翻转的,现在有一部分看不见了,是不是中心点的问题呢?我换个中心点可不可以呢?实验证明,分别设置左上、下;右上,下角点为中心点时,都会有部分图像显示不出来。我放弃center,改试dsize,改大一点,还是不行,只是显示和黑色面积分别更大了,显示的比例并没有改变什么。这时候就不必再试了,有这个功夫我还不如自己写个旋转函数呢。为什么会这样呢,因为原图的面积不够大啊,一旋转就出去了啊,所以依然不想自己写函数(不建议,因为写的函数代码很简单啊)的童鞋,用copyMakeborder函数在原图上添加边界再旋转吧。添加完边界后问题又来了:边界添加的小图像还是会旋出去。边界添加的大,图片本身就很小了(就像置身于一片苍茫的黑色的宇宙中,当然是夸张),接下来你要对原来的图片中的像素进行什么处理就更困难了,有点小了。必要时还得去边界,opencv里面好像可没有现成的去边界函数啊。

    所以(前言说的太长),我们有必要自己写个简单方便的旋转函数。先给出旋转矩阵及其变换关系(别问我怎么来的,我是拿现成的老师教得,不过知道的朋友可以图解一下)

    代码如下:

     1 Mat angleRectify(Mat img,float angle)
     2 {
     3     Mat retMat = Mat::zeros(550,850, CV_8UC3);
     4     float anglePI =(float) (angle * CV_PI / 180);
     5     int xSm, ySm;
     6     for(int i = 0; i < retMat.rows; i++)
     7         for(int j = 0; j < retMat.cols; j++)
     8         {
     9             xSm = (int)((i-retMat.rows/2)*cos(anglePI) - (j-retMat.cols/2)*sin(anglePI) + 0.5);
    10             ySm = (int)((i-retMat.rows/2)*sin(anglePI) + (j-retMat.cols/2)*cos(anglePI) + 0.5);
    11             xSm += img.rows / 2;
    12             ySm += img.cols / 2;
    13             if(xSm >= img.rows || ySm >= img.cols || xSm <= 0 || ySm <= 0){
    14                 retMat.at<Vec3b>(i, j) = Vec3b(0, 0);
    15             }
    16             else{
    17                 retMat.at<Vec3b>(i, j) = img.at<Vec3b>(xSm, ySm);
    18             }
    19         }
    20 
    21     return retMat;
    22 }

    这里有几个问题需要说明:

    1 本来需要变换后图片乘以 原图变换矩阵的逆矩阵 对应到原图中坐标。但是因为y轴方向向下,所以变换后图片乘以原图变换矩阵(无需逆矩阵)即可对应到原图中坐标

    2 第910行需要 -retMat.rows/2retMat.cols/2的原因在于,图像是以(retMat.cols/2,retMat.rows/2)为坐标原点旋转的,所以变换后图片中的每个像素点(i; j),需要平移到相对旋转中心的新坐标,即(i - Mat.rows/2; j - Mat.cols/2)

    1. 1112行表示在计算完成之后,需要再次还原到相对左上角原点的旧坐标;
    2. 矩阵下标与原图变换矩阵相乘之前,需要将矩阵下标两值互换。相乘之后,需要再次互换下标值还原成矩阵下标。这句话是什么意思呢,我们可以看出,但是实际 xSm = (int)((i-retMat.rows/2)*cos(anglePI) - (j-retMat.cols/2)*sin(anglePI) + 0.5);  (i-retMat.rows/2)是我们认为的纵坐标。
    3. 旋转角度整数代表顺时针,负数代表逆时针(这点在opencv里的很多情况下都使用)
    4. 这里表示彩色图的旋转,Vec3b是彩色类型,如果是灰度图,将<>里的类型换成uchar并将retMat.at<Vec3b>(i, j) = Vec3b(0, 0); =>retMat.at<uchar>(i, j) = uchar(0);
    5. 如果是要旋转90°这种已知dstsize的情况下可以指定reMatsize从而不边界,其他类型还是会有边界的(但是比自己copyMakeBorder会小一些),因为你不知道reMat需要多大。
    6.  

      如果是从原图* 变化矩阵算目标图的坐标就会产生一定的黑色点,那是因为再取整时,略过了一些点的坐标,而保留了他的初始化的像素值0 

      

    文章参考:http://blog.csdn.net/ironyoung/article/details/41117039?utm_source=tuicool

  • 相关阅读:
    opencv目录
    qt5-编译并添加opencv库
    java版gRPC实战之二:服务发布和调用
    java版gRPC实战之一:用proto生成代码
    github搜索技巧小结
    client-go实战之五:DiscoveryClient
    client-go实战之四:dynamicClient
    client-go实战之三:Clientset
    client-go实战之二:RESTClient
    client-go实战之一:准备工作
  • 原文地址:https://www.cnblogs.com/Daringoo/p/4419899.html
Copyright © 2020-2023  润新知