本文受PyImageSearch的读者Ezekiel的启发,他上个星期在邮件中咨询道:
由于这是一个如此常见的问题,并且是关于神经网络/深度学习目标检测器实际工作的一个误解,所以我决定在今天的博客中重温深度学习目标检测的话题。
具体地,你将在这篇文章中学到以下内容:
图像分类和目标检测的区别
深度学习目标检测器的组成:包含不同目标检测架构的区别和基本模型之间的区别
如何使用预训练模型进行深度学习目标检测
如何从一个深度学习模型中过滤或者忽略一些预测类别
向深度神经网络增加类别或从中删除类别时常见的误区和误解
为了更多地了解深度学习目标检测,并解释清楚我们对基于深度学习的目标检测的一些误区和误解,请继续阅读本文。
想要查看本文相关的源码?请查看原文的下载链接():
深度学习目标检测的一般指南
今天的博客是对基于深度学习的目标检测的简单介绍。我尽可能对深度学习目标检测器的组成做一个概述,包括使用预训练的目标检测器执行任务的源代码。
你可以使用这份指南来帮助学习深度学习目标检测,但是也要意识到,目标检测是高度细节化的工作,我不可能在一篇文章中包含关于深度学习目标检测的所有细节。
这篇文章将从讨论图像分类和目标检测之间的本质区别开始,其中包括判断一个图像分类网络是否可以用于目标检测,以及在什么情况下可以这样使用等话题。
当我们理解了什么是目标检测时,随后会概述一个深度学习目标检测器的核心模块。它一般包括目标检测架构和基本模型,不熟悉目标检测的读者可能会误解这两个部分。
在这里,我们将使用OpenCV来实现实时深度学习目标检测。我也会展示如何在不修改网络架构或者重新训练的情况下忽略或者过滤一些不感兴趣的目标类别。最后,我们通过讨论如何从深度学习目标检测器中增加或者删除类别来总结本文。
图像分类和目标检测的区别
图1:图像分类(左)和目标检测(右)的区别是比较直观和简单的。在图像分类中,整幅图像被分类为单一的标签。而在目标检测中,我们的神经网络还要找出图像中目标的位置(有可能是多个)。
在进行标准的图像分类时,我们将一张给定的图像输入到神经网络,然后得到一个最可能的标签,而且也许会同时得到相关的概率。
这个类别标签用来表征整个图像的内容,或者至少是图像最主要的可见内容。例如,上面的图1中,给定输入图像(左),我们的CNN给它的标签是「比格犬」。所以我们可以认为图像分类具有以下特点:
一张图像输入
一个类别标签输出
无论是通过深度学习还是其他计算机视觉技术的目标检测,都是基于图像分类构建的,只不过需要精确定位每个对象在图像中出现的位置。在进行目标检测的时候,给定一张输入图像,我们期望得到:
一个边界框列表,或者一幅图像中每个对象的(x,y)坐标
与每个边界框关联的类别标签
与每个边界框和类别标签关联的概率或者置信度得分
图1(右)展示了一个深度学习目标检测的例子。请注意,人物和狗都被用边界框找出了位置,同时类标签也被预测到了。
所以,目标检测允许我们:
向网络输入一张图像
得到多个边界框以及类别标签
深度学习图像分类可以被用于目标检测吗?
图2:非端到端深度学习的目标检测器使用一个滑动窗口(左)+图像金字塔(右)相结合的方法来分类。
所以现在你理解了图像分类和目标检测的根本区别:
在进行图像分类时,我们输入一张图像,得到一个输出类别
然而在进行目标检测时,我们输入一张图像,得到多个边界框以及类别标签的输出
这自然引发这么一个问题:
这个答案有些棘手,因为这在技术上是可以的,但是理由并不太明显。解决方案涉及:
1.应用基于计算机视觉的标准目标检测方法(非深度学习方法),例如滑动窗口和图像金字塔等方法通常被用在HOG+基于线性SVM的目标检测器。
2.采用预训练的网络,并将其作为深度学习目标检测架构的基本网络(例如FasterR-CNN,SSD,YOLO)。
方法2:目标检测架构的基本网络
第二个深度学习目标检测的方法允许我们将一个预训练的分类网络作为深度学习目标检测架构(例如FasterR-CNN、SSD或者YOLO)的基本网络。
这个方法的好处是:你可以创建一个基于深度学习的复杂端到端目标检测器。
而其不足之处是:它需要一些关于深度学习目标检测器如何工作的知识,我们将在后面的部分中讨论这个问题。
深度学习目标检测器的模块
图4:VGG16基本网络是SSD深度学习目标检测框架的一个特征抽取模块。
1.目标检测框架(不包括FasterR-CNN,SSD,YOLO)
2.适合目标检测框架的基本网络
你可能已经比较熟悉基本网络(只是你之前并没听到它被称作基本网络而已)。基本网络就是你常用的分类CNN架构,包括:
VGGNet
ResNet
MobileNet
DenseNet
通常这些网络在大数据集上进行预训练来进行分类,例如ImageNet,它们可以学习到很多具有鉴别能力的滤波器。
目标检测框架由很多组成部分和子模块构成。例如,FasterR-CNN框架包括:
候选区域网络(RPN)
一组锚点
ROI池化模块
最终基于区域的卷积神经网络
在使用SSD(单步检测器,singleshotdetectors)时,具有以下的组成部分:
多框(MultiBox)
先验(Priors)
固定先验(Fixedpriors)
请记住,基本网络只是整个深度学习目标检测框架的众多组件之一,上文图4描述了SSD框架内部的VGG-16网络。通常,我们需要在基本网络上进行「网络手术」。这种修改:
让它变成全卷积的形式,并接受任意输入维度。
剪除了基本网络中更深层的卷积和池化层,将它们以一系列新层(SSD)、新模块(FasterR-CNN),或者这两者的一些组合代替。
这里的「网络手术」是一种口语化的说法,它的意思是移除基本网络中的一些原始卷积层,将它们用新层替代。网络手术也是讲究策略的,我们移除一些不需要的部分,然后用一组新的部分来替代它们。
然后,当我们开始训练我们的框架进行目标检测时,(1)新层、模块和(2)基本网络的权重都被修改了。
再强调一次,综述关于不同深度学习目标检测框架是如何工作的(包括基本网络所起的作用)并不属于本文的探讨范围。
如果你对深度学习目标检测的完整综述(包括理论和实现)感兴趣,请参考机器之心曾经发过的文章:从RCNN到SSD,这应该是最全的一份目标检测算法盘点。
我是如何计算一个深度学习目标检测器的准确度的?
在评价目标检测器的性能时我们使用了一个叫做均值平均精度(mAP)的指标,它是以我们数据集中所有类别的交并比(IoU)为基础的。
交并比(IoU)
图5:在这个交并比的可视化例子中,标注边界框(绿色)可以与预测的边界框(红色)进行对比。IoU与mAP一起被用来评价一个深度学习目标检测器的精度。计算IoU的简单方程如图5(右)所示。
你通常会发现IoU和mAP被用于评价HOG+线性SVM检测器、Haarcascades以及基于深度学习的方法的性能;但是请记住,实际用于生成预测边界框的算法并不是那么重要。
任何一个以预测边界框作(以及可选择的标签)为输出的算法都可以用IoU来评价。更一般的地,为了使用IoU来评价任意一个目标检测器,我们需要:
1.真实的边界框(也就是测试集中表明我们的目标在图像的哪个位置的人工标签)
2.模型预测到的边界框
3.如果你想一起计算召回率和精度,那么还需要真实类别标签和预测类别标签
在图5(左)中,我展示了真实边界框(绿色)与预测边界框(红色)相比的可视化例子。IoU的计算可以用图5右边的方程表示。
仔细检查这个方程你会发现,IoU就是一个比值。在分子项中,我们计算了真实边界框和预测边界框重叠的区域。分母是一个并集,或者更简单地说,是由预测边界框和真实边界框所包括的区域。两者相除就得到了最终弄的得分:交并比。
平均精度均值(MAP)
图6:为了计算目标检测器的mAP@0.5,我们执行了以下计算。对于所有被标记为「正检测」(positivedetection)、具备至少0.5的交并比(IoU)的对象,我们对所有N个类别计算IoU(0.5)均值,然后对N个均值再求平均。这个指标就是mAP@0.5。
为了在我们的数据集中评估目标检测器,我们需要同时基于以下两者的IoU来计算mAP:
1.基于每个类别(也就是说每个类别的平均IoU);
2.数据集中所有类别(也就是说所有类别平均IoU的均值,所以这个术语就是平均精度均值)。
为了计算每个类别的平均精度,我们在所有的数据点上计算某个类别的IoU。一旦我们计算出了一个类别在每个数据点的IoU,我们对它们求一次平均(第一次平均)。
为了计算mAP,我们对所有的N个类别计算平均IoU,然后对这N个平均值取平均值(均值的平均)。
通常我们使用mAP@0.5,表示测试集中要被标记为「正检测」的目标必须具备的条件,真值不小于0.5的IoU(并可以被正确地标记)。这里的0.5是可以调整的,但是在所有的目标检测数据集和挑战中,0.5是一个相对标准的数值。
再次强调,这只是一个关于目标检测评价指标的快速指南,所以我将整个过程简化了一些。
使用OpenCV进行基于深度学习的目标检测
我们已经在本文以及之前的博客中讨论了深度学习和目标检测。出于完整性考虑,我们将在本文中概述实际的代码。
我们的例子包含以MobileNet作为基础模型的单次检测器(SSD)。该模型由GitHub用户chuanqi305()在COCO数据集上训练得到。更多细节请浏览我之前的文章(),这篇文章介绍了该模型以及相关的背景信息。
让我们回到Ezekiel在本文开始提出的第一个问题上。
我会在下面的示例代码中回答这个问题,但是首先你需要准备一下系统:
你需要在Python虚拟环境中安装版本不低于3.3的OpenCV(如果你在使用python虚拟环境的话)。+包含运行以下代码所需的DNN模块。确保使用链接中的OpenCV安装教程之一(),要额外注意你下载和安装的OpenCV版本。
你还应该安装我的imutils包()。为了在Python虚拟环境中安装/更新imutils,简单地使用以下命令即可:pipinstall--upgradeimutils。
系统准备好之后,创建一个新文件,命名为filter_object_。下面让我们开始:
在第2到8行中,我们导入了所需的包和模块,尤其是imultils和OpenCV。我们会使用我的VideoStream类处理从摄像头获取的帧。
我们已经具备了所需的工具,接着我们来解析命令行参数:
我们的脚本在运行时需要两个命令行参数:
--prototxt:ThepathtotheCaffeprototxtfilewhichdefinesthemodeldefinition.
--model:OurCNNmodelweightsfilepath.
--prototxt:Caffeprototxt文件的路径,它定义了模型的定义。
--model:CNN模型权重的文件路径。
你还可以有选择性地指定--confidence,这是过滤弱检测的阈值。
我们的模型可以预测21个对象类别:
CLASSES列表包含该网络训练时的所有类别标签(也就是COCO中的标签)。
对CLASSES列表的一个常见误解是你可以:
1.向列表增加一个新的类别标签;
2.或者从列表移除一个类别标签。
……以及以为网络可以自动「了解」你想要完成的任务。
不是这样的。
你不能简单地修改文本标签列表,让网络自动修改自己,在非训练所用数据上学习、增加或者移除模式。这并不是神经网络的运行方式。
也就是说,有一个快速的技巧,你可以使用它来过滤或者忽略你不感兴趣的预测。
解决方案就是:
1.定义一个IGNORE标签集合(即网络是在这个类别标签列表上进行训练的,但你现在想忽略这些标签)。
3.忽略类别标签存在于IGNORE集合中的所有预测结果。
在Python中实现时,IGNORE集合是这样的:
这里我们忽略所有具有类别标签「person」的预测对象(用于过滤的if语句会在后续内容中介绍)。
你可以很容易地增加额外的元素(CLASS列表中的类别标签)来忽略该集合。
第27行中名为COLORS的随机数组为21个类别中的每一个随机生成颜色。这些颜色会在后边用于显示。
第31行中使用函数加载我们的Caffe模型,我们所需的两个命令行参数作为参数被传递。
然后我们将VideoStream对象实例化为vs,并开始fps计数(36-38行)。2秒钟的sleep让我们的摄像头有足够的预热时间。
在第44行,我们抓取1帧,然后重新调整它的大小并保留用于显示的长宽比(第45行)。
我们从中提取高度和宽度,稍后会用到(第48行)。
第48行和49行从这一帧中生成blob。要了解更多blob,以及如何使用函数构建blob,请在以前的博文中查看所有细节()。
下一步,我们将blob发送到神经网络中来检测目标(54-55行)。
循环检测:
从第58行开始检测循环。
对于每一次检测,我们都提取confidence(65行)。当confidence超过最小值(默认的0.5可以通过命令行参数进行修改),我们可以认为这次检测是一次积极有效的检测,可以继续进行处理。
否则,我们检测到的目标就在白名单中,我们需要在该帧中显示对应的类别标签和矩形框:
在这段代码中,我们提取出了边界框的坐标(81-87)。
最后,仍然在这个while循环中,我们将结果显示在屏幕上:
我们显示出这一帧,并且捕捉按键(94-95)。
在后面几行中,当循环中断后,我们会显示时间+fps(帧每秒)指标然后清空。
运行你的深度学习目标检测器
为了运行今天的脚本,你需要滚动到下面的「下载」部分来抓取文件。
当你提取到文件之后,打开一个终端,切换到已下载代码+模型的路径。并在这里执行以下命令:
在上边的动图中,你在左边可以看到「person」类别被检测到了。这是由于IGNORE是空的。在右边的动图中,你可以看到我没有被检测到,这是由于我把「person」增加到了IGNORE集合了。
尽管我们的深度学习目标检测器仍然从技术上检测到了「person」类别,但是我们的后期处理代码将它过滤出来了。
也许你在运行这个深度学习目标检测器的时候会遇到错误?
排除故障的第一步是检查你是否连接了摄像头。如果这个是正常的,也许你会在你的终端中看到以下错误信息:
如果你看到这个信息,那说明你没有向程序传递「命令行参数」。如果他们不熟悉Python、argparse以及命令行参数的话()。
如何向深度学习目标检测器添加或者删除类别?
图7:深度学习目标检测的微调过程和迁移学习。
正如我在这份指南中前期所提到的,你不能在CLASS列表中简单地增加或者删除类别,基础的网络并没有改变。
你所能做的,最好就是修改一个能够列出所有类别标签的文本文件。
此外,如果你想显式地在神经网络中增加或者删除类别,你需要做的工作有:
1.从零开始训练
2.进行微调
从零开始训练通常会比较耗时间,是一个代价昂贵的操作,所以我们尽可能避免,但是在一些情况下是无法避免的。另一个选择就是进行微调。微调是一种迁移学习的形式,它是这样的过程:
1.删除负责分类/打标签的全连接层
2.并用全新的、随机初始化的全连接层替代
我们也可以选择性地修改网络中的其它层(包括在训练过程中冻结一些层的权重,以及解冻它们)。
准确来说,如何训练你自己的定制的深度学习目标检测器(包括微调和从零开始训练)是比较高级的话题,并不属于本文的讨论范围,但是阅读下面的部分可以有助于你开始这项工作。
我可以在哪里学到更多关于深度学习目标检测的内容?
图8:汽车前后视角的实时深度学习目标检测
正如我们在这篇博文中讨论过的,目标检测并不是像图像分类一样简单,其细节和复杂度超出了本文的范围(我已经啰嗦了好多遍了)。
本教程肯定不是我在深度学习目标检测方方面的最后一篇文章(毫无疑问,我会在未来写更多关于深度学习目标检测的文章),但是如果你对学习以下内容感兴趣:
1.为目标检测准备你的数据集。
2.在你的数据集上精调并训练你自己的定制化目标检测器,包括FasterR-CNN和SSD。
3.了解我的最好的实践做法、技术和过程,并使用它们来训练自己的深度学习目标检测器。
然后,你可能会想看一下我的新书()。在《DeepLearningforComputerVisionwithPython》一书中,我会一步一步地指导你构建自己的深度学习目标检测器。
总结
这篇博客简单介绍了深度学习目标检测所涉及的一些难点。我们以概述图像分类和目标检测之间的本质区别作为开始,包括如何将一个图像分类的神经网络用于目标检测。
然后我们概述了深度学习目标检测器的核心组成:
1.检测框架
2.基本模型
基本模型通常是一个预训练的(分类)网络,为了学习到一系列具有辨识能力的滤波器,一般是在大型图像数据集(例如ImageNet)上进行训练的。
我们也可以从零开始训练基本网络,但是,对于目标检测器而言,为了达到较合理的准确率。这通常需要更长的训练时间。
在绝大多数情况下,你应该以一个预训练的基本模型作为开始,而不是尝试着从零开始训练。
当我们对深度学习目标检测器有了充分的理解之后,我们就可以在OpenCV中实现能够实时运行的目标检测器。
我还概述了如何过滤或者忽略那些不感兴趣的类别标签。
最后,我们了解到:实际地向深度学习目标检测器增加一个类别标签,或者从深度学习目标检测器中删除一个类别标签并不是像从硬编码的标签列表张增加或者删除标签一样简单。
神经网络本身并不在乎你是否修改了类别标签,相反,你需要:
1.通过删除全连接目标预测层并进行调整来修改网络结构
2.或者从零开始训练目标检测网络框架
对于更多的深度学习目标检测项目,你需要从一个基于目标检测任务(例如COCO)的预训练深度学习目标检测器开始。然后基于预训练模型进行微调,以得到你自己的检测器。
训练一个端到端的定制深度学习目标检测器并不属于本文的范畴,所以,如果你对探索如何训练自己的目标检测器感兴趣,请参考我的书籍《deeplearningforcomputervisionwithpython》。
在这本书中,我描述了一些深度学习目标检测的例子,包括为以下任务训练你自己的深度学习目标检测器:
1.检测交通标志,例如Stop标志,人行横道标志等等。
2.汽车的前后视角




