一、程序运行
不考虑gamma校正,不考虑像素衰减,没有进行光度校正。可以使用下列方式运行dso。
./bin/dso_dataset \
files=~/cam0/data/ \
calib=~/cam0/camera.txt \
mode=1
二、读取图像
2.1 ImageFolderReader
从main函数开始,系统首先定义ImageFolderReader类的对象reader。在构造函数中完成文件路径读取操作。
ImageFolderReader* reader = new ImageFolderReader(source,calib, gammaCalib, vignette);
ImageFolderReader类的构造函数在DatasetReader.h文件,程序通过getdir()函数获取输入文件夹下的所有文件,也就是files下的所有文件。
inline int getdir (std::string dir, std::vector<std::string> &files){
DIR *dp;
struct dirent *dirp;
if((dp = opendir(dir.c_str())) == NULL) {
return -1;
}
while ((dirp = readdir(dp)) != NULL) {
std::string name = std::string(dirp->d_name);
if(name != "." && name != "..")
files.push_back(name);
}
closedir(dp);
std::sort(files.begin(), files.end());
if(dir.at( dir.length() - 1 ) != '/') dir = dir+"/";
for(unsigned int i=0;i<files.size();i++) {
if(files[i].at(0) != '/')
files[i] = dir + files[i];
}
return files.size();
}
getdir()是非常常用的从文件夹读取文件的函数,输入文件夹路径到dir,输出文件夹下所有文件路径到files。
函数先将文件夹的所有文件排序,std::sort()默认按升序排序,也可以用lambda表达式自定义排序方式。
std::sort(files.begin(), files.end(), [](int a, int b) {
return a < b; //降序排列写法,升序用>
});
然后把每个文件的路径按顺序存入reader对象的成员变量中的vector容器,方便读取。
loadTimestamps()函数会读取图像文件夹父文件夹下(cam0)的时间戳time.txt文件,建议相机内参camera.txt文件也放到这个文件夹下。
2.2 getImage
在main函数中,新建一个线程,把lambda表达式放到线程中运行
std::thread runthread([&](){......})//[&]表示用引用传递的方式表示lambda表达式以外的变量
在线程中执行getImage函数,ImageFolderReader中的成员函数
for(int ii=0;ii<(int)idsToPlay.size(); ii++){
if(!fullSystem->initialized){
gettimeofday(&tv_start, NULL);
started = clock();
sInitializerOffset = timesToPlayAt[ii];
}
......
img = reader->getImage(i);
......
if(ii < 250 || setting_fullResetRequested){
}
}
在这里要控制系统从某一帧开始往后读取图像,相当于完成rosbag play -s ii0 xxx.bag的操作。可以在运行程序的命令里加上
start=a,end=b。
在DatasetReader.h文件中,getImage函数调用成员函数getImage_internal(),然后函数里调用getImageRaw_internal(),最终调用IOWrap/ImageRW_OpenCV.cpp中的IOWrap::readImageBW_8U(files[id])函数。
在这时程序才开始执行图像读取操作。
MinimalImageB* readImageBW_8U(std::string filename){
cv::Mat m = cv::imread(filename, CV_LOAD_IMAGE_GRAYSCALE);
/**
*在这里可以对图像进行各种操作
*/
if(m.rows*m.cols==0) {
printf("cv::imread could not read image %s! this may segfault. \n", filename.c_str());
return 0;
}
if(m.type() != CV_8U) {
printf("cv::imread did something strange! this may segfault. \n");
return 0;
}
MinimalImageB* img = new MinimalImageB(m.cols, m.rows);
memcpy(img->data, m.data, m.rows*m.cols);
return img;
}
了解到这里之后,就可以对图像进行各种预处理操作。
比如说要进行直方图均衡化,就只需要添加头文件#include <opencv2/opencv.hpp>和
cv::Ptr<cv::CLAHE> clahe = cv::createCLAHE(3.0, cv::Size(8, 8));
clahe->apply(m, m);
需要注意的是,这里不能直接用rect剪切图像区域,图像可能错位。
m = m(cv::Rect(x,y, col, row));
一定要修剪图像,需要添加
cv::resize(m, m, cv::Size(col0, row0));
恢复图像原尺寸。
三、读取参数
这一步是紧跟在getdir之后,也就是在定义reader对象时ImageFolderReader的构造函数里。
undistort = Undistort::getUndistorterForFile(calibFile, gammaFile, vignetteFile);
函数首先读取calibFile里第一行,判断相机模型。
当参数文件image.txt中没有给定相机模型时,可能有几种情况
(Pinhole )fx fy cx cy 0
(FOV ) fx fy cx cy omega
(RadTan )fx fy cx cy k1 k2 r1 r2
对应不同的类,这些相机模型类继承自同一个类Undistort。然后函数利用calibFile开辟对应的相机模型空间。
在定义相机模型同时,调用对应的构造函数,然后程序会调用readFromFile。
void Undistort::readFromFile(const char* configFileName, int nPars, std::string prefix){
//这里是tadtan对应的参数读取过程
snprintf(buf, 1000, "%s%%lf %%lf %%lf %%lf %%lf %%lf %%lf %%lf %%lf %%lf", prefix.c_str());
if(std::sscanf(l1.c_str(), buf,
&parsOrg[0], &parsOrg[1], &parsOrg[2], &parsOrg[3], &parsOrg[4],
&parsOrg[5], &parsOrg[6], &parsOrg[7]) == 8 &&
std::sscanf(l2.c_str(), "%d %d", &wOrg, &hOrg) == 2){
}
}
int snprintf(char *str, size_t size, const char *format, ...);
这里的snprintf是将format复制到str中,并给其后添加一个字符串结束符(‘\0’),和strcpy很类似,或许可以替代。
char* strcpy(char* strDestination, const char* strSource);
std::sscanf的解析可以参考资料std::sscanf。
int sscanf( const char* buffer, const char* format, ... );
//Reads the data from null-terminated character string buffer
//从以空结束的字符串缓冲区读取数据
sscanf是从字符串读取数据,C++可以使用字符串流来实现数据读取。
void Undistort::readFromFile(string configFileName, int nPars, std::string prefix){
std::ifstream paramK;
paramK.open(configFileName.c_str(), std::ios::in);//只读
if(!paramK.is_open()){
cout << "open file failed" << endl;
return;
}
//while(!paramK.eof()){
paramK>>parsOrg[0]>>parsOrg[1]>>parsOrg[2]>>parsOrg[3]>>parsOrg[4]
>>parsOrg[5]>>parsOrg[6]>>parsOrg[7];
//}
paramK.close();
}
此时,程序将相机内参读入,保存到parsOrg,经过畸变等操作后存到Undistort类中的成员变量K。
在运行ImageFolderReader的构造函数之后,也就是程序获取所有图像路径,并且读入了内参之后,程序调用
reader->setGlobalCalibration();
在函数中调用globalCalib.cpp中的setGlobalCalib()函数,将Undistort类中的内参保存到全局变量KG。