OpenCV (用标定相机实现三维重建)
Eva.Q Lv9

利用不同视角下图像点之间的关系,计算出三维信息。

学些啥?

  1. 利用不同视角下图像点之间的关系,计算出三维信息

  2. 新的数学实体 —— 本质矩阵

  3. 三角剖分概念


实现

匹配两个视图的特征点

可利用 SIFT检测器 或 描述子

SIFT & SURF

SURF(加速稳健特征) 算法是 SIFT(尺度不变特征转换) 算法的加速版

SURF 特征检测属于 opencv_contrib 库,在编译时包含了附加模块才能使用。

1
2
3
4
// 创建SURF特征检测器对象
Ptr<xfeatures2d::SurfFeatureDetector> ptrSURF = xfeatures2d::SurfFeatureDetector::create((2000.0));
// 检测关键点
ptrSURF -> detect(image, keypoints);

SIFT

1
2
3
4
// 创建SIFT特征检测器对象
Ptr<xfeatures2d::SiftFeatureDetector> ptrSIFT = xfeatures2d::SiftFeatureDetector::create((2000.0));
// 检测关键点
ptrSIFT -> detect(image, keypoints);

画关键点

1
2
3
4
5
drawKeypoints(image,                                  // 原始图像
keypoints, // 关键点的向量
featureImage, // 结果图像
Scalar(255, 255, 255), // 点的颜色
DrawMatchesFlags::DRAW_RICH_KEYPOINTS); // 显示相关的因子尺度

包含被检测特征的结果图像

用不同的尺度对同一物体拍摄一张照片,特征检测的结果图像

DrawMatchesFlags::DRAW_RICH_KEYPOINTS 标志得到了关键点的圆,并且圆的尺寸与每个特征计算得到的尺度成正比。为了使特征具有旋转不变性,SURF还让每个特征关联了一个方向,由每个圆的辐射线表示。

特征描述子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 定义关键点的容器
vector<KeyPoint> keypoints1;
vector<KeyPoint> keypoints2;
// 定义特征检测器
Ptr<Feature2D> ptrFeature2D = xfeatures2d::SURF::create(2000.0);
// 检测关键点
ptrFeature2D -> detect(image1, keypoints1);
ptrFeature2D -> detect(image2, keypoints2);
// 提取描述子
Mat descriptors1;
Mat descriptors2;
ptrFeature2D -> compute(image1, keypoints1, descriptors1);
ptrFeature2D -> compute(image2, keypoints2, descriptors2);

// 构造匹配器
BFMatcher matcher(NORM_L2);
// 匹配两幅图像的描述子
vector<DMatch> matches;
matcher.match(descriptors1, descriptors2, matches);

Feature2D 类有一个很实用的函数,可在检测兴趣点的同时计算它们的描述子,调用方法如下

1
ptrFeature2D -> detectAndCompute(image, noArray(), keypoints, descriptors);
二值描述子

ORB

ORB 的用法与 SURF 、SIFT 没有什么区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 定义关键点的容器 和 描述子
vector<KeyPoint> keypoints1;
vector<KeyPoint> keypoints2;
Mat descriptors1;
Mat descriptors2;
// 定义特征检测器 / 描述子
Ptr<Feature2D> feature = ORB::create(60);
// 检测 并 描述 关键点
feature -> detectAndCompute(image1, noArray(), keypoints1, descriptors1);
feature -> detectAndCompute(image2, noArray(), keypoints2, descriptors2);

// 构造匹配器
BFMatcher matcher(NORM_HAMMING); // 二值描述子一律用HAMMING规范
// 匹配两幅图像的描述子
vector<DMatch> matches;
matcher.match(descriptors1, descriptors2, matches);

找出本质矩阵

https://www.cnblogs.com/yuanlibin/p/9462180.html

1
2
3
4
5
6
Mat inliers;
Mat essential findEssentialMat(points1, points2,
Matrix, // 内部参数
RANSAC,
0.9, 1.0, // RANSAC方法
inliers); // 提取到的内殿

还原相机的相对姿态

1
2
3
4
5
6
7
// 根据本质矩阵还原相机的相对姿态
Mat rotation, trnslation;
recoverPose(essential, // 本质矩阵
points1, points2, // 匹配的关键点
cameraMatrix, // 内部矩阵
rotation, translation, // 计算的移动值
inliers); // 内点匹配项

计算三角剖分

1
2
vector<Vec3d> points3D;
triangulate(projection1, projection2, points1u, points2u, points3D);


完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// 定义关键点的容器 和 描述子
vector<KeyPoint> keypoints1;
vector<KeyPoint> keypoints2;
Mat descriptors1;
Mat descriptors2;
// 定义SIFT特征检测器
Ptr<Feature2D> ptrFeature2D = xfeatures2d::SIFT::create(500);
// 检测 并 描述 关键点
feature -> detectAndCompute(image1, noArray(), keypoints1, descriptors1);
feature -> detectAndCompute(image2, noArray(), keypoints2, descriptors2);
// 构造匹配器
BFMatcher matcher(NORM_L2, true); // 交叉检查标志 true
// 匹配两幅图像的描述子
vector<DMatch> matches;
matcher.match(descriptors1, descriptors2, matches);

// 将关键点转换成 Point2f 类型
vector<Point2f> points1, points2;
for(vector<DMatch>::const_iterator it = matches.begin(); it!= matches.end(); ++it){
// 获取 左 右 侧关键点的位置
float x = keypoints1[it -> queryIdx].pt.x;
float y = keypoints1[it -> queryIdx].pt.y;
points1.push_back(Point2f(x, y));

float x = keypoints2[it -> queryIdx].pt.x;
float y = keypoints2[it -> queryIdx].pt.y;
points2.push_back(Point2f(x, y));
}
// 找出image1,image2之间的本质矩阵
Mat inliers;
Mat essential findEssentialMat(points1, points2,
Matrix, // 内部参数
RANSAC,
0.9, 1.0, // RANSAC方法
inliers); // 提取到的内殿

// 根据本质矩阵还原相机的相对姿态
Mat rotation, trnslation;
recoverPose(essential, // 本质矩阵
points1, points2, // 匹配的关键点
cameraMatrix, // 内部矩阵
rotation, translation, // 计算的移动值
inliers); // 内点匹配项

// 根据旋转量R和平移量T构建投影矩阵
Mat projection2(3, 4, CV_64F); // 3*4
rotation.copyTo(projection2(Rect(0, 0, 3, 3)));
translation.copyTo(projection2.colRange(3, 4));
// 构建通用投影矩阵
Mat projection1(3, 4, CV_64F, 0);
Mat diag(Mat::eye(3, 3, CV_64F));
diag.copyTo(projection1(Rect(0, 0, 3, 3)));

// 用于存储内点
vector<Vec2d> inlierPts1;
vector<Vec2d> inlierPts2;
// 创建输入内点的容器,用于三角剖分
int j(0);
for(int i = 0; i < inliers.rows; ++i){
if(inliers.at<uchar>(i)){
inlierPts1.push_back(Vec2d(points1[i].x, points1[i].y));
inlierPts2.push_back(Vec2d(points2[i].x, points2[i].y));
}
}

// 矫正并标准化图像点
vector<Vec2d> points1u;
undistortPoints(inlierPts1, points1u, cameraMatrix, cameraDistCoeffs);
vector<Vec2d> points2u;
undistortPoints(inlierPts2, points2u, cameraMatrix, cameraDistCoeffs);
// 三角剖分
vector<Vec3d> points3D;
triangulate(projection1, projection2, points1u, points2u,)
  • Post title:OpenCV (用标定相机实现三维重建)
  • Post author:Eva.Q
  • Create time:2021-08-02 16:26:31
  • Post link:https://qyy/2021/08/02/OPENCV/OPENCV1-5/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.