标定系列——基于OpenCV实现普通相机、鱼眼相机不同标定板下的标定(五)

标定系列——基于OpenCV实现相机标定(五)

  • 说明
  • 代码解析
    • VID5.xml
    • in_VID5.xml
    • camera_calibration.cpp

说明

该程序可以实现多种标定板的相机标定工作

代码解析

VID5.xml

<?xml version="1.0"?><!-- 相机拍摄的标定板图像路径名 -->
<opencv_storage>
<images>
images/CameraCalibration/VID5/xx1.jpg
images/CameraCalibration/VID5/xx2.jpg
images/CameraCalibration/VID5/xx3.jpg
images/CameraCalibration/VID5/xx4.jpg
images/CameraCalibration/VID5/xx5.jpg
images/CameraCalibration/VID5/xx6.jpg
images/CameraCalibration/VID5/xx7.jpg
images/CameraCalibration/VID5/xx8.jpg
</images>
</opencv_storage>

in_VID5.xml

<?xml version="1.0"?>
<opencv_storage>
<Settings><!-- 标定板尺寸. (可以是正方形、圆形) --><BoardSize_Width>9</BoardSize_Width><BoardSize_Height>6</BoardSize_Height><!-- 用户定义的方格的尺寸 (像素,毫米)--><Square_Size>50</Square_Size><Marker_Size>25</Marker_Size><!-- 相机标定所使用的标定板类型. 可以是CHESSBOARD CHARUCOBOARD CIRCLES_GRID ASYMMETRIC_CIRCLES_GRID --><Calibrate_Pattern>"CHESSBOARD"</Calibrate_Pattern><ArUco_Dict_Name>DICT_4X4_50</ArUco_Dict_Name><ArUco_Dict_File_Name></ArUco_Dict_File_Name><!--  用于标定的输入来源。使用输入摄像头 -> 提供摄像头的ID,例如 "1"使用输入视频  -> 提供输入视频的路径,例如 "/tmp/x.avi"使用图像列表  -> 提供含有图像列表的XML或YAML文件的路径,例如 "/tmp/circles_list.xml"--><Input>"images/CameraCalibration/VID5/VID5.xml"</Input><!--  如果为真(非零),则沿水平轴翻转输入图像 --><Input_FlipAroundHorizontalAxis>0</Input_FlipAroundHorizontalAxis><!-- 摄像头的帧之间的时间延迟 --><Input_Delay>100</Input_Delay>	<!--  用于标定的帧数量 --><Calibrate_NrOfFrameToUse>25</Calibrate_NrOfFrameToUse><!-- 只考虑fy作为自由参数,比率fx/fy与输入cameraMatrix中的相同 --><Calibrate_FixAspectRatio> 1 </Calibrate_FixAspectRatio><!-- 如果为真(非零),切向畸变系数将被设置为零并保持为零 --><Calibrate_AssumeZeroTangentialDistortion>1</Calibrate_AssumeZeroTangentialDistortion><!-- 如果为真(非零),在全局优化过程中主点不会改变 --><Calibrate_FixPrincipalPointAtTheCenter> 1 </Calibrate_FixPrincipalPointAtTheCenter><!-- 输出日志文件名 --><Write_outputFileName>"out_camera_data.xml"</Write_outputFileName><!-- 如果为真(非零),将检测到的特征点写入输出文件 --><Write_DetectedFeaturePoints>1</Write_DetectedFeaturePoints><!-- 如果为真(非零),我们将外部相机参数写入输出文件 --><Write_extrinsicParameters>1</Write_extrinsicParameters><!--  如果为真(非零),我们将优化后的3D目标网格点写入输出文件 --><Write_gridPoints>1</Write_gridPoints><!-- 如果为真(非零),校准后我们显示无畸变的图像 --><Show_UndistortedImage>1</Show_UndistortedImage><!-- 如果为真(非零),将使用鱼眼相机模型进行标定 --><Calibrate_UseFisheyeModel>0</Calibrate_UseFisheyeModel><!-- 如果为真(非零),畸变系数k1将等于零 --><Fix_K1>0</Fix_K1><!-- 如果为真(非零),畸变系数k2将等于零  --><Fix_K2>0</Fix_K2><!-- 如果为真(非零),畸变系数k3将等于零 --><Fix_K3>0</Fix_K3><!-- 如果为真(非零),畸变系数k4将等于零 --><Fix_K4>1</Fix_K4><!-- 如果为真(非零),畸变系数k5将等于零 --><Fix_K5>1</Fix_K5>
</Settings>
</opencv_storage>

camera_calibration.cpp

核心代码就是camera_calibration.cpp,主要通过多张标定板图像进行相机的内参和畸变参数的计算,大体看了一下,里面的逻辑很清晰,就不做过多注解了

#include <iostream>
#include <sstream>
#include <string>
#include <ctime>
#include <cstdio>#include <opencv2/core.hpp>
#include <opencv2/core/utility.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/calib3d.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/videoio.hpp>
#include <opencv2/highgui.hpp>
#include "opencv2/objdetect/charuco_detector.hpp"using namespace cv;
using namespace std;class Settings
{
public:Settings() : goodInput(false) {}enum Pattern { NOT_EXISTING, CHESSBOARD, CHARUCOBOARD, CIRCLES_GRID, ASYMMETRIC_CIRCLES_GRID };enum InputType { INVALID, CAMERA, VIDEO_FILE, IMAGE_LIST };void write(FileStorage& fs) const                        //将数据写入文件{fs << "{"<< "BoardSize_Width"  << boardSize.width<< "BoardSize_Height" << boardSize.height<< "Square_Size"         << squareSize<< "Marker_Size"      << markerSize<< "Calibrate_Pattern" << patternToUse<< "ArUco_Dict_Name"   << arucoDictName<< "ArUco_Dict_File_Name" << arucoDictFileName<< "Calibrate_NrOfFrameToUse" << nrFrames<< "Calibrate_FixAspectRatio" << aspectRatio<< "Calibrate_AssumeZeroTangentialDistortion" << calibZeroTangentDist<< "Calibrate_FixPrincipalPointAtTheCenter" << calibFixPrincipalPoint<< "Write_DetectedFeaturePoints" << writePoints<< "Write_extrinsicParameters"   << writeExtrinsics<< "Write_gridPoints" << writeGrid<< "Write_outputFileName"  << outputFileName<< "Show_UndistortedImage" << showUndistorted<< "Input_FlipAroundHorizontalAxis" << flipVertical<< "Input_Delay" << delay<< "Input" << input<< "}";}void read(const FileNode& node)                          //从文件中读{node["BoardSize_Width"] >> boardSize.width;node["BoardSize_Height"] >> boardSize.height;node["Calibrate_Pattern"] >> patternToUse;node["ArUco_Dict_Name"] >> arucoDictName;node["ArUco_Dict_File_Name"] >> arucoDictFileName;node["Square_Size"] >> squareSize;node["Marker_Size"] >> markerSize;node["Calibrate_NrOfFrameToUse"] >> nrFrames;node["Calibrate_FixAspectRatio"] >> aspectRatio;node["Write_DetectedFeaturePoints"] >> writePoints;node["Write_extrinsicParameters"] >> writeExtrinsics;node["Write_gridPoints"] >> writeGrid;node["Write_outputFileName"] >> outputFileName;node["Calibrate_AssumeZeroTangentialDistortion"] >> calibZeroTangentDist;node["Calibrate_FixPrincipalPointAtTheCenter"] >> calibFixPrincipalPoint;node["Calibrate_UseFisheyeModel"] >> useFisheye;node["Input_FlipAroundHorizontalAxis"] >> flipVertical;node["Show_UndistortedImage"] >> showUndistorted;node["Input"] >> input;node["Input_Delay"] >> delay;node["Fix_K1"] >> fixK1;node["Fix_K2"] >> fixK2;node["Fix_K3"] >> fixK3;node["Fix_K4"] >> fixK4;node["Fix_K5"] >> fixK5;validate();}// 输入值验证void validate(){goodInput = true;if (boardSize.width <= 0 || boardSize.height <= 0){cerr << "Invalid Board size: " << boardSize.width << " " << boardSize.height << endl;goodInput = false;}if (squareSize <= 10e-6){cerr << "Invalid square size " << squareSize << endl;goodInput = false;}if (nrFrames <= 0){cerr << "Invalid number of frames " << nrFrames << endl;goodInput = false;}if (input.empty())      // Check for valid inputinputType = INVALID;else{if (input[0] >= '0' && input[0] <= '9'){stringstream ss(input);ss >> cameraID;inputType = CAMERA;}else{if (isListOfImages(input) && readStringList(input, imageList)){inputType = IMAGE_LIST;nrFrames = (nrFrames < (int)imageList.size()) ? nrFrames : (int)imageList.size();}elseinputType = VIDEO_FILE;}if (inputType == CAMERA)inputCapture.open(cameraID);if (inputType == VIDEO_FILE)inputCapture.open(input);if (inputType != IMAGE_LIST && !inputCapture.isOpened())inputType = INVALID;}if (inputType == INVALID){cerr << " Input does not exist: " << input;goodInput = false;}flag = 0;if(calibFixPrincipalPoint) flag |= CALIB_FIX_PRINCIPAL_POINT;if(calibZeroTangentDist)   flag |= CALIB_ZERO_TANGENT_DIST;if(aspectRatio)            flag |= CALIB_FIX_ASPECT_RATIO;if(fixK1)                  flag |= CALIB_FIX_K1;if(fixK2)                  flag |= CALIB_FIX_K2;if(fixK3)                  flag |= CALIB_FIX_K3;if(fixK4)                  flag |= CALIB_FIX_K4;if(fixK5)                  flag |= CALIB_FIX_K5;if (useFisheye) {// the fisheye model has its own enum, so overwrite the flagsflag = fisheye::CALIB_FIX_SKEW | fisheye::CALIB_RECOMPUTE_EXTRINSIC;if(fixK1)                   flag |= fisheye::CALIB_FIX_K1;if(fixK2)                   flag |= fisheye::CALIB_FIX_K2;if(fixK3)                   flag |= fisheye::CALIB_FIX_K3;if(fixK4)                   flag |= fisheye::CALIB_FIX_K4;if (calibFixPrincipalPoint) flag |= fisheye::CALIB_FIX_PRINCIPAL_POINT;}calibrationPattern = NOT_EXISTING;if (!patternToUse.compare("CHESSBOARD")) calibrationPattern = CHESSBOARD;if (!patternToUse.compare("CHARUCOBOARD")) calibrationPattern = CHARUCOBOARD;if (!patternToUse.compare("CIRCLES_GRID")) calibrationPattern = CIRCLES_GRID;if (!patternToUse.compare("ASYMMETRIC_CIRCLES_GRID")) calibrationPattern = ASYMMETRIC_CIRCLES_GRID;if (calibrationPattern == NOT_EXISTING){cerr << " Camera calibration mode does not exist: " << patternToUse << endl;goodInput = false;}atImageList = 0;}// 获取图像Mat nextImage(){Mat result;if( inputCapture.isOpened() ){Mat view0;inputCapture >> view0;view0.copyTo(result);}else if( atImageList < imageList.size() )result = imread(imageList[atImageList++], IMREAD_COLOR);return result;}//读取图像名,保存在vectorstatic bool readStringList( const string& filename, vector<string>& l ){l.clear();FileStorage fs(filename, FileStorage::READ);if( !fs.isOpened() )return false;FileNode n = fs.getFirstTopLevelNode();if( n.type() != FileNode::SEQ )return false;FileNodeIterator it = n.begin(), it_end = n.end();for( ; it != it_end; ++it )l.push_back((string)*it);return true;}static bool isListOfImages( const string& filename){string s(filename);// Look for file extensionif( s.find(".xml") == string::npos && s.find(".yaml") == string::npos && s.find(".yml") == string::npos )return false;elsereturn true;}public:Size boardSize;              // The size of the board -> Number of items by width and heightPattern calibrationPattern;  // One of the Chessboard, ChArUco board, circles, or asymmetric circle patternfloat squareSize;            // The size of a square in your defined unit (point, millimeter,etc).float markerSize;            // The size of a marker in your defined unit (point, millimeter,etc).string arucoDictName;        // The Name of ArUco dictionary which you use in ChArUco patternstring arucoDictFileName;    // The Name of file which contains ArUco dictionary for ChArUco patternint nrFrames;                // The number of frames to use from the input for calibrationfloat aspectRatio;           // The aspect ratioint delay;                   // In case of a video inputbool writePoints;            // Write detected feature pointsbool writeExtrinsics;        // Write extrinsic parametersbool writeGrid;              // Write refined 3D target grid pointsbool calibZeroTangentDist;   // Assume zero tangential distortionbool calibFixPrincipalPoint; // Fix the principal point at the centerbool flipVertical;           // Flip the captured images around the horizontal axisstring outputFileName;       // The name of the file where to writebool showUndistorted;        // Show undistorted images after calibrationstring input;                // The input ->bool useFisheye;             // use fisheye camera model for calibrationbool fixK1;                  // fix K1 distortion coefficientbool fixK2;                  // fix K2 distortion coefficientbool fixK3;                  // fix K3 distortion coefficientbool fixK4;                  // fix K4 distortion coefficientbool fixK5;                  // fix K5 distortion coefficientint cameraID;vector<string> imageList;size_t atImageList;VideoCapture inputCapture;InputType inputType;bool goodInput;int flag;private:string patternToUse;};static inline void read(const FileNode& node, Settings& x, const Settings& default_value = Settings())
{if(node.empty())x = default_value;elsex.read(node);
}enum { DETECTION = 0, CAPTURING = 1, CALIBRATED = 2 };bool runCalibrationAndSave(Settings& s, Size imageSize, Mat&  cameraMatrix, Mat& distCoeffs,vector<vector<Point2f> > imagePoints, float grid_width, bool release_object);int main(int argc, char* argv[])
{const String keys= "{help h usage ? |           | print this message            }""{@settings      |default.xml| input setting file            }""{d              |           | actual distance between top-left and top-right corners of ""the calibration grid }""{winSize        | 11        | Half of search window for cornerSubPix }";CommandLineParser parser(argc, argv, keys);parser.about("This is a camera calibration sample.\n""Usage: camera_calibration [configuration_file -- default ./default.xml]\n""Near the sample file you'll find the configuration file, which has detailed help of ""how to edit it. It may be any OpenCV supported file format XML/YAML.");if (!parser.check()) {parser.printErrors();return 0;}if (parser.has("help")) {parser.printMessage();return 0;}//! [file_read]Settings s;const string inputSettingsFile = parser.get<string>(0);FileStorage fs(inputSettingsFile, FileStorage::READ); // Read the settingsif (!fs.isOpened()){cout << "Could not open the configuration file: \"" << inputSettingsFile << "\"" << endl;parser.printMessage();return -1;}fs["Settings"] >> s;fs.release();                                         // close Settings file//! [file_read]if (!s.goodInput){cout << "Invalid input detected. Application stopping. " << endl;return -1;}int winSize = parser.get<int>("winSize"); // 获取角点搜索窗口大小的一半float grid_width = s.squareSize * (s.boardSize.width - 1);if (s.calibrationPattern == Settings::Pattern::CHARUCOBOARD) {grid_width = s.squareSize * (s.boardSize.width - 2);}bool release_object = false;if (parser.has("d")) {grid_width = parser.get<float>("d");release_object = true;}// 创建CharucoBoard棋盘对象cv::aruco::Dictionary dictionary;// 如果标定模式为CHARUCOBOARD,创建相应的字典if (s.calibrationPattern == Settings::CHARUCOBOARD) {if (s.arucoDictFileName == "") {cv::aruco::PredefinedDictionaryType arucoDict;if (s.arucoDictName == "DICT_4X4_50") { arucoDict = cv::aruco::DICT_4X4_50; }else if (s.arucoDictName == "DICT_4X4_100") { arucoDict = cv::aruco::DICT_4X4_100; }else if (s.arucoDictName == "DICT_4X4_250") { arucoDict = cv::aruco::DICT_4X4_250; }else if (s.arucoDictName == "DICT_4X4_1000") { arucoDict = cv::aruco::DICT_4X4_1000; }else if (s.arucoDictName == "DICT_5X5_50") { arucoDict = cv::aruco::DICT_5X5_50; }else if (s.arucoDictName == "DICT_5X5_100") { arucoDict = cv::aruco::DICT_5X5_100; }else if (s.arucoDictName == "DICT_5X5_250") { arucoDict = cv::aruco::DICT_5X5_250; }else if (s.arucoDictName == "DICT_5X5_1000") { arucoDict = cv::aruco::DICT_5X5_1000; }else if (s.arucoDictName == "DICT_6X6_50") { arucoDict = cv::aruco::DICT_6X6_50; }else if (s.arucoDictName == "DICT_6X6_100") { arucoDict = cv::aruco::DICT_6X6_100; }else if (s.arucoDictName == "DICT_6X6_250") { arucoDict = cv::aruco::DICT_6X6_250; }else if (s.arucoDictName == "DICT_6X6_1000") { arucoDict = cv::aruco::DICT_6X6_1000; }else if (s.arucoDictName == "DICT_7X7_50") { arucoDict = cv::aruco::DICT_7X7_50; }else if (s.arucoDictName == "DICT_7X7_100") { arucoDict = cv::aruco::DICT_7X7_100; }else if (s.arucoDictName == "DICT_7X7_250") { arucoDict = cv::aruco::DICT_7X7_250; }else if (s.arucoDictName == "DICT_7X7_1000") { arucoDict = cv::aruco::DICT_7X7_1000; }else if (s.arucoDictName == "DICT_ARUCO_ORIGINAL") { arucoDict = cv::aruco::DICT_ARUCO_ORIGINAL; }else if (s.arucoDictName == "DICT_APRILTAG_16h5") { arucoDict = cv::aruco::DICT_APRILTAG_16h5; }else if (s.arucoDictName == "DICT_APRILTAG_25h9") { arucoDict = cv::aruco::DICT_APRILTAG_25h9; }else if (s.arucoDictName == "DICT_APRILTAG_36h10") { arucoDict = cv::aruco::DICT_APRILTAG_36h10; }else if (s.arucoDictName == "DICT_APRILTAG_36h11") { arucoDict = cv::aruco::DICT_APRILTAG_36h11; }else {cout << "incorrect name of aruco dictionary \n";return 1;}dictionary = cv::aruco::getPredefinedDictionary(arucoDict);}else {cv::FileStorage dict_file(s.arucoDictFileName, cv::FileStorage::Mode::READ);cv::FileNode fn(dict_file.root());dictionary.readDictionary(fn);}}else {// default dictionarydictionary = cv::aruco::getPredefinedDictionary(0);}// 创建CharucoBoard对象和检测器cv::aruco::CharucoBoard ch_board({s.boardSize.width, s.boardSize.height}, s.squareSize, s.markerSize, dictionary);cv::aruco::CharucoDetector ch_detector(ch_board);std::vector<int> markerIds;vector<vector<Point2f> > imagePoints;Mat cameraMatrix, distCoeffs;Size imageSize;int mode = s.inputType == Settings::IMAGE_LIST ? CAPTURING : DETECTION;clock_t prevTimestamp = 0;const Scalar RED(0,0,255), GREEN(0,255,0);const char ESC_KEY = 27;//! [get_input]// 循环处理图像for(;;){Mat view;bool blinkOutput = false;view = s.nextImage();//-----  If no more image, or got enough, then stop calibration and show result -------------if( mode == CAPTURING && imagePoints.size() >= (size_t)s.nrFrames ){// 调用标定函数,成功则切换到CALIBRATED模式,否则回到DETECTION模式if(runCalibrationAndSave(s, imageSize,  cameraMatrix, distCoeffs, imagePoints, grid_width,release_object))mode = CALIBRATED;elsemode = DETECTION;}if(view.empty())          // If there are no more images stop the loop{// if calibration threshold was not reached yet, calibrate nowif( mode != CALIBRATED && !imagePoints.empty() )runCalibrationAndSave(s, imageSize,  cameraMatrix, distCoeffs, imagePoints, grid_width,release_object);break;}//! [get_input]imageSize = view.size();  // Format input image.if( s.flipVertical )    flip( view, view, 0 );//! [find_pattern]vector<Point2f> pointBuf;bool found;int chessBoardFlags = CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_NORMALIZE_IMAGE;if(!s.useFisheye) {// fast check erroneously fails with high distortions like fisheyechessBoardFlags |= CALIB_CB_FAST_CHECK;}switch( s.calibrationPattern ) // Find feature points on the input format{case Settings::CHESSBOARD:found = findChessboardCorners( view, s.boardSize, pointBuf, chessBoardFlags);break;case Settings::CHARUCOBOARD:ch_detector.detectBoard( view, pointBuf, markerIds);found = pointBuf.size() == (size_t)((s.boardSize.height - 1)*(s.boardSize.width - 1));break;case Settings::CIRCLES_GRID:found = findCirclesGrid( view, s.boardSize, pointBuf );break;case Settings::ASYMMETRIC_CIRCLES_GRID:found = findCirclesGrid( view, s.boardSize, pointBuf, CALIB_CB_ASYMMETRIC_GRID );break;default:found = false;break;}//! [find_pattern]//! [pattern_found]if (found)                // If done with success,{// improve the found corners' coordinate accuracy for chessboardif( s.calibrationPattern == Settings::CHESSBOARD){Mat viewGray;cvtColor(view, viewGray, COLOR_BGR2GRAY);cornerSubPix( viewGray, pointBuf, Size(winSize,winSize),Size(-1,-1), TermCriteria( TermCriteria::EPS+TermCriteria::COUNT, 30, 0.0001 ));}if( mode == CAPTURING &&  // For camera only take new samples after delay time(!s.inputCapture.isOpened() || clock() - prevTimestamp > s.delay*1e-3*CLOCKS_PER_SEC) ){imagePoints.push_back(pointBuf);prevTimestamp = clock();blinkOutput = s.inputCapture.isOpened();}// Draw the corners.if(s.calibrationPattern == Settings::CHARUCOBOARD)drawChessboardCorners( view, cv::Size(s.boardSize.width-1, s.boardSize.height-1), Mat(pointBuf), found );elsedrawChessboardCorners( view, s.boardSize, Mat(pointBuf), found );}//! [pattern_found]//----------------------------- Output Text ------------------------------------------------//! [output_text]string msg = (mode == CAPTURING) ? "100/100" :mode == CALIBRATED ? "Calibrated" : "Press 'g' to start";int baseLine = 0;Size textSize = getTextSize(msg, 1, 1, 1, &baseLine);Point textOrigin(view.cols - 2*textSize.width - 10, view.rows - 2*baseLine - 10);if( mode == CAPTURING ){if(s.showUndistorted)msg = cv::format( "%d/%d Undist", (int)imagePoints.size(), s.nrFrames );elsemsg = cv::format( "%d/%d", (int)imagePoints.size(), s.nrFrames );}putText( view, msg, textOrigin, 1, 1, mode == CALIBRATED ?  GREEN : RED);if( blinkOutput )bitwise_not(view, view);//! [output_text]//------------------------- Video capture  output  undistorted ------------------------------//! [output_undistorted]if( mode == CALIBRATED && s.showUndistorted ){Mat temp = view.clone();if (s.useFisheye){Mat newCamMat;fisheye::estimateNewCameraMatrixForUndistortRectify(cameraMatrix, distCoeffs, imageSize,Matx33d::eye(), newCamMat, 1);cv::fisheye::undistortImage(temp, view, cameraMatrix, distCoeffs, newCamMat);}elseundistort(temp, view, cameraMatrix, distCoeffs);}//! [output_undistorted]//------------------------------ Show image and check for input commands -------------------//! [await_input]imshow("Image View", view);char key = (char)waitKey(s.inputCapture.isOpened() ? 50 : s.delay);if( key  == ESC_KEY )break;if( key == 'u' && mode == CALIBRATED )s.showUndistorted = !s.showUndistorted;if( s.inputCapture.isOpened() && key == 'g' ){mode = CAPTURING;imagePoints.clear();}//! [await_input]}// -----------------------Show the undistorted image for the image list ------------------------//! [show_results]if( s.inputType == Settings::IMAGE_LIST && s.showUndistorted && !cameraMatrix.empty()){Mat view, rview, map1, map2;if (s.useFisheye) // 如果使用鱼眼镜头模型进行畸变矫正{Mat newCamMat; // 定义新的相机矩阵// 估计畸变校正和矩形映射所需的新相机矩阵fisheye::estimateNewCameraMatrixForUndistortRectify(cameraMatrix, distCoeffs, imageSize,Matx33d::eye(), newCamMat, 1);// 初始化畸变矫正和矩形映射fisheye::initUndistortRectifyMap(cameraMatrix, distCoeffs, Matx33d::eye(), newCamMat, imageSize,CV_16SC2, map1, map2);}else{initUndistortRectifyMap(cameraMatrix, distCoeffs, Mat(),getOptimalNewCameraMatrix(cameraMatrix, distCoeffs, imageSize, 1, imageSize, 0), imageSize,CV_16SC2, map1, map2);}for(size_t i = 0; i < s.imageList.size(); i++ ){view = imread(s.imageList[i], IMREAD_COLOR);if(view.empty())continue;remap(view, rview, map1, map2, INTER_LINEAR);imshow("Image View", rview);char c = (char)waitKey();if( c  == ESC_KEY || c == 'q' || c == 'Q' )break;}}//! [show_results]return 0;
}//! [compute_errors] 计算重映射误差的函数
static double computeReprojectionErrors( const vector<vector<Point3f> >& objectPoints,const vector<vector<Point2f> >& imagePoints,const vector<Mat>& rvecs, const vector<Mat>& tvecs,const Mat& cameraMatrix , const Mat& distCoeffs,vector<float>& perViewErrors, bool fisheye)
{vector<Point2f> imagePoints2;size_t totalPoints = 0;double totalErr = 0, err;perViewErrors.resize(objectPoints.size());for(size_t i = 0; i < objectPoints.size(); ++i ){if (fisheye) // 如果是鱼眼镜头模型,使用fisheye命名空间的函数来投影点{fisheye::projectPoints(objectPoints[i], imagePoints2, rvecs[i], tvecs[i], cameraMatrix,distCoeffs);}else{projectPoints(objectPoints[i], rvecs[i], tvecs[i], cameraMatrix, distCoeffs, imagePoints2);}err = norm(imagePoints[i], imagePoints2, NORM_L2);size_t n = objectPoints[i].size();perViewErrors[i] = (float) std::sqrt(err*err/n);totalErr        += err*err;totalPoints     += n;}return std::sqrt(totalErr/totalPoints);
}
//! [compute_errors]
//! [board_corners]计算棋盘格角点位置
static void calcBoardCornerPositions(Size boardSize, float squareSize, vector<Point3f>& corners,Settings::Pattern patternType /*= Settings::CHESSBOARD*/)
{corners.clear();switch(patternType){case Settings::CHESSBOARD: // 标准棋盘格case Settings::CIRCLES_GRID: // 圆形网格棋盘for (int i = 0; i < boardSize.height; ++i) {for (int j = 0; j < boardSize.width; ++j) {corners.push_back(Point3f(j*squareSize, i*squareSize, 0));}}break;case Settings::CHARUCOBOARD: // CHARUCO棋盘for (int i = 0; i < boardSize.height - 1; ++i) {for (int j = 0; j < boardSize.width - 1; ++j) {corners.push_back(Point3f(j*squareSize, i*squareSize, 0));}}break;case Settings::ASYMMETRIC_CIRCLES_GRID: // 非对称圆形网格for (int i = 0; i < boardSize.height; i++) {for (int j = 0; j < boardSize.width; j++) {corners.push_back(Point3f((2 * j + i % 2)*squareSize, i*squareSize, 0));}}break;default:break;}
}
//! [board_corners]
static bool runCalibration( Settings& s, Size& imageSize, Mat& cameraMatrix, Mat& distCoeffs,vector<vector<Point2f> > imagePoints, vector<Mat>& rvecs, vector<Mat>& tvecs,vector<float>& reprojErrs,  double& totalAvgErr, vector<Point3f>& newObjPoints,float grid_width, bool release_object)
{//! [fixed_aspect]cameraMatrix = Mat::eye(3, 3, CV_64F);if( !s.useFisheye && s.flag & CALIB_FIX_ASPECT_RATIO )cameraMatrix.at<double>(0,0) = s.aspectRatio;//! [fixed_aspect]if (s.useFisheye) {distCoeffs = Mat::zeros(4, 1, CV_64F);} else {distCoeffs = Mat::zeros(8, 1, CV_64F);}vector<vector<Point3f> > objectPoints(1);calcBoardCornerPositions(s.boardSize, s.squareSize, objectPoints[0], s.calibrationPattern);if (s.calibrationPattern == Settings::Pattern::CHARUCOBOARD) {objectPoints[0][s.boardSize.width - 2].x = objectPoints[0][0].x + grid_width;}else {objectPoints[0][s.boardSize.width - 1].x = objectPoints[0][0].x + grid_width;}newObjPoints = objectPoints[0];objectPoints.resize(imagePoints.size(),objectPoints[0]);//Find intrinsic and extrinsic camera parametersdouble rms;if (s.useFisheye) {Mat _rvecs, _tvecs;rms = fisheye::calibrate(objectPoints, imagePoints, imageSize, cameraMatrix, distCoeffs, _rvecs,_tvecs, s.flag);rvecs.reserve(_rvecs.rows);tvecs.reserve(_tvecs.rows);for(int i = 0; i < int(objectPoints.size()); i++){rvecs.push_back(_rvecs.row(i));tvecs.push_back(_tvecs.row(i));}} else {int iFixedPoint = -1;if (release_object)iFixedPoint = s.boardSize.width - 1;rms = calibrateCameraRO(objectPoints, imagePoints, imageSize, iFixedPoint,cameraMatrix, distCoeffs, rvecs, tvecs, newObjPoints,s.flag | CALIB_USE_LU);}if (release_object) {cout << "New board corners: " << endl;cout << newObjPoints[0] << endl;cout << newObjPoints[s.boardSize.width - 1] << endl;cout << newObjPoints[s.boardSize.width * (s.boardSize.height - 1)] << endl;cout << newObjPoints.back() << endl;}cout << "Re-projection error reported by calibrateCamera: "<< rms << endl;bool ok = checkRange(cameraMatrix) && checkRange(distCoeffs);objectPoints.clear();objectPoints.resize(imagePoints.size(), newObjPoints);totalAvgErr = computeReprojectionErrors(objectPoints, imagePoints, rvecs, tvecs, cameraMatrix,distCoeffs, reprojErrs, s.useFisheye);return ok;
}// Print camera parameters to the output file
static void saveCameraParams( Settings& s, Size& imageSize, Mat& cameraMatrix, Mat& distCoeffs,const vector<Mat>& rvecs, const vector<Mat>& tvecs,const vector<float>& reprojErrs, const vector<vector<Point2f> >& imagePoints,double totalAvgErr, const vector<Point3f>& newObjPoints )
{FileStorage fs( s.outputFileName, FileStorage::WRITE );time_t tm;time( &tm );struct tm *t2 = localtime( &tm );char buf[1024];strftime( buf, sizeof(buf), "%c", t2 );fs << "calibration_time" << buf;if( !rvecs.empty() || !reprojErrs.empty() )fs << "nr_of_frames" << (int)std::max(rvecs.size(), reprojErrs.size());fs << "image_width" << imageSize.width;fs << "image_height" << imageSize.height;fs << "board_width" << s.boardSize.width;fs << "board_height" << s.boardSize.height;fs << "square_size" << s.squareSize;fs << "marker_size" << s.markerSize;if( !s.useFisheye && s.flag & CALIB_FIX_ASPECT_RATIO )fs << "fix_aspect_ratio" << s.aspectRatio;if (s.flag){std::stringstream flagsStringStream;if (s.useFisheye){flagsStringStream << "flags:"<< (s.flag & fisheye::CALIB_FIX_SKEW ? " +fix_skew" : "")<< (s.flag & fisheye::CALIB_FIX_K1 ? " +fix_k1" : "")<< (s.flag & fisheye::CALIB_FIX_K2 ? " +fix_k2" : "")<< (s.flag & fisheye::CALIB_FIX_K3 ? " +fix_k3" : "")<< (s.flag & fisheye::CALIB_FIX_K4 ? " +fix_k4" : "")<< (s.flag & fisheye::CALIB_RECOMPUTE_EXTRINSIC ? " +recompute_extrinsic" : "");}else{flagsStringStream << "flags:"<< (s.flag & CALIB_USE_INTRINSIC_GUESS ? " +use_intrinsic_guess" : "")<< (s.flag & CALIB_FIX_ASPECT_RATIO ? " +fix_aspectRatio" : "")<< (s.flag & CALIB_FIX_PRINCIPAL_POINT ? " +fix_principal_point" : "")<< (s.flag & CALIB_ZERO_TANGENT_DIST ? " +zero_tangent_dist" : "")<< (s.flag & CALIB_FIX_K1 ? " +fix_k1" : "")<< (s.flag & CALIB_FIX_K2 ? " +fix_k2" : "")<< (s.flag & CALIB_FIX_K3 ? " +fix_k3" : "")<< (s.flag & CALIB_FIX_K4 ? " +fix_k4" : "")<< (s.flag & CALIB_FIX_K5 ? " +fix_k5" : "");}fs.writeComment(flagsStringStream.str());}fs << "flags" << s.flag;fs << "fisheye_model" << s.useFisheye;fs << "camera_matrix" << cameraMatrix;fs << "distortion_coefficients" << distCoeffs;fs << "avg_reprojection_error" << totalAvgErr;if (s.writeExtrinsics && !reprojErrs.empty())fs << "per_view_reprojection_errors" << Mat(reprojErrs);if(s.writeExtrinsics && !rvecs.empty() && !tvecs.empty() ){CV_Assert(rvecs[0].type() == tvecs[0].type());Mat bigmat((int)rvecs.size(), 6, CV_MAKETYPE(rvecs[0].type(), 1));bool needReshapeR = rvecs[0].depth() != 1 ? true : false;bool needReshapeT = tvecs[0].depth() != 1 ? true : false;for( size_t i = 0; i < rvecs.size(); i++ ){Mat r = bigmat(Range(int(i), int(i+1)), Range(0,3));Mat t = bigmat(Range(int(i), int(i+1)), Range(3,6));if(needReshapeR)rvecs[i].reshape(1, 1).copyTo(r);else{//*.t() is MatExpr (not Mat) so we can use assignment operatorCV_Assert(rvecs[i].rows == 3 && rvecs[i].cols == 1);r = rvecs[i].t();}if(needReshapeT)tvecs[i].reshape(1, 1).copyTo(t);else{CV_Assert(tvecs[i].rows == 3 && tvecs[i].cols == 1);t = tvecs[i].t();}}fs.writeComment("a set of 6-tuples (rotation vector + translation vector) for each view");fs << "extrinsic_parameters" << bigmat;}if(s.writePoints && !imagePoints.empty() ){Mat imagePtMat((int)imagePoints.size(), (int)imagePoints[0].size(), CV_32FC2);for( size_t i = 0; i < imagePoints.size(); i++ ){Mat r = imagePtMat.row(int(i)).reshape(2, imagePtMat.cols);Mat imgpti(imagePoints[i]);imgpti.copyTo(r);}fs << "image_points" << imagePtMat;}if( s.writeGrid && !newObjPoints.empty() ){fs << "grid_points" << newObjPoints;}
}//! [run_and_save]
bool runCalibrationAndSave(Settings& s, Size imageSize, Mat& cameraMatrix, Mat& distCoeffs,vector<vector<Point2f> > imagePoints, float grid_width, bool release_object)
{vector<Mat> rvecs, tvecs;vector<float> reprojErrs;double totalAvgErr = 0;vector<Point3f> newObjPoints;bool ok = runCalibration(s, imageSize, cameraMatrix, distCoeffs, imagePoints, rvecs, tvecs, reprojErrs,totalAvgErr, newObjPoints, grid_width, release_object);cout << (ok ? "Calibration succeeded" : "Calibration failed")<< ". avg re projection error = " << totalAvgErr << endl;if (ok)saveCameraParams(s, imageSize, cameraMatrix, distCoeffs, rvecs, tvecs, reprojErrs, imagePoints,totalAvgErr, newObjPoints);return ok;
}
//! [run_and_save]

注:该标定例程为OpenCV自带,可自行查找,也可从我的博客下载https://download.csdn.net/download/jppdss/89046059

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/778073.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

每日一题 --- 反转字符串中的单词[力扣][Go]

反转字符串中的单词 题目&#xff1a;151. 反转字符串中的单词 给你一个字符串 s &#xff0c;请你反转字符串中 单词 的顺序。 单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。 返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符…

Zabbix6 - Centos7源码编译部署HA高可用集群手册

Zabbix6 - Centos7源码编译部署HA高可用集群手册 HA高可用集群 总所周知,在我们IT运维的圈圈中,HA高可用集群服务算是逼格最高的吧也是运维里保障力度最大的环境。 HA是HighlyAvailable缩写,是双机集群系统简称,提高可用性集群,是保证业务连续性的有效解决方案,一般有两个…

GitHub开源项目权限管理-使用账号和个人令牌访问

1.打开后台账号设置 2.找到左下角的Developer settings 3.找到Personal access tokens 的 Tokens(classic) 4.选择创建新证书 5.填写证书信息 6.点击生成证书&#xff0c;复制证书并且保存起来&#xff08;血泪教训&#xff0c;证书只会在创建时显示一次&#xff0c;以后就再也…

用于 Linux 运维的专门发行版,以及工具

Linux 运维必备的13款实用工具 https://blog.csdn.net/m0_46426259/article/details/121681374 2023 适用于安全专业人士的十款 Linux 发行版 https://www.sysgeek.cn/linux-distributions-for-security/#google_vignette 6个用于黑客攻击的最佳Linux发行版 https://zhuan…

WebView的使用详解

WebView 现在Android开发基本都会用到WebView,所以自己准备系统的整理下,供自己学习之用. 1.简介 WebView是一个基于webkit引擎、展现web页面的控件。 Android的Webview在低版本和高版本采用了不同的webkit版本内核&#xff0c;4.4后直接使用了Chrome。 2.作用 显示和渲染…

Django安装及第一个项目

1、安装python C:\Users\leell>py --version Python 3.10.6 可以看出我的环境python的版本3.10.6&#xff0c;比较新 2、 Python 虚拟环境创建 2.1 官网教程 目前&#xff0c;有两种常用工具可用于创建 Python 虚拟环境&#xff1a; venv 在 Python 3.3 及更高版本中默…

安全的内网通讯软件,WorkPlus定制化 IM/办公门户解决方案

如今处于数字化转型的“加速期”&#xff0c;政企正经历着一场数字化迭代升级的时代浪潮。而不少企业都已具备了数字化管理的意识&#xff0c;数字化应用场景也在全面推开。WorkPlus不断推动信息技术与企业业务深度融合&#xff0c;作为安全的内网通讯软件&#xff0c;为企业提…

[RoarCTF 2019]Online Proxy --不会编程的崽

这几天也是ctf做得有点头疼了。好些序列化的题没碰&#xff0c;一直做些sql注入类的题目。闲来无事&#xff0c;在更一次sql注入吧。 整个页面就这点信息。首先想想为什么他能获取你的ip。猜测是数据包X-Forwarded-For。 它还输出上次访问页面客户端的ip。很明显了&#xff0c…

低功耗、低成本 NAS/公共文件夹 的可能性

使用现状&#xff1a;多台工作电脑&#xff0c;家里人手一台&#xff0c;还在两个住处 有好几台工作电脑&#xff0c;不同电脑不同OS有不同的用途&#xff0c;最大的问题就是各个电脑上文件的同步问题&#xff0c;这里当然就需要局域网里的公共文件夹&#xff0c;在NAS的问题上…

GenICam-GenApi简介

EMVA 1288标准之GemICam-GenApi学习与解读 背景介绍 当前相机不仅用于传输图像&#xff0c;还打包了越来越多的功能。这就导致相机的编程接口越来越复杂。 GenICam的目标是为所有类型的相机提供一个通用的编程接口&#xff0c;无论相机使用何种接口技术&#xff0c;或者实现…

vue3从精通到入门6:v-memo指令

v-memo是一个用于优化组件渲染性能的指令。它允许你根据某个条件来缓存组件的虚拟 DOM 树&#xff0c;从而在条件没有变化时避免不必要的重新渲染。这对于那些接收大量 props 且渲染成本较高的组件来说非常有用。 用法 v-memo 指令接受一个表达式或一个数组作为参数&#xff0…

JVM实战之性能调优[2](线程转储案例认识和分析)

文章目录 版权声明案例1&#xff1a;CPU占用率高问题问题描述解决思路补充内容 案例2&#xff1a;接口响应时间长问题问题描述解决思路Arthas trace命令Arthas watch命令解决问题 案例3&#xff1a;定位偏底层性能问题问题描述解决思路&#xff1a;Arthas火焰图问题解决 案例4&…

论文阅读,Accelerating the Lattice Boltzmann Method(五)

目录 一、Article:文献出处&#xff08;方便再次搜索&#xff09; &#xff08;1&#xff09;作者 &#xff08;2&#xff09;文献题目 &#xff08;3&#xff09;文献时间 &#xff08;4&#xff09;引用 二、Data:文献数据&#xff08;总结归纳&#xff0c;方便理解&am…

nimbus-jose-jwt库简单使用 公私钥字符串互相转换

nimbus-jose-jwt库简单使用 对称密钥签名非对称密钥对公私钥与字符串互相转换生成密钥对公私钥转字符串字符串转公私钥公钥字符串转公钥对象私钥字符串转私钥对象 maven坐标 <dependency><groupId>com.nimbusds</groupId><artifactId>nimbus-jose-jwt&…

大话设计模式之原型模式

原型模式&#xff08;Prototype Pattern&#xff09;是一种创建型设计模式&#xff0c;它用于创建对象的复制&#xff0c;同时又能保持对象的封装。原型模式通过复制现有对象的方式来创建新的对象&#xff0c;而无需知道具体创建过程的细节。 在原型模式中&#xff0c;通常会有…

专题:一个自制代码生成器(嵌入式脚本语言)之对象模型

初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github&#xff1a;codetoys&#xff0c;所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的&#xff0c;可以在任何平台上使用。 专题&#xff1a;一个自制代码…

使用Python进行双色球选号

在这篇博客中&#xff0c;我们将介绍如何使用Python编写一个双色球选号的小程序。双色球是一种流行的彩票游戏&#xff0c;选号由6个红球号码和1个蓝球号码组成&#xff0c;红球号码范围是1到33&#xff0c;蓝球号码范围是1到16。 程序设计思路 我们的程序将使用Python的rand…

Windows系统搭建Oracle结合内网穿透实现公网访问本地数据库

文章目录 前言1. 数据库搭建2. 内网穿透2.1 安装cpolar内网穿透2.2 创建隧道映射 3. 公网远程访问4. 配置固定TCP端口地址4.1 保留一个固定的公网TCP端口地址4.2 配置固定公网TCP端口地址4.3 测试使用固定TCP端口地址远程Oracle 前言 Oracle&#xff0c;是甲骨文公司的一款关系…

3.28 c++

算数运算符 #include <iostream> using namespace std; class Num {int rel; //实部int vir; //虚部 public:Num():rel(2),vir(1){}Num(int rel,int vir):rel(rel),vir(vir){}Num &operator(const Num &other){cout << "Num的拷贝赋值函数"…

Linux(CentOS)/Windows-C++ 云备份项目(结项,项目总结)

项目名称&#xff1a;云备份系统 项目功能&#xff1a; 搭建云备份服务器和客户端。 客户端程序运行在Windows下&#xff0c;将指定的目录下的文件备份到服务器上&#xff0c;并且支持浏览器下载&#xff0c;查看。 其中下载支持断点续传&#xff0c;服务器对备份文件进行热…