一 、全自动泊车研究之鱼眼摄像头标定
整体介绍
全自动泊车项目代码:
链接: link.
本部分鱼眼相机的标定代码****/AutoParking/Script/calibration.cpp****
最近在整理毕业论文,正好回忆下关于全自动泊车的项目,这是关于基于视觉的全自动泊车的demo,采用4个鱼眼摄像头采集图像。
整个软件设计架构包括:鱼眼相机标定矫正,透视变换,360度环视图,车位线的检测以及障碍物(地锁,限位杆等)等其他模块。整个项目分为每个部分来记录;
相机模型(针孔相机模型)
-
介绍:
相机将三维世界中的坐标点(真实距离)映射到二维图像平面(像素)的过程,这一过程可以建成我们熟悉的小孔成像模型;由于相机的镜头的存在,使得光线投影到图像平面的过程中会发生畸变,这就需要我们前期进行标定求出相机的内参(有的相机买来可以询问商家获取内部参数);
-
坐标系
首先,世界坐标系、相机坐标系、图像坐标系、像素坐标系以及这四个坐标系的转换关系:①世界坐标系:是客观三维世界的绝对坐标系,也称客观坐标系。因为数码相机安放在三维空间中,我们需要世界坐标系这个基准坐标系来描述数码相机的位置,并且用它来描述安放在此三维环境中的其它任何物体的位置,用(Xw, Yw, Zw)表示其坐标值。
②相机坐标系(光心坐标系):以相机的光心为坐标原点,X 轴和Y 轴分别平行于图像坐标系的 X 轴和Y 轴,相机的光轴为Z 轴,用(Xc, Yc, Zc)表示其坐标值。
③图像坐标系:以图像平面的中心为坐标原点,X轴和Y 轴分别平行于图像平面的两条垂直边,用( x , y )表示其坐标值。图像坐标系是用物理单位(例如毫米)表示像素在图像中的位置。
④像素坐标系:以图像平面的左上角顶点为原点,X 轴和Y 轴分别平行于图像坐标系的 X 轴和Y 轴,用(u , v )表示其坐标值。数码相机采集的图像首先是形成标准电信号的形式,然后再通过模数转换变换为数字图像。每幅图像的存储形式是M × N的数组,M 行 N 列的图像中的每一个元素的数值代表的是图像点的灰度。这样的每个元素叫像素,像素坐标系就是以像素为单位的图像坐标系。
接下来进入正题,针孔相机模型如下:
在左图中设O-X-Y-Z为相机坐标,O为摄像机的光心,f为焦距(透镜到成像平面的距离),整个图像为物理成像平面,O’为图像中心,像素坐标原点通常在左上角。
将左图的模型简化为相似三角形,P为真实空间的一点,P’为P点投影到图像上的一点,根据三角形相似的性质得到: Z / f = X / X ′ = Y / Y ′ Z/ f = X / X' = Y/Y' Z/f=X/X′=Y/Y′
简单的手推:
最终的图像坐标到相机坐标的转化关系为:
其中fx, fy分别是图像水平轴和垂直轴的尺度因子。K的参数中只包含焦距、主点坐标等只由相机的内部结构决定,因此称 K 为内部参数矩阵,fx, fy , cx, cy叫做内部参数。本项目中只考虑内参,外参的标定后面特殊处理。
畸变模型(进行矫正)
-
介绍:
相机的前方通常会有一个透镜,会对成像产生影响,称为畸变。由透镜形状引起的畸变称为径向畸变,包括桶形畸变和枕形畸变。桶形畸变是由于图像放大率随着与光轴之间的距离增加而减小;枕形畸变相反。
径向畸变的程度会随着与中心距离的增加而增加,可以通过下面公式进行矫正。
透镜在装机过程中不和成像平面严格平行会引入切向畸变,切向畸变通过下面公式矫正。
最终的x,y方向的畸变系数为:
/AutoParking/Script/calibration.cpp
下面展示一些 内联代码片
。
// An highlighted block
Mat imageSrc = imread(imageFileName);
imshow(imageFileName, imageSrc);
Mat image;//
//copyMakeBorder(imageSrc, image, (int)(y_expand / 2), (int)(y_expand / 2), (int)(x_expand / 2), (int)(x_expand / 2), BORDER_CONSTANT);
Mat imageGray;
cvtColor(imageSrc, imageGray, CV_RGB2GRAY);//
bool patternfound = findChessboardCorners(imageSrc, board_size, corners, CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE +
CALIB_CB_FAST_CHECK);
if (!patternfound)
{
cout << "img" << i + 1 << endl;
conner_flag = false;
break;
}
else
{
/* SubPix */
cornerSubPix(imageGray, corners, Size(11, 11), Size(-1, -1), TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 30, 0.1));
Mat imageTemp = imageSrc.clone();//
for (int j = 0; j < corners.size(); j++)
{
circle(imageTemp, corners[j], 10, Scalar(0, 0, 255), 2, 8, 0);//
}
string imageFileName;
std::stringstream StrStm;
StrStm << i + 1;
StrStm >> imageFileName;
imageFileName += "_corner.jpg";
imageFileName = root+imageFileName;
imwrite(imageFileName, imageTemp);
cout << "img" << i + 1 << endl;
successImageNum = successImageNum + 1;
corners_Seq.push_back(corners);
}
image_Seq.push_back(imageSrc);
}
if (!conner_flag)
return 0;
cout << "......" << endl;
Size square_size = Size(20, 20);
vector<vector<Point3f>> object_Points;
vector<int> point_counts;
for (int t = 0; t < successImageNum; t++)
{
vector<Point3f> tempPointSet;
for (int i = 0; i < board_size.height; i++)
{
for (int j = 0; j < board_size.width; j++)
{
Point3f tempPoint;
tempPoint.x = i * square_size.width;
tempPoint.y = j * square_size.height;
tempPoint.z = 0;
tempPointSet.push_back(tempPoint);
}
}
object_Points.push_back(tempPointSet);
}
for (int i = 0; i < successImageNum; i++)
{
point_counts.push_back(board_size.width*board_size.height);
}
Size image_size = image_Seq[0].size();
cv::Matx33d intrinsic_matrix;
cv::Vec4d distortion_coeffs;
std::vector<cv::Vec3d> rotation_vectors;
std::vector<cv::Vec3d> translation_vectors;
int flags = 0;
flags |= cv::fisheye::CALIB_RECOMPUTE_EXTRINSIC;
flags |= cv::fisheye::CALIB_CHECK_COND;
flags |= cv::fisheye::CALIB_FIX_SKEW;
fisheye::calibrate(object_Points, corners_Seq, image_size, intrinsic_matrix, distortion_coeffs, rotation_vectors, translation_vectors, flags, cv::TermCriteria(3, 20, 1e-6));
cout << "\nStoring Intrinsics.xml and Distortions.xml files\n\n";
//FileStorage fs("intrinsics.xml", FileStorage::WRITE);
//fs << "image_width" << image_size.width << "image_height" << image_size.height
// << "camera_matrix" << intrinsic_matrix << "distortion_coefficients"
// << distortion_coeffs;
//fs.release();