一、成像原理
鱼眼镜头一般是由十几个不同的透镜组合而成的,在成像的过程中,入射光线经过不同程度的折射,投影到尺寸有限的成像平面上,使得鱼眼镜头与普通镜头相比起来拥有了更大的视野范围。下图表示出了鱼眼相机的一般组成结构。最前面的两个镜头发生折射,使入射角减小,其余的镜头相当于一个成像镜头,这种多元件的构造结构使对鱼眼相机的折射关系的分析变得相当复杂。
研究表明鱼眼相机成像时遵循的模型可以近似为单位球面投影模型。可以将鱼眼相机的成像过程分解成两步:第一步,三维空间点线性地投影到一个球面上,它是一个虚拟的单位球面,它的球心与相机坐标系的原点重合;第二步,单位球面上的点投影到图像平面上,这个过程是非线性的。
我们知道,普通相机成像遵循的是针孔相机模型,在成像过程中实际场景中的直线仍被投影为图像平面上的直线。但是鱼眼相机如果按照针孔相机模型成像的话,投影图像会变得非常大,当相机视场角达到180°时,图像甚至会变为无穷大。所以,鱼眼相机的投影模型为了将尽可能大的场景投影到有限的图像平面内,允许了相机畸变的存在。并且由于鱼眼相机的径向畸变非常严重,所以鱼眼相机主要的是考虑径向畸变,而忽略其余类型的畸变。
二、畸变矫正效果
三、鱼眼相机模型
鱼眼相机的内参模型依然可以表示为:
这与普通镜头的成像模型没有区别。两者之间的区别主要体现在畸变系数,鱼眼相机的畸变系数为{k1,k2,k3,k4k1,k2,k3,k4},畸变系数不同,就导致鱼眼相机的投影关系也发生了变化,主要变化发生在考虑畸变情况下的投影关系转化:
设(X,Y,Z)为空间中一个三维点,它在成像平面内的成像坐标为(u,v),在考虑畸变的情况下,
四、标定和畸变矫正过程
1、检测角点
cv::findChessboardCorners(InputArray image, Size patternSize, OutputArray corners, int flags=CALIB_CB_ADAPTIVE_THRESH+CALIB_CB_NORMALIZE_IMAGE}
获得棋盘标定板的角点位置,使用
cornerSubPix(InputArray image, InputOutputArray corners, Size winSize, Size zeroZone, TermCriteria criteria)获取角点更精细的检测结果
2、初始化标定板上角点的三维坐标
3、开始标定
double fisheye::calibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints, const Size& image_size, InputOutputArray K, InputOutputArray D,
OutputArrayOfArrays rvecs, OutputArrayOfArrays tvecs, int flags=0, TermCriteria criteria=TermCriteria(TermCriteria::COUNT + TermCriteria:: EPS, 100, DBL_EPSILON))
注意:K,D 分别表示内参矩阵和畸变系数向量,在定义时要定义为double型,这里推荐使用Matx33d和Vec4d数据类型,更为方便简单。objectPoints,imagePoints可以是float型,也可以是double型,但是再stereorectify中需要时double型。flags的可选项有很多,其中需要注意的是必须要指定CALIB_FIX_SKEW,代表求解时假设内参中fx=fyfx=fy.
4、评定误差(可选项)
五、代码
#include <opencv2/opencv.hpp>
#include <fstream>
#include <cv.h>
#include <highgui.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
using namespace cv;
int main(int argc, char* argv[])
{
ofstream fout("caliberation_result.txt");
int image_count= 10;
Size board_size = Size(9,6);
vector<Point2f> corners;
vector<vector<Point2f> > corners_Seq;
vector<Mat> image_Seq;
int successImageNum = 0;
int count = 0;
//if you want to calibrate online
VideoCapture cap(0);
Mat image;
while(successImageNum < image_count){
//if you want to calibrate online
cap >> image;
if(!image.empty())
imshow("caliberation", image);
//if you want to use image file to calibrate
/*
cout<<"Frame #"<<i+1<<"..."<<endl;
string imageFileName;
std::stringstream StrStm;
StrStm<<i+1;
StrStm>>imageFileName;
imageFileName += ".jpg";
cv::Mat image = imread("fish"+imageFileName);
*/
cv::Mat imageGray;
cvtColor(image, imageGray, CV_BGR2GRAY);
bool patternfound = findChessboardCorners(imageGray, board_size, corners, CALIB_CB_ADAPTIVE_THRESH );
if (!patternfound)
{
cout<<"can not find chessboard corners!\n";
}
else
{
cornerSubPix(imageGray, corners, Size(11, 11), Size(-1, -1), TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 30, 0.1));
Mat imageTemp = image.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<<successImageNum;
StrStm>>imageFileName;
imwrite(imageFileName+".jpg",image);
imageFileName += "_corner.jpg";
imwrite(imageFileName,imageTemp);
cout<<"Frame corner#"<<successImageNum<<"...end"<<endl;
count = count + corners.size();
successImageNum = successImageNum + 1;
corners_Seq.push_back(corners);
}
image_Seq.push_back(image);
if( cvWaitKey(15) == 27 )
return 0;
waitKey(30);
}
cout<<"角点提取完成!\n";
/************************************************************************
摄像机定标
*************************************************************************/
cout<<"开始定标………………"<<endl;
Size square_size = Size(20,20);
vector<vector<Point3f> > object_Points; /**** 保存定标板上角点的三维坐标 ****/
Mat image_points = Mat(1, count, CV_32FC2, Scalar::all(0)); /***** 保存提取的所有角点 *****/
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++)
{
/* 假设定标板放在世界坐标系中z=0的平面上 */
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; /* 摄像机的4个畸变系数:k1,k2,k3,k4*/
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<<"定标完成!\n";
/************************************************************************
对定标结果进行评价
*************************************************************************/
cout<<"开始评价定标结果………………"<<endl;
double total_err = 0.0; /* 所有图像的平均误差的总和 */
double err = 0.0; /* 每幅图像的平均误差 */
vector<Point2f> image_points2; /**** 保存重新计算得到的投影点 ****/
cout<<"每幅图像的定标误差:"<<endl;
cout<<"每幅图像的定标误差:"<<endl<<endl;
for (int i=0; i<image_count; i++)
{
vector<Point3f> tempPointSet = object_Points[i];
/**** 通过得到的摄像机内外参数,对空间的三维点进行重新投影计算,得到新的投影点 ****/
fisheye::projectPoints(tempPointSet, image_points2, rotation_vectors[i], translation_vectors[i], intrinsic_matrix, distortion_coeffs);
/* 计算新的投影点和旧的投影点之间的误差*/
vector<Point2f> tempImagePoint = corners_Seq[i];
Mat tempImagePointMat = Mat(1,tempImagePoint.size(),CV_32FC2);
Mat image_points2Mat = Mat(1,image_points2.size(), CV_32FC2);
for (size_t j = 0 ; j != tempImagePoint.size(); j++)
{
image_points2Mat.at<Vec2f>(0,j) = Vec2f(image_points2[j].x, image_points2[j].y);
tempImagePointMat.at<Vec2f>(0,j) = Vec2f(tempImagePoint[j].x, tempImagePoint[j].y);
}
err = norm(image_points2Mat, tempImagePointMat, NORM_L2);
total_err += err/= point_counts[i];
cout<<"第"<<i+1<<"幅图像的平均误差:"<<err<<"像素"<<endl;
fout<<"第"<<i+1<<"幅图像的平均误差:"<<err<<"像素"<<endl;
}
cout<<"总体平均误差:"<<total_err/image_count<<"像素"<<endl;
fout<<"总体平均误差:"<<total_err/image_count<<"像素"<<endl<<endl;
cout<<"评价完成!"<<endl;
/************************************************************************
保存定标结果
*************************************************************************/
cout<<"开始保存定标结果………………"<<endl;
Mat rotation_matrix = Mat(3,3,CV_32FC1, Scalar::all(0)); /* 保存每幅图像的旋转矩阵 */
fout<<"相机内参数矩阵:"<<endl;
fout<<intrinsic_matrix<<endl;
fout<<"畸变系数:\n";
fout<<distortion_coeffs<<endl;
for (int i=0; i<image_count; i++)
{
fout<<"第"<<i+1<<"幅图像的旋转向量:"<<endl;
fout<<rotation_vectors[i]<<endl;
/* 将旋转向量转换为相对应的旋转矩阵 */
Rodrigues(rotation_vectors[i],rotation_matrix);
fout<<"第"<<i+1<<"幅图像的旋转矩阵:"<<endl;
fout<<rotation_matrix<<endl;
fout<<"第"<<i+1<<"幅图像的平移向量:"<<endl;
fout<<translation_vectors[i]<<endl;
}
cout<<"完成保存"<<endl;
fout<<endl;
/************************************************************************
显示定标结果
*************************************************************************/
Mat mapx = Mat(image_size,CV_32FC1);
Mat mapy = Mat(image_size,CV_32FC1);
Mat R = Mat::eye(3,3,CV_32F);
cout<<"保存矫正图像"<<endl;
for (int i = 0 ; i != image_count ; i++)
{
cout<<"Frame #"<<i+1<<"..."<<endl;
//fisheye::initUndistortRectifyMap(intrinsic_matrix,distortion_coeffs,R,intrinsic_matrix,image_size,CV_32FC1,mapx,mapy);
fisheye::initUndistortRectifyMap(intrinsic_matrix,distortion_coeffs,R,
getOptimalNewCameraMatrix(intrinsic_matrix, distortion_coeffs, image_size, 1, image_size, 0),image_size,CV_32FC1,mapx,mapy);
Mat t = image_Seq[i].clone();
cv::remap(image_Seq[i],t,mapx, mapy, INTER_LINEAR);
string imageFileName;
std::stringstream StrStm;
StrStm<<i+1;
StrStm>>imageFileName;
imageFileName += "_d.jpg";
imwrite(imageFileName,t);
}
cout<<"保存结束"<<endl;
return 0;
}
六、参考
鱼眼相机成像模型:https://blog.csdn.net/u010128736/article/details/52864024
鱼眼相机标定及OpenCV实现:https://blog.csdn.net/u010784534/article/details/50474371
Github代码下载:https://github.com/linghugoogle/Fisheye_Calibrate
七、Matlab算法
代码下载:https://sites.google.com/site/scarabotix/ocamcalib-toolbox