使用OpenCV标定鱼眼镜头(C++)
CV_EXPORTS double 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));
一、使用的函数
由于鱼眼镜头和针孔镜头的模型不一样,对于鱼眼镜头的模型在之前的博客中已经做了详细介绍,这里直接使用OpenCV中的cv::fisheye::calibrate()函数进行标定。函数原型如下,需要输入目标点集,图像点集、图像尺寸。函数输出相机内参,畸变系数,旋转矩阵和平移向量,以及反投影误差。
二、采集标定图像
采集若干拍摄有标定棋盘格的图像,并使棋盘格出现在画面的各个位置,特别是边缘位置。如下图所示:
三、标定代码
#include "stdio.h"
#include <iostream>
#include <fstream>
#include <io.h>
#include "opencv2/opencv.hpp"
#include <opencv2/core/core.hpp>
#include "opencv2/calib3d/calib3d.hpp"
#include <opencv2/highgui/highgui.hpp>
using namespace std;
using namespace cv;
void getFiles(string path, vector<string>& files)
{
//文件句柄
intptr_t hFile = 0;
//文件信息
struct _finddata_t fileinfo;
string p;
if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo)) != -1)
{
do
{
//如果是目录,迭代之
//如果不是,加入列表
if ((fileinfo.attrib & _A_SUBDIR))
{
if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
getFiles(p.assign(path).append("\\").append(fileinfo.name), files);
}
else
{
files.push_back(p.assign(path).append("\\").append(fileinfo.name));
}
} while (_findnext(hFile, &fileinfo) == 0);
_findclose(hFile);
}
}
int main(int argc, char** argv)
{
string filePath = ".\\720PPcalib\\front";
vector<string> files;
////获取该路径下的所有文件
getFiles(filePath, files);
const int board_w = 6;
const int board_h = 4;
const int NPoints = board_w * board_h;//棋盘格内角点总数
const int boardSize = 30; //mm
Mat image,grayimage;
Size ChessBoardSize = cv::Size(board_w, board_h);
vector<Point2f> tempcorners;
int flag = 0;
flag |= cv::fisheye::CALIB_RECOMPUTE_EXTRINSIC;
//flag |= cv::fisheye::CALIB_CHECK_COND;
flag |= cv::fisheye::CALIB_FIX_SKEW;
//flag |= cv::fisheye::CALIB_USE_INTRINSIC_GUESS;
vector<Point3f> object;
for (int j = 0; j < NPoints; j++)
{
object.push_back(Point3f((j % board_w) * boardSize, (j / board_w) * boardSize, 0));
}
cv::Matx33d intrinsics;//z:相机内参
cv::Vec4d distortion_coeff;//z:相机畸变系数
vector<vector<Point3f> > objectv;
vector<vector<Point2f> > imagev;
Size corrected_size(1280, 720);
Mat mapx, mapy;
Mat corrected;
ofstream intrinsicfile("intrinsics_front1103.txt");
ofstream disfile("dis_coeff_front1103.txt");
int num = 0;
bool bCalib = false;
while (num < files.size())
{
image = imread(files[num]);
if (image.empty())
break;
imshow("corner_image", image);
waitKey(10);
cvtColor(image, grayimage, CV_BGR2GRAY);
IplImage tempgray = grayimage;
bool findchessboard = cvCheckChessboard(&tempgray, ChessBoardSize);
if (findchessboard)
{
bool find_corners_result = findChessboardCorners(grayimage, ChessBoardSize, tempcorners, 3);
if (find_corners_result)
{
cornerSubPix(grayimage, tempcorners, cvSize(5, 5), cvSize(-1, -1), cvTermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 30, 0.1));
drawChessboardCorners(image, ChessBoardSize, tempcorners, find_corners_result);
imshow("corner_image", image);
cvWaitKey(100);
objectv.push_back(object);
imagev.push_back(tempcorners);
cout << "capture " << num << " pictures" << endl;
}
}
tempcorners.clear();
num++;
}
cv::fisheye::calibrate(objectv, imagev, cv::Size(image.cols,image.rows), intrinsics, distortion_coeff, cv::noArray(), cv::noArray(), flag, cv::TermCriteria(3, 20, 1e-6));
fisheye::initUndistortRectifyMap(intrinsics, distortion_coeff, cv::Matx33d::eye(), intrinsics, corrected_size, CV_16SC2, mapx, mapy);
for(int i=0; i<3; ++i)
{
for(int j=0; j<3; ++j)
{
intrinsicfile<<intrinsics(i,j)<<"\t";
}
intrinsicfile<<endl;
}
for(int i=0; i<4; ++i)
{
disfile<<distortion_coeff(i)<<"\t";
}
intrinsicfile.close();
disfile.close();
num = 0;
while (num < files.size())
{
image = imread(files[num++]);
if (image.empty())
break;
remap(image, corrected, mapx, mapy, INTER_LINEAR, BORDER_TRANSPARENT);
imshow("corner_image", image);
imshow("corrected", corrected);
cvWaitKey(200);
}
cv::destroyWindow("corner_image");
cv::destroyWindow("corrected");
image.release();
grayimage.release();
corrected.release();
mapx.release();
mapy.release();
return 0;
}
四、标定结果
使用标定的结果进行畸变校正后的结果如下所示,可以看到,原本弯曲的曲线已经变直。
原文地址:http://blog.csdn.net/u010128736/article/details/53022892?locationNum=14&fps=1
原文文档下载地址:http://download.csdn.net/detail/qq_15807167/9590824
在平面透视投影情况下,透视投影图像必须满 足平面透视投影约束,即空间直线的透视投影必须 为图像平面上的直线.对于视场为100。左右的广角 镜头的校正,已有的方法是利用平面透视投影约束, 通过变形校正模型将空间直线的投影曲线映射为图 像平面上的直线.达到对变形图像的校正,这些方法的共同点是不使用任何标定块,直接 使用场景中的直线,且这些直线的位置和方向是任 意的,不要求这些直线之间相互平行或垂直,这与利 用已知空间点的三维坐标或利用多幅图像之间 的对应关系的校正方法不同.而对于视场大于 180。的鱼眼镜头,由于它能拍摄到与光轴夹角为90。 的光线,如果利用平面透视投影模型来表示完整的 校正图像,则校正后的图像大小将为无穷大.也就是说,所使用的方法不能直接应用在视场 大于180。鱼跟镜头的校正.对于视场为180。左右的 鱼眼图像,我们觉得采用球面透视投影模型来表示 完整的校正图像是比较合适的,此时,整幅鱼眼图像 将被映射到一个单位球面上.与平面透视投影的情 况不同,在球面透视投影情况下,空间直线不再投影 成图像平面直线,而是球面上的大圆. 本文探讨在球面透视投影模型下,利用空间直 线的球面透视投影为大圆这一球面透视投影约束来 恢复鱼跟变形校正参数的方法.由于在场景直线的 鱼眼投影曲线上选取采样点时,不能保证所有采样 点都严格位于曲线上,因此,在将这些采样点通过变 形校正模型映射为球面点后,需要对这些点进行大 圆拟合.本文采用的目标函数是最小化对应于同一条空间直线的球面点到相应拟合大圆的球面距离的平方和.因此,在鱼眼镜头校止中,需要使用参数来方便地描述大圆,以及定义球面点到大圆的球面距离等.本文将球面几何引入到鱼眼镜头校正中来,给出了大圆的球面坐标的概念以及球面点到大圆的球面距离计算公式等.上述理论使得空问直线的球面透视投影必为球面上大圆这一约束得以在鱼眼变形校正中实现。
球面透视投影模型
透视投影也叫线性投影.我们可以将透视投影图像的形成过程分为两步:第l步。将每一个空间点P映射为连结P与投影中心。的射线OP;第2步,将射线OP线性地映射为图像点.第2步中的映射是可逆的,因为每一条通过投影中心。的射线对应于图像上的唯一一点,而图像上的每一点对应于唯一一条过投影中心。的射线.图像可以被认为是对通过投影中心O的射线的参数化.在文献中.有两种标准的参数化图像的方法.第一种方法是用一个在投影中心附近但不过投影中心的平面与这些通过投影中心。的射线相交,此即为平面透视投影模型.第二种方法是使用球心在。点的单位球面与
这些通过投影中心的射线相交,此即为球面透视投影模型.平面透视投影模型如图2(a)所示,在投影中心。处建立摄像机坐标系,oZ为主轴(光轴)方向.则有
当摄像机具有较大视场时,可以采用单位球面代替投影平面,也就是采用球面透视投影模型,如图2(b)所示.三维空间点P在球面上的透视投影p的三维直角坐标为
在平面投影模型下,空间直线L在图像平面上的投影z为L与投影中心0确定的平面与投影平面的交线.而在球面投影模型下,空间直线L在球面上的透视投影为大圆g,此大圆g是由L与投影中心。确定的平面与投影球面的交线.在已知球面投影图像后,将其转化为平面投影图像是很方便的.由于球面点对应唯一一条过投影中心的射线,我们首先将所有的球面点映射为过投影中心的射线,然后,再将这砦过投影中心的射线透视投影到一个平面上去,这样就得到了平面透视投影图像.关于投影平面的方向、投影中心到投影平面的距离以及平面图像的大小等都可以根据需要指定.
鱼眼变形矫正模型
原文地址:http://blog.csdn.net/qq_15807167/article/details/52076986