0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

OpenMV-AGAST算法代码解析

云深之无迹 来源:云深之无迹 2023-08-07 14:19 次阅读

我以前研究过一段时间的OpenMV的源码,当时的功力太浅,看不大懂,现在又重新的翻出来看。

f7804440-3457-11ee-9e74-dac502259ad0.png

先确定代码在哪里,OpenMV是在资源受限的情况下执行视觉算法,所以里面的很多写法都是高效的,被优化过的,这也是我读的一个原因。

f79e2bfe-3457-11ee-9e74-dac502259ad0.png

第一先读第一个文件,是一个角点的快速查找算法。图像基础的操作都被封装在了我现在展示的这个文件里面。

什么是角点?

角点通常被定义为两条边的交点,或者说,角点的局部邻域应该具有两个不同区域的不同方向的边界。角点检测(Corner Detection)是计算机视觉系统中获取图像特征的一种方法,广泛应用于运动检测、图像匹配、视频跟踪、三维重建和目标识别等,也可称为特征点检测。

f7bab99a-3457-11ee-9e74-dac502259ad0.png

这张图可以说是非常的简单明了。

角点的基本算法:选取一个局部窗口,将这个窗口沿着各个方向移动,计算移动前后窗口内像素的差异的多少进而判断窗口对应的区域是否是角点。

f7e3f800-3457-11ee-9e74-dac502259ad0.png

这个就是数学上的描述

f7f9d45e-3457-11ee-9e74-dac502259ad0.png

我们寻找的角点就是去滑动判断。

f847b160-3457-11ee-9e74-dac502259ad0.png

这个就是我说的常见操作被打包的地方

它提供了低级图像处理操作的定义和函数。像素格式,基本图像统计,滤波,边缘检测,形状检测,条码识读等。

它针对嵌入式设备和微控制器设计,侧重效率和代码体积小。

使用定点数代替浮点数。直接对原始像素缓冲区进行操作。

支持常见的图像格式,像BMP,PPM,JPEG等。以及基本的机器视觉功能,比如模板匹配,QR码识读。

有在图像上绘制基本形状,线条,文字等的函数。

在可用时使用DMASIMD指令做硬件加速。不可用时回退到C实现。

使用C语言编写,但可以通过FFI在更高级语言如MicroPythonArduino中使用。

OpenMV开源开发,作为他们的机器视觉相机模块的一部分。但可以独立使用。

f873aafe-3457-11ee-9e74-dac502259ad0.png

这个是要看的算法的函数

这个算法现在讨论的很少,我就简单的说下:自适应通用角点检测(Adaptive and Generic Accelerated Segment Test,AGAST)算法,该算法是对FAST算法的一种改进主要提升了速度与亮度变化下的鲁棒性,但没有解决尺度不变性。

现在接着看代码。上面的代码里面大体是实现了:

init5_8_pattern() 函数:

初始化像素横竖方向偏移量,用于后续快速访问周围像素。

agast58_detect() 函数:

使用5x5个像素组成的模板匹配算法扫描图像,找到角点。

像素值大于或小于中心点像素值的偏移量编码成一个64位的特征码。

如果匹配了角点模板,记录下角点坐标。

agast58_score() 函数:

使用二分法找到最佳的阈值,进一步提高角点质量。

周围5x5像素值和该阈值进行比较,如果匹配角点模板,说明是角点。

不断调整阈值,找到使得匹配模板的像素数最多的阈值。

nonmax_suppression() 函数:

应用非极大值抑制进一步提炼角点。

抑制掉像素梯度较小的不明显角点。

alloc_keypoint() 函数:

将检测到的角点包装成 keypoints 数据结构。

f8a2f20a-3457-11ee-9e74-dac502259ad0.png

第一次见这种写法

该变量是用于储存图像像素的偏移量,用于快速访问像素周围的像素灰度值。

s_offset0 表示相对于当前像素的左上方像素的偏移量。

具体来说:

s_offset0 表示相对于(x,y)的(x-1, y-1)像素

s_offset1 表示相对于(x,y)的(x-1, y)像素

s_offset2 表示相对于(x,y)的(x, y-1)像素

以此类推

通过预先计算好这些固定的偏移量,就可以通过 指针偏移 的方式,快速获取周围像素的值,而不需要每次都计算坐标关系,从而提高效率。所以,这个 s_offset0 变量就是一个优化手段,用来加速周围像素访问。

f8c39fd2-3457-11ee-9e74-dac502259ad0.png

我们看第一个函数的签名,有个*,这里就要写一下C语言的知识了。

先说这个函数的作用-agast58_detect() 是AGAST算法中用于检测角点的主要函数。

它的主要功能是:

在输入图像img上,使用一个5x5像素模板滑动扫描。

将中心像素与周围像素进行比较,大于或小于阈值b的编码成一个特征码。

如果特征码与角点的模板匹配,则记录该像素为角点候选。

所有检测到的角点候选保存在 corner_t 结构体数组 corners 中。

num_corners 为输出参数,用于返回检测到的角点总数。

roi 参数用于指定只检测图像的某个区域。

其中corner_t结构体包含了每个检测到角点的x,y坐标和score明显性分数。

agast检测依赖于一个经过优化的像素访问顺序以及二值比较来实现高效运算。

在C语言中,函数名前面的*代表该函数返回一个指针类型。

对于agast58_detect这个函数:

返回值的类型是corner_t*,是一个指向corner_t结构体的指针。

这个指针指向一个动态分配的数组,用于存储检测到的所有角点。

加上*的原因:

返回一个指针,函数可以返回一个数组或对象,不仅仅是一个scalar值。

指针访问内存速度快,不需要拷贝整个数组。

函数执行结束后,指针变量还可以被外部代码访问,相当于函数可以修改外部变量。

把返回数组的内存管理交给调用者,函数执行完就可以释放内部内存,不用维护资源。

总结一下:

*表示返回一个指针

可以返回动态数组/对象

提高效率,不拷贝大数组

指针可修改外部变量

内存管理交给调用者

程序的实现里面大量的使用了指针的偏移,基本思想是:

直接通过指针运算获取相邻像素,而不用每次计算坐标。

预先计算好偏移量,例如左上角像素的偏移量是 -1行 -1列。

将这些固定偏移量存储在变量中,比如s_offset0。

在访问像素时,直接基于指针偏移这个固定的值,这样就跳过了坐标计算。

例如,当前指针指向像素 (x, y):

uint8_t *imgPtr = &img[y * width + x];

获取左上角像素,不用偏移:

uint8_t leftUp = img[ (y-1) * width + (x-1) ]; // 需要计算坐标

使用偏移:

int offset0 = -width - 1; // 预先计算偏移量 
uint8_t leftUp = imgPtr[offset0]; // 基于指针偏移

通过指针偏移,避免每次获取相邻像素时重复计算偏移量,这样可以明显减少计算量,从而加速像素访问。

f8e13a10-3457-11ee-9e74-dac502259ad0.png

再看这个函数,这个alloc_keypoint()函数是用于分配和初始化一个关键点结构kp_t的。

它做了以下几件事:

使用xalloc0()在堆上分配一个kp_t结构的内存,并初始化为0。

将传入的x,y坐标及score分数存入kp_t中。

注释里提到必须将描述子descriptor数组初始化为0。这里通过xalloc0()预先设置为0实现。

返回这个kp_t指针。

这样调用者就可以拿到一个堆上分配的、坐标与分数填充了、描述子初始化为0的关键点结构kp_t。

需要注意的是:

必须初始化描述子数组,后续的特征描述算子会填充描述子。

使用xalloc0()而不是malloc,可以自动初始化内存为0。

返回 kp_t 指针,调用者可以进一步访问关键点数据。

综上,这是一个辅助函数,用于根据坐标分数快速创建一个关键点结构.

f8fb83c0-3457-11ee-9e74-dac502259ad0.png

它自己又重写了一次这个malloc的函数,xalloc0()是一种自定义的内存分配函数,与malloc()类似,但是有一些额外的功能:

当size为0时,直接返回NULL,而不报错。这与malloc的行为不同。

使用gc_alloc在堆上分配内存,这是MaixPy特有的 gc 堆内存分配函数。

分配成功后用memset清零内存。这是xalloc0的关键功能之一。

如果分配失败,调用xalloc_fail导致程序异常。

返回清零后的内存指针。

这样使用xalloc0比malloc好在:

SIZE为0时不会错误。

自动清零内存,不需再memset。

与MaixPy的GC堆内存管理兼容。

出错时终止程序,不需要额外判断返回NULL情况。

f92229c6-3457-11ee-9e74-dac502259ad0.png

这个init5_8_pattern()函数是用于初始化图像像素的8方向偏移量,这是AGAST算法的一个优化。

它的作用是:

接收图像的宽度width作为参数。

如果当前宽度与已保存的s_width相同,直接返回,不再初始化,避免重复计算。

如果宽度变了,更新s_width为新宽度。

计算8个方向相对当前像素点的偏移量:

s_offset0 (-1, -1) 左上角

s_offset1 (-1, 0) 正上方

...

s_offset7 (-1, 1) 左下角

偏移量根据图像宽度width调整,即乘以width。

这样,在后续检测角点时,可以直接用这些预计算偏移访问周围像素,不需要每次都计算坐标偏移,加速了像素访问。通过一次初始化,减少重复计算,从而提升检测效率。充分利用了图像具有固定尺寸的特点。

f941928e-3457-11ee-9e74-dac502259ad0.png

这个agast_detect()函数实现了AGAST角点检测的完整流程:

初始化5x5窗口的偏移量init5_8_pattern()

调用agast58_detect()函数进行角点检测,返回检测到的角点数组

对每一个角点调用agast58_score()计算明显性分数

进行非极大值抑制nonmax_suppression(),过滤掉弱角点

将剩下的角点保存在输出的keypoints数组中

释放临时的角点内存fb_free()

所以这个函数将角点检测、评分和滤波三个阶段包装起来,实现了一个完整的AGAST角点检测器。

它接受输入图像,检测参数(threshold),区域等,最终输出经过优化的高质量角点集。

其实还没有完全说完,这个函数很长:

f9807b34-3457-11ee-9e74-dac502259ad0.png

大概是这样的

初始化循环边界 - xsizeB, ysizeB 定义了只在图像有效区域内循环

分配内存 - 使用 fb_alloc 在内存池中分配 corner_t 数组

双重循环 - 外层循环 y 方向,内层循环 x 方向扫描每个像素

像素比较 - 在 homogeneous 和 structured 两个标签下,使用偏移像素比较中心像素,判断是否匹配角点模式

记录角点 - 如果匹配就记录下角点坐标到 corners 数组

返回结果 - 将检测到的角点数量赋值给 num_corners,并返回角点数组

里面的循环做的这个事情比较多。

f99b5b66-3457-11ee-9e74-dac502259ad0.png

同样下头的还有一个函数, agast58_score() 函数主要实现了A-GAST算法中使用二分法搜索最佳阈值的步骤。

主要流程是:

初始化阈值的上下限 bmin、bmax。

不断循环,将当前阈值 b 代入角点模式比较。

如果匹配了角点模式,则把 b 作为新的下限 bmin。

如果不匹配角点模式,则把 b 作为新的上限 bmax。

通过二分不断逼近使得角点模式匹配的像素数最多的阈值。

当上下限只差1时,返回最佳阈值 bmin。

关键点:

使用二分搜索提升角点明显性。

像素比较使用偏移访问提速。

通过不断调整阈值 b 寻找最佳角点模式匹配。

返回最佳阈值,作为该像素角点的分数。

f9ed5a2e-3457-11ee-9e74-dac502259ad0.png

看下这个检测算法里面的这个句子,

循环遍历所有检测到的角点 corners

计算每个角点的像素指针 - 通过角点的 x,y 坐标,计算在图像像素数组中的偏移量

调用 agast58_score() 并传入像素指针和阈值threshold

agast58_score() 将返回0-255范围的分数,记录在 corner[i].score 中

最后每个角点除了有坐标x,y之外,还有一个分数score表示角点的明显程度。

所以这个过程对每一个角点候选运行了二分搜索,找到了最佳的阈值,作为该点的分数。分数高的角点匹配度更好,更明显,更稳定,这样后续就可以基于分数进行非极大值抑制来过滤掉弱角点。这种为每个角点单独密集计算的方式也是AGAST算法区别于FAST算法的主要特点之一。

fa05774e-3457-11ee-9e74-dac502259ad0.png

这个非极大值抑制(non-maximum suppression)函数的作用是去除重复或边缘响应较弱的非极大值角点,只保留每个局部区域响应最强的角点。

主要步骤是:

计算每一行的起始角点索引,用于快速查找上下行角点。

对每个角点,检查其上下左右4邻域是否存在更高分数的角点。

如果存在,则抑制该角点,不将其录入最终角点集。

只保留每个局部区域分数最高的角点。

检查内存是否足够,不足则试图释放内存使能继续录入角点。

将抑制后的角点保存到输出数组中。

这通过只保留局部最大值点,去除了边缘响应较弱的重复角点,提升了角点质量。

AGAST算法相比FAST算法加入了这个非极大值抑制步骤,可以有效提升角点的重复性和分布均匀性。

fa548dac-3457-11ee-9e74-dac502259ad0.png

里面有一个这样的句子,是初始化 row_start 数组,row_start 数组用来记录每一行的第一个角点在 corners 数组中的索引。

具体地:

row_start 的大小是图像总行数+1,即 last_row + 1

使用 -1 来表示该行没有检测到角点

初始化所有值为 -1,表示刚开始还没有任何角点

后面在检测到角点时会记录:

row_start[角点所在行] = 角点在corners数组中的索引

所以row_start[y] = x 表示:

第y行的第一个角点在corners数组中的索引为x

这样初始化row_start为-1VeryAmerican,在后续的非极大值抑制中,就可以通过row_start数组快速获取上下行的角点信息,从而高效实现非极大值抑制。





审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 机器视觉
    +关注

    关注

    161

    文章

    4043

    浏览量

    118360
  • C语言
    +关注

    关注

    180

    文章

    7530

    浏览量

    128711
  • BMP
    BMP
    +关注

    关注

    0

    文章

    48

    浏览量

    16965
  • 计算机视觉
    +关注

    关注

    8

    文章

    1600

    浏览量

    45616
  • openMV
    +关注

    关注

    3

    文章

    29

    浏览量

    9679

原文标题:OpenMV-AGAST算法代码阅读

文章出处:【微信号:TT1827652464,微信公众号:云深之无迹】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    openMV与stm32是如何进行通讯的

    (USART3_RX)脚与stm32的USART1交叉联接。openMV代码如下,功能是进行色块识别并把中心点传给stm32,需要注意的是对数据的打包格式,用到了ustruct.pack这个函数:im...
    发表于 08-05 06:51

    OpenMv和STM32通信问题

    问题,刚开始不知道哪里出了问题,一直通信失败,明明使用TTL串口接收OpenMv发送的数据是可以在串口调试助手上显示的,但就是无法发给Stm32的USART串口。经过了差不多一周的时间,终于解决了。于是在这里记录学习记录。一、OpenMv配置
    发表于 08-16 07:44

    OpenMV简单实现物体追踪

    简单实现物体追踪OpenMV简介材料准备OpenMV实现脱机运行1.将OpenMV与电脑连接2.使用IDE把软件内自带的代码烧录到OpenMVOpenMV简介open_mv是一款很方便
    发表于 11-30 06:59

    如何对基于stm32的json进行解析

    #适用于STM32F103芯片#使用stm32标准库#文档等更多参考之前在做openmv与单片机通讯时,遇到了json的解析问题,根据openmv中文平台的指导选择了jansson库——这是一个C
    发表于 01-12 08:04

    基于openMV的追球小车设计资料分享

    程序设计【microPython】2)STM32控制程序设计 【C语言】①对OpenMV发送的数据包进行解析②小车的简单PID控制③系统状态设定3.效果展示1).整体效果图注:本文仅用于学习交流...
    发表于 01-14 09:23

    一文解析OpenMv串口

    博主本人3天前接触OpenMv,有单片机基础,但是串口总也搞不透彻,特抽出时间作此备忘,希望可以和大家互相学习。
    发表于 02-14 06:52

    如何用cubemx写openmv与stm32通讯的代码

    由于之前接触openmv的时候是用库函数写的代码,在网上发现用hal库写接收代码的例子并不多,于是就想重新用cubemx写一次openmv与stm32通讯的
    发表于 02-21 07:28

    C++的G代码解析算法研究

    在数控技术发展过程中,G 代码解析优劣是促进数控技术的发展因素之一。但目前的解析算法,并不能更高效的进行解析处理。经过对G
    发表于 07-21 16:36 0次下载

    蚁群算法matlab程序代码

    本文详细解析了关于蚁群算法matlab程序代码,具体步骤请看下文。
    发表于 02-02 10:21 3.8w次阅读
    蚁群<b class='flag-5'>算法</b>matlab程序<b class='flag-5'>代码</b>

    OpenMV追踪物体应用教程(无需自写代码

    简单实现物体追踪OpenMV简介材料准备OpenMV实现脱机运行1.将OpenMV与电脑连接2.使用IDE把软件内自带的代码烧录到OpenMVOpenMV简介open_mv是一款很方便
    发表于 11-21 19:51 29次下载
    <b class='flag-5'>OpenMV</b>追踪物体应用教程(无需自写<b class='flag-5'>代码</b>)

    openMV摄像头循迹小车

    二、openMV代码THRESHOLD = (0, 20, -128, 127, -128, 127) # Grayscale threshold for dark things...import sensor, image, timefrom pyb import UA
    发表于 12-16 16:56 50次下载
    <b class='flag-5'>openMV</b>摄像头循迹小车

    stm32与openmv通讯实现识别颜色并读取坐标值[hal库]

    由于之前接触openmv的时候是用库函数写的代码,在网上发现用hal库写接收代码的例子并不多,于是就想重新用cubemx写一次openmv与stm32通讯的
    发表于 12-24 19:04 6次下载
    stm32与<b class='flag-5'>openmv</b>通讯实现识别颜色并读取坐标值[hal库]

    openmv4系列1----基本认知

    上,用C语言高效地实现了核心机器视觉算法,提供Python编程接口。2.openmv4的硬件主控STM32H743VI ARM Cortex M7 处理器,400 MHz ,1MB RAM,2 MB flash. 所有的 I/O 引脚输出 3.3V 并且 5V 耐受。这个
    发表于 12-29 18:51 8次下载
    <b class='flag-5'>openmv</b>4系列1----基本认知

    霍尔Foc算法解析代码 中颖单片机,3213 提供代码、电路图和pcb

    霍尔Foc算法解析代码中颖单片机,3213提供代码、电路图和pcb算法对开关霍尔的处理颇有独到之处,是做hallfoc的良好参考人间惆怅客
    发表于 12-29 19:30 42次下载
    霍尔Foc<b class='flag-5'>算法</b><b class='flag-5'>解析</b>,<b class='flag-5'>代码</b> 中颖单片机,3213 提供<b class='flag-5'>代码</b>、电路图和pcb

    【DIY】基于OpenMV的STM32追球小车

    简单识别程序设计【microPython】2)STM32控制程序设计 【C语言】①对OpenMV发送的数据包进行解析②小车的简单PID控制③系统状态设定3.效果展示1).整体效果图注:本文仅用于学习交流...
    发表于 01-14 13:01 33次下载
    【DIY】基于<b class='flag-5'>OpenMV</b>的STM32追球小车