原理:

平移的矩阵:


⎡⎣⎢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βα0001⎤⎦⎥⎡⎣⎢100010−x−y1⎤⎦⎥=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−α)x−yβxβ+(1−α)y1⎤⎦⎥ [ α β ( 1 − α ) x − y β − β α x β + ( 1 − α ) y 0 0 1 ]

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



⎡⎣⎢α−β0βα0(1−α)x−yβxβ+(1−α)y1⎤⎦⎥⎡⎣⎢XY1⎤⎦⎥ [ α β ( 1 − α ) x − y β − β α x β + ( 1 − α ) y 0 0 1 ] [ X Y 1 ]

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


[α−ββα(1−α)x−yβxβ+(1−α)y]⎡⎣⎢XY1⎤⎦⎥ [ α β ( 1 − α ) x − y β − β α x β + ( 1 − α ) y ] [ X Y 1 ]

注意,有的博客里面的公式M写错了,例如此篇

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

代码实现:

在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 <opencv2/imgproc/imgproc.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
#include<algorithm>
#include <memory>


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; y<iNewHeight; ++y)
    {
        for (int x = 0; x<iNewWidth; ++x)
        {
            int 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<cv::Vec3b>(y, x) = src.at<cv::Vec3b>(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; y<iNewHeight; ++y)
    {
        for (int x = 0; x<iNewWidth; ++x)
        {
            int 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];
                }
            }
        }
    }
}