目标检测任务是计算机视觉中的基础任务。一般情况下,目标检测任务是要求将图片中的目标通过矩形框形式检测出来,即通过一组检测框坐标(这里一般使用矩形对角顶点(xmin,ymin,xmax,ymax),或者是通过中心点加框长高(xc, yc, w, h)来表示)和对应类别信息(cls_id)。
一般来说,对于检测器(Detector)而言。输出结果还需要加上每一个框的置信度(confidence)。这种设计往往是允许将结果进行排序,以便对FP,FN样本进行权衡估计,将检测器调试到最佳性能。
这里不讨论一些特殊的检测问题以及其扩展问题(如:文字检测:OCR,旋转目标检测:Rotated Object Detection,视频目标检测 :Video Object Detection 等)。这里将目标检测输出框结果设定为矩形框,以便简单分析。
为了行文简单,以下默认读者了解常用分类模型评价指标的计算方法和相关概念。如:混淆矩阵(confusion matrix),准确度(Accuracy),精确度(Precision),召回率(Recall),F1-score(measure),PR曲线,ROC曲线。单标签多分类(multi-class)问题,多标签(multi-label)多分类问题。以下不加以具体介绍。
AP 指标可以看做是P-R曲线的AUC(Area under the Curve)的近似求解指标。其本身为一个多标签分类任务的评价指标。
P.S: 以下内容为个人对评价指标一些学习思考小总结:
1. 分类任务:大部分使用指标为Acc topk(这是由于最基本的分类任务为二分类和单标签多分类)。
2. 由于该指标受到验证集或者测试集数据类不平衡(class imbalance)性影响大,很多时候会考虑Precision,Recall,F-score(F-measure)指标(或者PR曲线的AUC指标),或者ROC曲线的AUC指标(下面简写为AUC ROC指标)。
3. Precision ,Recall ,F-score 和 AUC ROC指标不同在于:
1)前者聚焦于正例,后者考虑整体性能(正例和负例)。
2)测试集数据不平衡场景下,前者能够更好体现模型效果。
3)对于测试数据集固定,选择模型场景下,更优使用前者指标。
4)对于多批次测试数据,每组测试数据类别分布不同。如果只想评价模型好坏,推荐后者。
4. 但是对于多标签分类,如果使用Precision指标会经常导致:虽然漏检,但是Precison依旧很高的情况。
一个例子:
————————————————
如一张图片有4个人和2个车子,但实际上检测出1个人和1个车(假设都正确),还有将1个人检测为了车。
对于人:P = 100%, R = 25% , F1 = 40%,
对于车:P = 50%, R = 50% , F1 = 50%。
————————————————————————————————
从直观上将,这里F1,P指标有一种“偏高“的误感。这是由于多分类问题,输出结果不会有遗漏(必定有一个结果),然而多标签分类不仅仅要考虑检测是否正确(Precision),还要考虑是否有遗漏(Recall)。
如果分开考虑简单求解F1,往往会导致以上结果。所以该任务指标需要将P,R结果同时考虑,计算PR曲线下面积更好符合这一目标。
如何使用一个分类问题评价指标来评价一个检测问题。简单来说只需要将目标框评价设计就可以了,这里通常使用IOU的方式进行评价。由于篇幅不再赘述。
考虑到叙述简单,这里首先对单类检测问题进行讨论,多类别检测同理讨论即可:
假设检测出一系列框,输出为框坐标,置信度(B, C)。下面对这个结果进行评价:
通过与真实值IOU 阈值将输出框可以分为两类,一类为预测正确的(TP),一类为预测错误的(FP)。然后对于真实值框,没有预测出来的集合为(FN)。这里就可以计算该情况下P和R值。
进一步的,通过调节置信度分数阈值来控制输出框数量,从而得到P-R曲线(例如Fig 3)。
Fig3. 一个真实场景下的PR曲线绘制(这里设置IOU threshold = 0.75)。图片来源于[2]
一般来说有如下几种计算算法:
主要是测量了点 L=[0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0] 中的召回率,图示如4(左):公式为。
AP 11 = 1 11 ∑ R ∈ { 0 , 0.1 , ⋅ , 0.9 , 1 } . P in t e r p ( R ) , \begin{equation*} \text{AP}_{11}=\frac{1}{11}\sum_{R\in\{0,0.1,\cdot,0.9,1\}}.P_{\text{in}\mathrm{t}\mathrm{e}\mathrm{r}\mathrm{p}}(R), \end{equation*} AP11=111R∈{0,0.1,⋅,0.9,1}∑.Pinterp(R),
其中:
P i n t e r p ( R ) = max R ‾ : R ‾ ≥ R P ( R ~ ) . \begin{equation*} P_{\mathrm{i}\mathrm{n}\mathrm{t}\mathrm{e}\mathrm{r}\mathrm{p}}(R) = \max_{\overline{R}:\overline{R}\geq R}P(\tilde{R}). \end{equation*} Pinterp(R)=R:R≥RmaxP(R~).
针对全部的点(可以理解为每次阈值调节后,添加的正样例),使用如下插值算法:
AP a 11 = ∑ n ( R n + 1 − R n ) P in t e r p ( R n + 1 ) , \begin{equation*} \text{AP}_{\mathrm{a}11}=\sum_{n}(R_{n+1}-R_{n}) P_{\text{in}\mathrm{t}\mathrm{e}\mathrm{r}\mathrm{p}}(R_{n+1}),\end{equation*} APa11=n∑(Rn+1−Rn)Pinterp(Rn+1),
其中:
P in t e r p ( R n + 1 ) = max R ~ : R ~ ≥ R n + 1 P ( R ~ ) . \begin{equation*} P_{\text{in}\mathrm{t}\mathrm{e}\mathrm{r}\mathrm{p}}(R_{n+1}) = \max_{\tilde{R}:\tilde{R}\geq R_{n+1}}P(\tilde{R}).\end{equation*} Pinterp(Rn+1)=R~:R~≥Rn+1maxP(R~).
最后,mAP计算比较简单,直接将所有类型AP求平均即可。注意的是,VOC所使用的IOU threshold = 0.5。
在COCO比赛中,为了更加全面的评价,设计了如下指标
其中除了AP,还有AR(即在预测结果中占真实标签比率,计算方式简单这里不多赘述),并且对不同范围目标进行分类评价(samll ,medium ,large)。
其中AP与VOC不同的是,该方法是设置N = 101召回点进行插值计算。
并且通过阅读源码发现,这101个点的Precision值不是与该值大于等于Recall的所有Precision最大的那一个,而是寻找第一个改变的Precision,图示如下(绿色圆圈标注的为所采用的Precision的值)
P.S, 如果阅读源码,这一个插值实现的关键语句在COCOeval的accumulate(该函数功能是将每一张图检测结果汇总,计算每一个类,每一个阈值,每一个面积范围下recall,precision等结果)中的下面这句话:
由于VOC metric代码原始为matlab,在很多目标检测论文开源项目中可以找到,如Faster RCNN官方代码。这里不做细致解读。
对于coco源代码,针对目标检测部分进行简单阅读。
这里推荐和pycocoDemo.ipynb,pycocoEvalDemo.ipynb文件配合使用阅读更容易理解,下面根据这两个文件改写成测试代码,使用pdb等调试工具进行调试:
Tips:
如果操作系统是Windows ,请使用这个版本的cocoapi进行测试:
https://github.com/philferriere/cocoapi
如果操作系统是Linux,使用这个版本cocoapi进行测试:
https://github.com/cocodataset/cocoapi/tree/master/PythonAPI
# 注意使用这个文件需要放在pycocotools同级目录
# 首先阅读setup.py,对整个包进行编译处理后才能正常使用
# 注意results/ 文件夹下有instances_val2014_fakebbox100_results.json文件
# 可能需要修改的地方:class Params中 setDetParams函数 np.linspace 传入的num参数需要使用int.不然会报错(在我测试的版本中)
import os, sys, zipfile
import urllib.request
import shutil
import numpy as np
from pycocotools.coco import COCO
from pycocotools.cocoeval import COCOeval
### Step1. 下载val.json 数据测试文件
print("-" * 20 + "Download test data file" + "-" * 20)
dataType = 'val2014'
dataDir = './'
annDir = '{}/annotations'.format(dataDir)
annZipFile = '{}/annotations_train{}.zip'.format(dataDir, dataType)
annFile = '{}/instances_{}.json'.format(annDir, dataType)
annURL = 'http://images.cocodataset.org/annotations/annotations_train{}.zip'.format(dataType)
print(annFile, annZipFile, annURL)
if not os.path.exists(annDir):
os.makedirs(annDir)
if not os.path.exists(annFile):
if not os.path.exists(annZipFile):
print("Downloading zipped annotations to " + annZipFile + " ...")
with urllib.request.urlopen(annURL) as resp, open(annZipFile, 'wb') as out:
shutil.copyfileobj(resp, out)
print("... done downloading.")
print("Unzipping " + annZipFile)
with zipfile.ZipFile(annZipFile, "r") as zip_ref:
zip_ref.extractall(dataDir)
print("... done unzipping")
print("Will use annotations in " + annFile)
### Step2. 使用coco计算检测指标
print("-" * 20 + "Compute the metric" + "-" * 20)
annType = 'bbox'
prefix = 'instances'
print('Running demo for *%s* results.' % (annType))
#initialize COCO ground truth api
dataDir = './'
dataType = 'val2014'
annFile = '%s/annotations/%s_%s.json' % (dataDir, prefix, dataType)
cocoGt = COCO(annFile)
#initialize COCO detections api
resFile = '%s/results/%s_%s_fake%s100_results.json'
resFile = resFile % (dataDir, prefix, dataType, annType)
cocoDt = cocoGt.loadRes(resFile)
imgIds = sorted(cocoGt.getImgIds())
imgIds = imgIds[0:100]
imgId = imgIds[np.random.randint(100)]
cocoEval = COCOeval(cocoGt, cocoDt, annType)
cocoEval.params.imgIds = imgIds
cocoEval.evaluate()
cocoEval.accumulate()
cocoEval.summarize()
输出结果如下:
通过等调试工具运行上述代码,调试来理解清楚代码结构:
这里只展示本人阅读源码和调试中理解的该项目关键部分(源码比较复杂,写完估计文章太长了,而且里面有写代码使用了CPython加速,如计算iou阈值时候,考虑篇幅不多赘述)。
即构建的类和关键成员函数及其功能(下图使用百度脑图绘制:http://minder.yoqi.me/, 导出的原图和.xmind工程项目访问here (免费使用,转载等请注明来源)):