1.opencv自带标定需要定义棋盘格尺寸,必须拍全,大大限定了标定条件,本示例实现标定板角点提取,算法参考基于生长的棋盘格角点检测,二维码定义棋盘格实际坐标系和棋盘格规格,不用输入任何参数(自动识别棋盘格规格,本示例用的2*2mm标定片),绿色识别到的角点,蓝色标定后重投影点
2.将标定结果打包保存成本地文件,其中包括校正前后图片,保存内容如下图结构体ccbi2文件,大小大概5M
3.读取标定文件,并利用标定文件里的标定参数(相机内参,畸变,外参等)将世界坐标Z=0平面点转换成像素坐标,映射在畸变校正后的标定板图片上,基本每个角点都是对准的,如果用像素坐标(亚像素坐标)转世界坐标误差大概在0.01mm;
4.下面为手动估计着找的4个非亚像素坐标点,转换为世界坐标,映射到图片上的效果,如果是输入物理坐标,算出亚像素坐标,再将亚像素坐标转换为世界坐标,基本和输入的差距在0.01mm
以下是代码结构逻辑图
int main()
{
clock_t start, end;
start = clock();
checkerboard cb;
//创建并生成标定板
//cb.checkerboard_create(30,30,2, src1,true);
//Mat src1 = imread("E:\\Project_Opencv\\Class_Blues\\Class_Blues\\pic\\Image_3.png", IMREAD_GRAYSCALE);
相机标定
//CalibratedCheckerBoardInfo ccbi1 = cb.checkerboard_calibration(src1);
将标定结果保存于本地文件
//cb.CaliResultFileWrite("checkerboardresult.yml", ccbi1);
//读取本地标定信息
CalibratedCheckerBoardInfo ccbi2 ;
cb.CaliResultFileRead("checkerboardresult.yml", ccbi2);
//从本地标定文件拿到原始标定板图片
Mat img_src = ccbi2.Img_checkerboard;
//从本地文件拿到标定后校正图片
Mat img_cali = ccbi2.Img_Calibrated;
//校正前后后灰度图片转彩色
Mat img_cali_color,img_src_color,img_src_color1;
cvtColor(img_cali, img_cali_color,COLOR_GRAY2BGR);
cvtColor(img_src, img_src_color, COLOR_GRAY2BGR);
cvtColor(img_src, img_src_color1, COLOR_GRAY2BGR);
//undistort(color_img, img_calib, ccbi2.cameraMatrix, ccbi2.distCoeffs);
//定义世界坐标点
vector worldpoints;
for (int i = 10; i < 80; i++)
{
for (int j = -22; j < 70; j++)
{
Point3d temp;
temp.x = i * 2;
temp.y = j * 2;
temp.z = 0;
worldpoints.push_back(temp);
}
}
//世界坐标转换到图片像素坐标
struct CalibratedResultInfo caliInfo;
caliInfo.cameraMatrix = ccbi2.cameraMatrix;
caliInfo.distCoeffs = ccbi2.distCoeffs;
caliInfo.rvecsMat = ccbi2.rvecsMat;
caliInfo.tvecsMat = ccbi2.tvecsMat;
vector pixel_points;
cb.WorldPoints2PixelPoints(caliInfo, worldpoints, pixel_points);
//将世界坐标转换后的像素坐标画在图片上
for (int i = 0; i < pixel_points.size(); i++)
{
drawMarker(img_cali_color, pixel_points[i],Scalar(0,255,0), MARKER_TILTED_CROSS,20,2,8);
drawMarker(img_src_color, pixel_points[i], Scalar(0, 0, 255), MARKER_TILTED_CROSS, 20, 2, 8);
}
//绘制识别到的棋盘格角点和重投影点
for (int i = 0; i < ccbi2.Pixel_Points.size(); i++)
{
drawMarker(img_src_color1, ccbi2.Pixel_Points[i], Scalar(0, 255, 0), MARKER_SQUARE, 15, 2, 8);
drawMarker(img_src_color1, ccbi2.Reprojected_Points[i], Scalar(255, 0, 0), MARKER_TILTED_CROSS, 20, 2, 8);
}
//像素坐标转世界坐标
//定义图片上四个关键点的像素坐标
Point2f P0 = Point2f(1299,705);
Point2f P1 = Point2f(1880, 690);
Point2f P2 = Point2f(1892, 1282);
Point2f P3 = Point2f(1309, 1296);
vector checkerboard_pixel_points;
vector world_points;
checkerboard_pixel_points.push_back(P0);
checkerboard_pixel_points.push_back(P1);
checkerboard_pixel_points.push_back(P2);
checkerboard_pixel_points.push_back(P3);
cb.PixelPoint2WorldPoint(ccbi2.cameraMatrix, ccbi2.rvecsMat, ccbi2.tvecsMat, checkerboard_pixel_points, world_points);
//图片上画出像素转世界坐标位置
Mat img_cali_color1;
cvtColor(img_cali, img_cali_color1, COLOR_GRAY2BGR);
for (int i = 0; i < world_points.size(); i++)
{
cv::circle(img_cali_color1, checkerboard_pixel_points[i], 8, Scalar(0, 255, 0), 2, 8);
string temp_xy = format("X: %.2f Y:%.2f", world_points[i].x, world_points[i].y);
putText(img_cali_color1, temp_xy, Point(int(checkerboard_pixel_points[i].x-50), int(checkerboard_pixel_points[i].y-30)), FONT_HERSHEY_SIMPLEX, 1.5, Scalar(255, 255, 0),2, 8, 0);
}
imwrite("image_point.png", img_src_color1);
end = clock(); //程序结束用时
double endtime = (double)(end - start) / CLOCKS_PER_SEC;
cout << "Total time:" << endtime * 1000 << "ms" << endl; //ms为单位
imwrite("calibcheck.png", img_cali_color);
imwrite("uncalibcheck.png", img_src_color);
waitKey(0);
destroyAllWindows();
return 0;
}