难点
在完成两视图三维重建之后,接下来就是进行多视图重建。多视图重建的难点在于如何确定第 i i i( i i i>2)个相机到世界坐标系的位姿变换矩阵。
两视图重建时,是将第一个相机所在的坐标系视为世界坐标系,并计算第二个相机相对于第一个相机的位姿变换矩阵。但这种方法并不能用在多视图重建中,即不能将第 i i i( i i i>2)个视图依次与第一个视图进行特征点匹配,并根据匹配的特征点进行双目重建,因为随着帧数增加,当前帧与第一帧之间可能无法找到足够的匹配特征点;也不能采取1与2、2与3、3与4这样依次两两视图重建的方式,因为如此两视图重建得出的是 i + 1 i+1 i+1帧与第 i i i帧的相对位姿,且丢失了 t t t的尺度信息,无法通过依次连乘转到第1帧代表的世界坐标系下。
解决方法
其实这就是所谓的PnP问题了,“PnP是求解3D到2D点对运动的方法,它描述了在已知n个3D空间点以及它们的投影位置时,如何估计相机所在的位姿”。而Opencv中提供了相关的函数solvePnP及solvePnPRansac可直接使用。已知的3D空间点的坐标是世界坐标系下的坐标,因此使用solvePnP获得的相机位置也是在世界坐标系下位置,即第 i i i个相机到第一个相机的变换矩阵,在此基础上就可以使用三角法进行三维重建了。
本文采用增量SfM方式进行多视图三维重建
代码
环境:Win10+VS2015+OpenCV3.4+PCL1.8
#include <iostream>
#include <string>
#include <opencv2/opencv.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/xfeatures2d/nonfree.hpp>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/visualization/pcl_visualizer.h>
using namespace std;
using namespace cv;
using namespace pcl;
using namespace cv::xfeatures2d;
// 提取所有图像的特征点 及 特征点处的RGB
void extract_features(vector<string>& image_names, vector<vector<KeyPoint>>& keypoints_for_all, vector<Mat>& descriptor_for_all, vector<vector<Vec3b>>& colors_for_all);
// ratio & symmetry test
void ratioTest(vector<vector<DMatch>> &matches, vector<DMatch> &goodMatches);
void symmetryTest(const vector<DMatch>& matches1, const vector<DMatch>& matches2, vector<DMatch>& symMatches);
// 匹配所有特征点
void match_features(vector<Mat>& descriptor_for_all, vector<vector<DMatch>>& matches_for_all);
// 由匹配对提取特征点对
void get_matched_points(vector<KeyPoint> keypoints1,vector<KeyPoint> keypoints2,vector<DMatch> goodMatches,vector<Point2f>& points1,vector<Point2f>& points2);
// 获取匹配点的RGB
void get_matched_colors(vector<Vec3b>& color1, vector<Vec3b>& color2, vector<DMatch> matches, vector<Vec3b>& out_c1, vector<Vec3b>& out_c2);
// 剔除p1中mask值为0的元素
void maskout_points(vector<Point2f>& p1, Mat& mask);
void maskout_colors(vector<Vec3b>& p1, Mat& mask);
// 重建前2张图片
void reconstruct_first2imgs(Mat K, vector<vector<KeyPoint>>& key_points_for_all, vector<vector<Vec3b>>& colors_for_all, vector<vector<DMatch>>& matches_for_all, vector<Point3f>& structure, vector<vector<int>>& correspond_struct_idx, vector<Vec3b>& colors, vector<Mat>& rotations, vector<Mat>& translations);
// 三维重建
// 前两张图片重建
void reconstruct(Mat& K, vector<Point2f>& points1, vector<Point2f>& points2, Mat& R, Mat& t, Mat& mask, vector<Point3f>& points3D);
// 后续图片重建
void reconstruct(Mat& K, Mat& R1, Mat& T1, Mat& R2, Mat& T2, vector<Point2f>& points1, vector<Point2f>& points2, vector<Point3f>& points3D);
// 获得三维点与对应的像素点
void get_objpoints_and_imgpoints(vector<DMatch>& matches, vector<int>& struct_indices, vector<Point3f>& structure, vector<KeyPoint>& key_points, vector<Point3f>& object_points, vector<Point2f>& image_points);
// 点云融合
void fusion_pointscloud(vector<DMatch>& matches, vector<int>& struct_indices, vector<int>& next_struct_indices, vector<Point3f>& structure, vector<Point3f>& next_structure, vector<Vec3b>& colors, vector<Vec3b>& next_colors);
int main(int argc, char* argv[])
{
vector<string> img_names;
img_names.push_back(".\\images\\000.png");
img_names.push_back(".\\images\\001.png");
img_names.push_back(".\\images\\002.png");
img_names.push_back(".\\images\\003.png");
img_names.push_back(".\\images\\004.png");
img_names.push_back(".\\images\\005.png");
//img_names.push_back(".\\images\\006.png");
//img_names.push_back(".\\images\\007.png");
//img_names.push_back(".\\images\\008.png");
//img_names.push_back(".\\images\\009.png");
Mat K = (Mat_<double>(3, 3) << 2759.48, 0, 1520.69, 0, 2764.16, 1006.81, 0, 0, 1); // Fountain的内参数矩阵
vector<vector<KeyPoint>> key_points_for_all;
vector<Mat> descriptor_for_all;
vector<vector<Vec3b>> colors_for_all; // 以图片为一个vector单元,存放所有特征点的RGB,防止混淆
vector<vector<DMatch>> matches_for_all;
// 提取所有图像的特征点
extract_features(img_names, key_points_for_all, descriptor_for_all, colors_for_all);
// 对所有图像进行顺次的特征匹配
match_features(descriptor_for_all, matches_for_all);
// 重建前两张图片
vector<Point3f> points3D; // 存放重建后所有三维点
vector<vector<int>> correspond_struct_idx; // 若第i副图像中第j特征点对应位置的值是N,则代表该特征点对应的是重建后的第N个三维点
vector<Vec3b> colors; // 存放重建后所有三维点的RGB(作为最终重建结果,不需要以图片为单元分隔,)
vector<Mat> rotations; // 所有相机相对第一个相机的旋转矩阵
vector<Mat> translations; // 所有相机相对第一个相机的平移矩阵
cout << "key_points_for_all.size() = " << key_points_for_all.size() << endl;
cout << "matches_for_all.size() = " << matches_for_all