1. 基础矩阵F
根据对极约束有以下关系:
x 2 T t Λ R x 1 = 0 (1.1) \boldsymbol{x}_2^T\boldsymbol{t}^{\Lambda}\boldsymbol{R}\boldsymbol{x}_1 = 0 \tag{1.1} x2TtΛRx1=0(1.1)
其中x1和x2分别是两帧上匹配点对的归一化坐标,令基础矩阵F和本质矩阵E为
F = K − T E K − 1 = K − T t Λ R K − 1 (1.2) \boldsymbol{F}=\boldsymbol{K}^{-T}\boldsymbol{E}\boldsymbol{K}^{-1}=\boldsymbol{K}^{-T}\boldsymbol{t}^{\Lambda}\boldsymbol{R}\boldsymbol{K}^{-1} \tag{1.2} F=K−TEK−1=K−TtΛRK−1(1.2)
那么对极约束可以改写成
p 2 T F p 1 = x 2 T E x 1 = 0 (1.3) \boldsymbol{p}_2^T\boldsymbol{F}\boldsymbol{p}_1 =\boldsymbol{x}_2^T\boldsymbol{E}\boldsymbol{x}_1 = 0 \tag{1.3} p2TFp1=x2TEx1=0(1.3)
只需要8个点对就可以求解基础矩阵!
2. ORB中的实现
ORB中在求解基础前进行了归一化操作:
原因参考Hartey的分析:
利用8点法求基础矩阵不稳定的一个主要原因就是原始的图像像点坐标组成的系数矩阵A不好造成的,而造成A不好的原因是像点的齐次坐标各个分量的数量级相差太大
在应用8点法求基础矩阵之前,先对像点坐标进行归一化处理,即对原始的图像坐标做同向性变换,这样就可以减少噪声的干扰,大大的提高8点法的精度
2.1 归一化操作
归一化的直观理解如下图
使8对匹配点均匀分布,避免点对之间权重不一致而导致某一点的误差过大而引起基础矩阵较大偏差
变换的核心思想可归结为2点:
- 将八个点均值移到原点
- 对点集缩放使p到原点的平均距离是根号2倍
变换矩阵
H = S [ 1 0 − μ ^ 0 1 − ν ^ 0 0 1 / S ] (2.1) \boldsymbol{H} = S \begin{bmatrix} 1 & 0 & -\hat{\mu} \\ 0 & 1 & -\hat{\nu} \\ 0 & 0 & 1/S \end{bmatrix} \tag{2.1} H=S 100010−μ^−ν^1/S (2.1)
μ ^ = 1 N ∑ i = 1 N μ i , ν ^ = 1 N ∑ i = 1 N ν i , (2.2) \hat{\mu} = \frac{1}{N}\sum_{i=1}^{N} \mu_{i}, \hat{\nu} = \frac{1}{N}\sum_{i=1}^{N} \nu_{i}, \tag{2.2} μ^=N1i=1∑Nμi,ν^=N1i=1∑Nνi,(2.2)
S = 2 N ∑ i = 1 N ( μ i − μ ^ ) 2 + ( ν i − ν ^ ) 2 (2.3) S = \frac{\sqrt{2N}} { \sqrt{\sum_{i=1}^{N} (\mu_i-\hat{\mu})^2 + (\nu_i-\hat{\nu})^2 } } \tag{2.3} S=∑i=1N(μi−μ^)2+(νi−ν^)22N(2.3)
2.2 orb中的归一化实现
orb的实现更加简单,直接对2个维度独立归一化,看代码就能看懂,这里不再详述,思路是一致的
void Initializer::Normalize(const vector<cv::KeyPoint> &vKeys, vector<cv::Point2f> &vNormalizedPoints, cv::Mat &T) //将特征点归一化的矩阵
{
// 归一化的是这些点在x方向和在y方向上的一阶绝对矩(随机变量的期望)。
// Step 1 计算特征点X,Y坐标的均值 meanX, meanY
float meanX = 0;
float meanY = 0;
//获取特征点的数量
const int N = vKeys.size();
//设置用来存储归一后特征点的向量大小,和归一化前保持一致
vNormalizedPoints.resize(N);
//开始遍历所有的特征点
for(int i=0; i<N; i++)
{
//分别累加特征点的X、Y坐标
meanX += vKeys[i].pt.x;
meanY += vKeys[i].pt.y;
}
//计算X、Y坐标的均值
meanX = meanX/N;
meanY = meanY/N;
// Step 2 计算特征点X,Y坐标离均值的平均偏离程度 meanDevX, meanDevY,注意不是标准差
float meanDevX = 0;
float meanDevY = 0;
// 将原始特征点减去均值坐标,使x坐标和y坐标均值分别为0
for(int i=0; i<N; i++)
{
vNormalizedPoints[i].x = vKeys[i].pt.x - meanX;
vNormalizedPoints[i].y = vKeys[i].pt.y - meanY;
//累计这些特征点偏离横纵坐标均值的程度
meanDevX += fabs(vNormalizedPoints[i].x);
meanDevY += fabs(vNormalizedPoints[i].y);
}
// 求出平均到每个点上,其坐标偏离横纵坐标均值的程度;将其倒数作为一个尺度缩放因子
meanDevX = meanDevX/N;
meanDevY = meanDevY/N;
float sX = 1.0/meanDevX;
float sY = 1.0/meanDevY;
// Step 3 将x坐标和y坐标分别进行尺度归一化,使得x坐标和y坐标的一阶绝对矩分别为1
// 这里所谓的一阶绝对矩其实就是随机变量到取值的中心的绝对值的平均值(期望)
for(int i=0; i<N; i++)
{
//对,就是简单地对特征点的坐标进行进一步的缩放
vNormalizedPoints[i].x = vNormalizedPoints[i].x * sX;
vNormalizedPoints[i].y = vNormalizedPoints[i].y * sY;
}
// Step 4 计算归一化矩阵:其实就是前面做的操作用矩阵变换来表示而已
// |sX 0 -meanx*sX|
// |0 sY -meany*sY|
// |0 0 1 |
T = cv::Mat::eye(3,3,CV_32F);
T.at<float>(0,0) = sX;
T.at<float>(1,1) = sY;
T.at<float>(0,2) = -meanX*sX;
T.at<float>(1,2) = -meanY*sY;
}