单目初始化中基础矩阵推导计算、根据得分确定最佳单应矩阵和基础矩阵
- ComputeH21 结束 返回 FindHomography
- FindHomography 跳转 CheckHomography
- CheckHomography 结束 返回 FindHomography
- FindHomography 结束 返回 Initialize
- Initialize 跳转 FindFundamental
- FindFundamental 跳转 ComputeF21
- 求解基础矩阵F
- ComputeF21 结束 返回 FindFundamental
- FindFundamental 跳转 CheckFundamental
- CheckFundamental 结束 返回 FindFundamental
- FindFundamental 结束 返回 Initialize
ComputeH21 结束 返回 FindHomography
前面根据8对点(归一化)求出了单应阵H,下面反求归一化之前的
其中变量含义:
T1
T2
:归一化矩阵。vPn1
vPn2
:归一化坐标。mvKeys1
mvKeys2
:原坐标。
前面求的是归一化H,即vPn1与vPn2变换的单应阵。归一化是为了计算方便。我们需要的实际是原坐标mvKeys1与mvKeys2之间的变换的单应阵。
把 v P n 1 = T 1 ∗ m v K e y s 1 {vPn_1 = T_1 * mvKeys_1} vPn1=T1∗mvKeys1, v P n 2 = T 2 ∗ m v K e y s 2 {vPn_2 = T_2 * mvKeys_2} vPn2=T2∗mvKeys2代入 v P n 2 = H 21 ∗ v P n 1 {vPn_2=H_{21}*vPn_1} vPn2=H21∗vPn1得:
T 2 ∗ m v K e y s 2 = H 21 ∗ T 1 ∗ m v K e y s 1 {T_2 * mvKeys_2 = H_{21} * T_1 * mvKeys_1 } T2∗mvKeys2=H21∗T1∗mvKeys1
左右同时左乘 T 2 − 1 {T_2^{-1}} T2−1得到:
m v K e y s 2 = T 2 − 1 ∗ H 21 ∗ T 1 ∗ m v K e y s 1 {mvKeys_2 = T_2^{-1}*H_{21} * T_1 * mvKeys_1} mvKeys2=T2−1∗H21∗T1∗mvKeys1
记 H n = T 2 − 1 ∗ H 21 ∗ T 1 {H_n=T_2^{-1}*H_{21} * T_1 } Hn=T2−1∗H21∗T1,目标就是这个 H n {H_n} Hn。
// 单应矩阵原理:X2=H21*X1,其中X1,X2 为归一化后的特征点
// 特征点归一化:vPn1 = T1 * mvKeys1, vPn2 = T2 * mvKeys2 得到:T2 * mvKeys2 = Hn * T1 * mvKeys1
// 进一步得到:mvKeys2 = T2.inv * Hn * T1 * mvKeys1
H21i = T2inv*Hn*T1;
//然后计算逆
H12i = H21i.inv();
计算完单应阵H,下面对H用CheckHomography
函数进行评分。输入单应阵及其逆,输出的是特征点对的标记和测量误差。
// Step 4 利用重投影误差为当次RANSAC的结果评分
currentScore = CheckHomography(H21i, H12i, //输入,单应矩阵的计算结果
vbCurrentInliers, //输出,特征点对的Inliers标记
mSigma); //TODO 测量误差,在Initializer类对象构造的时候,由外部给定的
FindHomography 跳转 CheckHomography
作用:对给定的H阵打分。
/**
* @brief 对给定的homography matrix打分,需要使用到卡方检验的知识
*
* @param[in] H21 从参考帧到当前帧的单应矩阵
* @param[in] H12 从当前帧到参考帧的单应矩阵
* @param[in] vbMatchesInliers 匹配好的特征点对的Inliers标记
* @param[in] sigma 方差,默认为1
* @return float 返回得分
*/
float Initializer::CheckHomography(
const cv::Mat &H21, //从参考帧到当前帧的单应矩阵
const cv::Mat &H12, //从当前帧到参考帧的单应矩阵
vector<bool> &vbMatchesInliers, //匹配好的特征点对的Inliers标记
float sigma) //估计误差
{
// 说明:在已值n维观测数据误差服从N(0,sigma)的高斯分布时
// 其误差加权最小二乘结果为 sum_error = SUM(e(i)^T * Q^(-1) * e(i))
// 其中:e(i) = [e_x,e_y,...]^T, Q维观测数据协方差矩阵,即sigma * sigma组成的协方差矩阵
// 误差加权最小二次结果越小,说明观测数据精度越高
// 那么,score = SUM((th - e(i)^T * Q^(-1) * e(i)))的分数就越高
// 算法目标: 检查单应变换矩阵
// 检查方式:通过H矩阵,进行参考帧和当前帧之间的双向投影,并计算起加权最小二乘投影误差
// 算法流程
// input: 单应性矩阵 H21, H12, 匹配点集 mvKeys1
// do:
// for p1(i), p2(i) in mvKeys:
// error_i1 = ||p2(i) - H21 * p1(i)||2
// error_i2 = ||p1(i) - H12 * p2(i)||2
//
// w1 = 1 / sigma / sigma
// w2 = 1 / sigma / sigma
//
// if error1 < th
// score += th - error_i1 * w1
// if error2 < th
// score += th - error_i2 * w2
//
// if error_1i > th or error_2i > th
// p1(i), p2(i) are inner points
// vbMatchesInliers(i) = true
// else
// p1(i), p2(i) are outliers
// vbMatchesInliers(i) = false
// end
// end
// output: score, inliers
首先将H阵及其逆的元素取出。
初始化分数score
=0。
设置阈值th
= 5.991。
// 特点匹配个数
const int N = mvMatches12.size();
// Step 1 获取从参考帧到当前帧的单应矩阵的各个元素
const float h11 = H21.at<float>(0,0);
const float h12 = H21.at<float>(0,1);
const float h13 = H21.at<float>(0,2);
const float h21 = H21.at<float>(1,0);
const float h22 = H21.at<float>(1,1);
const float h23 = H21.at<float>(1,2);
const float h31 = H21.at<float>(2,0);
const float h32 = H21.at<float>(2,1);
const float h33 = H21.at<float>(2,2);
// 获取从当前帧到参考帧的单应矩阵的各个元素
const float h11inv = H12.at<float>(0,0);
const float h12inv = H12.at<float>(0,1);
const float h13inv = H12.at<float>(0,2);
const float h21inv = H12.at<float>(1,0);
const float h22inv = H12.at<float>(1,1);
const float h23inv = H12.at<float>(1,2);
const float h31inv = H12.at<float>(2,0);
const float h32inv = H12.at<float>(2,1);
const float h33inv = H12.at<float>(2,2);
// 给特征点对的Inliers标记预分配空间
vbMatchesInliers.resize(N);
// 初始化score值
float score = 0;
// 基于卡方检验计算出的阈值(假设测量有一个像素的偏差)
// 自由度为2的卡方分布,显著性水平为0.05,对应的临界阈值
const float th = 5.991;
//信息矩阵,方差平方的倒数
const float invSigmaSquare = 1.0/(sigma * sigma);
遍历匹配点信息,取出匹配点对的坐标。
// Step 2 通过H矩阵,进行参考帧和当前帧之间的双向投影,并计算起加权最小二乘投影误差
// H21 表示从img1 到 img2的变换矩阵
// H12 表示从img2 到 img1的变换矩阵
for(int i = 0; i < N; i++)
{
// 一开始都默认为Inlier
bool bIn = true;
// Step 2.1 提取参考帧和当前帧之间的特征匹配点对
const cv::KeyPoint &kp1 = mvKeys1[mvMatches12[i].first];
const cv::KeyPoint &kp2 = mvKeys2[mvMatches12[i].second];
const float u1 = kp1.pt.x;
const float v1 = kp1.pt.y;
const float u2 = kp2.pt.x;
const float v2 = kp2.pt.y;
用
H
12
{H_{12}}
H12计算重投影误差:
1.求出图像2在图像1中的归一化相机坐标u2in1
v2in1
:
x
1
=
H
12
∗
x
2
{x_1=H_{12}*x_2}
x1=H12∗x2
2.重投影误差=真实图1坐标-图2转化坐标
∣
∣
p
1
(
i
)
−
H
12
∗
p
2
(
i
)
∣
∣
2
{||p_1(i) - H_{12} * p_2(i)||_2}
∣∣p1(i)−H12∗p2(i)∣∣2
下面这个公式是不是有问题???
// Step 2.2 计算 img2 到 img1 的重投影误差
// x1 = H12*x2
// 将图像2中的特征点单应到图像1中
// |u1| |h11inv h12inv h13inv||u2| |u2in1|
// |v1| = |h21inv h22inv h23inv||v2| = |v2in1| * w2in1inv
// |1 | |h31inv h32inv h33inv||1 | | 1 |
// 计算投影归一化坐标
const float w2in1inv = 1.0/(h31inv * u2 + h32inv * v2 + h33inv);
const float u2in1 = (h11inv * u2 + h12inv * v2 + h13inv) * w2in1inv;
const float v2in1 = (h21inv * u2 + h22inv * v2 + h23inv) * w2in1inv;
// 计算重投影误差 = ||p1(i) - H12 * p2(i)||2
const float squareDist1 = (u1 - u2in1) * (u1 - u2in1) + (v1 - v2in1) * (v1 - v2in1);
const float chiSquare1 = squareDist1 * invSigmaSquare;
如果误差大于阈值,返回false。
如果误差不大于阈值,得分随误差大而减小。
// Step 2.3 用阈值标记离群点,内点的话累加得分
if(chiSquare1>th)
bIn = false;
else
// 误差越大,得分越低
score += th - chiSquare1;
// 计算从img1 到 img2 的投影变换误差
// x1in2 = H21*x1
// 将图像2中的特征点单应到图像1中
// |u2| |h11 h12 h13||u1| |u1in2|
// |v2| = |h21 h22 h23||v1| = |v1in2| * w1in2inv
// |1 | |h31 h32 h33||1 | | 1 |
// 计算投影归一化坐标
const float w1in2inv = 1.0/(h31*u1+h32*v1+h33);
const float u1in2 = (h11*u1+h12*v1+h13)*w1in2inv;
const float v1in2 = (h21*u1+h22*v1+h23)*w1in2inv;
// 计算重投影误差
const float squareDist2 = (u2-u1in2)*(u2-u1in2)+(v2-v1in2)*(v2-v1in2);
const float chiSquare2 = squareDist2*invSigmaSquare;
// 用阈值标记离群点,内点的话累加得分
if(chiSquare2>th)
bIn = false;
else
score += th - chiSquare2;
// Step 2.4 如果从img2 到 img1 和 从img1 到img2的重投影误差均满足要求,则说明是Inlier point
if(bIn)
vbMatchesInliers[i]=true;
else
vbMatchesInliers[i]=false;
}
return score;
}
将误差小于阈值的内点在vbMatchesInliers
中标记。返回得分score
。
CheckHomography 结束 返回 FindHomography
取出得分最高的矩阵作为单应矩阵H。返回这个值H21。
// Step 5 更新具有最优评分的单应矩阵计算结果,并且保存所对应的特征点对的内点标记
if(currentScore>score)
{
//如果当前的结果得分更高,那么就更新最优计算结果
H21 = H21i.clone();
//保存匹配好的特征点对的Inliers标记
vbMatchesInliers = vbCurrentInliers;
//更新历史最优评分
score = currentScore;
}
}
}
FindHomography 结束 返回 Initialize
求完H矩阵后,下面开线程计算基础矩阵F。函数过程与H矩阵相似。
// 计算fundamental matrix并打分,参数定义和H是一样的,这里不再赘述
thread threadF(&Initializer::FindFundamental,
this,
ref(vbMatchesInliersF),
ref(SF),
ref(F));
Initialize 跳转 FindFundamental
定义变量与取随机8对点与前面H矩阵相同。
/**
* @brief 计算基础矩阵,假设场景为非平面情况下通过前两帧求取Fundamental矩阵,得到该模型的评分
* Step 1 将当前帧和参考帧中的特征点坐标进行归一化
* Step 2 选择8个归一化之后的点对进行迭代
* Step 3 八点法计算基础矩阵矩阵
* Step 4 利用重投影误差为当次RANSAC的结果评分
* Step 5 更新具有最优评分的基础矩阵计算结果,并且保存所对应的特征点对的内点标记
*
* @param[in & out] vbMatchesInliers 标记是否是外点
* @param[in & out] score 计算基础矩阵得分
* @param[in & out] F21 从特征点1到2的基础矩阵
*/
void Initializer::FindFundamental(vector<bool> &vbMatchesInliers, float &score, cv::Mat &F21)
{
// 计算基础矩阵,其过程和上面的计算单应矩阵的过程十分相似.
// Number of putative matches
// 匹配的特征点对总数
const int N = vbMatchesInliers.size();
// Normalize coordinates
// Step 1 将当前帧和参考帧中的特征点坐标进行归一化,主要是平移和尺度变换
// 具体来说,就是将mvKeys1和mvKey2归一化到均值为0,一阶绝对矩为1,归一化矩阵分别为T1、T2
// 这里所谓的一阶绝对矩其实就是随机变量到取值的中心的绝对值的平均值
// 归一化矩阵就是把上述归一化的操作用矩阵来表示。这样特征点坐标乘归一化矩阵可以得到归一化后的坐标
vector<cv::Point2f> vPn1, vPn2;
cv::Mat T1, T2;
Normalize(mvKeys1,vPn1, T1);
Normalize(mvKeys2,vPn2, T2);
// ! 注意这里取的是归一化矩阵T2的转置,因为基础矩阵的定义和单应矩阵不同,两者去归一化的计算也不相同
cv::Mat T2t = T2.t();
// Best Results variables
//最优结果
score = 0.0;
vbMatchesInliers = vector<bool>(N,false);
// Iteration variables
// 某次迭代中,参考帧的特征点坐标
vector<cv::Point2f> vPn1i(8);
// 某次迭代中,当前帧的特征点坐标
vector<cv::Point2f> vPn2i(8);
// 某次迭代中,计算的基础矩阵
cv::Mat F21i;
// 每次RANSAC记录的Inliers与得分
vector<bool> vbCurrentInliers(N,false);
float currentScore;
// Perform all RANSAC iterations and save the solution with highest score
// 下面进行每次的RANSAC迭代
for(int it=0; it<mMaxIterations; it++)
{
// Select a minimum set
// Step 2 选择8个归一化之后的点对进行迭代
for(int j=0; j<8; j++)
{
int idx = mvSets[it][j];
// vPn1i和vPn2i为匹配的特征点对的归一化后的坐标
// 首先根据这个特征点对的索引信息分别找到两个特征点在各自图像特征点向量中的索引,然后读取其归一化之后的特征点坐标
vPn1i[j] = vPn1[mvMatches12[idx].first]; //first存储在参考帧1中的特征点索引
vPn2i[j] = vPn2[mvMatches12[idx].second]; //second存储在参考帧1中的特征点索引
}
计算基础矩阵F的函数ComputeF21。
// Step 3 八点法计算基础矩阵
cv::Mat Fn = ComputeF21(vPn1i,vPn2i);
FindFundamental 跳转 ComputeF21
首先看F矩阵求解原理:
求解基础矩阵F
由对极约束:
p
2
T
∗
F
21
∗
p
1
=
0
{p_2^T*F_{21}*p_1=0}
p2T∗F21∗p1=0
写成矩阵形式:
[
u
2
,
v
2
,
1
]
[
f
1
,
f
2
,
f
3
f
4
,
f
5
,
f
6
f
7
,
f
8
,
f
9
]
[
u
1
v
1
1
]
=
0
{ \begin{bmatrix} u_2,v_2,1 \end{bmatrix} \begin{bmatrix} f_1,f_2,f_3\\ f_4,f_5,f_6\\ f_7,f_8,f_9 \end{bmatrix} \begin{bmatrix} u_1\\v_1\\1 \end{bmatrix} = 0 }
[u2,v2,1]⎣⎡f1,f2,f3f4,f5,f6f7,f8,f9⎦⎤⎣⎡u1v11⎦⎤=0
上式可化为:
[
a
,
b
,
c
]
[
u
1
v
1
1
]
=
0
{ \begin{bmatrix} a,b,c \end{bmatrix} \begin{bmatrix} u_1\\v_1\\1 \end{bmatrix} = 0 }
[a,b,c]⎣⎡u1v11⎦⎤=0
其中:
a
=
f
1
∗
u
2
+
f
4
∗
v
2
+
f
7
{a=f_1*u_2+f_4*v_2+f_7}
a=f1∗u2+f4∗v2+f7 ;
b
=
f
2
∗
u
2
+
f
5
∗
v
2
+
f
8
{b=f_2*u_2+f_5*v_2+f_8}
b=f2∗u2+f5∗v2+f8 ;
c
=
f
3
∗
u
2
+
f
6
∗
v
2
+
f
9
{c=f_3*u_2+f_6*v_2+f_9}
c=f3∗u2+f6∗v2+f9
上式展开:
a
∗
u
1
+
b
∗
v
1
+
c
=
0
{a*u_1+b*v_1+c=0}
a∗u1+b∗v1+c=0
代入abc得:
f
1
∗
u
1
∗
u
2
+
f
2
∗
v
1
∗
u
2
+
f
3
∗
u
2
+
f
4
∗
u
1
∗
v
2
+
f
5
∗
v
1
∗
v
2
+
f
6
∗
v
2
+
f
7
∗
u
1
+
f
8
∗
v
1
+
f
9
=
0
{f_1*u_1*u_2+f_2*v_1*u_2+f_3*u_2+f_4*u_1*v_2+f_5*v_1*v_2+f_6*v_2+f_7*u_1+f_8*v_1+f_9=0}
f1∗u1∗u2+f2∗v1∗u2+f3∗u2+f4∗u1∗v2+f5∗v1∗v2+f6∗v2+f7∗u1+f8∗v1+f9=0
写成矩阵:
[
u
1
∗
u
2
,
v
1
∗
u
2
,
u
2
,
u
1
∗
v
2
,
v
1
∗
v
2
,
v
2
,
u
1
,
v
1
,
1
]
[
f
1
f
2
f
3
f
4
f
5
f
6
f
7
f
8
f
9
]
=
0
{ \begin{bmatrix} u_1*u_2,v_1*u_2,u_2,u_1*v_2,v_1*v_2,v_2,u_1,v_1,1 \end{bmatrix} \begin{bmatrix} f_1\\f_2\\f_3\\f_4\\f_5\\f_6\\f_7\\f_8\\f_9 \end{bmatrix} = 0 }
[u1∗u2,v1∗u2,u2,u1∗v2,v1∗v2,v2,u1,v1,1]⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎡f1f2f3f4f5f6f7f8f9⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎤=0
等式左边分别用 A {A} A, f {f} f表示,则有: A f = 0 {Af=0} Af=0
F共有9个元素,7个自由度(尺度等价性,秩为2),所以8对点提供8个约束方程就可以求解F。
然后使用SVD奇异值分解 A = U D V T {A=UDV^T} A=UDVT
假设我们使用8对点求解, A {A} A是8x9矩阵,分解后
U
{U}
U是左奇异向量,它是一个8x8的正交矩阵,
V
{V}
V是右奇异向量,是一个9x9的正交矩阵,
V
T
{V^T}
VT是
V
{V}
V的转置
D {D} D是一个8x9对角矩阵,除了对角线其他元素均为0,对角线元素称为奇异值,一般来说奇异值是按照从大到小的顺序降序排列。因为每个奇异值都是一个残差项,因此最后一个奇异值最小,其含义就是最优的残差。因此其对应的奇异值向量就是最优值,即最优解。 V T {V^T} VT中的每个列向量对应着 D {D} D中的每个奇异值,最小二乘最优解就是 V T {V^T} VT对应的第9个列向量,也就是基础矩阵F的元素。这里我们先记做Fpre,因为这个还不是最终的F。
基础矩阵F有个很重要的性质,就是秩为2,可以进一步约束求解准确的F:
上面的方法使用V对应的第9个列向量构造的Fpre秩通常不为2,我们可以继续进行SVD分解:
F
p
r
e
=
U
D
V
T
=
U
[
σ
1
,
0
,
0
0
,
σ
2
,
0
0
,
0
,
σ
3
]
V
T
{F_{pre}=UDV^T=U\begin{bmatrix} σ_1,0,0\\ 0,σ_2,0\\ 0,0,σ_3 \end{bmatrix} V^T}
Fpre=UDVT=U⎣⎡σ1,0,00,σ2,00,0,σ3⎦⎤VT
其最小奇异值人为置为0,这样F矩阵秩为2:
F
=
U
D
V
T
=
U
[
σ
1
,
0
,
0
0
,
σ
2
,
0
0
,
0
,
0
]
V
T
{F=UDV^T=U\begin{bmatrix} σ_1 ,0,0\\ 0,σ_2,0\\ 0,0,0 \end{bmatrix} V^T}
F=UDVT=U⎣⎡σ1,0,00,σ2,00,0,0⎦⎤VT
此时的就是最终得到的基础矩阵。
传入参数是归一化的8对点。
cv::Mat Initializer::ComputeF21(
const vector<cv::Point2f> &vP1, //归一化后的点, in reference frame
const vector<cv::Point2f> &vP2) //归一化后的点, in current frame
{
// x'Fx = 0 整理可得:Af = 0
// A = | x'x x'y x' y'x y'y y' x y 1 |, f = | f1 f2 f3 f4 f5 f6 f7 f8 f9 |
// 通过SVD求解Af = 0,A'A最小特征值对应的特征向量即为解
//获取参与计算的特征点对数
const int N = vP1.size();
//初始化A矩阵
cv::Mat A(N,9,CV_32F); // N*9维
// 构造矩阵A,将每个特征点添加到矩阵A中的元素
for(int i=0; i<N; i++)
{
const float u1 = vP1[i].x;
const float v1 = vP1[i].y;
const float u2 = vP2[i].x;
const float v2 = vP2[i].y;
A.at<float>(i,0) = u2*u1;
A.at<float>(i,1) = u2*v1;
A.at<float>(i,2) = u2;
A.at<float>(i,3) = v2*u1;
A.at<float>(i,4) = v2*v1;
A.at<float>(i,5) = v2;
A.at<float>(i,6) = u1;
A.at<float>(i,7) = v1;
A.at<float>(i,8) = 1;
}
//存储奇异值分解结果的变量
cv::Mat u,w,vt;
使用cv函数SVDecomp
进行SVD分解
// 定义输出变量,u是左边的正交矩阵U, w为奇异矩阵,vt中的t表示是右正交矩阵V的转置
cv::SVDecomp(A,w,u,vt,cv::SVD::MODIFY_A | cv::SVD::FULL_UV);
分解后取出第9列,即为 F p r e {F_{pre}} Fpre
// 转换成基础矩阵的形式
cv::Mat Fpre = vt.row(8).reshape(0, 3); // v的最后一列
将 F p r e {F_{pre}} Fpre传入,进行第二次SVD分解,并把第三个奇异值手动置为0。
//基础矩阵的秩为2,而我们不敢保证计算得到的这个结果的秩为2,所以需要通过第二次奇异值分解,来强制使其秩为2
// 对初步得来的基础矩阵进行第2次奇异值分解
cv::SVDecomp(Fpre,w,u,vt,cv::SVD::MODIFY_A | cv::SVD::FULL_UV);
// 秩2约束,强制将第3个奇异值设置为0
w.at<float>(2)=0;
返回最终的结果。
// 重新组合好满足秩约束的基础矩阵,作为最终计算结果返回
return u*cv::Mat::diag(w)*vt;
}
ComputeF21 结束 返回 FindFundamental
// 基础矩阵约束:p2^t*F21*p1 = 0,其中p1,p2 为齐次化特征点坐标
// 特征点归一化:vPn1 = T1 * mvKeys1, vPn2 = T2 * mvKeys2
// 根据基础矩阵约束得到:(T2 * mvKeys2)^t* Hn * T1 * mvKeys1 = 0
// 进一步得到:mvKeys2^t * T2^t * Hn * T1 * mvKeys1 = 0
F21i = T2t*Fn*T1;
相似的,对基础矩阵F评分。
// Step 4 利用重投影误差为当次RANSAC的结果评分
currentScore = CheckFundamental(F21i, vbCurrentInliers, mSigma);
FindFundamental 跳转 CheckFundamental
前面H矩阵的误差用的是重投影误差,这里求F矩阵的误差,使用的是点到线的距离。
由公式
l
2
−
F
∗
p
1
,
l
1
=
p
2
∗
F
{l_2-F*p_1 , l_1=p_2*F}
l2−F∗p1,l1=p2∗F
可以根据一点和基础矩阵F,求出该点在另一张图像中对应的极线。
然后用点到极线的距离作为打分的标准。
/**
* @brief 对给定的Fundamental matrix打分
*
* @param[in] F21 当前帧和参考帧之间的基础矩阵
* @param[in] vbMatchesInliers 匹配的特征点对属于inliers的标记
* @param[in] sigma 方差,默认为1
* @return float 返回得分
*/
float Initializer::CheckFundamental(
const cv::Mat &F21, //当前帧和参考帧之间的基础矩阵
vector<bool> &vbMatchesInliers, //匹配的特征点对属于inliers的标记
float sigma) //方差
{
// 说明:在已值n维观测数据误差服从N(0,sigma)的高斯分布时
// 其误差加权最小二乘结果为 sum_error = SUM(e(i)^T * Q^(-1) * e(i))
// 其中:e(i) = [e_x,e_y,...]^T, Q维观测数据协方差矩阵,即sigma * sigma组成的协方差矩阵
// 误差加权最小二次结果越小,说明观测数据精度越高
// 那么,score = SUM((th - e(i)^T * Q^(-1) * e(i)))的分数就越高
// 算法目标:检查基础矩阵
// 检查方式:利用对极几何原理 p2^T * F * p1 = 0
// 假设:三维空间中的点 P 在 img1 和 img2 两图像上的投影分别为 p1 和 p2(两个为同名点)
// 则:p2 一定存在于极线 l2 上,即 p2*l2 = 0. 而l2 = F*p1 = (a, b, c)^T
// 所以,这里的误差项 e 为 p2 到 极线 l2 的距离,如果在直线上,则 e = 0
// 根据点到直线的距离公式:d = (ax + by + c) / sqrt(a * a + b * b)
// 所以,e = (a * p2.x + b * p2.y + c) / sqrt(a * a + b * b)
// 算法流程
// input: 基础矩阵 F 左右视图匹配点集 mvKeys1
// do:
// for p1(i), p2(i) in mvKeys:
// l2 = F * p1(i)
// l1 = p2(i) * F
// error_i1 = dist_point_to_line(x2,l2)
// error_i2 = dist_point_to_line(x1,l1)
//
// w1 = 1 / sigma / sigma
// w2 = 1 / sigma / sigma
//
// if error1 < th
// score += thScore - error_i1 * w1
// if error2 < th
// score += thScore - error_i2 * w2
//
// if error_1i > th or error_2i > th
// p1(i), p2(i) are inner points
// vbMatchesInliers(i) = true
// else
// p1(i), p2(i) are outliers
// vbMatchesInliers(i) = false
// end
// end
// output: score, inliers
// 获取匹配的特征点对的总对数
const int N = mvMatches12.size();
// Step 1 提取基础矩阵中的元素数据
const float f11 = F21.at<float>(0,0);
const float f12 = F21.at<float>(0,1);
const float f13 = F21.at<float>(0,2);
const float f21 = F21.at<float>(1,0);
const float f22 = F21.at<float>(1,1);
const float f23 = F21.at<float>(1,2);
const float f31 = F21.at<float>(2,0);
const float f32 = F21.at<float>(2,1);
const float f33 = F21.at<float>(2,2);
// 预分配空间
vbMatchesInliers.resize(N);
// 设置评分初始值(因为后面需要进行这个数值的累计)
float score = 0;
// 基于卡方检验计算出的阈值
// 自由度为1的卡方分布,显著性水平为0.05,对应的临界阈值
// ?是因为点到直线距离是一个自由度吗?
const float th = 3.841;
// 自由度为2的卡方分布,显著性水平为0.05,对应的临界阈值
const float thScore = 5.991;
// 信息矩阵,或 协方差矩阵的逆矩阵
const float invSigmaSquare = 1.0/(sigma*sigma);
// Step 2 计算img1 和 img2 在估计 F 时的score值
for(int i=0; i<N; i++)
{
//默认为这对特征点是Inliers
bool bIn = true;
// Step 2.1 提取参考帧和当前帧之间的特征匹配点对
const cv::KeyPoint &kp1 = mvKeys1[mvMatches12[i].first];
const cv::KeyPoint &kp2 = mvKeys2[mvMatches12[i].second];
// 提取出特征点的坐标
const float u1 = kp1.pt.x;
const float v1 = kp1.pt.y;
const float u2 = kp2.pt.x;
const float v2 = kp2.pt.y;
// Reprojection error in second image
// Step 2.2 计算 img1 上的点在 img2 上投影得到的极线 l2 = F21 * p1 = (a2,b2,c2)
const float a2 = f11*u1+f12*v1+f13;
const float b2 = f21*u1+f22*v1+f23;
const float c2 = f31*u1+f32*v1+f33;
下面求误差,公式是点到直线的距离公式的平方。
// Step 2.3 计算误差 e = (a * p2.x + b * p2.y + c) / sqrt(a * a + b * b)
const float num2 = a2*u2+b2*v2+c2;
const float squareDist1 = num2*num2/(a2*a2+b2*b2);
// 带权重误差
const float chiSquare1 = squareDist1*invSigmaSquare;
// Step 2.4 误差大于阈值就说明这个点是Outlier
// ? 为什么判断阈值用的 th(1自由度),计算得分用的thScore(2自由度)
// ? 可能是为了和CheckHomography 得分统一?
if(chiSquare1>th)
bIn = false;
else
// 误差越大,得分越低
score += thScore - chiSquare1;
// 计算img2上的点在 img1 上投影得到的极线 l1= p2 * F21 = (a1,b1,c1)
const float a1 = f11*u2+f21*v2+f31;
const float b1 = f12*u2+f22*v2+f32;
const float c1 = f13*u2+f23*v2+f33;
// 计算误差 e = (a * p2.x + b * p2.y + c) / sqrt(a * a + b * b)
const float num1 = a1*u1+b1*v1+c1;
const float squareDist2 = num1*num1/(a1*a1+b1*b1);
// 带权重误差
const float chiSquare2 = squareDist2*invSigmaSquare;
// 误差大于阈值就说明这个点是Outlier
if(chiSquare2>th)
bIn = false;
else
score += thScore - chiSquare2;
// Step 2.5 保存结果
if(bIn)
vbMatchesInliers[i]=true;
else
vbMatchesInliers[i]=false;
}
// 返回评分
return score;
}
CheckFundamental 结束 返回 FindFundamental
// Step 5 更新具有最优评分的基础矩阵计算结果,并且保存所对应的特征点对的内点标记
if(currentScore>score)
{
//如果当前的结果得分更高,那么就更新最优计算结果
F21 = F21i.clone();
vbMatchesInliers = vbCurrentInliers;
score = currentScore;
}
}
}