一。标定的目的:
1.畸变矫正。
因为相机成像大都依赖于感光芯片和透镜,现实的感光芯片和透镜,以及其安装并不可能达到理论的完美情况,因此需要矫正由此成像造成影响。
畸变可分为以下几种因素:
①光轴中心与芯片中心不重合(感光芯片安装引起)。
需要求解光轴中心与感光芯片中心的位置偏移量 cx,cy;
②x向和y向 成像投影距离不对等(感光芯片和透镜安装共同引起)。
需要求解各自的投影距离fx,fy;
③径向畸变(透镜质量引起)。
通过泰勒级数展开式模拟矫正结果,需要求解展开式的各项系数k1,k2,k3...kn;
④切向畸变(感光芯片和透镜安装共同引起)。
需要求解切向畸变量参数 p1,p2;
2.求解像素当量,即像素量和物理世界的长度关系,如 每像素代表多少毫米。(有更简便的方法,以后会更新做法)
3.求解相机相对姿态。即了解当前相机对于某个特定物体的特定姿态。
二。如何标定
所有方法标定都大概可以分为以下几个步骤。
1.获取多组图像与物理世界的对应点。
2.求解标定参数。
3.通过标定参数对图像进行矫正
Talk is cheap,show you the code,
以下是Opencv棋盘方式标定的代码示例。
#pragma once
#include
/*创建棋盘图片
Cols_n 图片列数
Rows_n 图片行数
WidthOfROI 棋盘宽度(像素)
pMat 输出图片
*/
void f_get_ChessBoard(int Cols_n, int Rows_n, int WidthOfROI, cv::Mat* pMat)
{
(*pMat) = cv::Mat(WidthOfROI*Rows_n, WidthOfROI*Cols_n, CV_8UC1, cv::Scalar(0));
for (int i = 0; i < Cols_n; ++i)
{
for (int j = 0; j < Rows_n; ++j)
{
if ((i + j) % 2 == 1)
{
for (int x = 0; x < WidthOfROI; x++)
{
for (int y = 0; y < WidthOfROI; y++)
{
int theX = i*WidthOfROI + x;
int theY = j*WidthOfROI + y;
(*pMat).at(theY, theX) = 255;
}
}
}
}
}
}
//1.使用生成图片函数,保存出图片
//2.打印出图片,并使用固定好的相机,拍摄多张摆放位置不一样的棋盘图片
//3.套入以下函数测试。
/*标定相机
rVecChessboardImages 多张棋盘格的图片
cols_n 棋盘列数
rows_n 棋盘行数
square_length 棋盘边长(单位mm)
pCameraMatrix 相机内参矩阵
pDistCoeffs 透镜畸变多项式
*/
bool calibration_solve(const std::vector& rVecChessboardImagePath, const int cols_n, const int rows_n, const double square_length, cv::Mat* pCameraMatrix, cv::Mat*pDistCoeffs)
{
//读取图像文件
int ImgSize = rVecChessboardImagePath.size();
std::vector rVecChessboardImages(ImgSize);
for (int i = 0; i < ImgSize; i++)
{
cv::Mat& rObjImg = rVecChessboardImages[i];
rObjImg = cv::imread(rVecChessboardImagePath[i]);
if (rObjImg.empty())
{
return 0;//无法读取该文件
}
}
//第一步:获取多组图像与物理世界的对应点
//①获取图像点信息。
//在棋盘法标定中, 一般以内角点作为特征点,即以下开始获取每个图片的内角点信息
cv::Size Size_ChessboardCorners(cols_n - 1, rows_n - 1);//只检测内角点,因此需要行列数都减1,若背景为黑色,则行列都减3
std::vector>vec_ChessboardCorners;
for (int i = 0; i < rVecChessboardImages.size(); i++)
{
//开始找查某一个棋盘内角点
std::vector newChessboardCorners;
findChessboardCorners(rVecChessboardImages[i], Size_ChessboardCorners, newChessboardCorners, cv::CALIB_CB_NORMALIZE_IMAGE);
//若对当前角点位置精度效果不满意可进行再迭代
/*
if (1)
{
//方法1:findChessboardCorners中调用的方法,可通过自己再次调用提高效果
cv::TermCriteria criteria = cv::TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 40, 0.001);//迭代参数
cv::cornerSubPix(rVecChessboardImages[i], newChessboardCorners, cv::Size(9, 9), cv::Size(-1, -1), criteria);//findChessboardCorners内部已调用,再次调用,查找更精确位置
}
else
{
//方法2:(不建议此方法,由于对图像做了二值化处理,精度不佳,且部分点可能无法优化)
//此方法具体工作流程为:找到角点。黑白底分别对原图二值化,腐蚀后取轮廓,找到最接近角点的两对轮廓,对每个轮廓简化,求角点,四个轮廓角点交叉交点。作为新角点。
find4QuadCornerSubpix(rVecChessboardImages[i], newChessboardCorners, Size(10, 10));
}
*/
//如果需要查看每张图片的处理效果
if (true)
{
cv::Mat DrawMap;//保证颜色为彩
if (rVecChessboardImages[i].channels()==1)
cv::cvtColor(rVecChessboardImages[i], DrawMap, CV_GRAY2BGR);
else
DrawMap = rVecChessboardImages[i].clone();
cv::drawChessboardCorners(DrawMap, Size_ChessboardCorners, newChessboardCorners,1);
cv::namedWindow("drawMap",0);
cv::resizeWindow("drawMap", cv::Size(800, 600));
cv::imshow("drawMap", DrawMap);
cv::waitKey(1000);
}
vec_ChessboardCorners.push_back(newChessboardCorners);
}
//②获取物理世界信息。
//因为棋盘每个格子是正方形的,且都在同一平面,假设边长为L,则棋盘内角点的坐标为 (L*当前列,L*当前行,0)
int Size_getCornersSucceed = vec_ChessboardCorners.size();
std::vector>vec_ObjPoints(Size_getCornersSucceed, std::vector(Size_ChessboardCorners.width*Size_ChessboardCorners.height));//现实点
for (int y = 0; y < Size_ChessboardCorners.height; y++)
{
for (int x = 0; x < Size_ChessboardCorners.width; x++)
{
int index = y*Size_ChessboardCorners.width + x;
vec_ObjPoints[0][index].x = square_length*x;
vec_ObjPoints[0][index].y = square_length*y;
vec_ObjPoints[0][index].z = 0;//以标定板平面为绝对平面
}
}
for (int i = 1; i < Size_getCornersSucceed; i++)
{
vec_ObjPoints[i] = vec_ObjPoints[i - 1];
}
//第二步:求解标定参数。
//opecvn提供了非常方便的接口
cv::Mat CameraMatrix;
cv::Mat DistCoeffs;
std::vectorVecRotateMatrix;
std::vectorVecMoveMatrix;
double projectionError = cv::calibrateCamera(vec_ObjPoints, vec_ChessboardCorners, cv::Size(rVecChessboardImages[0].cols, rVecChessboardImages[0].rows), CameraMatrix, DistCoeffs, VecRotateMatrix, VecMoveMatrix, CV_CALIB_FIX_K3/*cv::CALIB_FIX_ASPECT_RATIO*/);
//该函数详细解释参考: 单目相机标定-函数解析:calibrateCamera()——》https://blog.csdn.net/ms_cz_1995/article/details/100183853
*pCameraMatrix = CameraMatrix;
*pDistCoeffs = DistCoeffs;
return true;
}
/*矫正图片
rIn_Mat 输入图片
CameraMatrix 相机内参矩阵
DistCoeffs 透镜畸变多项式
pOut_Mat 校正后的图片
*/
bool f_calibration(const cv::Mat& rIn_Mat, const cv::Mat& CameraMatrix, const cv::Mat& DistCoeffs, cv::Mat&Out_Mat)
{
//第三步:通过标定参数对图像进行矫正
//Opencv提供两种矫正方法
if (1)
{
//方法1,适合对单张图片的矫正
undistort(rIn_Mat, Out_Mat, CameraMatrix, DistCoeffs);
}
else
{
//方法2,适合对多张图片,或视频流等,同一个相机的多张图片进行矫正。
cv::Mat mapx;
cv::Mat mapy;
if (0)
{
//处理黑边版本
double alpha = 0;//用于控制边缘黑边,取值范围为0-1,alpha=1时保留所有像素,不处理黑边。
cv::Mat newCameraMatrix = cv::getOptimalNewCameraMatrix(CameraMatrix, DistCoeffs, cv::Size(rIn_Mat.cols, rIn_Mat.rows), alpha);
initUndistortRectifyMap(CameraMatrix, DistCoeffs, cv::noArray(), newCameraMatrix, cv::Size(rIn_Mat.cols, rIn_Mat.rows), CV_32FC1, mapx, mapy);
}
else
{
//不处理黑边版本
initUndistortRectifyMap(CameraMatrix, DistCoeffs, cv::noArray(), cv::noArray(), cv::Size(rIn_Mat.cols, rIn_Mat.rows), CV_32FC1, mapx, mapy);
}
remap(rIn_Mat, Out_Mat, mapx, mapy, cv::INTER_LINEAR);
}
return true;
}
int main(int argc, char** argv)
{
cv::Size ChessBoardSize(10, 7);
//cv::Mat ChessBoard;
//f_get_ChessBoard(ChessBoardSize.width, ChessBoardSize.height, 100, &ChessBoard);
//cv::imwrite("ChessBoard.bmp", ChessBoard);
//打印出"ChessBoard.bmp"放到相机下拍照,获取"1.bmp"
std::vectorVecChessboardImagePath;
VecChessboardImagePath.push_back("1.bmp");
cv::Mat CameraMatrix;
cv::Mat DistCoeffs;
calibration_solve(VecChessboardImagePath, ChessBoardSize.width, ChessBoardSize.height, 1, &CameraMatrix, &DistCoeffs);
cv::Mat inMat = cv::imread("1.bmp");
cv::Mat outMat;
f_calibration(inMat, CameraMatrix, DistCoeffs, outMat);
cv::imwrite("out0.bmp", outMat);
}
因为无大畸变镜头,原图为网图
输出图片: