1. 前言
最近半年一直在做边缘检测的工作,主要是阅读最新的论文、复现别人的代码(很多论文作者都上传了完整的项目)、验证算法结果和比较算法性能。在这个过程中遇到了一个问题:如何量化评估边缘检测算法。在边缘检测的论文中,均是以ODS-F和OIS-F作为评价指标和优化目标(具体原理可以参考这篇博客《边缘检测评估指标》),所使用的核心代码是BSDS500数据集所附带的benchmark文件(注:该数据集的官网为BSDS,但是BSDS500数据集从官网下载不到了,只能下载到BSDS300和benchmark文件)。但是大部分博客都是介绍原理和部分核心代码,并没有完整的使用过程,因此我在搜寻网上的博客以及自己本地跑通之后,决定写下来记录一下,一是为自己以后的工作铺路,防止自己更换工作环境遗忘;另外是为一些同样做边缘检测的人做一些参考。
但是,由于当时跑通整个代码的流程完全是自己一点点对着bug来改、来修正的,所以有很多步骤我在跑通代码的时候就记不起来了(这就是我为什么没有当时就写下来),但至少现在还有记着的东西,有总比没有强。所以,我希望看到这篇博客的人,能够留下来你在运行代码时出现的报错,可能会对我的回忆有帮助,我也尽力去解决,然后逐步完善这篇博客。
本文参考:保姆级 边缘检测评价OIS、ODS、PR曲线绘制 ——在自己的数据集上
2. 边缘检测评估代码
代码语言:MATLAB
软件版本:MATLABR2021b(在我参考的那篇博客中建议使用2016之前的版本,但是似乎经过我对代码的修改,之后的版本也没什么问题)
前文提到,评估代码的核心是BSDS500提供的benchmark文件,事实证明基本所有的边缘检测算法均使用这套代码进行评估。如果你看过我上文提到的评估原理参考的博客《边缘检测评估指标》,你会发现里面要计算许多参数,而这套代码把这些计算过程均完整的编写成函数,成为了一个“库”,然后调用这些库函数进行最终的计算,同时还提供了绘制PR曲线的函数,可以同时绘制多个算法的PR曲线进行比较。另外,由于基于神经网络的边缘检测算法输出的边缘都比较粗,因此需要先进行非极大值抑制(NMS,具体原理可自行搜索)操作,然后再进行评估指标的计算。因此许多边缘检测算法的作者均将NMS和计算评估指标的操作集成在一个文件中,方便使用。我这里使用的是网上流传较多的一个代码:hed,eval文件夹中eval_edge.m即为评估代码。
2.1 代码的使用
2.1.1 代码环境的配置
经过我对代码的修改以及他人运行验证,该代码可以完美运行。
运行评估代码需要安装pdollar toolbox和pdollar_edges两个工具箱,在刚才下载的hed文件夹中已经包含了,即edges和toolbox.badacost.public两个文件夹,并且在eval_edge.m文件的开头位置将这两个工具箱的位置添加进了matlab系统路径。
2.1.2 NMS代码
eval_edge.m分为两个部分,Section 1是NMS操作,Section 2是评估指标计算操作。这里NMS操作用的是.mat格式的文件,这里我的建议是不要用png、jpg等格式的图片转mat格式!!!这样NMS出来的结果质量非常差!受PIDINET算法代码的启发,正确的做法是:在模型预测边缘的时候,和输出图片一样,直接输出一个mat格式的文件:
#(这只是核心代码,根据自己的项目添加)
import scipy.io as sio
results = model(image)
result = torch.squeeze(results[-1]).cpu().detach().numpy()
sio.savemat(os.path.join(mat_dir, '%s.mat' % img_name), {'img': result}) #路径根据自己的代码修改
将生成的mat文件放在“data/算法名称/mat”文件夹下,目录结构为:
├── data
│ ├── 算法名称
│ │ ├── mat
│ │ │ └── 2018.mat
│ │ │ └── ...
│ │ └── nms
│ └── ...
├── edges
├── toolbox.badacost.public
└── edge_eval.m
这样输出的mat文件中存储的数据结构和BSDS官方给的test中的mat文件的数据结构不太一样(我对mat格式文件不是很懂,感兴趣的可以输出一下看看),如果你直接运行NMS代码会报错,因此需要将NMS部分的代码修改为:
% Section 1: NMS process (formerly nms_process.m from HED repo).
disp('NMS process...')%输出字符串
mat_dir = fullfile(data_dir, 'mat'); %拼接文件路径
nms_dir = fullfile(data_dir, 'nms');
mkdir(nms_dir)
files = dir(mat_dir);%获得所有子文件夹和文件并返回结构体,第一行和第二行是当前目录和上一级目录
files = files(3:end,:); % It means all files except ./.. are considered.
mat_names = cell(1,size(files, 1));%size(files, 1)返回files的行数,cell(1,n)返回1*n的元胞数组
nms_names = cell(1,size(files, 1));
for i = 1:size(files, 1)
mat_names{i} = files(i).name;
nms_names{i} = [files(i).name(1:end-4), '.png']; % Output PNG files.
end
for i = 1:size(mat_names,2)
matObj = matfile(fullfile(mat_dir, mat_names{i})); % Read MAT files.
varlist = who(matObj);%将文件名称存在元胞数组varlist中
x = matObj.(char(varlist));
if iscell(x)
x=x{1}
x = x.Boundaries; % 提取 'Boundaries' 字段的数值矩阵
end
E=convTri(single(x),1);
[Ox,Oy]=gradient2(convTri(E,4));
[Oxx,~]=gradient2(Ox); [Oxy,Oyy]=gradient2(Oy);
O=mod(atan(Oyy.*sign(-Oxy)./(Oxx+1e-5)),pi);
E=edgesNmsMex(E,O,1,5,1.01,4);
imwrite(uint8(E*255),fullfile(nms_dir, nms_names{i}))
end
这样输出的NMS结果就会保存到"data/算法名称/nms"文件夹中了。
2.1.3 评估代码
评估部分代码应该没有太大问题,可以直接使用,注意这里的gtDir是对应的真值mat格式的文件,比如你用BSDS的test文件夹中200张图片做的测试,就可以直接用BSDS官方给的真值文件进行计算,将文件路径改为你自己的路径即可,edgesEvalPlot函数是绘制PR曲线,在一张图上绘制多个算法的PR曲线可以参考plot-edge-pr-curves
% Section 2: Evaluate the edges (formerly EvalEdge.m from HED repo).
disp('Evaluate the edges...');
gtDir = 'data/groundTruth/test';
resDir = fullfile(data_dir, 'nms');
edgesEvalDir('resDir',resDir,'gtDir',gtDir, 'thin', 1, 'pDistr',{{'type','parfor'}},'maxDist',0.0075);
figure; edgesEvalPlot(resDir,'想要显式的名称');