赞
踩
仿射变换是矩阵乘法(线性变换)和向量加法的结合。它包含了:
本质上,仿射变换就是两个图像矩阵之间的运算。
通常用一个
2
×
3
2 \times 3
2×3的矩阵来展示仿射变换(向量加法):
A
=
[
a
00
a
01
a
10
a
11
]
2
×
2
B
=
[
b
00
b
10
]
2
×
1
A=
M
=
[
A
B
]
=
[
a
00
a
01
b
00
a
10
a
11
b
10
]
2
×
3
M =
M
M
M就是要进行仿射变换的矩阵,它可以由
A
A
A和
B
B
B相加得到。下面用一个2D向量
X
=
[
x
y
]
X=
T
=
A
⋅
[
x
y
]
+
B
T=A \cdot
也可以直接对
M
M
M进行计算。
T
=
M
⋅
[
x
,
y
,
1
]
T
T = M \cdot [x, y, 1]^T
T=M⋅[x,y,1]T
得到仿射变换后的结果
T
T
T:
T
=
[
a
00
x
a
01
y
b
00
a
10
x
a
11
y
b
10
]
T=
总结:变换矩阵
M
M
M,将原矩阵
X
X
X和结果矩阵
T
T
T联系起来了,
X
X
X通过
M
M
M的变换,得到
T
T
T
在实际操作中通常有两种情况:
也可以从几何学的角度来考虑第2种情况。如下图,图1中3个点组成的三角形经过仿射变换成了图2中的三角形:
因为三点可以确定一个平面,所以这个方法可以用在图片上;即确定图片上3个点的仿射变换,就相当于确定了整张图片的仿射变换。
如上所诉,要像对整张图片进行放射变换,首先要确定图片上3个点的仿射变换,这个操作在OpenCV中通过getAffineTransform
函数实现,其原型如下:
Mat cv::getAffineTransform( InputArray src,
OutputArray dst)
src
是包含确定仿射变换的3个点的坐标的数组dst
是仿射变换之后3个点的坐标结果的数组- 返回的矩阵储存了从
src
变换到dst
的变换方式,即第一章中讲的 2 × 3 2 \times 3 2×3的 M M M变换矩阵
这个函数的算法如下:
[
x
i
′
y
i
′
]
=
M
⋅
[
x
i
y
i
1
]
- i = 0 , 1 , 2 i=0, 1, 2 i=0,1,2,即代表3个点中的每一个
- 等号左边的向量为变换后的点坐标,即 d s t ( i ) = ( x i ′ , y i ′ ) dst(i)=(x'_i, y'_i) dst(i)=(xi′,yi′)
- M M M为函数返回的矩阵,即储存变换方式的矩阵
- x i x_i xi和 y i y_i yi为原来的点坐标,即 s r c ( i ) = ( x i , y i ) src(i)=(x_i, y_i) src(i)=(xi,yi)
确定了图片的变换方式之后,就可以将变换方式应用到图片上了。这时需要用到warpAffine()
函数,其原型如下:
void cv::warpAffine(InputArray src,
OutputArray dst,
InputArray M,
Size dsize,
int flags = INTER_LINEAR,
int borderMode = BORDER_CONSTANT,
const Scalar& borderValue = Scalar())
M
为储存转换方式的矩阵,即getAffineTransform
函数的输出结果dsize
为输出图片dst
的尺寸flags
指定插值计算方法,默认为INTER_LINEAR
,即双线性插值;特别地,当该参数的值为WARP_INVERSE_MAP
的时候,执行与转换矩阵M
相反的转换,即 d s t → s r c dst \rightarrow src dst→srcborderMode
指定外推计算方法,默认为BORDER_CONSTANT
,即用单色进行外推扩充;特别的,当该参数的值为BORDER_TRANSPARENT
时,超出原图范围的像素点将不被改函数修改borderValue
参数只有在borderMode = BORDER_CONSTANT
时,才需要提供,用来指定扩充的像素颜色
这个函数的算法如下:
d
s
t
(
x
,
y
)
=
s
r
c
(
M
11
x
+
M
12
y
+
M
13
,
M
21
x
+
M
22
y
+
M
23
)
dst(x,y)=src(M_{11}x+M_{12}y+M_{13}, M_{21}x+M_{22}y+M_{23})
dst(x,y)=src(M11x+M12y+M13,M21x+M22y+M23)
即第一章中讲的
T
=
M
⋅
[
x
,
y
,
1
]
T
T = M \cdot [x, y, 1]^T
T=M⋅[x,y,1]T
上述的变换还只能像第一章的图中所展示的那样将图片进行变形。如果想要图中的三角形旋转一定的角度,则需要用到getRotationMatrix2D()
方法,其原型如下:
Mat cv::getRotationMatrix2D(Point2f center,
double angle,
double scale)
该函数与
getAffineTransform
函数类似,返回一个转换矩阵
center
为旋转中心在原图中的位置坐标angle
为旋转角度,正值为逆时针旋转(坐标原点在左上角)scale
为各向同性缩放因子
该函数返回的变换矩阵如下:
[
α
β
(
1
−
α
)
⋅
c
e
n
t
e
r
.
x
−
β
⋅
c
e
n
t
e
r
.
y
−
β
α
β
⋅
c
e
n
t
e
r
.
x
+
(
1
−
α
)
⋅
c
e
n
t
e
r
.
y
]
- α = s c a l e ⋅ cos a n g l e \alpha=scale \cdot \cos angle α=scale⋅cosangle
- β = s c a l e ⋅ sin a n g l e \beta = scale \cdot \sin angle β=scale⋅sinangle
本示例先将图片进行仿射变换,再将其顺时针旋转50度,并缩小到0.6倍。完整代码如下:
#include <opencv2/imgproc.hpp> #include <opencv2/imgcodecs.hpp> #include <opencv2/highgui.hpp> using namespace cv; using namespace std; int main() { Mat src{ imread("lena.jpg") }; //原图中的3个点 Point2f srcTri[3]; srcTri[0] = Point2f(0.f, 0.f); srcTri[1] = Point2f(src.cols - 1.f, 0.f); srcTri[2] = Point2f(0.f, src.rows - 1.f); //变换后3个点的坐标 Point2f dstTri[3]; dstTri[0] = Point2f(0.f, src.rows * 0.33f); dstTri[1] = Point2f(src.cols*0.85f, src.rows * 0.25f); dstTri[2] = Point2f(src.cols*0.15f, src.rows * 0.7f); //获取变换矩阵 Mat warp_mat = getAffineTransform(srcTri, dstTri); //用于储存变换结果的矩阵(和原图有相同的尺寸和数据类型) Mat warp_dst{ Mat::zeros(src.rows, src.cols, src.type()) }; //仿射变换 warpAffine(src, warp_dst, warp_mat, warp_dst.size()); Point center{ Point(warp_dst.cols / 2, warp_dst.rows / 2) }; double angle{ -50.0 }; double scale{ 0.6 }; //获取旋转的变换矩阵 Mat rot_mat{ getRotationMatrix2D(center, angle, scale) }; //用于储存旋转结果的矩阵 Mat warp_rotate_dst; //旋转变换 warpAffine(warp_dst,warp_rotate_dst, rot_mat, warp_dst.size()); imshow("原图", src); imshow("仿射变换", warp_dst); imshow("仿射变换+旋转", warp_rotate_dst); waitKey(0); }
运行结果如下:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。