我们的分析是基于相机标定(1)的。在篇(1)中,了解了类Settings的作用,以及XML文件读入读出的方法,那么就往前再走一步
先看两个类外的小函数
static inline void read(const FileNode& node, Settings& x, const Settings& default_value = Settings()) { if(node.empty()) x = default_value; else x.read(node); } static inline void write(FileStorage& fs, const String&, const Settings& s ) { s.write(fs); }
再看read()这个函数的时候,可以发现一个叫node的FileNode型变量。我对FileNode型变量不算清除,但是通过对FileNode型变量的初始化,可以看出这个类FileNode的具体意义
node["BoardSize_Width" ] >> boardSize.width; node["BoardSize_Height"] >> boardSize.height; node["Calibrate_Pattern"] >> patternToUse; node["Square_Size"] >> squareSize;
用一个不恰当的比喻:整个XML文件可以当作一棵树,那么XML文件的不同段落就是这棵树的节点(Node)
那么现在就开始分析主函数吧:
Settings s; const string inputSettingsFile = argc > 1 ? argv[1] : "/Desktop/task/test_openCV/Project/in_VID5.xml"; FileStorage fs(inputSettingsFile, FileStorage::READ); // Read the settings if (!fs.isOpened()) { cout << "Could not open the configuration file: \"" << inputSettingsFile << "\"" << endl; return -1; } fs["Settings"] >> s; fs.release(); // close Settings file if (!s.goodInput) { cout << "Invalid input detected. Application stopping. " << endl; return -1; }
string变量inputSettingsFile储存XML文件in_VID5的位置(此处设置的是我的电脑中该文件的位置),这样的文件读取方法是只得借鉴的,今后可以用在自己的函数中。FileStorage变量fs读取XML文件in_VID5中的内容。
那么,问题来了,下述的这条语句是什么意思呢?
fs["Settings"] >> s;
如果为了理解这条语句而去了解Filestorage类的库函数的话,就有些麻烦。但是从这条语句中,可以猜到这条语句的执行过程:
1,Filestorage类变量fs准备复制给Settings类变量s
2,启用Settings类变量的read()函数进行拷贝工作
3,拷贝结束后,用Settings类的成员函数validate()判别文件内容信息是否有效(在上一篇讨论过)如果内容有效,那么bool型变量s.goodInput是True.
然后定义了一些变量。注意imagePoints的类型是vector<vector<Point2f>>指不同图片下像素点的像素坐标。
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;
那么mode这个变量含义是什么呢?别急,首先inputType是这样定义的,
enum InputType { INVALID, CAMERA, VIDEO_FILE, IMAGE_LIST } enum { DETECTION = 0, CAPTURING = 1, CALIBRATED = 2 }
可是mode这条语句后半段指什么呢?注意这条语句的优先级,首先判s.inputType == Settings::IMAGE_LIST,然后决定CAPTURING或者DETECTION赋值给mode
int mode = (s.inputType == Settings::IMAGE_LIST ? CAPTURING : DETECTION);
然而inputType又是怎样定义的,在下面这一段代码中找到答案(位于类Setting的成员函数validate()中)。在这段代码中可以发现,inputType的赋值跟String类型input这个变量密切相关。inputTYpe分为四类,即INVALID, CAMERA, VIDEO_FILE, IMAGE_LIST。input的值取决于设定文件(in_VID5.xml)的设定,在我们这里input存放的是图片的地址,于是inputType指IMAGE_LIST,变量nrFrames指图像的帧数。
if (input.empty()) // Check for valid input inputType = INVALID; else { if (input[0] >= '0' && input[0] <= '9') { stringstream ss(input); ss >> cameraID; inputType = CAMERA; } else { if (readStringList(input, imageList)) { inputType = IMAGE_LIST; nrFrames = (nrFrames < (int)imageList.size()) ? nrFrames : (int)imageList.size(); } else inputType = VIDEO_FILE; } if (inputType == CAMERA) inputCapture.open(cameraID); if (inputType == VIDEO_FILE) inputCapture.open(input); if (inputType != IMAGE_LIST && !inputCapture.isOpened()) inputType = INVALID; }
因此,mode等于CAPUTRING,或者说mode等于1(根据枚举数组的定义。。)。
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 ) { if( runCalibrationAndSave(s, imageSize, cameraMatrix, distCoeffs, imagePoints)) mode = CALIBRATED; else mode = DETECTION; } if(view.empty()) // If there are no more images stop the loop { // if calibration threshold was not reached yet, calibrate now if( mode != CALIBRATED && !imagePoints.empty() ) runCalibrationAndSave(s, imageSize, cameraMatrix, distCoeffs, imagePoints); break; }
view是一个Mat型变量,在第一次循环中保存第一幅图像(这幅图像可以来源自一个图片集,或者是一段视频)。在第一次循环过程中,尽管mode == CAPTURING,由于多维数组变量imagePoints刚刚才初始化,不能满足条件imagePoints.size() >= (size_t)s.nrFrames,所以不执行第一个if语句。另外,在第一次循环中,view非空,程序也不执行第二个if语句。注释信息说明第二个if就是循环结束的标志。
好,接着向下看,这个循环的第二部分,
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 fisheye chessBoardFlags |= 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::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; }
变量imageSize的意义就不解释了。如果图像是横着的,就用flip函数把图像变成竖着的。vector变量pointBuf记录这一帧图像的像素点。此次相机标定采用棋盘格法,不执行鱼眼这个判断。
int chessBoardFlags = CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_NORMALIZE_IMAGE;
这条语句指明图像应该先使用直方图均衡化做预处理,然后用自适应阈值分割找到标定板。针对不同的标定板,程序首先会寻找它们,如果找到了bool型变量found置为TRUE,否则就是FALSE。
找到了,才能标定嘛。至于寻找标定板的过程,这里暂且不分析,哈哈。