前言

  红胖子,来也!
  前面讲解了特征点,那么匹配特征点,就是匹配两者的相似度,相似度达到一定的阈值,则认为识别了。
  考虑性能,除开暴力匹配外,还有最近邻匹配。

 

Demo

  
  
  
  

 

最近邻匹配(FLANN)

  FlannBasedMatcher中FLANN的含义是Fast Library forApproximate Nearest Neighbors,目前最完整的(近似)最近邻匹配。不但实现了一系列查找算法,还包含了一种自动选取最快算法的机制。
  从字面意思可知它是一种近似法,算法更快但是找到的是最近邻近似匹配,所以当我们需要找到一个相对好的匹配但是不需要最佳匹配的时候往往使用FlannBasedMatcher。
  当然也可以通过调整FlannBasedMatcher的参数来提高匹配的精度或者提高算法速度,但是相应地算法速度或者算法精度会受到影响。

本篇章使用sift/surf特征点

sift特征点

  尺度不变特征变换(Scale-invariant feature transform,SIFT),是用于图像处理领域的一种描述。这种描述具有尺度不变性,可在图像中检测出关键点,是一种局部特征描述子。

surf特征点

  SURF算法采用了很多方法来对每一步进行优化从而提高速度。分析显示在结果效果相当的情况下SURF的速度是SIFT的3倍。SURF善于处理具有模糊和旋转的图像,但是不善于处理视角变化和光照变化。(SIFT特征是局部特征,其对旋转、尺度缩放、亮度变化保持不变性,对视角变化、仿射变换、噪声也保持一定程度的稳定性)。
针对图像场景的特点,选择不同的特征点,列出之前特征点相关的博文:
  《OpenCV开发笔记(六十三):红胖子8分钟带你深入了解SIFT特征点(图文并茂+浅显易懂+程序源码)
  《OpenCV开发笔记(六十四):红胖子8分钟带你深入了解SURF特征点(图文并茂+浅显易懂+程序源码)
  《OpenCV开发笔记(六十五):红胖子8分钟带你深入了解ORB特征点(图文并茂+浅显易懂+程序源码)

FlannBasedMatcher类的使用

定义

// 定义匹配器
cv::Ptr<cv::FlannBasedMatcher> pFlannBasedMatcher = cv::FlannBasedMatcher::create();
// 定义结果存放
std::vector<cv::DMatch> listDMatch;
// 存储特征点检测器检测特征后的描述字
cv::Mat descriptor1;
cv::Mat descriptor2;

特征点提取

pFlannBasedMatcher->detectAndCompute(srcMat1, cv::Mat(), keyPoints1, descriptor1);
pFlannBasedMatcher->detectAndCompute(srcMat1, cv::Mat(), keyPoints1, descriptor1);

匹配

// FlannBasedMatcher最近邻匹配
pFlannBasedMatcher->match(descriptor1, descriptor2, listDMatch);
FlannBasedMatcher相关函数原型
static Ptr<FlannBasedMatcher> create() ;

无参数

FlannBasedMatcher::match( InputArray queryDescriptors,
InputArray trainDescriptors,
std::vector<DMatch>& matches,
InputArray mask=noArray() ) const;
  • 参数一:InputArray类型的queryDescriptors,查询描述符集,一般cv::Mat,某个特征提取的描述符。
  • 参数二:InputArray类型的trainDescriptors,训练描述符集,此处输入的应该是没有加入到类对象集合种的(该类有训练的数据集合),一般cv::Mat,某个特征提取的描述符。
  • 参数三:std::vector类型的matches。如果在掩码中屏蔽了查询描述符,则不会为此添加匹配项描述符。因此,匹配项的大小可能小于查询描述符计数。
  • 参数四:InputArray类型的mask,指定输入查询和训练矩阵之间允许的匹配的掩码描述符。
    绘制匹配关系图函数原型
void drawMatches( InputArray img1,
const std::vector<KeyPoint>& keypoints1,
InputArray img2,
const std::vector<KeyPoint>& keypoints2,
const std::vector<DMatch>& matches1to2,
InputOutputArray outImg,
const Scalar& matchColor=Scalar::all(-1),
const Scalar& singlePointColor=Scalar::all(-1),
const std::vector<char>& matchesMask=std::vector<char>(),
int flags=DrawMatchesFlags::DEFAULT );
  • 参数一:InputArray类型的img1,图像1。
  • 参数二:std::vector类型的keypoints1,图像1的关键点。
  • 参数三:InputArray类型的img2,图像2。
  • 参数四:std::vector类型的keypoints2,图像2的关键点。
  • 参数五:std::vector类型的matchers1to2,从第一个图像匹配到第二个图像,这意味着keypoints1[i]在keypoints2中有一个对应的点[matches[i]]。
  • 参数六:InputOutputArray类型的outImg,为空时,默认并排绘制输出图像以及连接关键点;若不为空,则在图像上绘制关系点。
  • 参数七:Scalar类型的matcherColor,匹配颜色匹配(线和连接的关键点)的颜色。如果颜色为cv::Scalar::all(-1),则为随机颜色。
  • 参数八:Scalar类型的singlePointColor,颜色单个关键点(圆)的颜色,这意味着关键点没有匹配到的则认是该颜色。
  • 参数九:std::vector类型的matchersMask,确定绘制的匹配项目,若是为空,则表示全部绘制。
  • 参数十:int类型的flags,查看枚举DrawMatchesFlags,如下:
      
 

Demo

void OpenCVManager::testFlannBasedMatcher()
{
QString fileName1 = "21.jpg";
QString fileName2 = "24.jpg";
int width = 400;
int height = 300; cv::Mat srcMat = cv::imread(fileName1.toStdString());
cv::Mat srcMat3 = cv::imread(fileName2.toStdString());
cv::resize(srcMat, srcMat, cv::Size(width, height));
cv::resize(srcMat3, srcMat3, cv::Size(width, height)); cv::String windowName = _windowTitle.toStdString();
cvui::init(windowName); cv::Mat windowMat = cv::Mat(cv::Size(srcMat.cols * 2, srcMat.rows * 3),
srcMat.type()); cv::Ptr<cv::xfeatures2d::SIFT> _pSift = cv::xfeatures2d::SiftFeatureDetector::create();
cv::Ptr<cv::xfeatures2d::SURF> _pSurf = cv::xfeatures2d::SurfFeatureDetector::create(); cv::Ptr<cv::Feature2D> _pFeature2D; int type = 0;
int k1x = 0;
int k1y = 0;
int k2x = 100;
int k2y = 0;
int k3x = 100;
int k3y = 100;
int k4x = 0;
int k4y = 100; // 定义匹配器
cv::Ptr<cv::FlannBasedMatcher> pFlannBasedMatcher = cv::FlannBasedMatcher::create();
// 定义结果存放
std::vector<cv::DMatch> listDMatch;
// 存储特征点检测器检测特征后的描述字
cv::Mat descriptor1;
cv::Mat descriptor2; bool moveFlag = true; // 移动的标志,不用每次都匹配
windowMat = cv::Scalar(0, 0, 0);
while(true)
{
cv::Mat mat;
{
std::vector<cv::KeyPoint> keyPoints1;
std::vector<cv::KeyPoint> keyPoints2; int typeOld = type;
int k1xOld = k1x;
int k1yOld = k1y;
int k2xOld = k2x;
int k2yOld = k2y;
int k3xOld = k3x;
int k3yOld = k3y;
int k4xOld = k4x;
int k4yOld = k4y; mat = windowMat(cv::Range(srcMat.rows * 0, srcMat.rows * 1),
cv::Range(srcMat.cols * 0, srcMat.cols * 1));
mat = cv::Scalar(0); cvui::trackbar(windowMat, 0 + width * 0, 0 + height * 0, 165, &type, 0, 1);
cv::String str;
switch(type)
{
case 0:
str = "sift";
_pFeature2D = _pSift;
break;
case 1:
str = "surf";
_pFeature2D = _pSurf;
break;
default:
break;
}
cvui::printf(windowMat, width / 2 + width * 0, 20 + height * 0, str.c_str()); cvui::printf(windowMat, 0 + width * 0, 60 + height * 0, "k1x");
cvui::trackbar(windowMat, 0 + width * 0, 70 + height * 0, 165, &k1x, 0, 100);
cvui::printf(windowMat, 0 + width * 0, 120 + height * 0, "k1y");
cvui::trackbar(windowMat, 0 + width * 0, 130 + height * 0, 165, &k1y, 0, 100); cvui::printf(windowMat, width / 2 + width * 0, 60 + height * 0, "k2x");
cvui::trackbar(windowMat, width / 2 + width * 0, 70 + height * 0, 165, &k2x, 0, 100);
cvui::printf(windowMat, width / 2 + width * 0, 120 + height * 0, "k2y");
cvui::trackbar(windowMat, width / 2 + width * 0, 130 + height * 0, 165, &k2y, 0, 100); cvui::printf(windowMat, 0 + width * 0, 30 + height * 0 + height / 2, "k3x");
cvui::trackbar(windowMat, 0 + width * 0, 40 + height * 0 + height / 2, 165, &k3x, 0, 100);
cvui::printf(windowMat, 0 + width * 0, 90 + height * 0 + height / 2, "k3y");
cvui::trackbar(windowMat, 0 + width * 0, 100 + height * 0 + height / 2, 165, &k3y, 0, 100); cvui::printf(windowMat, width / 2 + width * 0, 30 + height * 0 + height / 2, "k4x");
cvui::trackbar(windowMat, width / 2 + width * 0, 40 + height * 0 + height / 2, 165, &k4x, 0, 100);
cvui::printf(windowMat, width / 2 + width * 0, 90 + height * 0 + height / 2, "k4y");
cvui::trackbar(windowMat, width / 2 + width * 0, 100 + height * 0 + height / 2, 165, &k4y, 0, 100); if( k1xOld != k1x || k1yOld != k1y
|| k2xOld != k2x || k2yOld != k2y
|| k3xOld != k3x || k3yOld != k3y
|| k4xOld != k4x || k4yOld != k4y
|| typeOld != type)
{
moveFlag = true;
} std::vector<cv::Point2f> srcPoints;
std::vector<cv::Point2f> dstPoints; srcPoints.push_back(cv::Point2f(0.0f, 0.0f));
srcPoints.push_back(cv::Point2f(srcMat.cols - 1, 0.0f));
srcPoints.push_back(cv::Point2f(srcMat.cols - 1, srcMat.rows - 1));
srcPoints.push_back(cv::Point2f(0.0f, srcMat.rows - 1)); dstPoints.push_back(cv::Point2f(srcMat.cols * k1x / 100.0f, srcMat.rows * k1y / 100.0f));
dstPoints.push_back(cv::Point2f(srcMat.cols * k2x / 100.0f, srcMat.rows * k2y / 100.0f));
dstPoints.push_back(cv::Point2f(srcMat.cols * k3x / 100.0f, srcMat.rows * k3y / 100.0f));
dstPoints.push_back(cv::Point2f(srcMat.cols * k4x / 100.0f, srcMat.rows * k4y / 100.0f)); cv::Mat M = cv::getPerspectiveTransform(srcPoints, dstPoints);
cv::Mat srcMat2;
cv::warpPerspective(srcMat3,
srcMat2,
M,
cv::Size(srcMat.cols, srcMat.rows),
cv::INTER_LINEAR,
cv::BORDER_CONSTANT,
cv::Scalar::all(0)); mat = windowMat(cv::Range(srcMat.rows * 0, srcMat.rows * 1),
cv::Range(srcMat.cols * 1, srcMat.cols * 2));
cv::addWeighted(mat, 0.0f, srcMat2, 1.0f, 0.0f, mat); if(moveFlag)
{
moveFlag = false;
//特征点检测
// _pSift->detect(srcMat, keyPoints1);
_pFeature2D->detectAndCompute(srcMat, cv::Mat(), keyPoints1, descriptor1);
//绘制特征点(关键点)
cv::Mat resultShowMat;
cv::drawKeypoints(srcMat,
keyPoints1,
resultShowMat,
cv::Scalar(0, 0, 255),
cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
mat = windowMat(cv::Range(srcMat.rows * 1, srcMat.rows * 2),
cv::Range(srcMat.cols * 0, srcMat.cols * 1));
cv::addWeighted(mat, 0.0f, resultShowMat, 1.0f, 0.0f, mat); //特征点检测
// _pSift->detect(srcMat2, keyPoints2);
_pFeature2D->detectAndCompute(srcMat2, cv::Mat(), keyPoints2, descriptor2);
//绘制特征点(关键点)
cv::Mat resultShowMat2;
cv::drawKeypoints(srcMat2,
keyPoints2,
resultShowMat2,
cv::Scalar(0, 0, 255),
cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
mat = windowMat(cv::Range(srcMat.rows * 1, srcMat.rows * 2),
cv::Range(srcMat.cols * 1, srcMat.cols * 2));
cv::addWeighted(mat, 0.0f, resultShowMat2, 1.0f, 0.0f, mat); // FlannBasedMatcher最近邻匹配
pFlannBasedMatcher->match(descriptor1, descriptor2, listDMatch);
// drawMatch绘制出来,并排显示了,高度一样,宽度累加(因为两个宽度相同,所以是两倍了)
cv::Mat matchesMat;
cv::drawMatches(srcMat,
keyPoints1,
srcMat2,
keyPoints2,
listDMatch,
matchesMat); mat = windowMat(cv::Range(srcMat.rows * 2, srcMat.rows * 3),
cv::Range(srcMat.cols * 0, srcMat.cols * 2));
cv::addWeighted(mat, 0.0f, matchesMat, 1.0f, 0.0f, mat);
}
}
cv::imshow(windowName, windowMat);
// 更新
cvui::update();
// 显示
// esc键退出
if(cv::waitKey(25) == 27)
{
break;
}
}
}
 

工程模板:对应版本号v1.62.0

  对应版本号v1.62.0

 

OpenCV开发笔记(六十八):红胖子8分钟带你使用特征点Flann最邻近差值匹配识别(图文并茂+浅显易懂+程序源码)的更多相关文章

  1. OpenCV开发笔记(六十九):红胖子8分钟带你使用传统方法识别已知物体(图文并茂+浅显易懂+程序源码)

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

  2. OpenCV开发笔记(六十五):红胖子8分钟带你深入了解ORB特征点(图文并茂+浅显易懂+程序源码)

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

  3. OpenCV开发笔记(六十四):红胖子8分钟带你深入了解SURF特征点(图文并茂+浅显易懂+程序源码)

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

  4. OpenCV开发笔记(五十六):红胖子8分钟带你深入了解多种图形拟合逼近轮廓(图文并茂+浅显易懂+程序源码)

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

  5. OpenCV开发笔记(五十五):红胖子8分钟带你深入了解Haar、LBP特征以及级联分类器识别过程(图文并茂+浅显易懂+程序源码)

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

  6. OpenCV开发笔记(七十一):红胖子8分钟带你深入级联分类器训练

    前言   红胖子,来也!  做图像处理,经常头痛的是明明分离出来了(非颜色的),分为几块区域,那怎么知道这几块区域到底哪一块是我们需要的,那么这部分就涉及到需要识别了.  识别可以自己写模板匹配.特征 ...

  7. OpenCV开发笔记(七十二):红胖子8分钟带你使用opencv+dnn+tensorFlow识别物体

    前言   级联分类器的效果并不是很好,准确度相对深度学习较低,本章使用opencv通过tensorflow深度学习,检测已有模型的分类.   Demo       可以猜测,1其实是人,18序号类是狗 ...

  8. OpenCV开发笔记(七十三):红胖子8分钟带你使用opencv+dnn+yolov3识别物体

      前言   级联分类器的效果并不是很好,准确度相对深度学习较低,上一章节使用了dnn中的tensorflow,本章使用yolov3模型,识别出具体的分类.   Demo   320x320,置信度0 ...

  9. 安卓开发笔记(十八):实现button按钮事件的三种方法

    Android开发中有三种主要的方式用于设置View的点击事件,1.创建内部类:2.主类中实现OnClickListener接口:3.使用匿名内部类.这三种方式都用到了OnClickListener接 ...

  10. .net开发笔记(十八) winform中的等待框

    winform中很多任务是需要在后台线程(或类似)中完成的,也就是说,经常容易涉及到UI界面与后台工作线程之间的交互.比如UI界面控制后台工作的执行(启动.暂停.停止等),后台工作进度在UI界面上的显 ...

随机推荐

  1. [转帖]金仓数据库KingbaseES V8R6 中unlogged表

    KingbaseESV8R6有一种表称为unlogged,在该表新建的索引也属于unlogged.和普通表的区别是,对该表进行DML操作时候不将该表的变更记录变更写入到wal文件中.在数据库异常关机或 ...

  2. 【转帖】3.JVM内存结构概述

    目录 1.JVM内存结构 1.JVM内存结构 在JVM系列的第一篇文章中已经给出了JVM内存结构的简图,下面是JVM内存结构更加详细的图. 同样,JVM的内存结构可以分为上中下3层. 上层主要是类加载 ...

  3. Nginx与Tomcat作为前端服务器的性能比较

    Nginx与Tomcat作为前端服务器的性能比较 摘要 最近总遇到使用tomcat还是使用nginx进行前端文件访问的争论 想着出差周末在酒店, 可以自己进行一下简单的测试. 希望能够对未来的工作进行 ...

  4. OpenEuler切换内核的方法-bcc学习后续

    OpenEuler切换内核的方法 摘要 昨天使用OpenEuler 22.03 LTS学习bcc但是一直不行. 没办法切换到CentOS8 还有 Anolis 8 很容易就可以直接还是用了 yum i ...

  5. TypeScript中Never类型和类型断言

    Never 类型 never类型表示:那些永不存在的值的类型. 例如:never类型是那些总是会[抛出异常]或根本就[不会有返回值的函数表达式]或[箭头函数表达式的返回值类型] never类型是任何类 ...

  6. export default 和 export 这两种方式导出的区别

    参考地址 https://blog.csdn.net/sleepwalker_1992/article/details/81461543 使用export向外暴露的成员,只能使用{ }的形式来接收,这 ...

  7. windwos10-11打开任意文件弹出警告

    如下打开exe或者视频.图片都弹出警告 解决方案输入快捷键win+s换出搜索框 输入Internet 选项 进入安全选项点击自定义级别 找到,加载应用程序和不安全文件 勾选启用(不安全) 然后确定-在 ...

  8. 使用Git 命令行拉取、提交、推送、合并 代码

    1.拉取 1.1.拉取该分支的最新代码(远程分支是与当前分支相同) git pull origin updateCode 1.2.拉取最新代码(远程分支是与当前分支不相同,但要合并) git pull ...

  9. C#使用命令行打开diskpart修改盘符

    参考链接: https://www.cnblogs.com/k98091518/p/6019296.html https://learn.microsoft.com/zh-cn/windows-ser ...

  10. 强化学习从基础到进阶-常见问题和面试必知必答[5]::梯度策略、添加基线(baseline)、优势函数、动作分配合适的分数(credit)

    强化学习从基础到进阶-常见问题和面试必知必答[5]::梯度策略.添加基线(baseline).优势函数.动作分配合适的分数(credit) 1.核心词汇 策略(policy):在每一个演员中会有对应的 ...