赞
踩
图像最基本的变换即仿射变换(Affine Transform
)和透射变换(Perspective Transform
)。仿射变换是对一个向量空间进行一次线性变换并接上一次平移。透射变换是中心投影的射影变换。
仿射变换是线性变换与平移的组合。
首先,线性变换是什么?线性变换是满足以下两条性质的变换:1)直线在变换后仍然为直线,不能有所弯曲。2)原点必须保持固定。常见的线性有绕原点的旋转,以原点为中心的缩放,原点不变的错切/推移/剪切(shear)。平移(translation)是将物件的每点向同一方向移动相同距离。图像变换中涉及宽度和高度两个方向,以二维为例。
1)平移
原点O(0,0)
沿
p
→
\overrightarrow{p}
p
平移后到达p
点,平移的关系可表示为:
[
p
x
′
p
y
′
]
=
[
p
x
p
y
]
+
[
b
x
b
y
]
[p′xp′y]
2)旋转
如上图,坐标系{A}
中的向量
p
p
p逆时针旋转
θ
\theta
θ到
p
′
p'
p′位置。求
p
′
p'
p′在坐标系{A}
中的坐标。
假设坐标系{A}
也逆时针旋转
θ
\theta
θ得{B}
,则
p
′
p'
p′在{B}
中的坐标为
(
p
x
,
p
y
)
(p_x, p_y)
(px,py),{B}
相对于{A}
的变换可通过{B}
中的基在{A}
的基上投影求嘚。
x
^
A
=
(
1
,
0
)
\hat{x}_A=(1,0)
x^A=(1,0)
y
^
A
=
(
0
,
1
)
\hat{y}_A=(0,1)
y^A=(0,1)
x
^
B
=
(
c
o
s
θ
,
s
i
n
θ
)
\hat{x}_B=(cos\theta,sin\theta)
x^B=(cosθ,sinθ)
y
^
B
=
(
−
s
i
n
θ
,
c
o
s
θ
)
\hat{y}_B=(-sin\theta,cos\theta)
y^B=(−sinθ,cosθ)
B
A
R
=
[
X
^
B
X
^
A
X
^
B
Y
^
A
Y
^
B
X
^
A
Y
^
B
Y
^
A
]
=
[
c
o
s
θ
−
s
i
n
θ
s
i
n
θ
c
o
s
θ
]
_{B}^{A}\textrm{R}=[ˆXBˆXAˆXBˆYAˆYBˆXAˆYBˆYA]
[
p
x
′
p
y
′
]
=
B
A
R
[
p
x
p
y
]
=
[
c
o
s
θ
−
s
i
n
θ
s
i
n
θ
c
o
s
θ
]
[
p
x
p
y
]
[p′xp′y]{A}
中。
3)缩放
则向量
p
→
=
[
p
x
p
y
]
\overrightarrow{p} = [pxpy]
4)剪切
图片参考自[1]
如上图是 Y Y Y轴不变, X X X轴逆时针旋转 ψ \psi ψ发生的错切,以 B B B点为例,错切后得 B ′ B' B′点, B B B点为 ( x , y ) (x, y) (x,y),则 B ′ B' B′点为 ( x , y + x t a n ψ ) (x, y+xtan\psi) (x,y+xtanψ)。
将平移,旋转,缩放和错切写成齐次形式,可写成如下矩阵形式:
仿射变换是以上四种变换的组合,可写成:
T
=
T
s
T
a
T
r
T
t
=
[
a
00
a
01
a
02
a
10
a
11
a
12
0
0
1
]
T = T_sT_aT_rT_t = [a00a01a02a10a11a12001]
P
′
=
T
P
P'=TP
P′=TP,即:
[
p
x
′
p
y
′
1
]
=
[
a
00
a
01
a
02
a
10
a
11
a
12
0
0
1
]
[
p
x
p
y
1
]
[p′xp′y1]
上式是矩阵的乘法,其可看作三维空间的线性变换,而放射变换是在
z
=
1
z=1
z=1上图形发生的变化。
图片参考自here
从1.1中可知,要进行仿射变换需先求出变换矩阵,而变换矩阵
T
=
[
a
00
a
01
a
02
a
10
a
11
a
12
0
0
1
]
T = [a00a01a02a10a11a12001]
OpenCV
中给出变换前后对应的变换后的3对点可使用getAffineTransform
方法得到变换矩阵, warpAffine
对源图像变换得到变换图像。
#include <opencv2/highgui.hpp> #include <opencv2/imgproc.hpp> #include <iostream> int main(int argv, char **argc) { cv::Mat image = cv::imread("imgs/test.jpeg"); int width = image.cols; int height = image.rows; cv::Point2f src[3]; cv::Point2f dst[3]; // 实现图像逆时针旋转90度的仿射变换 // 1. src:左上角点 -> dst:左下角点 src[0] = cv::Point2f(0., 0); dst[0] = cv::Point2f(0., width); // 2. src:上边点 -> dst:左边点 src[1] = cv::Point2f(1, 0); dst[1] = cv::Point2f(0, width-1); // 3. src:左边点 -> dst:下边点 src[2] = cv::Point2f(0, 1.); dst[2] = cv::Point2f(1., width); cv::Mat transform_mat = cv::getAffineTransform(src, dst); std::cout << "Affine Transform Matrix: " << transform_mat << std::endl; cv::Mat affine_img; cv::warpAffine(image, affine_img, transform_mat, cv::Size(height, width)); cv::imwrite("affine_img.png", affine_img); return 0; }
getRotationMatrix2D
对于旋转这一仿射变换的特例,OpenCV
中定义了更为简单的API
来获取变换矩阵,那就是getRotationMatrix2D
。通过此方法仅通过指定旋转点和旋转角度,即可得到旋转矩阵,该方法还接受1个Scale
参数,若仅旋转图像不缩放,最后1个参数Scale
可设置为1。如下以绕图像中心为例旋转图像。
值得注意的时有时候我们旋转图像后不希望丢失原图像信息,因此需要计算旋转后包围图像的最小矩形的宽高。一种是使用OpenCV
自带的方法计算,一种是手动计算。
#include <opencv2/highgui.hpp> #include <opencv2/imgproc.hpp> #include <iostream> int main(int argv, char **argc) { cv::Mat image = cv::imread("../imgs/test.jpeg"); double angle = 30; int width = image.cols; int height = image.rows; cv::Mat rot_matrix = cv::getRotationMatrix2D(cv::Point2f(0.5 * width, 0.5 * height), angle, 1.0); // 2.1 double sin_angle = sin(abs(angle) / 180 * M_PI); double cos_angle = cos(abs(angle) / 180 * M_PI); int new_heiht = width * sin_angle + height * cos_angle; int new_width = width * cos_angle + height * sin_angle; rot_matrix.at<double>(0, 2) += (new_width - width) / 2; rot_matrix.at<double>(1, 2) += (new_heiht - height) / 2; // 2.2 cv::Rect2f bbox = cv::RotatedRect(cv::Point2f(width / 2, height / 2), image.size(), angle).boundingRect2f(); cv::Mat rot_img; cv::warpAffine(image, rot_img, rot_matrix, bbox.size()); cv::imwrite("rot_img.png", rot_img); }
1中的仿射变换是在平面上的线性变换加平移,根据其性质可知变换后平行四边形依然是平行四边形,不改变直线的平行关系。透射变换即中心投影变换,利用透视中心、像点、目标点三点共线的条件,按透视旋转定律使承影面(透视面)绕迹线(透视轴)旋转某一角度,破坏原有的投影光线束,仍能保持承影面上投影几何图形不变的变换3。
透视变换公式:
[
X
Y
Z
]
=
[
a
00
a
01
a
02
a
10
a
11
a
12
a
20
a
21
1
]
[
x
y
1
]
[XYZ]
从变换公式可知,仿射变换是透射变换的特例。
再通过除以Z轴转换成二维坐标:
{
x
′
=
X
Z
=
a
00
x
+
a
01
y
+
a
02
a
20
x
+
a
21
y
+
1
y
′
=
Y
Z
=
a
10
x
+
a
11
y
+
a
12
a
20
x
+
a
21
y
+
1
\left\{x′=XZ=a00x+a01y+a02a20x+a21y+1y′=YZ=a10x+a11y+a12a20x+a21y+1
透视变换(Perspective Transformation)是将二维的图片投影到一个三维视平面上,然后再转换到二维坐标下,所以也称为投影映射(Projective Mapping)。
移动投影中心和承影面,可得到各种形状的变换。
图片来自于3
透射变换矩阵
[
a
00
a
01
a
02
a
10
a
11
a
12
a
20
a
21
1
]
[a00a01a02a10a11a12a20a211]OpenCV
中提供了getPerspectiveTransform
方法来计算变换矩阵,提供了warpPerspective
方法来对图像进行变换。
#include <opencv2/highgui.hpp> #include <opencv2/imgproc.hpp> #include <iostream> int main(int argv, char **argc) { cv::Mat image = cv::imread("imgs/paper.jpg"); float w = 420, h = 596; cv::Point2f per_src[4]; per_src[0] = cv::Point2f(380, 444); per_src[1] = cv::Point2f(895 , 520); per_src[2] = cv::Point2f(88, 1126); per_src[3] = cv::Point2f(921, 1272); cv::Point2f per_dst[4] = {{0.0f, 0.0f}, {w, 0.0f}, {0.0f, h}, {w, h}}; cv::Mat per_mat = cv::getPerspectiveTransform(per_src, per_dst); cv::Mat perspective_img; cv::warpPerspective(image, perspective_img, per_mat, cv::Size(w, h)); cv::imwrite("perspective_img.png", perspective_img); return 0; }
当然,这里的四个角点是假设已知的,在实际中可通过轮廓检测findContour
来获取,后面会把这个补上,已经补上了见,update 2022.11.13。
参考:
1.https://cloud.tencent.com/developer/article/1638969
2.https://baike.baidu.com/item/%E9%80%8F%E8%A7%86%E5%8F%98%E6%8D%A2/8746342
3.https://www.jianshu.com/p/1fd77aa1e69e
4.https://www.computervision.zone/my-account/?password-reset=true
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。