Linemod;理解

Linemod 代码笔记

2019年03月11日 16:18:30 haithink 阅读数:197

最近了解到 Linemod 这个模板匹配算法,印象不错
准备仔细学习一下,先做点代码笔记,免得后面不好回顾
目前的笔记基本上把 核心流程都分析得比较清楚了,除了一些阈值的选取

opencv 的contrib 模块有这个算法的实现

我看的代码来自这里
https://github.com/meiqua/shape_based_matching

先大概记录下 代码思路:
分两个阶段, train 和 test

Train

Train 中 , shapeInfo_producer 负责用来对 模板进行 各种旋转和尺度缩放,
shapes.src_of 可以根据旋转和尺度 生成变换后的 模板

对每一个模板 执行 detector.addTemplate 操作,

最后调用 shapes.save_infos 和 detector.writeClasses 这两个保存训练 结果。保存的信息用于 后续的匹配中。

首先构造
line2Dup::Detector detector(20, { 4, 8 });
第一个参数为 特征点个数 , 第二个参数是一个 vector, 每个元素代表每一层的T
构建 this->modality 对象

shape_based_matching::shapeInfo_producer shapes(padded_img, padded_mask);
两个入参都是 图像,第一个是用 输入图像构建,填充像素为0, 第二个用输入图像大小的大小构建掩码图像,掩码为1, 填充像素为0

然后填充shapes.scale_range、 shapes.scale_step、 shapes.angle_range 、shapes.angle_step
这四个是对模板图像进行 尺度缩放 和 旋转的 量

shapes.produce_infos();
主要是用 尺度范围 和 旋转范围 的组合 构建 std::vector infos
然后 就是 遍历 shapes.infos
执行
detector.addTemplate(shapes.src_of(info), class_id, shapes.mask_of(info));

shapes.src_of(info) 产生变换后的图像
class_id 是一个固定的字符串
shapes.mask_of(info) 返回 shapes.src_of(info) 产生变换后的图像是否大于0的 掩码图像
addTemplate 是 核心函数,主要作用为 提取模板图像的特征点,即梯度较强的点,得到 这些点的坐标和梯度方向值。

接着调用两个函数

  1. shapes.save_infos 保存 的信息是 每张图片是 原始图像经过哪种旋转和缩放得到的
  2. detector.writeClasses 则 保存 每个模板 的信息,包括cropTemplates(tp) 后的高宽和坐标、 特征点坐标信息,特征点的label 就是梯度方向

=============================================================================

Detector::addTemplate

构建模板的流程图
1 modality->process(source, object_mask)

这个是 直接构造一个 ColorGradientPyramid 对象,返回其指针
ColorGradientPyramid 构造函数中 update(); ,内部是
quantizedOrientations(src, magnitude, angle, weak_threshold);
先做 高斯模糊, 然后 在水平和垂直方向 调用 Sobel,
调用 phase 计算梯度方向,

调用 hysteresisGradient, 主要输出就是 quantized_angle
过程为: 先把 连续的梯度方向 划分为16个区间, 然后量化为8个方向
quant_r[c] &= 7; 这个代码还没看明白,这 相当于把一个整数 对8 求模
这么做没问题应该是因为 认为 180度和190度之间的方向 和0度到10度之间的 方向是一个方向。

然后就是 对梯度幅值 超过一定阈值的 像素点 的 3*3 邻域 求 梯度直方图
投票数 超过 阈值的 方向 作为最终的 量化方向

至此, modality->process 完成
返回 一个 Ptr qp

然后 开始遍历金字塔每一层, 如果不是最底层, 那么 qp 降采样,并且 做梯度量化操作, 即调用上面的 update()

然后qp->extractTemplate(tp[l])

这一步是 提取第 L 层特征点, 保存在 tp[l]中。 细节参考后文
说明: tp是个vector, 每个 元素都是一个模板,对应金字塔某一层提取出来的特征点

每一层都遍历完后, cropTemplates(tp)

这个函数 先 遍历每一个 模板, 找出特征点最大最小坐标,注意,高层次的金字塔图像的坐标会进行放大(根据层次)
得到 4个最小、最大坐标。 注意: 是所有层共用信息

然后再一次遍历每个模板, 调整 templ.width ,templ.height ,templ.tl_x,templ.tl_y
然后用 templ.tl_x,templ.tl_y 修正了特征点坐标,
TODO: 这就 有点麻烦了, 修正后的 坐标肯定和 原始图像 对应不上了啊!

返回 Rect(min_x, min_y, max_x - min_x, max_y - min_y)
但 外部并未接收 这个返回值

addTemplate 的最后 template_pyramids.push_back(tp);
ColorGradientPyramid::extractTemplate(Template &templ)
函数输出应该是 templ.features, 即提取出 特征点
先对 mask 进行 腐蚀,

Magnitude 是 之前 quantizedOrientations 中计算出的梯度幅值(梯度平方和)

对 Magnitude 搞一个 遍历,
如果对每个像素,如果 magnitude_valid 值 大于0
如果其邻域内 有像素的梯度幅值超过它,
那么 is_max 为 false, 如果遍历完后 , is_max 为true, 那么 所有 邻域像素对应 magnitude_valid 值 置为0

通过上述检验的点 , 如果 幅值超过阈值, 且 方向不为 0, 进入 candidates
(注意 opencv在这里的实现方法, 先设置了一个 score = 0, 如果没通过上述检验, 该值依然为0, 这种实现方法好吗?)

遍历完后,如果 candidates 个数低于阈值, 返回 false, 此次 抽取失败。。。

对 candidates 按照 score 进行一次稳定排序
selectScatteredFeatures 最后 从 candidates 中 选取一些 散得 比较开的点, 这里while 循环写得还比较有技巧, 如果遍历完一轮, 数量不够,那么 降低 距离阈值, 再选!
和 orb-slam或者说opencv 里面 ORBextractor 提取特征点 那个 四叉树的方法谁优谁劣?

选取的特征点保存 在 templ.features 中

Test

先读取 train 阶段保存的两个信息文件
detector.readClasses(ids, prefix + “myCase/%s_templ.yaml”);
读取 每个模板 的信息,包括cropTemplates(tp) 后的高宽和坐标、 特征点坐标信息,特征点的label 就是梯度方向。
构建出: class_templates

shape_based_matching::shapeInfo_producer::load_infos
每张图片是 原始图像经过哪种旋转和缩放得到的

对测试图像 进行一下调整, 使得高宽都是 16 的倍数

auto matches = detector.match(img, 90, ids);
90 是阈值, ids 是 训练时 指定的id字符串 test

然后 modality->process(source, mask),
这个调用在前面已经介绍过了,会 构造一个 ColorGradientPyramid 对象,对source图像计算量化后的梯度信息

然后遍历 金字塔, construct response map
先不看 具体的函数调用实现过层, 从函数名字 和 注释来看, 这就是 论文当中第三节讲的东西, 包括 方向扩散spread、 梯度响应计算computeResponseMaps、 线性化存储linearize。 最终存在在 LinearMemoryPyramid 结构里面。

遍历class_ids, 从 class_templates获取 对应 std::vector
matchClass(lm_pyramid, sizes, threshold, matches, it->first, it->second);
这个函数完成整个匹配过程

=============================================================================

Detector::matchClass

遍历template_pyramids, 提取出 每个 Template,
调用 similarity, 计算相似性, similarity中, 核心调用是 accessLinearMemory,
这里面第一行代码
const Mat &memory_grid = linear_memories[f.label];
很关键,这是根据模板中特征点 来 定位 response map 相应的数据
定位到以后,然后 就是 SIMD 指令 来 累加数据了!

static void spread(const Mat &src, Mat &dst, int T)

这个地方实现的是 论文3.3 节的所谓 梯度方向展开
所要实现的功能很好理解, 即把每个像素及其邻域的离散化的梯度方向进行 或运算。
OpenCV 这里再一次展现了实现技巧, 最直观的方法是 每次遍历一个像素时,取出其所有邻域内的像素的梯度方向值,然后做一个或运算, 这样做 内存访问性能较低, 因为图像的下一行和上一行 距离较大, 很可能缓存命中失败。

OpenCV 的做法是: 每次遍历时, 只做整个邻域内某个特定位置的像素梯度方向值 的 或运算,这个地方说的邻域包含像素自身,即邻域中心。 所以总共循环 T*T次。 T 为邻域直径。
这样做, 内存访问友好,并且方便使用 SSE指令进行优化, 因为连续参与运算的数据在内存中是连续的!
梯度方向在邻域中的传播

static void computeResponseMaps

(const Mat &src, std::vector &response_maps)

实现论文3.4节 响应图的计算
这个地方 把论文中的相似度 也给离散化了。
并且事先计算了 某个方向 和 某组方向的余弦值的最大值,并且离散化, (或者称为根据余弦值 实行打分制) 存储到一个数组SIMILARITY_LUT 中,即查找表。 这个查找表中针对某个方向的值有32个元素, 总共8个方向, 所以有 256个元素。 32个元素中 , 又分为两组, 前16个是8个方向中前4个方向的各种组合 与 当前32个元素针对的方向 的余弦值的最大值对应的得分。

这个数组, 上交这个学生 对原来的值 进行了修改: 1,2–>0 3–>1
为什么这么改?
https://zhuanlan.zhihu.com/p/35683990
这篇文章给出了 修改的解释

论文3.4 节 也给出了 这个查找表的计算啊!

疑问待定: n0 为8的时候, 针对某个方向的查找表元素 按照论文实际上应该是有 2的8次方, 即 256种情况。 这个地方是不想搞出那么大一个数组, 所以, 把8位分拆成两组, 每组只需16个元素, 然后再进行一次比较,拿到最终的最大值? 为啥不直接构建大小为 256*8的查找表? 这样可以省掉一次 max的运算。
看了下 _mm_shuffle_epi8 的介绍
SSE指令
这个地方 index 只用低4位进行运算, 也就是只支持 4个bit作为索引值,
如果只能用这个指令,的确 只能把 8位拆分成两组4位,再max
不知道有没有 能直接用8位作为 所以索引的SSE指令
查找表,即预先计算好梯度方向之间的差异

static void linearize

(const Mat &response_map, Mat &linearized, int T)

这个是改变存储方式,先行后列, 间隔T 读取,然后写入。没有比较复杂和特殊的处理。
改变存储方式

similarity_64

这个函数计算 模板和 输入图像的 相似性, 即论文中的 similarity map
计算相似性的时候, 并不是 把 模板上的每个像素都和 输入图像上对应的像素 一一对应,然后进行 某种计算, 这和 NCC, SSD 这些方法的做法不一样!一开始受这些方法先入为主的影响,导致论文里的Fig 7 以及代码中的操作

实际上, 只比较模板上提取的特征点, 以及 模板 覆盖在 输入图像上某个位置时, 这些模板特征点对应到 输入图像上的像素点 之间的梯度差异。

意识到这点以后,就比较好理解代码了。 因为模板需要在输入图像上进行 滑动,所以产生了 similarity map。 每次滑动,模板和输入图像产生一个 相似度。 模板在 水平和垂直方向进行滑动, 所以 产生一个 二维的相似度矩阵。这个矩阵的宽 自然就是 输入图像的宽减去模板的宽, 也就是代码中的span_x。 高的情况类似。

代码当中用 template_positions 表示 模板的当前滑动位置。

计算similarity map最直观的方法是:对每个模板位置, 找出所有特征点在输入图像上对应的像素, 计算所有梯度方向的相似性,累加。 然后 处理下一个模板位置。

但代码中的做法是: 对每个特征点,计算出所有模板位置上 这个特征点 和 所有输入图像上对应点的 梯度方向相似性,保存到similarity map中。 然后 计算下一个特征点的相似性,累加到 similarity map中。

整个算法中 不是第一次使用这种思路了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/251978.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

手眼标定

Eye-in-hand和Eye-to-hand问题求解和实验 2018年12月07日 00:00:40 百川木易 阅读数 3018 2018/12/5 By Yang Yang(yangyangipp.ac.cn) 本文所有源码和仿真场景文件全部公开,点击Gitee仓库链接。 文章目录 问题描述Eye-in-hand问题求解公式…

RNN总结

RNN既可以表述为循环神 经网络(recurrent neural network),也可以表述为递归神经网络(recursive neural network),前者一般用于处理以时间序列为输入的问题(比如把一个句子看成词组成的序列&…

linux硬链接与软链接

Linux 系统中有软链接和硬链接两种特殊的“文件”。 软链接可以看作是Windows中的快捷方式,可以让你快速链接到目标档案或目录。 硬链接则透过文件系统的inode来产生新档名,而不是产生新档案。 创建方法都很简单: 软链接(符号链接…

企业级区块链现状研究报告:小企业的投资总额是大企业的28倍

根据企业级区块链现状研究报告表明,当前企业采用区块链技术的势头正在逐步增强。参与该报告的企业表示,区块链投资今年共增长了 62% ,预计到 2025 年区块链将成为主流技术。其中,有 28% 的企业正在积极开展区块链发展计划。现在看…

特征匹配

Python 使用Opencv实现图像特征检测与匹配 2018-06-13 11:36:58 Xy-Huang 阅读数 19203更多 分类专栏: Python 人工智能 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接…

bzoj 1015 并查集

代码&#xff1a; //这题可以反着想&#xff0c;把要去掉的点倒着处理变成往图中一个一个的加点&#xff0c;然后用并查集处理联通快就好了。 #include<iostream> #include<cstdio> #include<cstring> #include<vector> using namespace std; const in…

画极线

OpenCV学习日记5 2017-05-27 10:44:35 1000sprites 阅读数 2339更多 分类专栏&#xff1a; 计算机视觉 版权声明&#xff1a;本文为博主原创文章&#xff0c;遵循 CC 4.0 BY-SA 版权协议&#xff0c;转载请附上原文出处链接和本声明。 本文链接&#xff1a;https://blog.cs…

最近很火的MySQL:抛开复杂的架构设计,MySQL优化思想基本都在这

优化一览图 优化 笔者将优化分为了两大类&#xff1a;软优化和硬优化。软优化一般是操作数据库即可&#xff1b;而硬优化则是操作服务器硬件及参数设置。 1、软优化 1&#xff09;查询语句优化 首先我们可以用EXPLAIN或DESCRIBE(简写:DESC)命令分析一条查询语句的执行信息。 例…

《JAVA与模式》之桥梁模式

在阎宏博士的《JAVA与模式》一书中开头是这样描述桥梁&#xff08;Bridge&#xff09;模式的&#xff1a; 桥梁模式是对象的结构模式。又称为柄体(Handle and Body)模式或接口(Interface)模式。桥梁模式的用意是“将抽象化(Abstraction)与实现化(Implementation)脱耦&#xff0…

Java基础教程:面向对象编程[2]

Java基础教程&#xff1a;面向对象编程[2] 内容大纲 访问修饰符 四种访问修饰符 Java中&#xff0c;可以使用访问控制符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限。 default (即缺省&#xff0c;什么也不写&#xff09;: 在同一包内可见&#xff…

【javascript】异步编年史,从“纯回调”到Promise

异步和分块——程序的分块执行 一开始学习javascript的时候&#xff0c; 我对异步的概念一脸懵逼&#xff0c; 因为当时百度了很多文章&#xff0c;但很多各种文章不负责任的把笼统的描述混杂在一起&#xff0c;让我对这个 JS中的重要概念难以理解&#xff0c; “异步是非阻塞的…

if _name_ == _main_

1.作用 py文件有2种使用方法&#xff0c;第1是自己本脚本自己独立执行&#xff1b;第2是被import到其他文件脚本中执行. if _name_ " _main_" 该语句控制其他下一步的脚本是否执行。如果是自己本脚本独立执行&#xff0c;那就运行该if条件下的脚本&#xff1b;如果…

LLVM完整参考安装

文章目录 一、直接下载编译好的,见图片命令二、下载源代码自己编译安装 下面提供下载并mv完全的文件包三、安装LLVM编译器一、直接下载编译好的,见图片命令 这里使用llvm官网编译好的包, 直接解压即可用LLVM下载官网点击这里下载llvm-6.0.1 下载完成后解压tar -vxf clangllv…

微软正式释出基于 Chromium 的 Edge 预览版本

百度智能云域名服务&#xff0c;.com新用户首购仅需25元 微软基于 Chromium 的全新版本 Edge 一直吸引着开发者与用户的目光&#xff0c;当地时间 8 日&#xff0c;官方终于释出了第一个 Dev 和 Canary 频道构建版本。 Dev 与 Canary build 都是开发者预览版&#xff0c;同属…

下载和安装R、RStudio !

现如今&#xff0c;R语言是统计领域广泛使用的工具&#xff0c;是属于GNU系统的一个自由、免费、源代码开放的软件&#xff0c;是用于统计计算和统计绘图的优秀工具。而RStudio是R的集成开发环境&#xff0c;用它进行R编程的学习和实践会更加轻松和方便。下面就教大家如何下载并…

豆瓣首页话题输入框的实现

在做问答的时候&#xff0c;遇到一个需求&#xff0c;用户的问题需要限制字数&#xff0c;不仅显示计算的超出字数&#xff0c;还需在超出的内容上加一些提醒的效果&#xff0c;例如豆瓣首页的话题输入框&#xff0c;抽时间研究了下&#xff0c;需要考虑下面几个问题&#xff1…

pytorch 吸烟检测yolov5s

YOLOV5s 吸烟目标检测 参考学习 文章目录 本原创项目长期更新&#xff0c;旨在完成校园异常行为实时精检测&#xff0c;作到集成N次开发优化&#xff08;不止局限于调包&#xff09;为止&#xff0c;近期将不断更新如下模型数据标注文件教程。关注博主&#xff0c;Star 一下g…

JQuery的ajax函数执行失败,alert函数弹框一闪而过

先查看<form>标签是否有action属性&#xff0c;如果没有&#xff0c;并且最后<button>标签的type属性为submit‘时&#xff0c;默认提交位置就是当前页面 如果在页面右键检查&#xff0c;点击网络&#xff0c;会在开头发现这样的post包&#xff1a; 在右侧消息头处…

postgresql 不同数据库不同模式下的数据迁移

编写不容易,转载请注明出处谢谢, 数据迁移 因为之前爬虫的时候&#xff0c;一部分数据并没有上传到服务器&#xff0c;在本地。本来用的就是postgresql&#xff0c;也没用多久&#xff0c;数据迁移的时候&#xff0c;也遇到了很多问题&#xff0c;第一次使pg_dump xx > file…

Oracle中主键自增长

最近在学习Oracle和MySql&#xff0c;MySql有自动配置主键自增长auto_increment&#xff0c;这样在输入数据的时候可以不考虑主键的添加&#xff0c;方便对数据库的操作。 在Oracle中设置自增长首先用到sequence序列&#xff1b; 以创建学生表为例&#xff1a; create table St…