相机模型
介绍
以针孔相机模型为例,介绍一下相机模型:
假设相机坐标系和世界坐标系之间的转换关系为:
[
u
v
1
]
=
[
f
x
0
c
x
0
f
y
c
y
0
0
1
]
[
r
11
r
12
r
13
t
1
r
21
r
22
r
23
t
2
r
31
r
32
r
33
t
3
0
0
0
1
]
[
X
w
Y
w
Z
w
1
]
\begin{bmatrix} u \\ v \\ 1 \end{bmatrix} = \begin{bmatrix} f_x & 0 & c_x \\ 0 & f_y & c_y \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} r_{11} & r_{12} & r_{13} & t_1 \\ r_{21} & r_{22} & r_{23} & t_2 \\ r_{31} & r_{32} & r_{33} & t_3 \\ 0 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} X_w \\ Y_w \\ Z_w \\ 1 \end{bmatrix}
uv1
=
fx000fy0cxcy1
r11r21r310r12r22r320r13r23r330t1t2t31
XwYwZw1
其中, ( u , v ) (u,v) (u,v)是成像平面上的像素坐标, ( X w , Y w , Z w ) (X_w,Y_w,Z_w) (Xw,Yw,Zw)是场景中的一个点的世界坐标, f x f_x fx和 f y f_y fy是相机的焦距, c x c_x cx和 c y c_y cy是成像平面的中心点坐标, r 11 , r 12 , r 13 , r 21 , r 22 , r 23 , r 31 , r 32 , r 33 r_{11},r_{12},r_{13},r_{21},r_{22},r_{23},r_{31},r_{32},r_{33} r11,r12,r13,r21,r22,r23,r31,r32,r33和 t 1 , t 2 , t 3 t_1,t_2,t_3 t1,t2,t3是相机的外参参数, [ R ∣ t ] [R|t] [R∣t]是相机的变换矩阵。
注意,右边做了矩阵变换之后维度为4×1,去掉最后的1,变为3×1,这个时候再做相机模型的变换,进一步表示:
[ u v 1 ] = [ f x 0 c x 0 f y c y 0 0 1 ] [ X Y Z ] \begin{bmatrix}u \\ v \\ 1 \end{bmatrix} = \begin{bmatrix} f_x & 0 & c_x \\ 0 & f_y & c_y \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix}X \\ Y \\ Z \end{bmatrix} uv1 = fx000fy0cxcy1 XYZ
其中, ( u , v ) (u,v) (u,v) 是像素坐标系中的点, ( X , Y , Z ) (X,Y,Z) (X,Y,Z) 是相机坐标系中的点, f x f_x fx 和 f y f_y fy 是相机的焦距, c x c_x cx 和 c y c_y cy 是图像中心的坐标。
去畸变
介绍
在OpenCV中,常用的相机模型有针孔相机模型和广角相机模型两种。其中,针孔相机模型假设成像过程中光线都是直线,而广角相机模型则考虑了光线的弯曲。
在现实拍摄中,由于相机透镜的形状和制造工艺等原因,所拍摄的图像可能会出现畸变。常见的畸变有径向畸变和切向畸变两种。径向畸变会使得直线看起来弯曲,而切向畸变会使得图像出现倾斜。
代码
OpenCV中提供了undistort()函数,可以将畸变图像转换为没有畸变的图像。这个函数需要输入原始畸变图像、相机内参矩阵、畸变系数等参数。
x
d
i
s
t
o
r
t
e
d
=
x
(
1
+
k
1
r
2
+
k
2
r
4
+
k
3
r
6
)
+
2
p
1
x
y
+
p
2
(
r
2
+
2
x
2
)
x_{distorted} = x(1+k_1r^2+k_2r^4+k_3r^6) + 2p_1xy+p_2(r^2+2x^2)
xdistorted=x(1+k1r2+k2r4+k3r6)+2p1xy+p2(r2+2x2)
y
d
i
s
t
o
r
t
e
d
=
y
(
1
+
k
1
r
2
+
k
2
r
4
+
k
3
r
6
)
+
p
1
(
r
2
+
2
y
2
)
+
2
P
2
x
y
y_{distorted} = y(1+k_1r^2+k_2r^4+k_3r^6) + p_1(r^2+2y^2) + 2P_2xy
ydistorted=y(1+k1r2+k2r4+k3r6)+p1(r2+2y2)+2P2xy
其中, x , y x,y x,y是归一化坐标。
代码如下:
#include <opencv2/opencv.hpp>
using namespace cv;
int main() {
// 读入图像和相机参数
Mat img = imread("input.jpg");
Mat K = (Mat_<double>(3,3) << focal_length_x, 0, principal_point_x,
0, focal_length_y, principal_point_y,
0, 0, 1);
Mat dist_coeffs = (Mat_<double>(5,1) << k1, k2, p1, p2, k3);
// 进行去畸变
Mat img_undistorted;
undistort(img, img_undistorted, K, dist_coeffs);
// 显示结果
namedWindow("input", WINDOW_NORMAL);
namedWindow("undistorted", WINDOW_NORMAL);
imshow("input", img);
imshow("undistorted", img_undistorted);
waitKey(0);
destroyAllWindows();
return 0;
}
其中,focal_length_x和focal_length_y是相机的焦距,principal_point_x和principal_point_y是相机的主点,k1、k2、p1、p2和k3是相机的畸变系数。这些参数需要根据相机型号进行确定。在代码中,Mat_表示数据类型为double的Mat对象,<<用于初始化Mat对象。
相机的畸变可以使用opencv中的undistort函数进行去畸变,如果不想使用opencv库,可以自己编写畸变矫正的函数。下面是一个简单的C++代码示例,使用OpenCV相机模型进行去畸变:
#include <cmath>
#include <iostream>
#include <vector>
using namespace std;
// 相机参数
double fx, fy, cx, cy; // 相机内参
double k1, k2, p1, p2, k3; // 畸变系数
// 去畸变函数
void undistortPoints(const vector<cv::Point2f>& src, vector<cv::Point2f>& dst) {
double x, y, r, x_distorted, y_distorted;
for (int i = 0; i < src.size(); i++) {
x = (src[i].x - cx) / fx;
y = (src[i].y - cy) / fy;
r = sqrt(x * x + y * y);
x_distorted = x * (1 + k1 * r*r + k2 * r*r*r*r + k3 * r*r*r*r*r*r) + 2 * p1*x*y + p2 * (r*r + 2*x*x);
y_distorted = y * (1 + k1 * r*r + k2 * r*r*r*r + k3 * r*r*r*r*r*r) + 2 * p2*x*y + p1 * (r*r + 2*y*y);
dst[i].x = fx * x_distorted + cx;
dst[i].y = fy * y_distorted + cy;
}
}
int main() {
// 读入图像和相机参数
vector<cv::Point2f> src_points, dst_points;
// 读入src_points
undistortPoints(src_points, dst_points);
// 显示结果
for (int i = 0; i < src_points.size(); i++) {
cout << "src_point: (" << src_points[i].x << ", " << src_points[i].y << "), "
<< "dst_point: (" << dst_points[i].x << ", " << dst_points[i].y << ")" << endl;
}
return 0;
}
其中,fx和fy是相机的焦距,cx和cy是相机的主点,k1、k2、p1、p2和k3是相机的畸变系数。这些参数需要根据相机型号进行确定。函数undistortPoints输入为原始像素坐标点集src,输出为去畸变后的像素坐标点集dst。去畸变的具体过程可以参考OpenCV官方文档中的相机模型。
如果对整张图像做了去畸变,还可以考虑插值取值。