OpenCV实现SfM(二):双目三维重建
来源:互联网 发布:淘宝 麻辣小黑粉 编辑:程序博客网 时间:2024/05/18 06:25
注意:本文中的代码必须使用OpenCV3.0或以上版本进行编译,因为很多函数是3.0以后才加入的。
目录:
- 极线约束与本征矩阵
- 特征点提取与匹配
- 三维重建
- 测试
极线约束与本征矩阵
在三维重建前,我们先研究一下同一点在两个相机中的像的关系。假设在世界坐标系中有一点
设
由于K是可逆矩阵,两式坐乘K的逆,有
设
我们一般称
由于世界坐标系可以任意选择,我们将世界坐标系选为第一个相机的相机坐标系,这时
将第一式带入第二式,有
即
令
可以看出,上式是同一点在两个相机中的像所满足的关系,它和点的空间坐标、点到相机的距离均没有关系,我们称之为极线约束,而矩阵
如何从
特征点提取与匹配
从上面的分析可知,要求取两个相机的相对关系,需要两幅图像中的对应点,这就变成的特征点的提取和匹配问题。对于图像差别较大的情况,推荐使用SIFT特征,因为SIFT对旋转、尺度、透视都有较好的鲁棒性。如果差别不大,可以考虑其他更快速的特征,比如SURF、ORB等。
本文中使用SIFT特征,由于OpenCV3.0将SIFT包含在了扩展部分中,所以官网上下载的版本是没有SIFT的,为此需要到这里下载扩展包,并按照里面的说明重新编译OpenCV(哎~真麻烦,-_-!)。如果你使用其他特征,就不必为此辛劳了。
下面的代码负责提取图像特征,并进行匹配。
void extract_features( vector<string>& image_names, vector<vector<KeyPoint>>& key_points_for_all, vector<Mat>& descriptor_for_all, vector<vector<Vec3b>>& colors_for_all ){ key_points_for_all.clear(); descriptor_for_all.clear(); Mat image; //读取图像,获取图像特征点,并保存 Ptr<Feature2D> sift = xfeatures2d::SIFT::create(0, 3, 0.04, 10); for (auto it = image_names.begin(); it != image_names.end(); ++it) { image = imread(*it); if (image.empty()) continue; vector<KeyPoint> key_points; Mat descriptor; //偶尔出现内存分配失败的错误 sift->detectAndCompute(image, noArray(), key_points, descriptor); //特征点过少,则排除该图像 if (key_points.size() <= 10) continue; key_points_for_all.push_back(key_points); descriptor_for_all.push_back(descriptor); vector<Vec3b> colors(key_points.size()); for (int i = 0; i < key_points.size(); ++i) { Point2f& p = key_points[i].pt; colors[i] = image.at<Vec3b>(p.y, p.x); } colors_for_all.push_back(colors); }}void match_features(Mat& query, Mat& train, vector<DMatch>& matches){ vector<vector<DMatch>> knn_matches; BFMatcher matcher(NORM_L2); matcher.knnMatch(query, train, knn_matches, 2); //获取满足Ratio Test的最小匹配的距离 float min_dist = FLT_MAX; for (int r = 0; r < knn_matches.size(); ++r) { //Ratio Test if (knn_matches[r][0].distance > 0.6*knn_matches[r][1].distance) continue; float dist = knn_matches[r][0].distance; if (dist < min_dist) min_dist = dist; } matches.clear(); for (size_t r = 0; r < knn_matches.size(); ++r) { //排除不满足Ratio Test的点和匹配距离过大的点 if ( knn_matches[r][0].distance > 0.6*knn_matches[r][1].distance || knn_matches[r][0].distance > 5 * max(min_dist, 10.0f) ) continue; //保存匹配点 matches.push_back(knn_matches[r][0]); }}
需要重点说明的是,匹配结果往往有很多误匹配,为了排除这些错误,这里使用了Ratio Test方法,即使用KNN算法寻找与该特征最匹配的2个特征,若第一个特征的匹配距离与第二个特征的匹配距离之比小于某一阈值,就接受该匹配,否则视为误匹配。当然,也可以使用Cross Test(交叉验证)方法来排除错误。
得到匹配点后,就可以使用OpenCV3.0中新加入的函数findEssentialMat()来求取本征矩阵了。得到本征矩阵后,再使用另一个函数对本征矩阵进行分解,并返回两相机之间的相对变换R和T。注意这里的T是在第二个相机的坐标系下表示的,也就是说,其方向从第二个相机指向第一个相机(即世界坐标系所在的相机),且它的长度等于1。
bool find_transform(Mat& K, vector<Point2f>& p1, vector<Point2f>& p2, Mat& R, Mat& T, Mat& mask){ //根据内参矩阵获取相机的焦距和光心坐标(主点坐标) double focal_length = 0.5*(K.at<double>(0) + K.at<double>(4)); Point2d principle_point(K.at<double>(2), K.at<double>(5)); //根据匹配点求取本征矩阵,使用RANSAC,进一步排除失配点 Mat E = findEssentialMat(p1, p2, focal_length, principle_point, RANSAC, 0.999, 1.0, mask); if (E.empty()) return false; double feasible_count = countNonZero(mask); cout << (int)feasible_count << " -in- " << p1.size() << endl; //对于RANSAC而言,outlier数量大于50%时,结果是不可靠的 if (feasible_count <= 15 || (feasible_count / p1.size()) < 0.6) return false; //分解本征矩阵,获取相对变换 int pass_count = recoverPose(E, p1, p2, R, T, focal_length, principle_point, mask); //同时位于两个相机前方的点的数量要足够大 if (((double)pass_count) / feasible_count < 0.7) return false; return true;}
三维重建
现在已经知道了两个相机之间的变换矩阵,还有每一对匹配点的坐标。三维重建就是通过这些已知信息还原匹配点在空间当中的坐标。在前面的推导中,我们有
这个等式中有两个未知量,分别是
整理一下可以得到一个关于空间坐标X的线性方程
上面的方程不能直接取逆求解,因此化为其次方程
用SVD求X左边矩阵的零空间,再将最后一个元素归一化到1,即可求得X。其几何意义相当于分别从两个相机的光心作过
void reconstruct(Mat& K, Mat& R, Mat& T, vector<Point2f>& p1, vector<Point2f>& p2, Mat& structure){ //两个相机的投影矩阵[R T],triangulatePoints只支持float型 Mat proj1(3, 4, CV_32FC1); Mat proj2(3, 4, CV_32FC1); proj1(Range(0, 3), Range(0, 3)) = Mat::eye(3, 3, CV_32FC1); proj1.col(3) = Mat::zeros(3, 1, CV_32FC1); R.convertTo(proj2(Range(0, 3), Range(0, 3)), CV_32FC1); T.convertTo(proj2.col(3), CV_32FC1); Mat fK; K.convertTo(fK, CV_32FC1); proj1 = fK*proj1; proj2 = fK*proj2; //三角化重建 triangulatePoints(proj1, proj2, p1, p2, structure);}
测试
我用了下面两幅图像进行测试
得到了着色后的稀疏点云,是否能看出一点轮廓呢?!
图片中的两个彩色坐标系分别代表两个相机的位置。
在接下来的文章中,会将相机的个数推广到任意多个,成为一个真正的SfM系统。
关于源代码的使用
代码是用VS2013写的,OpenCV版本为3.0且包含扩展部分,如果不使用SIFT特征,可以修改源代码,然后使用官方未包含扩展部分的库。软件运行后会将三维重建的结果写入Viewer目录下的structure.yml文件中,在Viewer目录下有一个SfMViewer程序,直接运行即可读取yml文件并显示三维结构。
代码下载
- OpenCV实现SfM(二):双目三维重建
- OpenCV实现SfM(二):双目三维重建
- OpenCV实现SfM(二):多目三维重建
- 三维重建-opencv实现sfm
- OpenCV实现SfM(一):双目三维重建(包含SIFT特征点提取)
- 双目测距与三维重建的OpenCV实现问题集锦(二)双目定标与双目校正
- 双目测距与三维重建的OpenCV实现问题集锦(二)双目定标与双目校正
- OpenCV实现SfM(三):多目三维重建
- OpenCV实现SfM(三):多目三维重建
- OpenCV实现SfM(三):三维重建+Bundle Adjustment
- OpenCV学习笔记(17)双目测距与三维重建的OpenCV实现问题集锦(二)双目定标与双目校正
- OpenCV学习笔记(17)双目测距与三维重建的OpenCV实现问题集锦(二)双目定标与双目校正
- OpenCV学习笔记(17)双目测距与三维重建的OpenCV实现问题集锦(二)双目定标与双目校正
- 双目测距与三维重建的OpenCV实现问题集锦(一)双目定标与双目校正
- 双目测距与三维重建的OpenCV实现问题集锦(四)三维重建与OpenGL显示
- OpenCV学习笔记(19)双目测距与三维重建的OpenCV实现问题集锦(四)三维重建与OpenGL显示
- OpenCV学习笔记(19)双目测距与三维重建的OpenCV实现问题集锦(四)三维重建与OpenGL显示
- OpenCV学习笔记(19)双目测距与三维重建的OpenCV实现问题集锦(四)三维重建与OpenGL显
- android 快速开发系列 10个常用工具类
- 五子棋--小游戏
- Mac下使用CornerstoneSVN版本管理中iOS静态库被忽略问题解决
- Kmeans聚类算法 java精简版设计实现编程
- hdu 2001 计算两点间的距离
- OpenCV实现SfM(二):双目三维重建
- string类的用法总结
- IntelliJ Idea 常用快捷键列表
- (相似度、邻近及聚类)Similarity, Neighbors, and Clusters
- 零基础 JNI 入门 + 进阶
- 【备忘】swing基础例子
- python-格式化(format())输出字符串 详解 及 代码
- gson的使用
- [工作笔记之三] 通过adb查看android手机CPU和内存占用情况