由于本文程序直接使用opencv库,因此对于SIFT和SURF算法的原理不做详细介绍。
简单来说,该两种算法使用特征向量来描述特征点,我们可以通过计算特征向量的距离来匹配两张图中对应的特征点。值得注意的是,SIFT由于计算方法不同,且维度较高,计算速度上较SURF更慢。
下面简单介绍一下双目测距原理,这也是本次实验的关键。
下图是双目摄像头成像示意图,其中P点为目标物体,O1和O2分别为左右摄像头的投影中心,x1和x2分别是目标物体在左右摄像头成像平面上的坐标,f为焦距,T为左右摄像头中心线距离,Z为摄像头投影中心到目标物体的距离。
通过计算相似三角形我们可以得到:
其中,
1.(x1-x2)实际上就是视差,单位是实际物理量mm,用D
来表示这个视差,并将单位换成像素,多出来的常数放到分子中;
2.分母中的fT是一个常数,我们用K
来表示,这里面还可以包括像素单位到mm单位的比例系数。
整理一下可以得到:
.
其中,
1.?
(mm)为摄像头投影中心到物体的距离;
2.?
(像素)为视差,与?1−?2 成比例关系;
3.?
(mm*像素)为常数。
*从公式可以看出,距离Z和视差D是成反比例关系的。因此,我们只要得到反比例系数K,就可以通过视差值来计算距离值。
由于双目测距中视差和距离成反比例关系,从上图可以看出:
1.在距离较近的地方,较小的距离变化会引起较大的视差变化;
2.在距离较远的地方,较大的距离变化仅引起较小的视差变化。
因此,双目视差测距有一定的局限性,即在较远或较近的地方效果不会很好。
由于博主假期在家,未带回双目摄像头,因此本次实验使用手机拍摄的照片模拟左右图像,仅实现视差的获取(完整的双目测距下篇文章再续)。
主要步骤:
1、导入左右图像;
2、使用SiftFeatureDetector
或SurfFeatureDetector
检测关键点(ps:此处为opencv2.4.13版本的检测器,3.0版本之后不太一样);
3、使用SiftDescriptorExtractor
或SurfDescriptorExtractor
提取各个关键点的描述(特征向量);
4、使用FlannBasedMatcher
匹配器进行两图像的关键点匹配(这里面会包含很多错误匹配);
5、计算出所有匹配中的最小distance
,筛选出小于两倍最小匹配距离的匹配作为正确的匹配;
6、计算出正确匹配的两特征点的X坐标差,即为该匹配点的像素视差;
7、去除掉偏差值较大的视差,计算视差均值作为最后的结果。
匹配效果:
匹配点视差数据:
筛选后视差结果:
ps:两种方法最后计算出来的视差结果差别不大,但是SIFT算法的速度明显慢于SURF很多,不过前者获得的匹配点数量也更多。
#include
#include
#include
#include
#include
#include
#include
#include
using namespace cv;
using namespace std;
class parallax
{
public:
double leftX;
double rightX;
double paraValue;
};
bool ascendPara(parallax a, parallax b)
{
return a.paraValue < b.paraValue;
}
int main()
{
Mat leftImg, rightImg;
leftImg = imread("limg.jpg");
rightImg = imread("rimg.jpg");
imshow("limg", leftImg);
imshow("rimg", rightImg);
int minhessian = 1000;//threshold of hessian in SIFT or SURF algorithm
vector<KeyPoint>l_keyPoint, r_keyPoint;
Mat l_descriptor, r_descriptor;
SurfFeatureDetector detector(minhessian);//define a feature detection class object
detector.detect(leftImg, l_keyPoint);
detector.detect(rightImg, r_keyPoint);
//compute descriptor (feature vector) of key points
SurfDescriptorExtractor extractor;
extractor.compute(leftImg, l_keyPoint, l_descriptor);
extractor.compute(rightImg, r_keyPoint, r_descriptor);
//FLANN algorithm to match feature vector
FlannBasedMatcher matcher;
vector<DMatch>matches;
matcher.match(l_descriptor, r_descriptor, matches);
//calculate the max and min distance between key points
double maxdist = 0; double mindist = 100;
for (int i = 0; i < l_descriptor.rows; i++)
{
double dist = matches[i].distance;
if (dist < mindist)mindist = dist;
if (dist > maxdist)maxdist = dist;
}
cout << "Matching quantity:" << matches.size() << endl;
//select the good match points
vector<DMatch>goodMatches;
for (int i = 0; i < l_descriptor.rows; i++)
{
if (matches[i].distance<2 * mindist)
{
goodMatches.push_back(matches[i]);
}
}
cout << "Good matching quantity:" << goodMatches.size() << endl;
//calculate parallax
vector<parallax>para;
for (int i = 0; i < goodMatches.size(); i++)
{
parallax temp;
temp.leftX = l_keyPoint[goodMatches[i].queryIdx].pt.x;
temp.rightX = r_keyPoint[goodMatches[i].trainIdx].pt.x;
temp.paraValue = temp.leftX - temp.rightX;
para.push_back(temp);
cout << "No." << i + 1 << ":\t l_X ";
cout << para[i].leftX << "\t r_X " << para[i].rightX;
cout << "\t parallax " << para[i].paraValue << endl;
}
sort(para.begin(), para.end(), ascendPara);
int idxMedian = int(para.size()/2);
double paraMedian = para[idxMedian].paraValue;
vector<parallax>::iterator it;
double errorRange = 0.005;
for (it=para.begin(); it!=para.end(); )
{
if (it->paraValue<((1 - errorRange)*paraMedian) || it->paraValue>((1 + errorRange)*paraMedian))
it = para.erase(it);
else
it++;
}
cout << "Final data..." << endl;
double paraSum = 0;
double paraMean;
for (int i = 0; i < para.size(); i++)
{
paraSum = paraSum + para[i].paraValue;
cout << "No." << i << "\t" << para[i].paraValue << endl;
}
paraMean = paraSum / para.size();
cout << "Parallax is " << paraMean << " pixel." << endl;
//draw the match image
Mat matchImg;
drawMatches(leftImg, l_keyPoint, rightImg, r_keyPoint, goodMatches, matchImg,
Scalar::all(-1), Scalar::all(-1), vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
imshow("match", matchImg);
waitKey(0);
return 0;
}
ps:1.代码中使用的是SURF算法,需要用SIFT的只需要将代码中SurfFeatureDetector
和SurfDescriptorExtractor
改成SiftFeatureDetector
和SiftDescriptorExtractor
即可;
2.匹配结果DMatch
中存储着匹配点对的索引,分别在DMatch[i].queryIdx
和DMatch[i].trainIdx
中,这是找到匹配对的关键。
本次仅实现了像素视差的获取,下篇待博主回校后使用双目摄像头来试验实际测距效果。
下篇链接:https://blog.csdn.net/qinchang1/article/details/88412058
如有错误,欢迎指正!