目标检测:在图片中对可变数量的目标进行查找和分类
温馨提示:学习目标检测前必须要先掌握图像分类的知识体系
学习资料必备:Github上tensorflow官方提供的目标检测与分类库
- 在Github上搜索tensorflow models,转到如下界面:
- 点击research,这里面有很多tensorflow官方的工具可以用来使用,分类模型库在slim文件夹中,有很多预训练模型可供下载,点击code即可查看源代码,之前官方实现还是基于tensorflow1的版本实现的,现在已经有tensorflow2版本的了,另外Checkpoint就是官方为我们提供好的预训练模型,是在ImageNet数据集上训练好的,可以用这些模型进行迁移学习,根据需求选择不同的模型进行使用,如下图所示:
- 目标检测库是research下的object_detection文件,红框里有官方提供的预训练模型,这里都是在COCO训练集上面预训练好的模型,模型旁会给出检测一张图片所需要的时间、COCO数据集上的准确率等,如下图所示,建议大家尽量多看官方文档和顶刊论文,少看博客,否则可能因为一些版本不匹配问题会出现很多bug。
目标检测目前主流两大体系:
对于所有的目标检测网络,能够分成两种体系:One-Stage和Two-Stage
- Two-Stage:例如Faster-RCNN,对于这一系列网络,其检测过程可分为两步,第一步就是通过专门的模块(RPN)去生成候选框,这个过程其实也就是寻找前景及调整边界框(基于anchors),我们只是将感兴趣目标进行寻找并框选出来,没有对其进行分类。第二步就是基于第一步生成的候选框进行分类及调整边界框(基于proposals)
- One-Stage:例如SSD和YoLo,直接基于anchors进行分类及调整边界框,
- 对比:One-Stage的检测速度肯定比Two-Stage快,因为只需要一步就能实现检测,Two-Stage虽然更加的耗时,但换来的却是更高的准确度,具体要使用哪一种就要根据自己的项目需求进行选择,
PASCAL VOC数据集
PASCAL VOC挑战赛是一个世界级的计算机视觉挑战赛,PASCAL的全称Pattern Analysis,Statical Modeling and Computational Learning,是一个由欧盟资助的网络组织。挑战赛主要包括:图像分类,目标检测,目标分割和动作识别。该数据集一共有20个类别,主要分为交通工具,家具,动物和其他,下图是下载好的数据集的文件结构:
Annotation:标注信息文件,包含所有图像的标注信息,每个图像对应一个XML文件;
ImageSets:包含不同任务的分类信息,训练集是哪些图像,验证集是哪些图像;
黑色的注释涉及到分割,简单的一笔带过。
注:在coco数据集出来之前,基本都用PASCAL VOC,现在都用coco了
目标检测中常见的指标
训练网络时,在验证集上会得到一个COCO的评价列表,如图所示:在分类任务中,通常统计在验证集中分类正确的个数除以验证集的总样本数就能得到准确率,在目标检测中,肯定没有这么简单,那么怎样才算检测到一个目标呢?通常在论文中能看到网络在某一个数据集上的mAP,什么是mAP呢?现在引入几个概念:
- TP(True Positive):表示预测正确的边界框的个数,这里可以指定当预测边界框与gtbox的IoU值大于0.5(一般约定的LoU=0.5,来判断边界框是否正确),就认为匹配成功,就比如下面这个图,概率为0.9的红色边界框对于绿色的gtbox而言就算匹配到了,它就是一个TP
- FP(False Positive):可以理解为假阳性,即本来不是目标,却认成了目标(误检),预测边界框与gtbox的IoU值小于0.5,图中概率为0.3预测边界框与绿色的gtbox的IoU是小于0.5的,就可以称该红色的边界框为FP。
- FN(False Negative):没有检测到的GT的数量,下图中的右下角还有一只猫,但网络的检测过程中并没有匹配到,相当于漏检的情况。
通过上面的几个参数,进一步引入几个概念:
- Precision:TP/(TP+FP),即模型预测的所有目标当中,预测正确的比例,称为查准率;
- Recall:TP/(TP+FN),所有真实目标中,模型预测正确的目标比例,称为查全率。
再引入一个概念:
- AP:P-R曲线下的面积,
- P-R曲线:Precision-Recall曲线
- mAP:mean Average Precision 因为之前是针对每一个类别分别求AP值,所以一个类别对应一个AP,我们在目标检测中可能有多个类别,对多个类别取平均值就得到了mAP
接着进入到主题,coco数据集中每一个评价指标的含义:
- 第一组参数中AP里一共有三个值,第二个值是当IoU取0.5时,计算的AP的一个数值,这个数值也是PASCAL
VOC数据集所提供的评价指标,当然在coco的评价指标中依然存在。在coco数据集的评价指标中,最主要的还是AP,也就是第一个值,其指的是IoU从0.5~0.95间隔为0.05一共十个IoU上mAP的一个均值,这里虽然说的是AP,但其实就是刚才所计算的mAP。当IoU设置的越大,就要求目标检测的边界框与gtBox重合度越来越高,目标的定位越来越准, 所以表格中的IoU=0.75注释是更严格的标准。 - 在第二组参数Across Scales中,有AP分别针对小/中/大面积的,如果目标的像素面积是小于32平方的话就归为小目标,大于96平方的话归为大目标,通过这一组参数,可以了解到目标检测网络对于不同尺度的目标检测的效果,比如检测的目标较小,很可能就关注与APsmall这个值。
- 第三组参数:AR参数,Recall可以理解为查全率,这里分别对应有max=1,10,100三种情况,对于普通的计算,经过非极大值抑制后,每个图片最多预测一百个目标,取不同的值代表每个图片所给的边界框数量。
- 第四组参数,不同目标尺度AR的值,其和第二组参数类似,不进行说明。
- 下面列出平时使用过程中需要注意的一些参数:
几个目标检测的概念(具体的实现等看完论文再细说)
- LoU:交并比(loU)函数做的是计算两个边界框交集和并集之比,其衡量了两个边界框重叠地相对大小,一般约定,在计算机检测任务中,如果loU≥0.5,就说检测正确,如果预测器和实际边界框完美重叠,loU就是1,一般约定,0.5是阈值,用来判断预测的边界框是否正确。一般是这么约定,但如果希望更严格一点,则可将loU定得更高,比如说大于0.6或者更大的数字,loU越高,边界框越精确。
- 非极大抑制NSM:保证算法对每个对象只检测一次,选取那些邻域里分数最高的窗口,同时抑制那些分数低的窗口,即去除冗余的检测框,保留最好的一个。具体做法是:分别判断得到的一组检测框中最大概率的与剩余框的重叠度IOU是否大于某个设定的阈值,就扔到,保留概率最大的,再对剩余的检测框做相同的操作直到情况列表。
- Anchor box:对一个中心点,取不同的窗口,从而用来检测重叠在一起的多个目标量。关于用在哪个阶段什么时候来触发下来会细说。
XML文件
xml基本构成:
- 1>标签: <标签名> ,起始标签和结束标签是成对存在的。且结束标签多了个 /
格式:<起始标签> … </ 结束标签> - 2>属性:只出现在起始标签,如 <?xml version="1.0" ?> 其中 version=“1.0” 为属性
成份访问流程:
- 1.构建树和根节点
- 2.获取子节点:直接获取名称相同的直接子节点
find(节点名称) #获取同名直接子节点(缺点:只能根据提供的名称获取第一个子节点)
findall(节点名称) #获取所有同名直接子节点,返回的节点会存在一个列表里面 - 3.值的访问:
节点.tag #获取节点标签
节点.attrib #获取节点属性 如 图片=“1.png”
节点.test #获取文本 即末端叶节点间的文本,如 time1.jpg
需要关注的一般只有:
- filename :图片名称
- size:width,heights 图片尺寸
- object:图片中标注的目标,可能含有多个目标,这个xml就有2个标注目标
- ----- name:标注目标 类别标签 labels
- ----- bndbox :标注目标框 xmin ,ymin ,xmax ,ymax (左上角,右下角坐标)
标注数据解析代码
import numpy as np
import xml.etree.ElementTree as ET
CLASSES = ('aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus', 'car',
'cat', 'chair', 'cow', 'diningtable', 'dog', 'horse', 'motorbike',
'person', 'pottedplant', 'sheep', 'sofa', 'train', 'tvmonitor')
index_map = dict(zip(CLASSES, range(len(CLASSES))))
print(index_map)
anno_path='./001234.xml'
tree = ET.parse(anno_path)
root = tree.getroot()
size=root.find('size')
width = float(size.find('width').text)
height = float(size.find('height').text)
print(width,height)
def validate_label(xmin, ymin, xmax, ymax, width, height):
"""Validate labels."""
assert 0 <= xmin < width, "xmin must in [0, {}), given {}".format(width, xmin)
assert 0 <= ymin < height, "ymin must in [0, {}), given {}".format(height, ymin)
assert xmin < xmax <= width, "xmax must in (xmin, {}], given {}".format(width, xmax)
assert ymin < ymax <= height, "ymax must in (ymin, {}], given {}".format(height, ymax)
label=[]
for obj in root.iter('object'):
difficult = int(obj.find('difficult').text)
cls_name = obj.find('name').text.strip().lower()
if cls_name not in CLASSES:
continue
cls_id = index_map[cls_name]
xml_box = obj.find('bndbox')
xmin = (float(xml_box.find('xmin').text) - 1)
ymin = (float(xml_box.find('ymin').text) - 1)
xmax = (float(xml_box.find('xmax').text) - 1)
ymax = (float(xml_box.find('ymax').text) - 1)
try:
validate_label(xmin, ymin, xmax, ymax, width, height)
except AssertionError as e:
raise RuntimeError("Invalid label at {}, {}".format(anno_path, e))
label.append([xmin, ymin, xmax, ymax, cls_id, difficult])
label=np.array(label)
print(label)
Output:
[[ 69. 13. 181. 442. 14. 0.]
[ 32. 166. 189. 499. 14. 0.]
[ 0. 424. 61. 474. 11. 0.]
[ 2. 279. 70. 383. 19. 0.]]
下面内容存为001234.xml:
<annotation>
<folder>VOC2007</folder>
<filename>001234.jpg</filename>
<source>
<database>The VOC2007 Database</database>
<annotation>PASCAL VOC2007</annotation>
<image>flickr</image>
<flickrid>338514152</flickrid>
</source>
<owner>
<flickrid>bensalem5g</flickrid>
<name>Brian</name>
</owner>
<size>
<width>260</width>
<height>500</height>
<depth>3</depth>
</size>
<segmented>0</segmented>
<object>
<name>person</name>
<pose>Frontal</pose>
<truncated>0</truncated>
<difficult>0</difficult>
<bndbox>
<xmin>70</xmin>
<ymin>14</ymin>
<xmax>182</xmax>
<ymax>443</ymax>
</bndbox>
</object>
<object>
<name>person</name>
<pose>Frontal</pose>
<truncated>1</truncated>
<difficult>0</difficult>
<bndbox>
<xmin>33</xmin>
<ymin>167</ymin>
<xmax>190</xmax>
<ymax>500</ymax>
</bndbox>
</object>
<object>
<name>dog</name>
<pose>Right</pose>
<truncated>1</truncated>
<difficult>0</difficult>
<bndbox>
<xmin>1</xmin>
<ymin>425</ymin>
<xmax>62</xmax>
<ymax>475</ymax>
</bndbox>
</object>
<object>
<name>tvmonitor</name>
<pose>Unspecified</pose>
<truncated>1</truncated>
<difficult>0</difficult>
<bndbox>
<xmin>3</xmin>
<ymin>280</ymin>
<xmax>71</xmax>
<ymax>384</ymax>
</bndbox>
</object>
</annotation>