常用的标定函数和流程,网上一大堆,这里就不想详细写了
这里说一下标定后常见的问题和我自己的一些做法。
1.标定后丢失部分像素信息
畸变校正后,边缘处出现一些黑色像素区域,其实也算是正常的,图片去畸变后补充的像素
可以用initUndistortRectifyMap,传递新的相机参数矩阵得到新的mapx,mapy来解决。代码如下
Mat NewCameraMatrix = cameraMatrix.clone();
double scale = 0.9;//比例系数
NewCameraMatrix.at<double>(0, 0) = NewCameraMatrix.at<double>(0, 0) / scale;
NewCameraMatrix.at<double>(1, 1) = NewCameraMatrix.at<double>(1, 1) / scale;
initUndistortRectifyMap(cameraMatrix, distCoeffs, Matx33d::eye(), NewCameraMatrix, PicSize, CV_32FC1, mapx, mapy);
for (int i = 0; i < files.size(); i++)
{
img = imread(files.at(i));
remap(img, showimg, mapx, mapy, INTER_LINEAR, BORDER_CONSTANT);
imshow("img", showimg);
waitKey(0);
}
个人理解,修改fx,fy相当于修改了相机成像模型中最后一步,相机物理坐标系->相机像素坐标系这一步,因此看起来的效果实际只是比例缩放。效果图(图像分辨率不变)如下
2.调整世界坐标系方向与图像方向一致
这种需求相当于相机安装不正的情形吧,效果上相当于使棋盘图片方向与图像方向一致。这部分还没办法完全理解,暂时先放下了
3.调整世界坐标原点
这个主要是一些工业应用的方便性,例如直接在图像作矢量图,直接切割使切的位置就是矢量图在图像上显示的位置。这部分应该用仿射变换就行了。暂时没做。
相机标定流程和常用的算子代码如下:
#include "LearnBasic.h"
#include "stdio.h"
#include <iostream>
#include <fstream>
#include <io.h>
void listFiles(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)
listFiles(p.assign(path).append("\\").append(fileinfo.name), files);
}
else
{
//cout << fileinfo.name << endl;
files.push_back(p.assign(path).append("\\").append(fileinfo.name));
}
} while (_findnext(hFile, &fileinfo) == 0);
_findclose(hFile);
}
}
void Calibration()
{
//读取文件夹下的图片
String path = "C:\\zyk\\calibration";
vector<string> files;
listFiles(path, files);
//基本参数
int Rownum = 7;
int Colnum = 9;
double space = 15.0;
Size PicSize(2592, 1944);
vector<vector<Point2f>> ImgPoints;
vector<vector<Point3f>> WorldPoints;
vector<cv::Point2f> ImgPoint;
vector<Point3f> WorldPoint;
Mat cameraMatrix;//相机内参,最好不要指定类型,否则某些算子类型不符合的话报错
Mat distCoeffs;//相机畸变系数
vector<Mat> rvecs;//旋转矢量
vector<Mat> tvecs;//偏移矢量
//构建棋盘坐标
int NPoints = Rownum * Colnum;
for (int j = 0; j < NPoints; j++)
{
WorldPoint.push_back(Point3f((j % Colnum) * space, (j / Colnum) * space, 0));//x,y,z
}
//检测所有图片棋盘角点
Mat img, img_Gray, showimg;
namedWindow("img", WINDOW_NORMAL);
for (int i = 0; i < files.size(); i++)
{
img = imread(files.at(i));
cvtColor(img, img_Gray, COLOR_BGR2GRAY);
if (cv::findChessboardCorners(img_Gray, cv::Size(Colnum, Rownum), ImgPoint, 0) == 0)
{
cout << "drop out:" << files.at(i) << endl;
continue;
}
WorldPoints.push_back(WorldPoint);
cornerSubPix(img_Gray, ImgPoint, cv::Size(3, 3), cv::Size(-1, -1),
cv::TermCriteria(cv::TermCriteria::MAX_ITER + TermCriteria::EPS, 30, 0.1));
ImgPoints.push_back(ImgPoint);
drawChessboardCorners(img, cv::Size(Colnum, Rownum), ImgPoint, true);
// imshow("img", img);
// waitKey(0);
}
double err = cv::calibrateCamera(WorldPoints, ImgPoints, PicSize,
cameraMatrix, distCoeffs, rvecs, tvecs
/*,CALIB_THIN_PRISM_MODEL*/);//flag不设置,只计算五个畸变系数。其他相机模型要详细研究flag参数
//通过得到的摄像机内外参数,对角点的空间三维坐标进行重新投影计算
cout << "cameraMatrix:" << cameraMatrix << endl;
cout << "distCoeffs:" << distCoeffs << endl;
cout << "标定误差:" << err << endl;
vector<Point2f> prj_points, undistort_points;
for (int i = 0; i < files.size(); i++)
{
img = imread(files.at(i));
showimg = img.clone();
projectPoints(WorldPoints[i], rvecs[i], tvecs[i], cameraMatrix, distCoeffs, prj_points);//世界坐标->图像坐标点(畸变)
drawChessboardCorners(showimg, cv::Size(Colnum, Rownum), prj_points, true);
imshow("img", showimg);
waitKey(0);
undistort(img, showimg, cameraMatrix, distCoeffs, cameraMatrix);//图像畸变校正
undistortPoints(ImgPoints[i], undistort_points, cameraMatrix, distCoeffs, noArray(), cameraMatrix);//畸变坐标点->无畸变坐标点
drawChessboardCorners(showimg, cv::Size(Colnum, Rownum), undistort_points, true);
imshow("img", showimg);
waitKey(0);
}
//另一种畸变校正图像方式,undistort内部好像每次也是调用initUndistortRectifyMap和remap,效率不高
Mat mapx, mapy;
initUndistortRectifyMap(cameraMatrix, distCoeffs, Matx33d::eye(), cameraMatrix, PicSize, CV_32FC1, mapx, mapy);
for (int i = 0; i < files.size(); i++)
{
img = imread(files.at(i));
remap(img, showimg, mapx, mapy, INTER_LINEAR, BORDER_CONSTANT);
imshow("img", showimg);
waitKey(0);
}
//显示优化、调整
//图像畸变校正后,边缘容易一些黑色像素,不美观
//可以虚拟修改相机fx,fy参数,即相当于使用了一个更小或更大的靶面尺寸
Mat NewCameraMatrix = cameraMatrix.clone();
double scale = 0.95;//比例系数
NewCameraMatrix.at<double>(0, 0) = NewCameraMatrix.at<double>(0, 0) / scale;
NewCameraMatrix.at<double>(1, 1) = NewCameraMatrix.at<double>(1, 1) / scale;
initUndistortRectifyMap(cameraMatrix, distCoeffs, Matx33d::eye(), NewCameraMatrix, PicSize, CV_32FC1, mapx, mapy);
for (int i = 0; i < files.size(); i++)
{
img = imread(files.at(i));
remap(img, showimg, mapx, mapy, INTER_LINEAR, BORDER_CONSTANT);
imshow("img", showimg);
waitKey(0);
}
//若想最终得到的图像方向跟世界坐标系方向一致,可以加入旋转矩阵
Mat RotateMtx;
NewCameraMatrix = cameraMatrix.clone();
for (int i = 0; i < files.size(); i++)
{
//这部分可能理解有问题,旋转矢量这个还不太明白,为什么要负方向
Rodrigues(-rvecs[i], RotateMtx);
initUndistortRectifyMap(cameraMatrix, distCoeffs, RotateMtx, cameraMatrix, PicSize, CV_32FC1, mapx, mapy);
img = imread(files.at(i));
remap(img, showimg, mapx, mapy, INTER_LINEAR, BORDER_CONSTANT);
imshow("img", showimg);
waitKey(0);
}
cv::destroyWindow("img");
}