图像的平移、旋转、缩放

原理:

平移的矩阵:

100010xy1 [ 1 0 x 0 1 y 0 0 1 ]

缩放的矩阵:

a000b0001 [ a 0 0 0 b 0 0 0 1 ]

旋转矩阵:

αβ0βα0001 [ α β 0 − β α 0 0 0 1 ]

其中 α=cosθβ=sinθ α = c o s θ ; β = s i n θ

有的公式两个β的符号相反,并不是公式不正确,而是表示顺时针或逆时针旋转

理论:

那么,如果想要对一个图片进行旋转操作,由于opencv的原点设置在左上角,需要将图像的中心移动到 (width2,height2) ( w i d t h 2 , h e i g h t 2 ) 处,其中width和height表示图像的宽度和高度。

然后对图像进行旋转

旋转后在移动会左上角

如果想要缩放,那么在最后乘上一个缩放矩阵即可

那么公式叠在一起就是(此处先不考虑缩放)

x,y表示要横向和纵向要平移的长度

100010xy1αβ0βα0001100010xy1=M [ 1 0 x 0 1 y 0 0 1 ] [ α β 0 − β α 0 0 0 1 ] [ 1 0 − x 0 1 − y 0 0 1 ] = M

公式M整理后为

αβ0βα0(1α)xyβxβ+(1α)y1 [ α β ( 1 − α ) x − y β − β α x β + ( 1 − α ) y 0 0 1 ]

那么,一个坐标点 (X,Y) ( X , Y ) 经过M旋转后的公式就应该是

αβ0βα0(1α)xyβxβ+(1α)y1XY1 [ α β ( 1 − α ) x − y β − β α x β + ( 1 − α ) y 0 0 1 ] [ X Y 1 ]

在运算的过程中,把M的齐次部分取消,变成

[αββα(1α)xyβxβ+(1α)y]XY1 [ α β ( 1 − α ) x − y β − β α x β + ( 1 − α ) y ] [ X Y 1 ]

注意,有的博客里面的公式M写错了,例如此篇
http://lib.csdn.net/article/opencv/28860

不过这篇博客代码整理的还是挺好的,而且代码里面的东西没写错-_-

代码实现:

在CV的源码当中,使用了几个技巧

  1. 求M矩阵的逆矩阵
  2. 对小数进行放大,变成整数进行运算,能够提高运算效率,这个技巧在插值部分已经提过

对矩阵M求逆矩阵是什么个意思呢?

此部分代码

const double degree = 45;//角度
double angle = degree * CV_PI / 180.;
double alpha = cos(angle);
double beta = sin(angle);
int iWidth = matSrc.cols;
int iHeight = matSrc.rows;
int iNewWidth = cvRound(iWidth * fabs(alpha) + iHeight * fabs(beta));
int iNewHeight = cvRound(iHeight * fabs(alpha) + iWidth * fabs(beta));
/*
m0 m1 m2   =   alpha beta  ...
m3 m4 m5       -beta alpha ...
*/

double m[6];//旋转矩阵
m[0] = alpha;
m[1] = beta;
m[2] = (1 - alpha) * iWidth / 2. - beta * iHeight / 2.;
m[3] = -m[1];
m[4] = m[0];
m[5] = beta * iWidth / 2. + (1 - alpha) * iHeight / 2.;

cv::Mat M = cv::Mat(2, 3, CV_64F, m);
cv::Mat matDst1 = cv::Mat(cv::Size(iNewWidth, iNewHeight), matSrc.type(), cv::Scalar::all(0));

//求M的逆矩阵,即将m变成m的逆
double D = m[0] * m[4] - m[1] * m[3];
D = D != 0 ? 1. / D : 0;
double A11 = m[4] * D, A22 = m[0] * D;
m[0] = A11; m[1] *= -D;
m[3] *= -D; m[4] = A22;
double b1 = -m[0] * m[2] - m[1] * m[5];
double b2 = -m[3] * m[2] - m[4] * m[5];
m[2] = b1; m[5] = b2;

上面代码部分可以见到m中存储的是旋转矩阵M,D中存储的是m[0] * m[4] - m[1] * m[3],通过带入α和β可以知道D等于1
当然,这是在图像缩放大小都等于1的情况下,也就是图像不进行缩放变换,如果添加的图像的缩放,那么 D=1ab D = 1 a b

接下来说一下为什么要计算逆矩阵?

在图像的变换中,原始图像是S,目标图像是D

同时,设D(x,y)表示图像D的第x行,第y列,同理S

按照道理,应该应用如下数学公式计算,即

D(x,y)=MS(x,y) D ( x , y ) = M S ( x , y )

但是,我们在代码当中枚举的行和列是目标图像D的行和列

也就是我想要知道在图像D(x,y)处的像素值,对应于原始图像S处像素值S(x,y)经过旋转平移缩放等一系列操作后的值应该是多少呢?

不聪明的小伙伴(比如我),估计也能想出来的,答案就是原始图像的逆操作。

代码:

#include 
#include 
#include 
#include 
#include
#include 


using namespace cv;
using namespace std;

void WarpAffine(const Mat &src, Mat &dst,double *m)
{


    int iNewWidth = cvRound(src.cols * fabs(m[0]) + src.rows * fabs(m[1]));//旋转后新图像的大小
    int iNewHeight = cvRound(src.rows * fabs(m[0]) + src.cols * fabs(m[1]));
    /*
    m0 m1 m2   =   alpha beta  ...
    m3 m4 m5       -beta alpha ...
    */



    dst.create(Size(iNewWidth, iNewHeight), src.type());

    //求M的逆矩阵
    double D = m[0] * m[4] - m[1] * m[3];
    D = D != 0 ? 1. / D : 0;
    double A11 = m[4] * D, A22 = m[0] * D;
    m[0] = A11; m[1] *= -D;
    m[3] *= -D; m[4] = A22;
    double b1 = -m[0] * m[2] - m[1] * m[5];
    double b2 = -m[3] * m[2] - m[4] * m[5];
    m[2] = b1; m[5] = b2;

    int round_delta = 512;//由于数据扩大了1024倍,此部分相当于对X0和Y0增加0.5
    for (int y = 0; yfor (int x = 0; xint adelta = cv::saturate_cast<int>(m[0] * x * 1024);
            int bdelta = cv::saturate_cast<int>(m[3] * x * 1024);
            int X0 = cv::saturate_cast<int>((m[1] * y + m[2]) * 1024) + round_delta;
            int Y0 = cv::saturate_cast<int>((m[4] * y + m[5]) * 1024) + round_delta;
            int X = (X0 + adelta) >> 10;
            int Y = (Y0 + bdelta) >> 10;

            if ((unsigned)X < src.cols && (unsigned)Y < src.rows)
            {
                dst.at(y, x) = src.at(Y, X);//src对应逆旋转操作后的像素点
            }
        }
    }
}

int main()
{
    ios::sync_with_stdio(false);

    Mat src = imread("src.jpg");
    Mat dst;

    double degree = 45;
    double angle = degree * CV_PI / 180.;
    double alpha = cos(angle);
    double beta = sin(angle);

    double m[6];
    m[0] = alpha;
    m[1] = beta;
    m[2] = (1 - alpha) * src.cols / 2. - beta * src.rows / 2.;
    m[3] = -m[1];
    m[4] = m[0];
    m[5] = beta * src.cols / 2. + (1 - alpha) * src.rows / 2.;

    WarpAffine(src, dst,m);


    imshow("dst2", src);
    imshow("dst", dst);

    waitKey();

    system("pause");

    return 0;
}

指针遍历

void WarpAffine(const Mat &src, Mat &dst,double *m)
{


    int iNewWidth = cvRound(src.cols * fabs(m[0]) + src.rows * fabs(m[1]));//旋转后新图像的大小
    int iNewHeight = cvRound(src.rows * fabs(m[0]) + src.cols * fabs(m[1]));
    /*
    m0 m1 m2   =   alpha beta  ...
    m3 m4 m5       -beta alpha ...
    */



    dst.create(Size(iNewWidth, iNewHeight), src.type());

    //求M的逆矩阵
    double D = m[0] * m[4] - m[1] * m[3];
    D = D != 0 ? 1. / D : 0;
    double A11 = m[4] * D, A22 = m[0] * D;
    m[0] = A11; m[1] *= -D;
    m[3] *= -D; m[4] = A22;
    double b1 = -m[0] * m[2] - m[1] * m[5];
    double b2 = -m[3] * m[2] - m[4] * m[5];
    m[2] = b1; m[5] = b2;

    uchar *ps = src.data;
    uchar *pd = dst.data;
    int channel = src.channels();

    int round_delta = 512;//由于数据扩大了1024倍,此部分相当于对X0和Y0增加0.5
    for (int y = 0; yfor (int x = 0; xint adelta = cv::saturate_cast<int>(m[0] * x * 1024);
            int bdelta = cv::saturate_cast<int>(m[3] * x * 1024);
            int X0 = cv::saturate_cast<int>((m[1] * y + m[2]) * 1024) + round_delta;
            int Y0 = cv::saturate_cast<int>((m[4] * y + m[5]) * 1024) + round_delta;
            int X = (X0 + adelta) >> 10;
            int Y = (Y0 + bdelta) >> 10;

            if ((unsigned)X < src.cols && (unsigned)Y < src.rows)
            {
                for (int c = 0; c < channel; c++)
                {
                    pd[(y * dst.cols + x) * channel + c] = ps[(Y * src.cols + X) * channel + c];
                }
            }
        }
    }
}

你可能感兴趣的:(图像的平移、旋转、缩放)