摘要:opencv里面似乎没有直接的旋转图片的接口,这里实现一个旋转任意角度的方法,在旋转的时候调用opencv里面的仿射变换函数实现。有两种旋转模式:一种按图片中心旋转,尺寸与原图一致;另外一种模式是扩充图片尺寸以包含所有像素点。
1. 示例:
2. 原理
旋转平移的坐标变换
设有任意一点
p
,
在
平
面
直
角
坐
标
中
其
坐
标
为
(
x
,
y
)
,
在
平
面
极
坐
标
系
中
其
坐
标
为
(
ρ
,
θ
)
p,在平面直角坐标中其坐标为(x,y), 在平面极坐标系中其坐标为(\rho,\theta)
p,在平面直角坐标中其坐标为(x,y),在平面极坐标系中其坐标为(ρ,θ),则根据坐标系转换公式有
{
x
=
ρ
∗
c
o
s
θ
y
=
ρ
∗
s
i
n
θ
\begin{cases} x = \rho * cos\theta \\ y = \rho * sin\theta \end{cases}
{x=ρ∗cosθy=ρ∗sinθ
在这里
x
,
y
∈
[
−
∞
,
∞
]
,
ρ
∈
[
0
,
∞
]
,
θ
∈
[
0
,
2
π
)
x,y \in [-\infty,\infty], \rho \in[0,\infty], \theta \in [0,2\pi)
x,y∈[−∞,∞],ρ∈[0,∞],θ∈[0,2π). 假设对
p
p
p点对应的极径
o
p
‾
逆
时
针
旋
转
α
弧
度
\overline{op} 逆时针旋转\alpha弧度
op逆时针旋转α弧度得到
o
p
′
‾
\overline{op'}
op′,则
p
′
p'
p′点的坐标为
{
x
′
=
ρ
∗
c
o
s
(
θ
+
α
)
=
ρ
∗
c
o
s
θ
∗
c
o
s
α
−
ρ
∗
s
i
n
θ
∗
sin
α
=
x
∗
c
o
s
α
−
y
∗
s
i
n
α
y
′
=
ρ
∗
s
i
n
(
θ
+
α
)
=
ρ
∗
s
i
n
θ
∗
c
o
s
α
+
ρ
∗
c
o
s
θ
∗
sin
α
=
y
∗
c
o
s
α
+
x
∗
s
i
n
α
\begin{cases} x' &= \rho * cos(\theta + \alpha)\\ &=\rho * cos\theta *cos\alpha -\rho*sin\theta*\sin\alpha\\ &= x*cos\alpha - y*sin\alpha\\ \\ y' &= \rho * sin(\theta + \alpha)\\ &=\rho * sin\theta *cos\alpha + \rho*cos\theta*\sin\alpha\\ &= y*cos\alpha + x*sin\alpha\\ \end{cases}
⎩⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎧x′y′=ρ∗cos(θ+α)=ρ∗cosθ∗cosα−ρ∗sinθ∗sinα=x∗cosα−y∗sinα=ρ∗sin(θ+α)=ρ∗sinθ∗cosα+ρ∗cosθ∗sinα=y∗cosα+x∗sinα
再在直角坐标系中按向量
(
T
x
,
T
y
)
(Tx,Ty)
(Tx,Ty)平移
p
′
p'
p′到
p
′
′
p''
p′′,则
p
′
′
p''
p′′的坐标为
{
x
′
′
=
x
′
+
T
x
=
x
∗
c
o
s
α
−
y
∗
s
i
n
α
+
T
x
y
′
′
=
y
′
+
T
y
=
y
∗
c
o
s
α
+
x
∗
s
i
n
α
+
T
y
\begin{cases} x''&=x'+Tx\\ &=x*cos\alpha - y*sin\alpha+Tx \\\\ y''&=y'+Ty\\ &=y*cos\alpha + x*sin\alpha+Ty \end{cases}
⎩⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎧x′′y′′=x′+Tx=x∗cosα−y∗sinα+Tx=y′+Ty=y∗cosα+x∗sinα+Ty
转为矩阵形式为
[
x
′
′
y
′
′
]
=
[
cos
α
−
s
i
n
α
T
x
s
i
n
α
cos
α
T
y
]
⋅
[
x
y
1
]
\begin{bmatrix} x'' \\ y'' \end{bmatrix} = \begin{bmatrix} \cos\alpha & -sin\alpha &Tx\\ sin\alpha &\cos\alpha &Ty \end{bmatrix} \cdot \begin{bmatrix} x \\ y \\ 1 \end{bmatrix}
[x′′y′′]=[cosαsinα−sinαcosαTxTy]⋅⎣⎡xy1⎦⎤
也就是说只要知道仿射变换矩阵,那么每一个像素点(x,y)都可以用上面的公式直接计算了。
3. 两种方式
- 方式一
从仿射变换矩阵 [ cos α − s i n α T x s i n α cos α T y ] \begin{bmatrix} \cos\alpha & -sin\alpha &Tx\\ sin\alpha &\cos\alpha &Ty \end{bmatrix} [cosαsinα−sinαcosαTxTy]看,参数有3个: α , T x , T y \alpha,Tx,Ty α,Tx,Ty,如果有把它们都看成未知数,那么只要有三个独立的方程就可以解出仿射变换矩阵,对应实际的情况就是不共线的三个点。而opencv中提供了一个函数cv::getAffineTransform,只需要输入三个对应点对就可以得到仿射变换矩阵,然后用cv::warpAffine进行变换。这种方式主要是因为矩形的角点很容易计算,只需要计算三个点就可以得到变换矩阵。 - 方式二
直接构造仿射变换矩阵,角度已知,只需要求平移量,而平移量就是“最负”的点的坐标值,因为负数坐标的地方不显示,加上一点偏移使得所有坐标为负的点都变为正的。
4. 源码
#include <iostream>
#include<algorithm>
#include "opencv.hpp"
int rotateImage(const cv::Mat &src, cv::Mat &dst, const double angle, const int mode)
{
//mode = 0 ,Keep the original image size unchanged
//mode = 1, Change the original image size to fit the rotated scale, padding with zero
if (src.empty())
{
std::cout << "Damn, the input image is empty!\n";
return -1;
}
if (mode == 0)
{
cv::Point2f center((src.cols - 1) / 2.0, (src.rows - 1) / 2.0);
cv::Mat rot = cv::getRotationMatrix2D(center, angle, 1.0);
cv::warpAffine(src, dst, rot, src.size());//the original size
}
else {
double alpha = -angle * CV_PI / 180.0;//convert angle to radian format
cv::Point2f srcP[3];
cv::Point2f dstP[3];
srcP[0] = cv::Point2f(0, src.rows);
srcP[1] = cv::Point2f(src.cols, 0);
srcP[2] = cv::Point2f(src.cols, src.rows);
//rotate the pixels
for (int i=0;i<3;i++)
dstP[i] = cv::Point2f(srcP[i].x*cos(alpha) - srcP[i].y*sin(alpha), srcP[i].y*cos(alpha) + srcP[i].x*sin(alpha));
double minx, miny, maxx, maxy;
minx = std::min(std::min(std::min(dstP[0].x, dstP[1].x), dstP[2].x),float(0.0));
miny = std::min(std::min(std::min(dstP[0].y, dstP[1].y), dstP[2].y),float(0.0));
maxx = std::max(std::max(std::max(dstP[0].x, dstP[1].x), dstP[2].x),float(0.0));
maxy = std::max(std::max(std::max(dstP[0].y, dstP[1].y), dstP[2].y),float(0.0));
int w = maxx - minx;
int h = maxy - miny;
//translation
for (int i = 0; i < 3; i++)
{
if (minx < 0)
dstP[i].x -= minx;
if (miny < 0)
dstP[i].y -= miny;
}
cv::Mat warpMat = cv::getAffineTransform(srcP, dstP);
cv::warpAffine(src, dst, warpMat, cv::Size(w, h));//extend size
}//end else
return 0;
}
int rotateImage2(const cv::Mat &src, cv::Mat &dst, const double angle, const int mode)
{
//mode = 0 ,Keep the original image size unchanged
//mode = 1, Change the original image size to fit the rotated scale, padding with zero
if (src.empty())
{
std::cout << "Damn, the input image is empty!\n";
return -1;
}
if (mode == 0)
{
cv::Point2f center((src.cols - 1) / 2.0, (src.rows - 1) / 2.0);
cv::Mat rot = cv::getRotationMatrix2D(center, angle, 1.0);
cv::warpAffine(src, dst, rot, src.size());//the original size
}
else {
double alpha = -angle * CV_PI / 180.0;//convert angle to radian format
cv::Point2f srcP[3];
cv::Point2f dstP[3];
srcP[0] = cv::Point2f(0, src.rows);
srcP[1] = cv::Point2f(src.cols, 0);
srcP[2] = cv::Point2f(src.cols, src.rows);
//rotate the pixels
for (int i = 0; i < 3; i++)
dstP[i] = cv::Point2f(srcP[i].x*cos(alpha) - srcP[i].y*sin(alpha), srcP[i].y*cos(alpha) + srcP[i].x*sin(alpha));
double minx, miny, maxx, maxy;
minx = std::min(std::min(std::min(dstP[0].x, dstP[1].x), dstP[2].x), float(0.0));
miny = std::min(std::min(std::min(dstP[0].y, dstP[1].y), dstP[2].y), float(0.0));
maxx = std::max(std::max(std::max(dstP[0].x, dstP[1].x), dstP[2].x), float(0.0));
maxy = std::max(std::max(std::max(dstP[0].y, dstP[1].y), dstP[2].y), float(0.0));
int w = maxx - minx;
int h = maxy - miny;
cv::Mat warpMat =cv::Mat::zeros(cv::Size(3,2),CV_64F);//rows=2,cols=3
std::cout << warpMat.type() << std::endl;
std::cout << warpMat.size()<<std::endl;
warpMat.at<double>(0, 0) = cos(alpha);
warpMat.at<double>(0, 1) = 0- sin(alpha);
warpMat.at<double>(1, 0) = sin(alpha);
warpMat.at<double>(1, 1) = cos(alpha);
warpMat.at<double>(0, 2) =0- minx;
warpMat.at<double>(1, 2) =0-miny;
//std::cout << warpMat;
cv::warpAffine(src, dst, warpMat, cv::Size(w, h));//extend size
}//end else
return 0;
}
int main()
{
std::cout << "Hello World!\n";
std::string filePath = "K:\\imageData\\lena\\images.png";
cv::Mat src = cv::imread(filePath);
cv::Mat dst1,dst2;
for (double i = -360; i <= 360; i++)
{
int flg1 = rotateImage(src, dst1, i, 0);
int flg2 = rotateImage(src, dst2, i, 1);
if (flg1 == -1 || flg2 == -1) continue;
cv::imshow("src", src);
cv::imshow("dst1", dst1);
cv::imshow("dst2", dst2);
cv::waitKey(5);
}
cv::waitKey(0);
cv::destroyAllWindows();
}