描述
生成词典
案例步骤:
- 加载 图片,注:相对路径指基于可执行文件的路径
- 检测ORB 特征和计算描述子
detectAndCompute
- 创建词典,描述子创建词袋
DBoW3::Vocabulary::create
- 保存词袋
DBoW3::Vocabulary::save
与已有词典匹配
案例步骤:
- 加载词典
DBoW3::Vocabulary
- 加载 图片,注:相对路径指基于可执行文件的路径
- 检测ORB 特征和计算描述子
detectAndCompute
- 根据词典计算词袋向量,进而计算相似度
- 有了词典对象,就可以对新的图像计算词袋向量(BowVector)了
DBoW3::Vocabulary::transform
- 当前描述子生成的词袋向量 与 每张图片生成的词袋向量比较计算得分
DBoW3::Vocabulary::score
- 相似度的 结果为0到1,越大相似度越高
- 有了词典对象,就可以对新的图像计算词袋向量(BowVector)了
- 词袋建立索引得出最优相似度
- 参数true表明建立正向索引
DBoW3::Database db( vocab, false, 0)
- 将图像的描述子加入其词袋数据库,
db.add(descriptors[i])
- 利用逆序索引搜索最相似的图像
DBoW3::QueryResults ret
db.query( descriptors[i], ret, 4);
- 参数true表明建立正向索引
CV Matcher
两张图片的匹配
- 计算两张图片的特征点和描述子
- 对两幅图像中的BRIEF描述子进行匹配,使用 Hamming 距离
- 匹配点对筛选,基于hamming距离
- 当描述子之间的距离大于两倍的最小距离时,即认为匹配有误.但有时候最小距离会非常小,设置一个经验值30作为下限
- 绘制匹配结果
- 把匹配点转换为
vector<Point2f>
的形式 - 绘制原始图中一个框,测试匹配的如何
- 计算两张图片的 单映射变换矩阵
- 显示匹配的结果
Coding
#include "DBoW3/DBoW3.h"
#include "opencv2/opencv.hpp"
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <iostream>
#include <vector>
#include <string>
/***************************************************
* 演示了ch12下如何根据data/目录下的十张图训练字典
* ************************************************/
int main( int argc, char** argv )
{
if(false){ // 生成词典
using namespace cv;
using namespace std;
// 读取图片
cout<<"reading images... "<<endl;
vector<Mat> images;
for ( int i=0; i<10; i++ )
{
string path = "../data/"+to_string(i+1)+".png";
cv::Mat src_image = imread(path);
images.push_back( src_image);
}
// 检测ORB 特征和计算描述子
cout<<"detecting ORB features ... "<<endl;
Ptr< Feature2D > detector = ORB::create();
vector<Mat> descriptors;
for ( Mat& image:images )
{
vector<KeyPoint> keypoints;
Mat descriptor;
detector->detectAndCompute( image, Mat(), keypoints, descriptor );
std::cout<<"key_size: "<<keypoints.size()<<" "<<descriptor.rows<<" "<<descriptor.cols<<std::endl;
descriptors.push_back( descriptor );
}
// 创建词典,描述子创建词袋
cout<<"creating vocabulary ... "<<endl;
DBoW3::Vocabulary vocab;
vocab.create( descriptors );
cout<<"vocabulary info: "<<vocab<<endl;
// 保存词袋
vocab.save( "vocabulary.yml.gz" );
cout<<"done"<<endl;
}
if(false){ // 与已有词典进行匹配
using namespace cv;
using namespace std;
// 读取词袋,上面生成的
cout<<"reading database"<<endl;
DBoW3::Vocabulary vocab("./vocabulary.yml.gz");
// DBoW3::Vocabulary vocab("./vocab_larger.yml.gz"); // use large vocab if you want:
if ( vocab.empty() )
{
cerr<<"Vocabulary does not exist."<<endl;
return 1;
}
// 读取图片
vector<Mat> images;
for ( int i=0; i<10; i++ )
{
string path = "../data/"+to_string(i+1)+".png";
images.push_back( imread(path) );
}
// 检测ORB 特征和计算描述子
cout<<"detecting ORB features ... "<<endl;
Ptr< Feature2D > detector = ORB::create();
vector<Mat> descriptors;
for ( Mat& image:images )
{
vector<KeyPoint> keypoints;
Mat descriptor;
detector->detectAndCompute( image, Mat(), keypoints, descriptor );
descriptors.push_back( descriptor );
}
// 我们使用这个库是用来做回环检测的,通过计算相似度我们可以得到哪些图像相似度最高,
// images :
cout<<"comparing images with images "<<endl;
for ( int i=0; i<images.size(); i++ )
{
DBoW3::BowVector v1;
// 有了词典对象,就可以对新的图像计算词袋向量(BowVector)了
vocab.transform( descriptors[i], v1 );
// 当前描述子生成的词袋向量 与 每张图片生成的词袋向量比较计算得分
for ( int j=i; j<images.size(); j++ )
{
DBoW3::BowVector v2;
vocab.transform( descriptors[j], v2 );
// 有了图像的词袋向量就可以计算这两张图像之间的相似度了,结果为0到1,越大相似度越高
double score = vocab.score(v1, v2);
cout<<"image "<<i<<" vs image "<<j<<" : "<<score<<endl;
}
cout<<endl;
}
// 当然DBoW提供了另外一个效率更高的方法来帮助我们完成这个目标——建立索引库
cout<<"comparing images with database "<<endl;
DBoW3::Database db( vocab, false, 0); //参数true表明建立正向索引
for ( int i=0; i<descriptors.size(); i++ )
db.add(descriptors[i]);
cout<<"database info: "<<db<<endl;
for ( int i=0; i<descriptors.size(); i++ )
{
DBoW3::QueryResults ret;
// 利用逆序索引搜索最相似的图像,找
db.query( descriptors[i], ret, 4); // max result=4
cout<<"searching for image "<<i<<" returns "<<ret<<endl<<endl;
}
}
if(true){ // cv Match
/// 步骤1:加载两张图片
cv::Mat img_1 = cv::imread("../data/1.png",1);
cv::Mat img_2 = cv::imread("../data/1.png",1);
// 检测和描述,其实都可以 cv::Feature2D
cv::Ptr<cv::FeatureDetector> detector = cv::ORB::create();
cv::Ptr<cv::DescriptorExtractor> descriptor = cv::ORB::create();
// 如果使用 sift, surf ,之前要初始化nonfree模块
cv::Ptr<cv::DescriptorMatcher> matcher = cv::DescriptorMatcher::create ( "BruteForce-Hamming" );
/// 步骤2:得到特征值和描述子
//-- 检测 Oriented FAST 角点位置
std::vector<cv::KeyPoint> keypoints_1, keypoints_2;
detector->detect( img_1,keypoints_1 );
detector->detect( img_2,keypoints_2 );
//-- 根据角点位置计算 BRIEF 描述子
cv::Mat descriptors_1, descriptors_2;
descriptor->compute ( img_1, keypoints_1, descriptors_1 );
descriptor->compute ( img_2, keypoints_2, descriptors_2 );
/// 步骤3:对两幅图像中的BRIEF描述子进行匹配,使用 Hamming 距离
std::vector<cv::DMatch> matches;
matcher->match ( descriptors_1, descriptors_2, matches );
/// 步骤4:匹配点对筛选,基于hamming距离
double min_dist=10000, max_dist=0;
//找出所有匹配之间的最小距离和最大距离, 即是最相似的和最不相似的两组点之间的距离
for ( int i = 0; i < descriptors_1.rows; i++ ) {
double dist = matches[i].distance;
if ( dist < min_dist ) min_dist = dist;
if ( dist > max_dist ) max_dist = dist;
}
// 计算最大最小距离
min_dist = std::min_element( matches.begin(), matches.end(), []
(const cv::DMatch& m1, const cv::DMatch& m2) {return m1.distance<m2.distance;} )->distance;
max_dist = std::max_element( matches.begin(), matches.end(), []
(const cv::DMatch& m1, const cv::DMatch& m2) {return m1.distance<m2.distance;} )->distance;
std::cout<<"dist min: "<<min_dist<<" max:"<<max_dist<<std::endl;
//当描述子之间的距离大于两倍的最小距离时,即认为匹配有误.但有时候最小距离会非常小,设置一个经验值30作为下限.
std::vector<cv::DMatch > good_matches;
for ( int i = 0; i < descriptors_1.rows; i++ )
{
if ( matches[i].distance <= std::max(2*min_dist, 30.0 ) )
{
good_matches.push_back ( matches[i] );
}
}
std::cout<<"match: "<<matches.size()<<" good: "<<good_matches.size()<<std::endl;
matches = good_matches;
/// 步骤5:绘制匹配结果
if(false) {
cv::Mat img_match;
cv::Mat img_goodmatch;
cv::drawMatches ( img_1, keypoints_1, img_2, keypoints_2, matches, img_match );
cv::drawMatches ( img_1, keypoints_1, img_2, keypoints_2, good_matches, img_goodmatch );
cv::imshow ( "all_match_points", img_match );
cv::waitKey(200);
cv::imshow ( "opt_match_points", img_goodmatch );
std::string save_paths = "./";
cv::imwrite(save_paths + "all_match_points.png",img_match);
cv::imwrite(save_paths + "opt_match_points.png",img_goodmatch);
cv::waitKey(200);
cv::waitKey(1000);
}
/// 步骤6:把匹配点转换为vector<Point2f>的形式
std::vector<cv::Point2f> points1;
std::vector<cv::Point2f> points2;
for ( int i = 0; i < ( int ) matches.size(); i++ ) {
points1.push_back ( keypoints_1[matches[i].queryIdx].pt );
points2.push_back ( keypoints_2[matches[i].trainIdx].pt );
}
// 绘制原始图中一个框,测试匹配的如何
std::vector<cv::Point2f> obj_corners(4);
obj_corners[0] = cvPoint(243.0,102.0);
obj_corners[1] = cvPoint(380.0,104.0);
obj_corners[2] = cvPoint(374.0,218.0);
obj_corners[3] = cvPoint(245.0,211.0);
{
std::vector<cv::Point2f> corners = obj_corners;
cv::Scalar scalar = cv::Scalar(0, 0, 255);
cv::line( img_1, corners[0] , corners[1] , scalar , 2 );
cv::line( img_1, corners[1] , corners[2] , scalar, 2 );
cv::line( img_1, corners[2] , corners[3] , scalar, 2 );
cv::line( img_1, corners[3] , corners[0] , scalar, 2 );
}
/// 步骤7:计算两张图片的 单映射变换矩阵
if(true){
std::cout<<"---------------------"<<std::endl;
// 计算多个二维点对之间的最优单映射变换矩阵 H(3行x3列) ,使用最小均方误差或者RANSAC方法 。
cv::Mat auto_matrix = cv::findHomography ( points1, points2, cv::RANSAC, 3 );
// cv::Mat auto_matrix = cv::findHomography ( points1, points2,0 );
std::cout<<"matrix is "<<std::endl<< auto_matrix<<std::endl;
double theta = std::atan2(auto_matrix.at<double>(1,0),
auto_matrix.at<double>(0,0));
theta *=180./3.1415926;
auto result = cv::Vec3d(auto_matrix.at<double>(0,2),auto_matrix.at<double>(1,2),theta);
std::cout<<"FindHomography is: "<<result<<std::endl;
/// 基于此变换矩阵将上述4个点的连线绘制到第二幅图上
{
std::vector<cv::Point2f> scene_corners(4);
cv::perspectiveTransform( obj_corners, scene_corners, auto_matrix);
std::vector<cv::Point2f> corners = scene_corners;
cv::Scalar scalar = cv::Scalar(0, 255, 0);
cv::line( img_2, corners[0] , corners[1] , scalar, 2 );
cv::line( img_2, corners[1] , corners[2] , scalar, 2 );
cv::line( img_2, corners[2] , corners[3] , scalar, 2 );
cv::line( img_2, corners[3] , corners[0] , scalar, 2 );
}
}
/// 步骤8: 显示匹配的结果
{
cv::Mat img_goodmatch;
cv::drawMatches ( img_1, keypoints_1, img_2, keypoints_2, good_matches, img_goodmatch );
cv::imshow ( "all_match_points", img_goodmatch );
cv::waitKey(200);
std::string save_paths = "./";
cv::imwrite(save_paths + "opt_match_points.png",img_goodmatch);
cv::waitKey(200);
}
}
return 0;
}