上文我们说到【行人检测】miss rate versus false positives per image (FPPI) 前世今生(理论篇),今天我们来跑通作者绘制FPPI图的代码以及源码解读
(第一、二、三、四都在上一篇文章中,所以这篇文章直接从五开始)
五、miss rate versus false positives per image (FPPI) 官方绘制方法
接下来我们用作者提供的数据,跑通作者绘图的代码
(很多人可能会好奇,python现在这么热门,居然不是用python画的,主要原因有两个:1. 因为原作者提供的代码就是matlab的;2. 因为我在网上没有找到一个比较靠谱的python写法,所以目前先只讨论matlab版的。如果后续看到有比较好的python版画法,我再加上吧)
1. 安装第三方工具包
按照这篇文章Caltech评估方法的0 安装
步骤安装
2. 下载annotations和res
annotations是数据集的ground truth,res是模型的预测
下载地址:https://drive.google.com/drive/folders/1IBlcJP8YsCaT81LwQ2YwQJac8bf1q8xF
两个都要下载
文件放置如图所示(res文件夹需要自己你新建):
annotations压缩包解压到../code3.2.1/data-USA
中,annotations文件内如图所示:
res文件里的所有压缩包分别解压到独立文件夹中,并放在../code3.2.1/data-USA/res
中,res文件内如图所示:
如果你觉得下载annotations和res嫌麻烦的话,可以直接下载我的这个,直接运行dbEval.m
就好了
下载地址:miss rate versus false positives per image(FPPI)绘制代码
3. 运行绘图代码
绘图工具的代码地址在:http://www.vision.caltech.edu/Image_Datasets/CaltechPedestrians/
在Download标题下的Matlab evaluation/labeling code (3.2.1).
,解压后里面有个dbEval.m
文件,这个文件就是主要用来绘制FPPI曲线的。
打开dbEval.m
,将第136行的GDFL算法注释掉,不然会报错
在dbEval.m
第292行前加一句:
saveas(gcf, './results/mr-fppi.png')
并且注释掉第293~297行的代码,如下图所示
然后运行dbEval.m
,第一遍运行的时候会有点久,成功后results文件夹下输出名叫mr-fppi.png
的图片(results文件夹是自动生成的)
因为算法太多了,所以legend框中只显示了一部分
如果模型更新了,想重新检测的话,记得把原本生成的results文件夹删掉,否则的话就会一直重新读取原本的FPPI图
以上,就是跑通作者绘图的代码的过程
六、代码解读
接下来,我们把FPPI绘图原理和dbEval.m
代码给对应上
1. 首先看看有哪些函数
- function dbEval:主函数,评估并绘制所有行人检测结果
- function res = evalAlgs( plotName, algs, exps, gts, dts ):在每个实验中评估每种算法
- function plotExps( res, plotRoc, plotAlg, plotNum, plotName, …
samples, lims, colors, styles ):绘制所有的ROC或PR曲线 - function plotBbs( res, plotName, pPage, type ):给定algs/exps,绘制fp/tp/fn样本的检测框
- function plotBbSheet( bb, ind, bbo, varargin ):画检测框
- function A = loadVbb( s, v ):加载给定的annotation
- function gts = loadGt( exps, plotName, aspectRatio, bnds ):对于所有帧,加载所有试验的grouth truth
- function dts = loadDt( algs, plotName, aspectRatio ):对于所有帧,加载所有算法的检测结果
2. 看看主函数的流程
- 设置实验类型
exps
- 设置每种算法的名字及是否需要rescale height of each box、对应曲线的颜色、线性
algs
- 设置数据集名称
dataNames
- 参数选择:
- 需要验证的数据集
dataNames
- 进行的实验种类
exps
- 用到的算法
algs
- 检测框的默认纵横比
aspectRatio
- 丢弃超出此像素范围的检测框
bnds
- 是否画ROC曲线或PR曲线
plotRoc
- 是否为每一个算法一张图或者每一个实验一张图
plotAlg
- 是否只显示最佳的plotNum曲线
plotNum
- 曲线下计算面积的样本范围
samples
- ROC曲线的坐标范围
lims
- 是否可视化每个算法或每个实验的样本
bbsShow
- 可视化的种类(fp/tp/fn/dt)
bbsType
5. 接下来是一个大的for循环,在for循环中完成图片的绘制,这个for循环是真针对数据集来循环的,因为我们目前只有一个数据集,所以相当于只跑了一遍
所以上面的流程中,前面都是在设置参数,比较关键的就是for循环
3. 看看for循环内部的流程
- 选择拥有当前这个数据集检测结果的算法,若有则变量
keep
对应位置为1,否则为0 - 处理特定于数据库的特殊情况,我们的数据集UsaTest是不会经过这一步的
- 设置绘图文件的保存名称及保存地址
- 加载检测结果函数
loadDt
、加载ground truth函数loadGt
、计算评估结果函数evalAlgs
- 绘制曲线函数
plotExps
- 可视化检测框函数
plotBbs
(默认是不可视化的)
所以上面的流程中,比较关键的就是计算评估结果函数evalAlgs
和函数plotExps
4. 看看评估结果函数evalAlgs
内部的流程
函数的input为:
- 保存文件的名称
plotName
- 算法种类
algs
- 试验种类
exps
- 该数据集上的ground truth
gts
及检测结果dts
gts
是一个1*1的cell,表示评估一种实验类型。这个cell内部的维度是1*4024,表示这个数据集中有4024张图片。
dts
是一个1*78的cell,表示涉及到78种算法。这个cell内部的维度是1*4024,表示这个数据集中有4024张图片。
函数的output为res
,包含四列:
- 算法名称
- 实验名称
gtr
:每一帧ground truth的结果,具体为[x y w h match]dtr
:每一帧检测的结果,具体为[x y w h score match]
gtr
和dtr
中的match为一个匹配标志位,表示是否有检测结果dt
检测到ground truthgt
或是否有ground truthgt
对应这个检测结果dt
,用于统计后续的fp、fn、tp:
- 对于gt: -1=ignore, 0=fn [unmatched], 1=tp [matched]
- 对于dt: -1=ignore, 0=fp [unmatched], 1=tp [matched]
在下面的评估函数bbGt
中会有具体解释。
我们可以看到,函数evalAlgs
最主要是两个嵌套的for循环,第一个nGt
的循环表示根据实验类型循环,第二个nDt
的循环表示根据算法类型循环。由此得到每一种算法在每一种实验类型下的检测评估结果。
我们直接看最里层nDt
的循环在做什么:
- 读取某一实验种类ground truth和某一种算法的检测结果、算法名称、实验名称
- 设置保存文件的名称、显示当前验证进度
- 根据实验种类,设置检测框的height的阈值,并且从所有检测结果中,提取出符合height阈值范围的检测框
- 通过函数
bbGt('evalRes',gt,dt,exps(g).overlap)
,计算ground truth与检测结果的匹配程度,并保存到res
中
所以上面的流程中,比较关键的就是函数bbGt
,前面的设置主要是为了各种实验类型、算法而准备的。
函数bbGt
其实内部还有很多个函数,因为用的是参数'evalRes'
,所以实际调用的是函数[gt,dt] = evalRes( gt0, dt0, thr, mul )
,所以我们来看看函数evalRes
5. 看看函数evalRes
内部的流程
函数的input为:
- ground truth
gt0
:维度为m*5,m为实际的行人数量,每一行为[x y w h ignore]。 - 检测结果
dt0
:维度为n*5,n为输出的检测框数量,每一行为[x y w h score] thr
表示重合程度的阈值,因为判断一个框是否真的检测到某一行人,还需要判断ground truth和检测结果之间的IoU,所以需要用到重合程度的阈值mul
:每个gt
是否允许匹配多个dt
,默认为0不可
[x y w h]分别为左上角的x坐标、左上角的y坐标、框的宽度、框的高度。
ignore表示这个ground truth是否需要忽略,若为1,则评估的性能的时候忽略这个ground truth。为什么要有ignore呢,因为在论文中作者分别评估了不同类型的行人,例如评估near scale的行人时,不满足near scale的行人就需要忽略掉,ignore设置为1
函数的output为:
gt
:维度为m*5,m为实际的行人数量。对于gt,第五列: -1=ignore, 0=fn [unmatched], 1=tp [matched]dt
:维度为n*5,n为输出的检测框数量。对于dt,第五列: -1=ignore, 0=fp [unmatched], 1=tp [matched]
因为对于每一张图片来说,计算gt
和dt
匹配程度的方式是一样的,而由于我们输进这个函数数据格式是一个元胞、每个元胞表示一张图片的结果,所以作者通过递归的写法,把每一张图片的信息分别取出来去算gt
和dt
匹配程度(感觉这段代码作者是在秀操作啊)
针对每一张图片,计算gt
和dt
匹配程度的流程为:
- 检测框按照置信度降序排列
- 计算所有ground truth与所有检测框之间的IoU
- 对于每一个的检测框,遍历每个ground truth
- 判断这个ground truth是否已经被匹配了,若是则遍历下一个ground truth;若否则继续往下
- 判断这个检测结果是否已经找到最佳匹配,若是则为下一个检测框找匹配;若否则继续往下
- 判断IoU是否满足IoU阈值条件,若不满足,则遍历下一个ground truth;若是则继续往下
- 将最佳匹配的ground truth与检测框标记为1,即“已匹配”
通过上面的流程,我们就可以找到所有的tp、fn、fp了,因为求tp、fn、fp的过程本质就是在计数:
- gt中匹配成功的,就是成功检测出来的行人,即tp
- gt中没有匹配成功的,就是漏掉的行人,即fn
- dt中没有匹配成功的,就是明明没有行人却认为有行人,即fp
6. 看看函数`plotExps``内部的流程
函数的input为:
res
评估的结果- 画图种类的参数:
plotRoc
、plotAlg
、plotNum
、plotName
- 综合评估的参数
samples
- 图形的参数
lims
、colors
、styles
第一步,对于每一种实验、每一种算法,计算其ROC需要用到的x点、y点、综合评分score
[xs{g,d},ys{g,d},~,score] = ... bbGt('compRoc',res(g,d).gtr,res(g,d).dtr,plotRoc,samples);
此时返回的xs
表示x轴FPPI、ys
表示TPR。我们知道在FPPI曲线中,y轴其实是miss rate,那怎么办呢,也很简单,作者通过ys{g,d}=1-ys{g,d}
将TPR变成了miss rate。
TPR就是recall!miss rate = 1 - reacall = 1 - TPR!!!忘了的朋友请回到上一篇文章的《四、由ROC曲线得到FPPI曲线》再复习下【行人检测】miss rate versus false positives per image (FPPI) 前世今生(理论篇)
综合评分score
指理论篇中提到的量化评价指标,是9个参考点处的miss rate。
例如在代码中,9个参考点的选取方式为samples = 10.^(-2:.25:0)
,即范围从 1 0 − 2 10^{-2} 10−2 到 1 0 0 10^{0} 100之间,均匀地取9个fppi值
然后再找到这9个fppi值对应的miss rate值
然后通过score=exp(mean(log(score)))
变成一个值,即log-average miss rate
第二步,根据plotName
,提取前N个分数最高的算法相关数据
第三步,绘制图形,并调整为图片的细节,例如坐标轴、字体大
以上,就是FPPI绘图代码的解读。
在下一篇文章中【行人检测】miss rate versus false positives per image (FPPI) 前世今生(实战篇-下),我们将介绍如何通过使用自己的数据,绘制FPPI图