计算机视觉之单发多框检测(Single Shot MultiBox Detector)模型《3》

news2024/12/29 9:21:43

         有了前面两节的背景知识,我们来构造一个目标检测模型,就是来自Wei Liu大神之作的SSD了,有兴趣的可以查阅论文:SSD: Single Shot MultiBox Detector

计算机视觉之目标检测(object detection)《1》icon-default.png?t=M85Bhttps://blog.csdn.net/weixin_41896770/article/details/128062645
计算机视觉之目标检测训练数据集(皮卡丘)《2》icon-default.png?t=M85Bhttps://blog.csdn.net/weixin_41896770/article/details/128147117

一般我都会先看完原论文,里面除了讲解模型以及和其他模型的对比,还有一些开放式的讨论,可以留给自己思考。
SSD由于它简单且快速,得到了广泛的应用,该模型的一些设计思想和实现细节常适用于其他目标检测模型。SSD单发多框检测模型,是一个多尺度的目标检测模型,基于基础网络块和各个多尺度特征块生成不同数量和不同大小的锚框,并通过预测锚框的类别和偏移量检测不同大小的目标。

本人还是画个图,让大家直观感受下这个模型的层级构造,每层都会输出锚框(且数量和大小都不一样),然后依据锚框的类别与偏移量(即预测边界框)来检测不同大小的目标。这也是SSD属于多尺度的目标检测模型的原因。

类别预测层与预测边界框

从图中我们可以看到,每个层都输出不同数量和不同大小的锚框,并预测锚框的类别和偏移量(预测边界框)来检测不同大小的目标,我们先实现类别预测与预测边界框的方法:

import d2lzh as d2l
from mxnet import gluon, image, nd, init, contrib, autograd
from mxnet.gluon import loss as gloss, nn
import time


def cls_predictor(num_anchors, num_classes):
    '''
    类别预测层

    参数
    ------
    通道数:num_anchors*(num_classes+1)
        其中类别数需要加一个背景
    卷积核大小:3,填充:1
        可以保持输出的高宽不变
    '''
    return nn.Conv2D(num_anchors*(num_classes+1), kernel_size=3, padding=1)

def bbox_predictor(num_anchors):
    '''
    边界框预测层

    参数
    -----
    通道数:num_anchors*4
        为每个锚框预测4个偏移量
    '''
    return nn.Conv2D(num_anchors*4,kernel_size=3,padding=1)

def forward(x,block):
    block.initialize()
    return block(x)
Y1=forward(nd.zeros((2,8,20,20)),cls_predictor(5,10))
Y2=forward(nd.zeros((2,16,10,10)),cls_predictor(3,10))
print(Y1.shape,Y2.shape)#(2, 55, 20, 20) (2, 33, 10, 10)

可以看出计算出的输出形状中的通道数:55=5*(10+1),33=3*(10+1)符合我们的预期,留给大家一个思考,为什么这里出现通道数或者说要转换到通道数?
每个尺度上特征图的形状或以同一单元为中心生成的锚框数量可能不同,所以不同尺度的预测输出的形状就可能不同。

形状转换连结

上面提到的预测的形状可能不同,又如何将他们的多尺度预测输出都连结起来呢?这里需要做一个形状的变换,我们先来看个小示例:

def flatten_pred(pred):
    return pred.transpose((0,2,3,1)).flatten()
def concat_preds(preds):
    return nd.concat(*[flatten_pred(p) for p in preds],dim=1)
print(flatten_pred(Y1).shape,flatten_pred(Y2).shape)#(2, 22000) (2, 3300)
print(concat_preds([Y1,Y2]).shape)#(2, 25300)

从结果也看出,虽然Y1,Y2的形状不同(批量大小一样),但是通过flatten()转换之后,我们还是可以将这两个不同尺度的预测结果连结在一起(维度1上进行连结)。
注意与numpy里的区别,不要搞混淆了,在np里面是摊平成一维数组,比如:
nd中flatten_pred(Y1).shape返回的是形状为(2, 22000)的二维数组,如果是np的话,形状则为(44000,)的一维数组
附带说下那个*星号的用法,简单粗暴的理解就是剥掉最外面的一个中括号:

>>> print([i for i in [1,2,3,4]])
[1, 2, 3, 4]
>>> print(*[i for i in [1,2,3,4]])
1 2 3 4
>>> print([i for i in [[1,2,3,4],[5,6,7,8]]])
[[1, 2, 3, 4], [5, 6, 7, 8]]
>>> print(*[i for i in [[1,2,3,4],[5,6,7,8]]])
[1, 2, 3, 4] [5, 6, 7, 8]

SSD模型

然后我们来构造一个SSD的模型,我们回看上面的图来构造,设计基础网络块,用来从原始图像提取特征,这里构造一个小的基础网络,串联3个高宽减半块,以及将通道数翻倍。
先实现一个高宽减半块,这样的模块是为了最终让输出特征图中每个单元的感受野变得更广阔。

def down_sample_blk(num_channels):
    '''
    高宽减半块
        步幅为2的2x2的最大池化层将特征图的高宽减半
    串联两个卷积层和一个最大池化层
    '''
    blk = nn.Sequential()
    for _ in range(2):
        blk.add(nn.Conv2D(num_channels, kernel_size=3, padding=1),
                nn.BatchNorm(in_channels=num_channels), nn.Activation('relu'))
    blk.add(nn.MaxPool2D(pool_size=(2, 2), strides=2))
    return blk
print(forward(nd.zeros((2, 3, 20, 20)), down_sample_blk(10)).shape)# (2, 10, 10, 10)

def base_net():
    '''
    基础网络块
        串联3个高宽减半块,以及通道数翻倍
    '''
    blk = nn.Sequential()
    for n in [16, 32, 64]:
        blk.add(down_sample_blk(n))
    return blk

#print(forward(nd.zeros((2, 3, 256, 256)), base_net()).shape)  #(2, 64, 32, 32)

 然后对每个块做前向计算,一层:基础网络:二三四层:高宽减半块;五层:全局最大池化层

def get_blk(i):
    if i == 0:
        blk = base_net()
    elif i == 4:
        blk = nn.GlobalMaxPool2D()
    else:
        blk = down_sample_blk(128)
    return blk


def blk_forward(X, blk, size, ratio, cls_predictor, bbox_predictor):
    Y = blk(X)
    anchors = contrib.nd.MultiBoxPrior(Y, sizes=size, ratios=ratio)
    cls_preds = cls_predictor(Y)
    bbox_preds = bbox_predictor(Y)
    return (Y, anchors, cls_preds, bbox_preds)

sizes = [[0.2, 0.272], [0.37, 0.447], [0.54, 0.619], [0.71, 0.79], [0.88, 0.961]]
ratios = [[1, 2, 0.5]]*5
num_anchors = len(sizes[0])+len(ratios[0])-1


class TinySSD(nn.Block):
    def __init__(self, num_classes, **kwargs):
        super(TinySSD, self).__init__(**kwargs)
        self.num_classes = num_classes
        for i in range(5):
            setattr(self, 'blk_%d' % i, get_blk(i))
            setattr(self, 'cls_%d' %i, cls_predictor(num_anchors, num_classes))
            setattr(self, 'bbox_%d' % i, bbox_predictor(num_anchors))

    def forward(self, X):
        anchors, cls_preds, bbox_preds = [None]*5, [None]*5, [None]*5
        for i in range(5):
            X, anchors[i], cls_preds[i], bbox_preds[i] = blk_forward(X, getattr(self, 'blk_%d' % i), sizes[i], ratios[i],
                                                                     getattr(self, 'cls_%d' % i),
                                                                     getattr(self, 'bbox_%d' % i))
        return (nd.concat(*anchors, dim=1),
                concat_preds(cls_preds).reshape(0, -1, self.num_classes+1),
                concat_preds(bbox_preds))

net=TinySSD(num_classes=1)
net.initialize()
X=nd.zeros((32,3,256,256))
anchors,cls_preds,bbox_preds=net(X)
print("锚框:",anchors.shape)#锚框: (1, 5444, 4)
print("类别预测:",cls_preds.shape)#类别预测: (32, 5444, 2)
print("预测边界框:",bbox_preds.shape)#预测边界框: (32, 21776)

其中锚框数是5444个,是怎么得来的:一层输出的特征图形状是32x32,二层到四层是高宽减半,分别为16x16、8x8、4x4的宽高,第五层是全局池化层到1x1,由于是以特征图每个单元为中心生成4个锚框,所以每个图像在5个尺度下生成的锚框总数为(32x32+16x16+8x8+4x4+1)x4=5444

训练模型

同样使用前面用到的皮卡丘数据集,加载并初始化模型参数:

#加载皮卡丘数据集并初始化模型
batch_size=8#本人配置不行,批处理大小调小点
train_iter,_=d2l.load_data_pikachu(batch_size)
ctx,net=d2l.try_gpu(),TinySSD(num_classes=1)
net.initialize(init=init.Xavier(),ctx=ctx)
trainer=gluon.Trainer(net.collect_params(),'sgd',{'learning_rate':0.2,'wd':5e-4})

接下来定义损失函数,这里的损失函数和前面的训练模型的损失函数有点区别。
需要两个损失:一个是有关锚框类别的损失,另一个是有关正类锚框偏移量的损失。然后将两个损失相加就得到了最终损失函数。

#定义损失函数
cls_loss=gloss.SoftmaxCrossEntropyLoss()
bbox_loss=gloss.L1Loss()
def calc_loss(cls_preds,cls_labels,bbox_preds,bbox_labels,bbox_masks):
    cls=cls_loss(cls_preds,cls_labels)
    bbox=bbox_loss(bbox_preds*bbox_masks,bbox_labels*bbox_masks)
    return cls+bbox
#评价函数
def cls_eval(cls_preds,cls_labels):
    #类别预测结果放在最后一维,所以argmax需指定最后一维
    return (cls_preds.argmax(axis=-1)==cls_labels).sum().asscalar()
def bbox_eval(bbox_preds,bbox_labels,bbox_masks):
    return ((bbox_labels-bbox_preds)*bbox_masks).abs().sum().asscalar()

#训练模型
for epoch  in range(20):
    acc_sum,mae_sum,n,m=0.0,0.0,0,0
    train_iter.reset()
    start=time.time()
    for batch in train_iter:
        X=batch.data[0].as_in_context(ctx)
        Y=batch.label[0].as_in_context(ctx)
        with autograd.record():
            #生成多尺度锚框,为每个锚框预测类别和偏移量
            anchors,cls_preds,bbox_preds=net(X)
            bbox_labels,bbox_masks,cls_labels=contrib.nd.MultiBoxTarget(anchors,Y,cls_preds.transpose((0,2,1)))
            l=calc_loss(cls_preds,cls_labels,bbox_preds,bbox_labels,bbox_masks)
        l.backward()
        trainer.step(batch_size)
        acc_sum+=cls_eval(cls_preds,cls_labels)
        n+=cls_labels.size
        mae_sum+=bbox_eval(bbox_preds,bbox_labels,bbox_masks)
        m+=bbox_labels.size
    if(epoch+1)%5==0:
        print('迭代次数:%2d,类别损失误差:%.2e,正类锚框偏移量平均绝对误差:%.2e,耗时:%.1f秒'%(epoch+1,1-acc_sum/n,mae_sum/m,time.time()-start))
net.collect_params().save('ssd.params')

迭代次数: 5,类别损失误差:2.50e-03,正类锚框偏移量平均绝对误差:2.61e-03,耗时:17.4秒
迭代次数:10,类别损失误差:2.31e-03,正类锚框偏移量平均绝对误差:2.32e-03,耗时:17.4秒
迭代次数:15,类别损失误差:2.09e-03,正类锚框偏移量平均绝对误差:2.06e-03,耗时:17.4秒
迭代次数:20,类别损失误差:2.04e-03,正类锚框偏移量平均绝对误差:1.96e-03,耗时:17.4秒

相关知识,有兴趣可以查阅:Softmax回归模型的构建和实现(Fashion-MNIST图像分类)
另外掩码变量bbox_masks令负类锚框和填充锚框不参与损失的计算。

多尺度目标检测测试

现在我们来进入最后的预测阶段,将图像中我们感兴趣的目标检测出来。我们通过MultiBoxDetection函数根据锚框以及预测偏移量得到预测边界框,并通过非极大值抑制移除相似的预测边界框。最后我们将置信度不低于0.3的边界框筛选为最终输出用以展示。

img=image.imread('pikachu.png')#自己PS一张图
feature=image.imresize(img,256,256).astype('float32')
X=feature.transpose((2,0,1)).expand_dims(axis=0)#转换成卷积层需要的四维格式

net.collect_params().load('ssd.params')

def predict(X):
    anchors,cls_preds,bbox_preds=net(X.as_in_context(ctx))
    cls_probs=cls_preds.softmax().transpose((0,2,1))
    output=contrib.nd.MultiBoxDetection(cls_probs,bbox_preds,anchors)
    idx=[i for i,row in enumerate(output[0]) if row[0].asscalar()!=-1]
    return output[0,idx]

output=predict(X)

d2l.set_figsize((5,5))

def display(img,output,threshold):
    fig=d2l.plt.imshow(img.asnumpy())
    for row in output:
        score=row[1].asscalar()
        if score<threshold:
            continue
        h,w=img.shape[0:2]
        bbox=[row[2:6]*nd.array((w,h,w,h),ctx=row.context)]
        d2l.show_bboxes(fig.axes,bbox,'%.2f'%score,'w')

display(img,output,threshold=0.1)
d2l.plt.show()

当然在做预测测试的时候最好训练一遍就将优化好的参数保存起来,不然每次都训练一次还是很耽误时间的,也没必要。

net.collect_params().save('ssd.params')

这样我们在进行输入预测之前就可以加载优化好的参数了。 

net.collect_params().load('ssd.params')

我将置信度调到了0.1,不然有时候还找不到^_^
从预测图来看,置信度不怎么准确,原因估计是皮卡丘的数据集比较小的关系,戴帽子的皮卡丘没有找到,当然也跟我的批处理大小调小了也有关系,大家硬件配置好的就调大点,有兴趣的可以自己试试,选择一张背景图然后上面放一些皮卡丘的图。

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

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

相关文章

[附源码]Python计算机毕业设计Django招聘系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

C#语言实例源码系列-实现图片合成功能

专栏分享点击跳转>Unity3D特效百例点击跳转>案例项目实战源码点击跳转>游戏脚本-辅助自动化点击跳转>Android控件全解手册 &#x1f449;关于作者 众所周知&#xff0c;人生是一个漫长的流程&#xff0c;不断克服困难&#xff0c;不断反思前进的过程。在这个过程中…

关于质量分析

背景 在日常工作过程中&#xff0c;作为测试&#xff0c;我们要及时反思总结历史出现的问题&#xff0c;以便于对日常的工作流程规范以及质量防护措施作出相应的调整作为下属&#xff0c;我们要定期的向上级反馈近期的产品质量情况基于以上的背景&#xff0c;我们需要定期对产…

java简单做一个消费者/生产者案例

首先 我们创建一个包 在包下创建 四个类 首先是奶箱类 milkBox 在业务中充当数据的存储类 参考代码如下 public class milkBox {private int milk;public void put (int milk){this.milk milk;System.out.println("送奶工将第"this.milk"瓶奶放入奶箱");…

华为是如何从0到1打造以项目为中心运作的项目管理体系的?

本文介绍了华为公司打破传统的功能型组织结构&#xff0c;从弱矩阵项目管理向强矩阵转变的历程。 内容包括如何全面铺设项目管理组织&#xff0c;转向“以项目为中心”。同时&#xff0c;通过一系列的政策、标准和规定统一项目管理语言&#xff0c;建设项目管理流程体系。 其…

Servlet与表单数据交互(如文本框、单选按钮、复选框、下拉列表等)

✅作者简介&#xff1a;热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏&#xff1a;JAVA开发者…

基于遗传算法解决TSP问题(Matlab代码实现)

目录 1 概述 2 运行结果 3 Matlab代码实现 4 结语 5 参考文献 1 概述 旅行商问题是一个经典的路径规划问题&#xff0c;传统TSP假设客户位置和客户之间旅行时间是固定不变的&#xff0c;而在现实生活中交通状况和客户要求可能会随着时间而改变&#xff0c;为了找到最经济的…

(附源码)ssm教育机构管理系统 毕业设计 010224

ssm教育机构管理系统 摘 要 随着互联网趋势的到来&#xff0c;各行各业都在考虑利用互联网将自己推广出去&#xff0c;最好方式就是建立自己的互联网系统&#xff0c;并对其进行维护和管理。在现实运用中&#xff0c;应用软件的工作规则和开发步骤&#xff0c;采用Java技术建设…

被AI包裹的冬天,智慧供热打破城市供热的“不可能三角”

最近很多城市都迎来了今冬以来的最强寒潮&#xff0c;在拉尼娜事件的影响下&#xff0c;一个格外寒冷的冬天似乎不可避免。而每次降温&#xff0c;有暖气的北方城市就会引来网络上一片艳羡之声。前不久网络上就流传着一个段子“南方人的命也是命&#xff0c;请给南方装暖气 ”&…

自定义jdk镜像

启动虚拟机&#xff0c;进入centos 1.创建文件夹上传jdk的安装包,和在同级目录下编写Dockerfile文件 #创建镜像目录 mkdir –p /javaxl_docker/jdk #安装lrzsz命令 yum install lrzsz -y #选择文件进行上传 rz # 注意d是大写 touch Dockerfile 2.编写 Dockerfile 文件 #1.指定基…

使用C++实现Windows和Linux的Flutter文档扫描插件

文档扫描应用大都是移动应用。结合手机摄像头可以实现文档拍摄&#xff0c;边缘检测&#xff0c;自动剪裁&#xff0c;透视矫正&#xff0c;滤镜等功能。但是桌面办公也少不了文档处理。这里分享下如何使用Dynamsoft Document Normalizer C SDK实现用于桌面文档处理的Flutter插…

抖音最新版抓包方案,修改so绕过ssl

dy过ssl charles抓包 及xposed的justtrustme安装到手机上只能过系统的ssl。 抖音写了一个非系统的ssl所以需要反编译so来处理。 第一步&#xff0c;charles 我用的是magisk手机&#xff0c;先重charles把证书下载到pc端 选项1&#xff0c;安装证书到本地 选项2&#xff0c…

Android桌面图标快捷方式

一、背景 长按桌面图标实现快捷方式最早是iOS提供的功能,而Android最早在Android 7.1版本也提供了对这方面的支持,于是在短时间内,像微信,支付宝,头条等流量级应用都提供了这方面的支持,如下图。 现在,长按桌面图标快捷方式已经是很成熟的功能,实现上也比较简单,主…

知识图谱-KGE-语义匹配-双线性模型(打分函数用到了双线性函数)-2011:RESCAL【双线性模型的开山之作】【把每个关系对应的邻接矩阵进行了矩阵的分解】

【paper】 A Three-Way Model for Collective Learning on Multi-Relational Data 【简介】 这篇文章应该算是双线性模型的开山之作。是德国的一个团队发表在 ICML 2011 上的工作&#xff0c;比较老了&#xff0c;主要思想是三维张量分解。 想研究啥&#xff0c;啥就很重要 Re…

Activity7-流程初体验

流程符号详解&#xff1a; 创建Activiti工作流的主要步骤包含以下几步: 1. 定义流程。按照BPMN的规范&#xff0c;使用流程定义工具&#xff0c;将整个流程描述出来。 2. 部署流程。把画好的BPMN流程定义文件加载到数据库中&#xff0c;生成相关的表数据。 3. 启动流程。使用…

基于粒子群优化算法的微电网调度(光伏、储能、电动车、电网交互)(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清…

目标检测3

还是目标检测相关~这次是Box Size置信度偏差会损害目标检测器 检测器的置信度预测在目标大小和位置方面存在偏差&#xff0c;但目前尚不清楚这种偏差与受影响的目标检测器的性能有何关系。 无数应用依赖于目标检测器的可靠置信度估计的准确预测。然而&#xff0c;众所周知&am…

[附源码]Python计算机毕业设计Django在线招聘网站

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

突破40%!新能源汽车L2级辅助驾驶搭载率创新高

新能源汽车正在成为智能化的主力军。 高工智能汽车研究院监测数据显示&#xff0c;2022年1-10月中国市场&#xff08;不含进出口&#xff09;新能源汽车前装标配搭载L2级辅助驾驶交付上险167.51万辆&#xff0c;前装搭载率首次突破40%&#xff0c;达到41.93%&#xff1b;同期&…

[Java反序列化]CommonsBeanutils1利用链学习

0x01 前篇shiro的利用&#xff0c;需要动态字节码 &#xff0c;而这种方式需要我们自己添加依赖&#xff0c;所以很局限&#xff0c;而CommonsBeanutils 是shiro的依赖&#xff0c; CommonsBeanutils 是应用于 javabean 的工具 javabean的定义 类必须是具体的和公共的&#…