原理:
平移的矩阵:
⎡⎣⎢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的源码当中,使用了几个技巧
- 求M矩阵的逆矩阵
- 对小数进行放大,变成整数进行运算,能够提高运算效率,这个技巧在插值部分已经提过
对矩阵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];
}
}
}
}
}