我们的分析是基于相机标定(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。
找到了,才能标定嘛。至于寻找标定板的过程,这里暂且不分析,哈哈。