1.畸变产生原因
相机畸变主要有径向畸变和切向畸变。
1.1 径向畸变
由于相机透镜的固有特性(凸透镜汇聚光线,凹透镜发散光线)导致成像时直线会变成曲线,所以径向畸变产生原因主要是透镜的几何形状改变了直线的形状。
径向畸变主要包括:桶形畸变(barrel distortion)、枕形畸变(pincushion distortion)、八字胡畸变(mustache distortion)
径向畸变矫正
径向畸变模型可以以下低阶多项式模型来表示:
x
u
n
d
i
s
t
=
x
d
i
s
t
(
1
+
k
1
r
2
+
k
2
r
4
+
k
3
r
6
)
y
u
n
d
i
s
t
=
y
d
i
s
t
(
1
+
k
1
r
2
+
k
2
r
4
+
k
3
r
6
)
其中
r
2
=
x
d
i
s
t
2
+
y
d
i
s
t
2
,
(
x
d
i
s
t
,
y
d
i
s
t
)
是归一后的相机坐标系的点,即坐标原点已移到主点,并且像素坐标除以焦距。
x
d
i
s
t
=
X
Z
=
u
−
u
0
f
x
,
y
d
i
s
t
=
Y
Z
=
v
−
v
0
f
y
x_{undist}=x_{dist}(1 + k_1r^2 + k_2r^4 + k_3r^6) \\ y_{undist}=y_{dist}(1 + k_1r^2 + k_2r^4 + k_3r^6) \\ 其中r^2=x_{dist}^2 + y_{dist}^2,(x_{dist},y_{dist})是归一后的相机坐标系的点,即坐标原点已移到主点,并且像素坐标除以焦距。\\ x_{dist}=\frac{X}{Z}=\frac{u-u_0}{f_x},y_{dist} = \frac{Y}{Z} = \frac{v-v_0}{f_y}
xundist=xdist(1+k1r2+k2r4+k3r6)yundist=ydist(1+k1r2+k2r4+k3r6)其中r2=xdist2+ydist2,(xdist,ydist)是归一后的相机坐标系的点,即坐标原点已移到主点,并且像素坐标除以焦距。xdist=ZX=fxu−u0,ydist=ZY=fyv−v0
其中
k
1
、
k
2
、
k
3
k_1、k_2、k_3
k1、k2、k3是径向畸变参数
1.2 切向畸变
切向畸变由相机senser与透镜不平行导致。
切向畸变矫正
切向畸变模型可以用以下低阶多项式模型来表示:
x
u
n
d
i
s
t
=
x
d
i
s
t
+
[
2
p
1
x
d
i
s
t
y
d
i
s
t
+
p
2
(
r
2
+
2
x
d
i
s
t
2
)
]
y
u
n
d
i
s
t
=
y
d
i
s
t
+
[
p
1
(
r
2
+
2
y
d
i
s
t
2
)
+
2
p
2
x
d
i
s
t
y
d
i
s
t
]
x_{undist} = x_{dist} + [2p_1x_{dist}y_{dist} + p2(r^2 + 2x_{dist}^2)]\\ y_{undist} = y_{dist} + [p_1(r^2 + 2y_{dist}^2) + 2p_2x_{dist}y_{dist}]
xundist=xdist+[2p1xdistydist+p2(r2+2xdist2)]yundist=ydist+[p1(r2+2ydist2)+2p2xdistydist]
p
1
、
p
2
为切向畸变参数
p_1、p_2为切向畸变参数
p1、p2为切向畸变参数
1.3 总畸变矫正
同时考虑径向畸变和切向畸变:
x
u
n
d
i
s
t
=
x
d
i
s
t
(
1
+
k
1
r
2
+
k
2
r
4
+
k
3
r
6
)
+
[
2
p
1
x
d
i
s
t
y
d
i
s
t
+
p
2
(
r
2
+
2
x
d
i
s
t
2
)
]
y
u
n
d
i
s
t
=
y
d
i
s
t
(
1
+
k
1
r
2
+
k
2
r
4
+
k
3
r
6
)
+
[
p
1
(
r
2
+
2
y
d
i
s
t
2
)
+
2
p
2
x
d
i
s
t
y
d
i
s
t
]
x_{undist} = x_{dist}(1 + k_1r^2 + k_2r^4 + k_3r^6) + [2p_1x_{dist}y_{dist} + p_2(r^2 + 2x_{dist}^2)]\\ y_{undist} = y_{dist}(1 + k_1r^2 + k_2r^4 + k_3r^6) + [p_1(r^2 + 2y_{dist}^2) + 2p_2x_{dist}y_{dist}]
xundist=xdist(1+k1r2+k2r4+k3r6)+[2p1xdistydist+p2(r2+2xdist2)]yundist=ydist(1+k1r2+k2r4+k3r6)+[p1(r2+2ydist2)+2p2xdistydist]
共有
5
个畸变参数
k
1
、
k
2
、
k
3
、
p
1
、
p
2
,这
5
个畸变参数与内参矩阵一起,都是需要进行相机标定。
共有5个畸变参数k_1、k_2、k_3、p_1、p_2,这5个畸变参数与内参矩阵一起,都是需要进行相机标定。
共有5个畸变参数k1、k2、k3、p1、p2,这5个畸变参数与内参矩阵一起,都是需要进行相机标定。
以上畸变方程由Brown在《Close-Range Camera Calibration》一文中所提出。
2.算法实现
2.1 标定流程
1)在相机视野范围内放置棋盘格,移动棋盘格并取图像,尽量覆盖整个相机的视野范围,且取图数量不少于10张(取图越多覆盖范围越广,标定结果越准确)
2)遍历每一张图像,重复以下算法步骤:图像二值化(非必须,可加快查找棋盘格角点算法处理速度)、查找棋盘格角点、亚像素精确化、记录角点像素坐标和角点世界坐标(角点0的世界坐标为零点,角点1的世界坐标为零点+实际棋盘格物理距离)
3)调用opencv处理计算相机内参和畸变系数计算函数,得出结果
2.2 标定代码
bool success = false;
try {
//棋盘格行列角点数
cv::Size patternSize(6, 4);
// 棋盘格的边长(单位:mm)
float squareSize = 30.0;
cv::Size imageSize(5440,3648);
// 存储棋盘格图像的角点坐标
std::vector<std::vector<cv::Point2f>> imagePoints;
// 存储棋盘格的世界坐标
std::vector<std::vector<cv::Point3f>> objectPoints;
// 加载棋盘格图像并提取角点
for (int i = 0; i < m_imgList.size(); i++) {
// 读取图像
cv::Mat image = transmitQImage2cvMat(&m_imgList[i]);
if (image.empty()) {
QMessageBox::warning(Q_NULLPTR, tr("Warn"), tr("image index %1 transmitQImage2cvMat fail").arg(i));
continue;
}
cv::Mat binary;
cv::threshold(image, binary, 75, 255, cv::THRESH_BINARY);
// 寻找角点
std::vector<cv::Point2f> corners;
bool found = cv::findChessboardCorners(binary, patternSize, corners);
if (found) {
// 亚像素精确化,使角点更准确
cv::Mat gray;
cv::cvtColor(binary, gray, cv::COLOR_BGR2GRAY);
cv::cornerSubPix(gray, corners, cv::Size(11, 11), cv::Size(-1, -1),
cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 30, 0.1));
// 添加角点坐标和世界坐标
imagePoints.push_back(corners);
std::vector<cv::Point3f> objectCorners;
for (int y = 0; y < patternSize.height; y++) {
for (int x = 0; x < patternSize.width; x++) {
objectCorners.push_back(cv::Point3f(x * squareSize, y * squareSize, 0));
}
}
objectPoints.push_back(objectCorners);
} else {
QString err = tr("iamge index %1 find corners fail").arg(i);
qWarning() << err;
QMessageBox::warning(Q_NULLPTR, tr("Warn"), err);
}
}
// 相机内参矩阵和畸变系数
cv::Mat cameraMatrix, distCoeffs;
std::vector<cv::Mat> rvecs, tvecs;
double rms = cv::calibrateCamera(objectPoints, imagePoints, imageSize, cameraMatrix, distCoeffs, rvecs, tvecs);
// 打印标定结果
qDebug() << "(cameraMatrix):";
qDebug() << "[" <<cameraMatrix.at<double>(0,0) << " " << cameraMatrix.at<double>(0,1) << " " << cameraMatrix.at<double>(0,2);
qDebug() << cameraMatrix.at<double>(1,0) << " " << cameraMatrix.at<double>(1,1) << " " << cameraMatrix.at<double>(1,2);
qDebug() << cameraMatrix.at<double>(2,0) << " " << cameraMatrix.at<double>(2,1) << " " << cameraMatrix.at<double>(2,2) << "]";
qDebug() << "(distCoeffs):";
qDebug() << "[" << distCoeffs.at<double>(0,0) << distCoeffs.at<double>(0,1) << distCoeffs.at<double>(0,2)
<< distCoeffs.at<double>(0,3) << distCoeffs.at<double>(0,4) << "]";
}
catch(const cv::Exception& e) {
qWarning() << QString::fromStdString(e.what());
}
2.3 使用标定结果
//其中,undist为原图,dist为去畸变后的结果,m_cameraMatrix为上述标定结果的内参矩阵,m_distCoeffs为上述标定结果的畸变矫正矩阵
//注意,dist必须初始化内存跟undist内存大小一致,否则函数调用会失败
cv::Mat undist = take_image_from_camera();
cv::Mat dist = undist.clone();
cv::undistort(undist, dist, m_cameraMatrix, m_distCoeffs);