双目相机标定主要是为了获得摄像头的内参(f,1/dx,1/dy,cx,cy)、畸变参数(k1,k2,k3,p1,p1)和外参(R,t),用于接下来的双目校正和深度图生成。
具体标定步骤如下:
一、获得棋盘格图像
首先需要准备一张棋盘,如下图所示。对于标定不同测距范围相机所用的棋盘方格宽度会有所不同。对于短焦双目相机(测距范围在20m以内),棋盘中方格的宽度达到20mm即可;对于长焦双目相机(测距范围在40m左右),棋盘中方格的宽度需要尽量大,否则会影响标定的精度,一般至少达到60mm。
![](https://i-blog.csdnimg.cn/blog_migrate/303989e0fead2e4dfff5b73aade70439.jpeg)
这里有一段c++代码,用于生成棋盘格图像:
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace cv;
using namespace std;
void main()
{
//---生成标定图
IplImage *img;
int chess_size = 500;
int dx = 10; //棋盘格大小,像素为单位
int dy = 7; //棋盘格数目
img = cvCreateImage(cvSize(chess_size*dy, chess_size*dx), IPL_DEPTH_8U, 1);
cvZero(img);
int flag = 0;
for (int i = 0; i < dx; i++)
for (int j = 0; j < dy; j++)
{
flag = (i + j) % 2;
if (flag == 0)
{
for (int m = i*chess_size; m < (i + 1)*chess_size; m++)
for (int n = j*chess_size; n < (j + 1)*chess_size; n++)
*(img->imageData + m*img->widthStep + n) = 255;
}
}
cvSaveImage("标定图2.jpg", img);
// 生成的棋盘格图保存在该工程目录下
cvNamedWindow("cab", 1);
cvShowImage("cab", img);
char ch = cv::waitKey(0);
if (ch == 27)
{
exit(0);
}
//system("PAUSE");
//---END生成标定图
}
笔者在标定长焦相机时方格长宽选择60mm。然后运行项目文件中photo.py脚本文件对棋盘进行多角度拍摄,每按下一次s键,会保存一组左右镜头的照片,照片保存的路径参数可以由用户自由设定,参数名为folder。
# !/usr/bin/python
# -*- coding: utf-8 -*-
import cv2
import time
AUTO = False # 自动拍照,或手动按s键拍照
INTERVAL = 2 # 自动拍照间隔
cap = cv2.VideoCapture(0 + cv2.CAP_DSHOW) # windows下开启摄像头是采用如下语句(微软特有):cv2.VideoCapture( camera_number + cv2.CAP_DSHOW)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 2560) # 设置双目的宽度
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) # 设置双目的高度
# 显示缓存数
# print(cap.get(cv2.CAP_PROP_BUFFERSIZE))
# 设置缓存区的大小
# cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
print(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
print(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
#设置FPS
# print('setfps', cap.set(cv2.CAP_PROP_FPS, 25))
# print(cap.get(cv2.CAP_PROP_FPS))
counter = 0
utc = time.time()
folder = "images/" # 拍照文件目录
def shot(pos, frame):
global counter
path = folder + pos + "_" + str(counter) + ".jpg"
cv2.imwrite(path, frame)
print("snapshot saved into: " + path)
while True:
ret, frame = cap.read()
if not ret:
print("camera is not connected!")
break
left_frame = frame[0:720, 0:1280]
right_frame = frame[0:720, 1280:2560]
cv2.imshow("left", left_frame)
cv2.imshow("right", right_frame)
now = time.time()
if AUTO and now - utc >= INTERVAL:
shot("left", left_frame)
shot("right", right_frame)
counter += 1
utc = now
key = cv2.waitKey(1)
if key == ord("q"):
break
elif key & 0xFF == ord("s"):
shot("left", left_frame)
shot("right", right_frame)
counter += 1
cap.release()
注意尽量拍摄多组照片,这样可以提高标定效果,标定效果的好坏直接影响到测距的精度。对于短焦相机通常拍摄40组照片即可;长焦相机通常会需要更多组照片,笔者在标定长焦相机时拍摄了60组。
照片拍摄好后,进入matlab标定工具箱,如下图所示。注意:不要选择matlab2020b版本,笔者测试过该版本无法正常使用标定工具箱,可以使用matlab2020a版本。
其中Stero Camera Cailbrator为双目标定工具箱,Camera Cailbrator为单目相机标定工具箱,因此这里选择Stero Camera Cailbrator工具箱。进入工具箱以后,选择Add Images。然后选择左右相机照片的路径,Size of checkerboard square为棋盘中每一个方格的长度,单位为毫米,一定要准确测量方格的长度,如下图所示。
![](https://i-blog.csdnimg.cn/blog_migrate/e039bcdda8d38f5045b06e6737bb8bd8.png)
点击确定以后,Radial Distortion Compute选择3 Coefficients和Tangential Distortion,然后点击Calibrate进行校准。不过校准前需要剔除一些原点不一致的点,保证所有照片的原点一致。校准过程中可以可以Reprojection Errors曲线,降低误差,如下图所示。
![](https://i-blog.csdnimg.cn/blog_migrate/8522814796b6b44dfbc4c2254b6d838f.png)
点击确定以后,Radial Distortion Compute选择3 Coefficients和Tangential Distortion,然后点击Calibrate进行校准。不过校准前需要剔除一些原点不一致的点,保证所有照片的原点一致。校准过程中可以可以Reprojection Errors曲线,降低误差,如下图所示。
![](https://i-blog.csdnimg.cn/blog_migrate/1b985b0547b6bd5886cb9eb9c0f25df1.png)
3 导出标定数据
标定好后将标定数据导入到工作空间,点击Export Camera Parameters即可。此时我们已经拿到标定数据了,为了避免手工获取数据时出错,笔者写了一个脚本可以直接获取标定数据,并保存到表格文件中,之后直接复制粘贴即可。
rowName = cell(1,10);
rowName{1,1} = '平移矩阵';
rowName{1,2} = '旋转矩阵';
rowName{1,3} = '相机1内参矩阵';
rowName{1,4} = '相机1径向畸变';
rowName{1,5} = '相机1切向畸变';
rowName{1,6} = '相机2内参矩阵';
rowName{1,7} = '相机2径向畸变';
rowName{1,8} = '相机2切向畸变';
rowName{1,9} = '相机1畸变向量';
rowName{1,10} = '相机2畸变向量';
xlswrite('out.xlsx',rowName(1,1),1,'A1');
xlswrite('out.xlsx',rowName(1,2),1,'A2');
xlswrite('out.xlsx',rowName(1,3),1,'A5');
xlswrite('out.xlsx',rowName(1,4),1,'A8');
xlswrite('out.xlsx',rowName(1,5),1,'A9');
xlswrite('out.xlsx',rowName(1,6),1,'A10');
xlswrite('out.xlsx',rowName(1,7),1,'A13');
xlswrite('out.xlsx',rowName(1,8),1,'A14');
xlswrite('out.xlsx',rowName(1,9),1,'A15');
xlswrite('out.xlsx',rowName(1,10),1,'A16');
xlswrite('out.xlsx',stereoParams.TranslationOfCamera2,1,'B1'); % 平移矩阵
xlswrite('out.xlsx',stereoParams.RotationOfCamera2.',1,'B2'); % 旋转矩阵
xlswrite('out.xlsx',stereoParams.CameraParameters1.IntrinsicMatrix.',1,'B5'); % 相机1内参矩阵
xlswrite('out.xlsx',stereoParams.CameraParameters1.RadialDistortion,1,'B8'); % 相机1径向畸变(1,2,5)
xlswrite('out.xlsx',stereoParams.CameraParameters1.TangentialDistortion,1,'B9'); % 相机1切向畸变(3,4)
xlswrite('out.xlsx',stereoParams.CameraParameters2.IntrinsicMatrix.',1,'B10'); % 相机2内参矩阵
xlswrite('out.xlsx',stereoParams.CameraParameters2.RadialDistortion,1,'B13'); % 相机2径向畸变(1,2,5)
xlswrite('out.xlsx',stereoParams.CameraParameters2.TangentialDistortion,1,'B14'); % 相机2切向畸变(3,4)
xlswrite('out.xlsx',[stereoParams.CameraParameters1.RadialDistortion(1:2), stereoParams.CameraParameters1.TangentialDistortion,...
stereoParams.CameraParameters1.RadialDistortion(3)],1,'B15'); % 相机1畸变向量
xlswrite('out.xlsx',[stereoParams.CameraParameters2.RadialDistortion(1:2), stereoParams.CameraParameters2.TangentialDistortion,...
stereoParams.CameraParameters2.RadialDistortion(3)],1,'B16'); % 相机2畸变向量
标定数据文件保存的路径即为当前程序的路径,要想保存到其他路径直接修改脚本中的路径即可,导出的参数如下图所示。
![](https://i-blog.csdnimg.cn/blog_migrate/9adf8bd8ece2a6c9dbf9d72ff590fa2d.png)
将表格中的数据复制到双目相机配置文件中,其中相机1内参复制到left_camera_matrix中,相机1畸变复制到left_distortion中,相机2内参复制到right_camera_matrix中,相机2畸变复制到right_distortion中,旋转矩阵复制到R中,转移矩阵复制到T中,如下图所示。
![](https://i-blog.csdnimg.cn/blog_migrate/77694f85066aafe0d3b6ec9c0902ccd9.png)
至此,双目标定部分就已经完成。注意:标定时照片的尺寸与测距时照片的尺寸一定要保持一致。