前言

  红胖子,来也!
  特征点、匹配,那么如何使用特征点和匹配来识别已有的物体,也就剩最关键的最后一步:寻找已知的物体了。

 

Demo

  
  
  
  

 

寻找已知物体

本篇章使用sift/surf特征点

sift特征点

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

surf特征点

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

本篇章使用暴力、最邻近差值匹配

暴力匹配

  最佳特征匹配总是尝试所有可能的匹配,从而使得它总能够找到最佳匹配,这也是BruteForce(暴力法)的原始含义,涉及到的类为BFMatcher类。
  《OpenCV开发笔记(六十七):红胖子8分钟带你深入了解特征点暴力匹配(图文并茂+浅显易懂+程序源码)

最近邻差值匹配

  一种近似法,算法更快但是找到的是最近邻近似匹配,所以当我们需要找到一个相对好的匹配但是不需要最佳匹配的时候往往使用FlannBasedMatcher。
  《OpenCV开发笔记(六十八):红胖子8分钟带你使用特征点Flann最邻近差值匹配识别(图文并茂+浅显易懂+程序源码)

概述

  对已知物体:过滤、去噪后、提取已知物体的特征点;
  对场景:过滤、去噪后、提取场景的特征点;
  对已知物体特征点集合和场景中的特征点集合去匹配,计算投影矩阵;
  若成功计算变换矩阵就表示识别到物体;
  通过原始的四个点位置进行变换矩阵计算,即可得到场景中的已知物体的四个顶点,该四个顶点连接起来就是已知物体的位置。

特征点集合计算变换矩阵函数原型

Mat findHomography(InputArray srcPoints,
InputArray dstPoints,
int method = 0,
double ransacReprojThreshold = 3,
OutputArray mask=noArray(),
const int maxIters = 2000,
const double confidence = 0.995);
  • 参数一:InputArray类型的srcPoints,源平面上的对应点,可以是CV_32FC2的矩阵类型或者vector;
  • 参数二:InputArray类型的dstPoints;目标平面上的对应点 , 可 以 是
    CV 32FC2 的矩阵类型或者 vector;
  • 参数三:int类型的method,用于计算单应矩阵的方法,如下图:
      
  • 参数四:double类型的ransacReprojThreshold,最大允许重投影错误将点对视为内联线(仅用于RANSAC和RHO方法);
  • 参数五:OutputArray类型的mask,由鲁棒方法(RANSAC或LMEDS)设置的可选输出掩码。注意输入掩码值被忽略。;
  • 参数六:const int类型的maxIters,RANSAC迭代的最大数量。;
  • 参数七:const double类型的confidence,置信水平,介于0和1之间;

矩阵变换函数原型

void perspectiveTransform( InputArray src,
InputArray dst,
InputArray m);
  • 参数一:InputArray类型的src,输入两通道或三通道浮点数组;每个元素是要转换的二维/三维向量。
  • 参数二:InputArray类型的dst,与src大小和类型相同的输出数组;
  • 参数三:InputArray类型的h,3x3或4x4浮点转换矩阵。
 

Demo源码

void OpenCVManager::testFindKnownObject()
{
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(450, 10, 10, true, true); cv::Ptr<cv::Feature2D> _pFeature2D;
cv::Ptr<cv::DescriptorMatcher> _pDescriptorMatcher; int type = 0;
int findType = 0;
int k1x = 25;
int k1y = 25;
int k2x = 75;
int k2y = 25;
int k3x = 75;
int k3y = 75;
int k4x = 25;
int k4y = 75; // 定义匹配器
cv::Ptr<cv::FlannBasedMatcher> pFlannBasedMatcher = cv::FlannBasedMatcher::create();
cv::Ptr<cv::BFMatcher> pBFMatcher = cv::BFMatcher::create();
// 定义结果存放
std::vector<cv::DMatch> listDMatch;
// 存储特征点检测器检测特征后的描述字
cv::Mat descriptor1;
cv::Mat descriptor2; bool moveFlag = true; // 移动的标志,不用每次都匹配 std::vector<cv::Point2f> obj_corners(4);
std::vector<cv::Point2f> scene_corners(4); 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 findTypeOld = findType;
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 / 4 + width * 0 - 20, 40 + height * 0, str.c_str()); cvui::trackbar(windowMat, width / 2 + width * 0, 0 + height * 0, 165, &findType, 0, 1);
switch(findType)
{
case 0:
str = "BFMatcher";
_pDescriptorMatcher = pBFMatcher;
break;
case 1:
str = "FlannBasedMatcher";
_pDescriptorMatcher = pFlannBasedMatcher;
break;
default:
break;
}
cvui::printf(windowMat, width / 4 * 3 + width * 0 - 20, 40 + 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 || findTypeOld != findType)
{
typeOld = type;
findTypeOld = findType;
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最近邻匹配
_pDescriptorMatcher->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); // 定义两个局部变量
std::vector<cv::Point2f> obj;
std::vector<cv::Point2f> scene;
// 从匹配成功的匹配对中获取关键点
for(int index = 0; index < listDMatch.size(); index++)
{
obj.push_back(keyPoints1[listDMatch[index].queryIdx].pt);
scene.push_back(keyPoints2[listDMatch[index].trainIdx].pt);
}
// 计算透视变换
cv::Mat H = cv::findHomography(obj, scene, CV_RANSAC);
// 从待测图片中获取角点 obj_corners[0] = cv::Point2f(0,0);
obj_corners[1] = cv::Point2f(srcMat.cols,0);
obj_corners[2] = cv::Point2f(srcMat.cols, srcMat.rows);
obj_corners[3] = cv::Point2f(0, srcMat.rows); // 进行透视变换
cv::perspectiveTransform(obj_corners, scene_corners, H); }
// 绘制出角点之间的线
qDebug() << __FILE__ << __LINE__
<< scene_corners[0].x
<< scene_corners[0].y
<< scene_corners[1].x
<< scene_corners[1].y;
cv::line(windowMat,
scene_corners[0] + cv::Point2f(srcMat.cols * 1, srcMat.rows * 0),
scene_corners[1] + cv::Point2f(srcMat.cols * 1, srcMat.rows * 0),
cv::Scalar(0, 0, 255), 2);
cv::line(windowMat,
scene_corners[1] + cv::Point2f(srcMat.cols * 1, srcMat.rows * 0),
scene_corners[2] + cv::Point2f(srcMat.cols * 1, srcMat.rows * 0),
cv::Scalar(0, 0, 255), 2);
cv::line(windowMat,
scene_corners[2] + cv::Point2f(srcMat.cols * 1, srcMat.rows * 0),
scene_corners[3] + cv::Point2f(srcMat.cols * 1, srcMat.rows * 0),
cv::Scalar(0, 0, 255), 2);
cv::line(windowMat,
scene_corners[3] + cv::Point2f(srcMat.cols * 1, srcMat.rows * 0),
scene_corners[0] + cv::Point2f(srcMat.cols * 1, srcMat.rows * 0),
cv::Scalar(0, 0, 255), 2);
}
cv::imshow(windowName, windowMat);
// 更新
cvui::update();
// 显示
// esc键退出
if(cv::waitKey(25) == 27)
{
break;
}
}
}
 

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

  对应版本号v1.63.0

 

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

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

    若该文为原创文章,未经允许不得转载原博主博客地址: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分钟带你深入了解多种图形拟合逼近轮廓(图文并茂+浅显易懂+程序源码)

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

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

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

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

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

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

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

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

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

  8. .Net开发笔记(十九) 创建一个可以可视化设计的对象

    阅读本篇博客之前需要了解VS窗体设计器的工作原理,详细可参见本系列博客(十).(十一).(十二).必须需要知道的一条结论就是:处于窗体设计器(Form Designer)中的任何组件(包含控件,下同) ...

  9. Java开发笔记(十九)规律变化的for循环

    前面介绍while循环时,有个名叫year的整型变量频繁出现,并且它是控制循环进出的关键要素.不管哪一种while写法,都存在三处与year有关的操作,分别是“year = 0”.“year<l ...

随机推荐

  1. 如果你每次面试前都要去背一篇Spring中Bean的生命周期,请看完这篇文章

    前言 当你准备去复习Spring中Bean的生命周期的时候,这个时候你开始上网找资料,很大概率会看到下面这张图: 先不论这张图上是否全面,但是就说这张图吧,你是不是背了又忘,忘了又背? 究其原因在于, ...

  2. Ubuntu18.04安装Docker并部署(编译、发布、构建镜像)Asp.NetCore项目全过程笔记

      环境准备:阿里云Ubuntu18.04 全新安装   一.安装Docker 1.删除旧版本并更新包索引: sudo apt-get remove docker docker-engine dock ...

  3. java中实现无限层级的树形结构

    本文展示了两个实现方法的代码.两个代码的实现方法不同,代码2更为简单. 先看一下最后实现的结果: 最后结果-json 代码1: 实现过程: 1.传入一段json字符串 2.将字符串转换成对象存入节点列 ...

  4. C++语法小记---同名覆盖

    同名覆盖 子类中的同名成员会覆盖父类中的同名成员,但是在内存中仍然存在,只是无法直接访问,需要加上域名才能访问 子类中的同名函数会覆盖父类中的函数,复写是同名覆盖的一种特殊情况,只要不是多态场景,复写 ...

  5. vs coed的使用(二) 如何运行cpp文件(不用插件比如code runner)

    一.前提 1.配置好编译运行的环境,比如系统变量.vs code的settings.json 2.检查配置好的环境没有问题 我配置结果 [环境变量] [系统变量] 确定settings.json里面的 ...

  6. socket链接

    服务端: package com.batch.service.impl; import java.io.BufferedReader; import java.io.BufferedWriter; i ...

  7. django-rest-framework-源码解析003-视图家族和路由(APIView/GenericAPIView/mixins/generics/viewsets)

    视图家族 视图家族在rest_framework源码位置和学习曲线为: rest_framework.views: 基本视图(APIView) rest_framework.generics: 工具视 ...

  8. @Autowired @Qualifier

    spring2.1中允许用户通过@Autowired注解对Bean的属性变量.属性Setter方法以及构造函数进行标注,配合AutowiredAnnotationBeanProcessor完成Bean ...

  9. 一个在raw里面放着数据库文件的网上例子

    https://www.cnblogs.com/yutingliuyl/p/6880103.html

  10. python爬虫常用headers设置

    import random import re def headers(url, use='pc'): pc_agent = [ "Mozilla/5.0 (Macintosh; U; In ...