用OpenCV实现超轻量的NanoDet目标检测模型!

点击上方“3D视觉工坊”,选择“星标”

干货第一时间送达

作者丨nihate

审稿丨邓富城

编辑丨极市平台

导读

 

本文作者用OpenCV部署了超轻量目标检测模型NanoDet,并实现了C++和Python两个版本,并对此进行了解析,附完整代码。 

2020年,在深度学习目标检测领域诞生了yolov4,yolov5和nanodet这些优秀的检测模型,有许多的微信公众号报道这些算法模型。深度学习目标检测方法可划分为 Anchor-base 和 Anchor-free 两大类,nanodet是一个速度超快和轻量级的移动端 Anchor-free 目标检测模型,并且它的精度不亚于yolo系列的。

nanodet通过一些论文里的trick组合起来得到了一个兼顾精度、速度和体积的检测模型。作者用到的一些trick,主要参考自:(1)参考FCOS 式的单阶段 anchor-free 目标检测模型,FCOS特点是让模型学习feature map中每个位置到检测框的四条边的距离,如下图所示。

(2)使用 ATSS 进行目标采样,该方法提出了自适应训练样本选择方法,该方法根据目标的统计特征(方差和均值)自动划分正训练样本和负训练样本,弥合了基于锚的探测器与无锚探测器之间的差距。(3)使用 Generalized Focal Loss 损失函数执行分类和边框回归(box regression),该函数能够去掉 FCOS 的 Centerness 分支,省去这一分支上的大量卷积,从而减少检测头的计算开销。

为了达到轻量化的目的,作者在设计网络结构时,使用 ShuffleNetV2 1.0x 作为骨干网络,他去掉了该网络的最后一层卷积,并且抽取 8、16、32 倍下采样的特征输入到 PAN 中做多尺度的特征融合。

在FPN模块里,去掉所有卷积,只保留从骨干网络特征提取后的 1x1 卷积来进行特征通道维度的对齐,上采样和下采样均使用插值来完成。与 YOLO 使用的 concat操作不同,项目作者选择将多尺度的 Feature Map 直接相加,使整个特征融合模块的计算量变得非常小。

在检测头模块里,使用了共享权重的检测头,即对 FPN 出来的多尺度 Feature Map 使用同一组卷积预测检测框,然后每一层使用一个可学习的 Scale 值作为系数,对预测出来的框进行缩放。与此同时,使用了 Group Normalization(GN)作为归一化方式.FCOS 的检测头使用了 4 个 256 通道的卷积作为一个分支,也就是说在边框回归和分类两个分支上一共有 8 个 c=256 的卷积,计算量非常大。为了将其轻量化,项目作者首先选择用深度可分离卷积替换普通卷积,并且将卷积堆叠的数量从 4 个减少为 2 组。在通道数上,将 256 维压缩至 96 维,之所以选择 96,是因为需要将通道数保持为 8 或 16 的倍数,能够享受到大部分推理框架的并行加速。

最后,项目作者借鉴了 YOLO 系列的做法,将边框回归和分类使用同一组卷积进行计算,然后 split 成两份。最后,项目作者借鉴了 YOLO 系列的做法,将边框回归和分类使用同一组卷积进行计算,然后 split 成两份,这样就组成了nanodet网络。

作者把nanodet发布在github上,项目地址: https://github.com/RangiLyu/nanodet,下载代码和模型文件到本地,按照README文档运行一个前向推理程序。接下来,我阅读前向推理主程序demo.py文件,尝试理解在运行这个主程序时需要调用哪些函数和.py文件。在前向推理主程序demo.py文件,对一幅图片做目标检测是在Predictor类的成员函数inference里实现的,它里面包含了对输入图片做预处理preprocess,前向计算forward和后处理postprocess这三个步骤。Predictor类的定义如下图所示

对输入原图做预处理,预处理模块是使用Pipeline类实现,对应的代码是

看到这段代码时,我有些懵逼了。第一次见到functools.partial这个模块,我百度查了一下它的作用是包装函数,接着看warp_resize函数,这个函数对应的代码很复杂,里面有多个if条件判断,调用了多个自定义函数。限于篇幅,在这里展示部分截图代码,如下图所示

从代码不难猜测出warp_resize函数的作用是对原图做resize,于是我把warp_resize函数返回的图像做可视化并打印出图像的尺寸是高宽:320x320,可视化结果如下图所示。

从图中可以看到,warp_resize函数是保持原图高宽比的resize,短边剩下的部分用黑色像素填充。这种功能在ultralytics的yolov3和yolov5代码仓库里有一个letterbox函数实现的,在letterbox函数使用opencv库里的resize和copyMakeBorder就可以实现保持高宽比的resize,这种方法简洁明了。接着我对warp_resize函数和letterbox函数对同一幅图片做保持原图高宽比的resize的结果比较。可视化结果如下,从视觉上看不出它们有何差异。把这两幅图的像素矩阵做减法比较,发现它们并不等于0,也是是说它们的像素值还是有差异的。

接着看预处理模块Pipeline类里的第二个函数color_aug_and_norm,代码截图如下。可以看出,这个函数的作用是对输入图片的RGB三通道分别做减均值除以标准差的操作,不过在最开始对img除以255,在最后对均值和标准差分别除以255,这三次除以255是完全没必要的,因为在最后一步 (img - mean) / std,分子分母可以约掉1/255,这和img,mean,std不做除以255这一步计算,直接(img - mean) / std是等价的。

综上所述,在预处理模块Pipeline类包含了很多冗余的计算,图像预处理本身是一个简单问题,但是在官方代码里却把简单问题搞复杂化了。

官方代码仓库(https://github.com/RangiLyu/nanodet)里提供了基于 ncnn 推理框架的实现,基于mnn,libtorch,openvino的实现,但是没有基于Opencv的dnn模块的实现。于是我就编写一套基于Opencv的dnn模块的实现,程序里包含Python和C++两个版本的代码。

地址是: 

https://github.com/hpc203/nanodet-opncv-dnn-cpp-python

在这套程序里,图像预处理模块沿用了ultralytics代码仓库里的letterbox函数使用opencv库里的resize和copyMakeBorder就可以实现保持高宽比的resize。此外,在网上有很多介绍nanodet网络结构的文章,但是在文章里没有对nanodet后处理模块做详细介绍的。因此,在编写这套程序时,我最关注的是nanodet的后处理模块,在nanodet网络输出之后,经过怎样的矩阵计算之后得到检测框的左上和右下两个顶点的坐标(x,y)的值的。接下来,我结合代码来理解后处理模块的运行原理。首先,原图经过预处理之后形成一个320x320的图片作为nanodet网络的输入,经过forward前向计算后会得到40x40,20x20,10x10这三种尺度的特征图(换言之就是原图缩小8倍,16倍,32倍),在程序代码里设断点调试,查看中间变量,截图如下:

从上图可以看到,经过forward前向计算后,有6个输出矩阵。第1个输出的维度是(1600,80),它对应的是40x40的特征图(拉平后是长度为1600的向量,也就是说一共有1600个像素点)里的每个像素点在coco数据集的80个类别里的每个类的置信度。第2个输出的维度是(1600,32),它对应的是40x40的特征图(拉平后是长度为1600的向量,也就是说一共有1600个像素点)里的每个像素点的检测框的预测偏移量,可以看到这个预测偏移量是一个长度为32的向量,它可以分成4份,每份向量的长度为8,接下来的第3,4,5,6个输出矩阵的意义以此类推。

前面讲到过nanodet的特点是让神经网络学习feature map中每个位置到检测框的四条边的距离,接下来我们继续在程序里设断点调试,来理解这4份长度为8的预测偏移量是如何经过一系列的矩阵计算后得到到检测框的四条边的距离。代码截图如下:

从上图可以看到,把形状为(1600,32)的矩阵reshape成(6400,8)的矩阵bbox_pred,其实就等同于把每一行切分成4份组成新的矩阵,然后做softmax变换,把数值归一化到0至1的区间内。继续调试接下来的一步,代码截图如下:

可以看到project是一个长度8的向量,元素值是从0到7。形状为(6400,8)的矩阵bbox_pred与向量project做乘法得到6400的列向量,然后reshape为(1600,4)的矩阵,接下来乘以缩放步长。这时候就得到的形状为(1600,4)的矩阵bbox_pred,它的几何意义就是40x40的特征图里的每个像素点到检测框的四条边的距离。有了这个值之后,接下来的计算就简单了,在此不做详细讲述,可以参阅我的代码。简单来说就是计算特征图的每个像素点在coco数据集里的80类里的最大score值作为类别置信度,然后把特征图的所有像素点的类别置信度从高到低排序,取前topk个像素点,然后根据上一步计算出的到检测框四条边的距离换算出检测框的左上和右下两个顶点的(x,y)值,最后做NMS去除重叠的检测框。为了更好的理解从nanodet输出特征图到最终计算出目标检测框的左上和右下顶点坐标(x,y)值的这个过程,我在草稿纸上画图演示,如下所示:

在编写完调用opencv的做nanodet目标检测的程序后,为了验证程序的有效性,从COCO数据集里选取几张图片测试并且与官方代码做比较,官方代码是用python编写的调用pytorch框架做目标检测的。结果如下,左边的图是官方代码的可视化结果,右边的图是opencv做nanodet目标检测的可视化结果。

把官方代码和我编写的代码做了一个性能比较的实验,实验环境是ubuntu系统,8G显存的gpu机器。在实验中读取一个视频文件,对视频里的每一帧做目标检测,分别运行官方的调用pytorch框架做目标检测的python代码和我编写的调用opencv做目标检测的python代码,在terminal终端输入top查看这两个程序运行时占用的内存,截图如下。第一行是opencv做nanodet目标检测程序运行时占用的内存,第二行是官方代码运行时占用的内存,可以看到使用opencv做nanodet目标检测对内存的消耗明显要低于官方代码的pytorch框架做nanodet目标检测的。

GitHub传送门

https://github.com/hpc203/nanodet-opncv-dnn-cpp-python

本文仅做学术分享,如有侵权,请联系删文。

下载1

在「3D视觉工坊」公众号后台回复:3D视觉即可下载 3D视觉相关资料干货,涉及相机标定、三维重建、立体视觉、SLAM、深度学习、点云后处理、多视图几何等方向。

下载2

在「3D视觉工坊」公众号后台回复:3D视觉github资源汇总即可下载包括结构光、标定源码、缺陷检测源码、深度估计与深度补全源码、点云处理相关源码、立体匹配源码、单目、双目3D检测、基于点云的3D检测、6D姿态估计源码汇总等。

下载3

在「3D视觉工坊」公众号后台回复:相机标定即可下载独家相机标定学习课件与视频网址;后台回复:立体匹配即可下载独家立体匹配学习课件与视频网址。

重磅!3DCVer-学术论文写作投稿 交流群已成立

扫码添加小助手微信,可申请加入3D视觉工坊-学术论文写作与投稿 微信交流群,旨在交流顶会、顶刊、SCI、EI等写作与投稿事宜。

同时也可申请加入我们的细分方向交流群,目前主要有3D视觉CV&深度学习SLAM三维重建点云后处理自动驾驶、多传感器融合、CV入门、三维测量、VR/AR、3D人脸识别、医疗影像、缺陷检测、行人重识别、目标跟踪、视觉产品落地、视觉竞赛、车牌识别、硬件选型、学术交流、求职交流、ORB-SLAM系列源码交流、深度估计等微信群。

一定要备注:研究方向+学校/公司+昵称,例如:”3D视觉 + 上海交大 + 静静“。请按照格式备注,可快速被通过且邀请进群。原创投稿也请联系。

▲长按加微信群或投稿

▲长按关注公众号

3D视觉从入门到精通知识星球:针对3D视觉领域的知识点汇总、入门进阶学习路线、最新paper分享、疑问解答四个方面进行深耕,更有各类大厂的算法工程人员进行技术指导。与此同时,星球将联合知名企业发布3D视觉相关算法开发岗位以及项目对接信息,打造成集技术与就业为一体的铁杆粉丝聚集区,近3000星球成员为创造更好的AI世界共同进步,知识星球入口:

学习3D视觉核心技术,扫描查看介绍,3天内无条件退款

 圈里有高质量教程资料、可答疑解惑、助你高效解决问题

觉得有用,麻烦给个赞和在看~  

3D视觉工坊 CSDN认证博客专家 算法 3D视觉
个人公众号:3D视觉工坊。公众号特邀嘉宾及合伙人,先后就职于国内知名研究机构、自动驾驶公司、海康研究院,主要研究方向为深度学习、目标检测、语义分割、图像处理、自动驾驶感知算法等,CSDN博客专家。博主先后任职于国内知名研究院、知名大厂,致力于3D视觉算法、VLAM算法开发,涉及相机标定、手眼标定、结构光、点云后处理、三维重建等相关领域的研究,同时也是CSDN博客专家。3D视觉工坊坚持原创,近一年来输出了非常多的高质量文章,获得了粉丝的一致好评,我们将始终坚持走原创路线,打造一个铁杆粉丝的聚集区。
已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 书香水墨 设计师:CSDN官方博客 返回首页
实付 49.90元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值