
简介面向显著目标检测SOD研究的MATLAB显著性评估工具箱为算法验证与论文实验提供一站式量化方案。压缩包共38个文件以23个MATLAB脚本为核心辅以12张示例结果图、2个数据文件及1份说明文档整体仅101KB轻量便于迁移。工具箱集成E-measure、S-measure、加权F度量、F-measure、MAE分数、PR曲线及边界位移误差等主流评估指标只需准备显著图与真值图运行main.m即可批量输出分数与曲线并生成直观的可视化对比。针对不同数据集和算法场景附带README详细说明用法方便快速上手与二次改造。目前已有1455人学习/下载适合需要系统评估显着性模型性能、撰写论文或横向对比算法的研究者和开发者。1. 显著性对象检测评估工具箱到底解决什么问题显著性对象检测salient object detection的产出是一张概率图或二值图但“效果好不好”从来不是看一眼就能下结论的。同样一个模型在准确率上领先可能在边界完整度上一塌糊涂在整体像素上接近真值却在目标中心区域产生不可接受的偏差。这个标题里的工具箱把 E-measure、S-measure、加权 F、F-measure、MAE 和 PR 曲线全部收纳在一起本质上是在回答一个长期困扰测评者的核心问题当不同指标各说各话时怎么用一个统一流程获得可复现、可对比、有解释力的评测结果。这套指标组合不是随意拼凑。E-measure 关注边缘感知S-measure 关注结构相似性F-measure 和加权 F 评估像素级准确率与召回率的加权调和MAE 给出最直观的绝对误差PR 曲线则展示了阈值从 0 到 1 扫描时的整体性能。它们彼此补充单独使用任何一个都可能得出误导性结论。对于刚接触显著性检测的研究者这个工具箱能帮你省掉重复造轮子的时间对于已经跑过大量实验的工程师它提供了把预测结果与真值对齐、批量计算并在论文中呈现标准表格和曲线的完整路径。这篇文章会从指标定义讲起给出可直接复制的计算代码再讨论阈值扫描和批处理实战最后落在几个容易让人翻车的细节上。全程不依赖某个我不知道来源的开源项目所有公式和实现都是该领域公认的通用做法你完全可以用同样的逻辑把自己手头的预测图接进来。2. E-measure、S-measure、F-measure 与加权 F公式和适用场景2.1 从像素匹配到结构感知为什么要区分不同指标的侧重点显著性检测的评估历史是从像素级二元分类开始的。最初的 F-measure 直接比较预测二值图与真值图的逐像素匹配公式是 (F_\beta \frac{(1\beta^2) \cdot Precision \cdot Recall}{\beta^2 \cdot Precision Recall})其中 (\beta^2) 通常取 0.3意思是更看重准确率。这背后的逻辑是对检测任务来说把背景误判为目标的代价高于把目标漏掉一部分。然而 F-measure 有一个先天缺陷——它要求你先把预测概率图二值化而阈值的选择会显著影响结果所以单点 F-measure 并不能代表模型在所有阈值下的表现。PR 曲线通过扫描阈值解决了这个问题。2.1.1 自适应阈值与固定阈值在 PR 曲线中的角色PR 曲线的横轴是召回率纵轴是准确率。每设定一个阈值就把概率图大于阈值的像素置为目标从而计算出一对 Precision 和 Recall。常用的阈值集合有两类一是从 0 到 255 均匀取 256 个值二是直接用 (2 \times) 图像平均概率作为自适应阈值。这个工具箱同时支持这两种策略因为它们回答的问题不同。固定阈值扫描展示了模型在极端苛刻和极端宽松条件下的表现范围自适应阈值则模拟实际应用中“没有真值参考时通常怎么切”的默认做法。2.2 加权 F-measure纠正小目标和大目标的像素偏差标准 F-measure 对所有像素一视同仁但这在显著性检测里会带来偏差。假如一幅图里真值目标只占全图的 5%那么一个把所有像素都预测为背景的模型其准确率是 95%F-measure 也能达到很高的数值可这显然违背检测意图。加权 F-measure 的提出就是为了弱化这种大面积背景对指标的稀释作用。Margolin 等人提出的加权 F-measure 在计算 Precision 和 Recall 时根据像素与真值区域的距离赋予不同权重。具体做法是涉及相邻像素的权重 (\omega_i) 基于该像素属于前景或背景的连通性来调整让远离目标边界的正确背景像素贡献降低靠近边界的像素贡献提升。实现时不需要手动指定一个超参数而是通过生成一个与真值图同尺寸的权重矩阵来完成。常见的开源实现包括wFb函数输入预测概率图和真值图先计算对齐矩阵再得到加权 Precision 和 Recall最终合成 (F_\beta^w)。2.3 E-measure边缘感知的增强一致性指标E-measureEnhanced-alignment measure是 Fan 等人在 2018 年提出的用来解决传统 IoU 和 F-measure 对边缘不敏感的问题。它的核心思想是把预测图和真值图分别减去各自的全局均值得到两个“去均值”图然后计算它们的相关性公式为[ Q \frac{2 \cdot \text{cov}(x, y)}{\text{cov}(x, x) \text{cov}(y, y)} ]其中 (x) 和 (y) 是展平后的预测与真值概率向量。这个值越高说明整体形状和灰度分布越接近。E-measure 通常结合 4 邻域或 8 邻域的对齐策略得到局部与全局的增强相关性最终输出一个在 0 到 1 之间的得分。它在评估边界质量时比 IoU 敏感得多一个像素级偏移 2 像素的预测图IoU 可能只下降几个点E-measure 却会给出明显更低的分数。2.4 S-measure结构与区域感知的融合S-measureStructure measure由 Fan 等人在 2017 年提出目标是同时评估区域object-level和轮廓boundary-level的结构相似性。它把预测图与真值图的重叠部分分别计算两项区域相似度 (S_r) 和对象相似度 (S_o)然后加权合并[ S \alpha \cdot S_o (1 - \alpha) \cdot S_r ]这里的 (\alpha) 通常取 0.5代表两者同等重要。区域相似度实际是基于真值前景的均值池化然后计算预测图在该区域内的均值与真值在该区域内的均值之间的 IoU 变体轮廓相似度则通过 Canny 边缘或形态学梯度来比较边界像素的重合程度。S-measure 的优势在于它不依赖固定阈值可以直接在概率图上计算因此作为论文中的“结构分”常与 MAE 并列展示。2.5 MAE最笨也最稳定的绝对误差平均绝对误差Mean Absolute Error, MAE是所有指标里最直白的将预测概率图与真值二值图归一化到 [0,1] 后逐像素求差的绝对值再取平均[ MAE \frac{1}{W \times H} \sum_{i1}^{W} \sum_{j1}^{H} |P(i,j) - G(i,j)| ]MAE 的优点是不需要二值化直接衡量概率分布与真值分布的接近程度。它适合快速判断模型的总体偏离水平但缺点也明显当目标很小时即使目标内部误差巨大平均后也可能被背景的正确预测掩盖。因此 MAE 通常与 F-measure、E-measure 配合使用作为最底层的“兜底”指标。2.6 指标适用场景速查表指标输入要求输出范围主要用途对边缘敏感度F-measure概率图真值图需要阈值0~1像素级准确率/召回率权衡低加权 F概率图真值图自动权重0~1抑制大背景对F值的干扰中E-measure概率图真值图无需阈值0~1整体形状与灰度分布一致性高S-measure概率图真值图无需阈值0~1区域轮廓结构相似度中高MAE概率图真值图无需阈值0~1绝对误差平均水平低PR 曲线概率图真值图阈值扫描0~1曲线所有阈值下的综合性能中从表中可以看出E-measure 和 S-measure 是针对显著性检测提出的较新指标而 F-measure 和 MAE 来自通用分割与图像恢复领域。一个合格的评估工具箱应当支持这些指标在同一个脚本中批量计算而不是让用户手动拼接多个库。3. 用工具箱在本地跑通最小评估流程代码与命令3.1 环境准备与文件组织常见做法是使用 Python 3.8 以上环境依赖 NumPy、OpenCV 和 SciPy。如果你手头有一套预测图和真值图建议按下面的目录结构放置experiment/ ├── predictions/ │ ├── img1.png │ ├── img2.png │ └── ... ├── masks/ │ ├── img1.png │ └── img2.png └── evaluate.py预测图是模型输出的概率图像素值范围 0~255越亮代表目标可能性越高。真值图是二值图前景为白色背景为黑色。两者必须同名且尺寸一致否则对齐时会出现索引错位。3.2 核心指标计算的 Python 实现下面这段代码实现了 F-measure、加权 F、MAE 和 E-measure 的计算不依赖特定工具箱可直接保存为evaluate.py运行。S-measure 的实现稍长我会单独拆开讲解。import numpy as np import cv2 from scipy import ndimage def cal_mae(pred, gt): pred pred.astype(np.float32) / 255.0 gt gt.astype(np.float32) / 255.0 return np.mean(np.abs(pred - gt)) def cal_fmeasure(pred, gt, beta_sq0.3, threshold128): pred_bin (pred threshold).astype(np.uint8) gt_bin (gt threshold).astype(np.uint8) tp np.sum((pred_bin 1) (gt_bin 1)) fp np.sum((pred_bin 1) (gt_bin 0)) fn np.sum((pred_bin 0) (gt_bin 1)) precision tp / (tp fp 1e-8) recall tp / (tp fn 1e-8) f (1 beta_sq) * precision * recall / (beta_sq * precision recall 1e-8) return f, precision, recall def cal_emeasure(pred, gt): pred pred.astype(np.float32) / 255.0 gt gt.astype(np.float32) / 255.0 pred_m pred - np.mean(pred) gt_m gt - np.mean(gt) # 计算协方差和方差 cov np.mean(pred_m * gt_m) var_pred np.mean(pred_m ** 2) var_gt np.mean(gt_m ** 2) emeasure 2 * cov / (var_pred var_gt 1e-8) return emeasure def cal_weighted_f(pred, gt): # 简化实现使用连通域加权 # 实际中需要更精细的权重矩阵这里演示思路 pred pred.astype(np.float32) / 255.0 gt gt.astype(np.float32) / 255.0 # 基于真值图生成距离权重 dist ndimage.distance_transform_edt(gt 0) max_dist np.max(dist) 1e-8 weight 1 - dist / max_dist # 计算加权精度和召回 pred_bin (pred 128).astype(np.float32) tp_w np.sum(weight * pred_bin * gt) fp_w np.sum(weight * pred_bin * (1 - gt)) fn_w np.sum(weight * (1 - pred_bin) * gt) precision_w tp_w / (tp_w fp_w 1e-8) recall_w tp_w / (tp_w fn_w 1e-8) f_w (1.3 * precision_w * recall_w) / (0.3 * precision_w recall_w 1e-8) return f_w代码逻辑解释cal_mae直接对归一化后的图像做逐像素差绝对值的均值没有任何参数需要调整。cal_fmeasure接受一个固定的阈值参数默认 128你可以把它改成自适应阈值。cal_emeasure先减去各自的均值相当于去除亮度偏移的影响然后计算两个矩阵之间的归一化内积数值越接近 1 代表预测图和真值图的形状越相关。cal_weighted_f用距离变换生成权重矩阵离前景边界越远的背景像素权重越低从而降低大面积背景对指标的影响。函数名输入参数输出注意事项cal_maepred, gt标量两张图必须尺寸一致uint8 输入需先归一化cal_fmeasurepred, gt, beta_sq, thresholdf, precision, recall阈值选择会显著影响结果cal_emeasurepred, gt标量对均值偏移不敏感cal_weighted_fpred, gt标量距离变换需要 scipy 支持3.3 S-measure 的完整实现与参数含义S-measure 的实现相对复杂需要区分区域项和轮廓项。区域项通过计算前景/背景均值差异得到轮廓项则依赖形态学梯度。这是简化但可用的版本def cal_smeasure(pred, gt, alpha0.5): pred pred.astype(np.float32) / 255.0 gt gt.astype(np.float32) / 255.0 # 区域相似度比较前景区域均值差异 fg_pred pred[gt 0.5] bg_pred pred[gt 0.5] fg_mean np.mean(fg_pred) if fg_pred.size 0 else 0 bg_mean np.mean(bg_pred) if bg_pred.size 0 else 0 r (fg_mean - bg_mean) / (fg_mean bg_mean 1e-8) # 轮廓相似度用形态学梯度提取边界 gt_grad cv2.morphologyEx(gt.astype(np.uint8), cv2.MORPH_GRADIENT, np.ones((3,3), np.uint8)) pred_grad cv2.morphologyEx(pred.astype(np.uint8), cv2.MORPH_GRADIENT, np.ones((3,3), np.uint8)) # 计算轮廓区域的重叠度 inter np.sum(gt_grad * pred_grad) union np.sum(gt_grad) np.sum(pred_grad) 1e-8 obj_s 2 * inter / union # 合并 s alpha * obj_s (1 - alpha) * r return s这里的alpha控制轮廓和区域的重要性论文中默认取 0.5。你可以通过改变alpha来观察模型在不同结构维度上的差异当你的模型侧重捕捉整体形状时调大alpha如果主要优化边界贴合度则调小alpha。不过实际评估中为了不同模型可比最好固定为 0.5。3.4 批量计算与结果汇总单张图计算没意义实际使用时需要遍历整个测试集并保存汇总结果。下面的代码演示如何循环读取目录下的所有图片并将各指标写入 CSV 文件import os, csv, glob pred_dir predictions mask_dir masks output_csv metrics.csv image_paths glob.glob(os.path.join(pred_dir, *.png)) with open(output_csv, w, newline) as f: writer csv.writer(f) writer.writerow([image, mae, fmeasure, emeasure, smeasure, wf]) for pred_path in sorted(image_paths): name os.path.basename(pred_path) mask_path os.path.join(mask_dir, name) if not os.path.exists(mask_path): continue pred cv2.imread(pred_path, cv2.IMREAD_GRAYSCALE) gt cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE) if pred.shape ! gt.shape: gt cv2.resize(gt, (pred.shape[1], pred.shape[0])) mae cal_mae(pred, gt) f, _, _ cal_fmeasure(pred, gt, threshold128) emeasure cal_emeasure(pred, gt) smeasure cal_smeasure(pred, gt) wf cal_weighted_f(pred, gt) writer.writerow([name, mae, f, emeasure, smeasure, wf]) print(f{name}: MAE{mae:.4f}, F{f:.4f}, E{emeasure:.4f}, S{smeasure:.4f}, wF{wf:.4f})执行python evaluate.py后你会在当前目录得到metrics.csv可以直接导入 Excel 或 pandas 做统计分析。4. PR 曲线与 MAE从单张图到数据集级别的评估4.1 为什么单点指标容易骗人假设你只计算了阈值 128 下的 F-measure两个模型可能恰好得到相同的分但它们的概率分布差异极大一个在目标处输出接近 255 的高置信度另一个则在大部分像素上徘徊在 150 左右。阈值一抬高高置信度模型依然准确低置信度模型立刻失效。PR 曲线通过扫描全部阈值直观展示这种差异曲线的纵轴越高、整体越靠近右上角说明模型在不同严格程度下都保持稳定。绘制 PR 曲线需要先在所有图像上收集每个阈值下的总体 Precision 和 Recall。这里有一个常见误区不能对每张图单独画曲线再求平均而是应该把所有图像的像素统计汇总后再计算这样才符合数据集的整体性能评估。4.2 用 Python 生成 PR 曲线数据下面的代码会在 256 个阈值上扫描整个数据集并输出每个阈值对应的精度和召回率def compute_pr_curve(pred_paths, gt_paths, num_thresholds256): precisions [] recalls [] for t in range(num_thresholds): t t * 255 // (num_thresholds - 1) tp_total, fp_total, fn_total 0, 0, 0 for pred_path, gt_path in zip(pred_paths, gt_paths): pred cv2.imread(pred_path, cv2.IMREAD_GRAYSCALE) gt cv2.imread(gt_path, cv2.IMREAD_GRAYSCALE) if pred.shape ! gt.shape: gt cv2.resize(gt, (pred.shape[1], pred.shape[0])) pred_bin (pred t).astype(np.uint8) gt_bin (gt 128).astype(np.uint8) # 真值图通常是0或255 tp_total np.sum((pred_bin 1) (gt_bin 1)) fp_total np.sum((pred_bin 1) (gt_bin 0)) fn_total np.sum((pred_bin 0) (gt_bin 1)) prec tp_total / (tp_total fp_total 1e-8) rec tp_total / (tp_total fn_total 1e-8) precisions.append(prec) recalls.append(rec) return recalls, precisions注意真值图二值化时阈值应取 128因为真值本身就是 0 或 255。如果直接用gt t当扫描阈值超过 128 时真值会全部变成背景这是最常犯的错误。4.3 计算并绘制 PR 曲线得到 recalls 和 precisions 后用 Matplotlib 绘制即可import matplotlib.pyplot as plt recalls, precisions compute_pr_curve(pred_paths, mask_paths) plt.plot(recalls, precisions, linewidth2, labelmodel) plt.xlabel(Recall) plt.ylabel(Precision) plt.xlim(0, 1) plt.ylim(0, 1) plt.grid(True) plt.legend() plt.savefig(pr_curve.png, dpi150)如果你的模型在低召回率区域也有高精度说明它的高置信度预测非常可靠如果曲线只能在召回率很低的地方维持精度说明模型虽然能检出少数显著性区域但容易误报。PR 曲线下面积AP 值是另一个常用指标可以通过np.trapz(precisions, recalls)近似计算。4.4 MAE 在数据集级别上的正确聚合方式MAE 的聚合有两种做法一种是先对每张图计算 MAE再对所有图取平均即 mean of MAE另一种是先把所有图的预测与真值像素堆叠成一个巨大矩阵再统一计算 MAE。两者理论上不等价但标准评估协议通常采用后者——也就是把所有图片的所有像素放在一起统计。这样计算出的 MAE 更能反映数据集整体的像素级误差避免小图对结果产生过大影响。all_pred [] all_gt [] for pred_path, gt_path in zip(pred_paths, mask_paths): pred cv2.imread(pred_path, cv2.IMREAD_GRAYSCALE).astype(np.float32) / 255.0 gt cv2.imread(gt_path, cv2.IMREAD_GRAYSCALE).astype(np.float32) / 255.0 all_pred.append(pred.reshape(-1)) all_gt.append(gt.reshape(-1)) all_pred np.concatenate(all_pred) all_gt np.concatenate(all_gt) mae_dataset np.mean(np.abs(all_pred - all_gt))注意这样计算需要确保所有图像尺寸一致如果不一致建议统一 resize 到相同尺寸后再拼接。多数公开数据集如 DUTS、ECSSD 都有统一的分辨率实际问题不大。4.5 指标异常时的诊断思路当你看到 F-measure 很高但 E-measure 很低说明预测图与真值在整体形状上不一致可能存在位置偏移或尺度缩放。当 S-measure 高而 MAE 高时意味着结构相似但内部灰度偏差严重常见于模型预测的概率不够饱和。PR 曲线整体偏低且波动大往往表示模型输出的概率值范围没有铺满 0~255例如被激活函数限制在 [0.3, 0.8] 区间。这种问题可以在评估前使用直方图均衡化预处理来检查。5. 进阶技巧阈值自动选择与指标之间的交叉验证5.1 自适应阈值让 F-measure 更接近应用直觉固定阈值 128 适用于概率图均衡分布的场景但很多模型输出的概率图整体偏移比如背景概率在 0.6 以上。此时固定阈值会让 Precision 虚高。自适应阈值的公式是[ T \frac{2}{W \times H} \sum_{i,j} P(i,j) ]也就是图像全局平均概率的两倍。用这个阈值替代固定值可以让 F-measure 反映模型自身的分割特性而不需要人为挑选数值。在代码中实现只需要替换cal_fmeasure的threshold参数threshold 2 * np.mean(pred) # 每张图单独计算 f, p, r cal_fmeasure(pred, gt, thresholdthreshold)5.2 加权 F 的权重矩阵优化前面展示的cal_weighted_f是一个简化版本。更精确的加权 F 需要为每个像素计算一个基于真值区域的权重通常使用下面的策略对于真值前景内部离边界较远的像素赋予较高权重因为这些像素的分类相对容易背景离边界近的像素权重也较高因为那里是误报高发区。生成方法可以是前景欧氏距离图与背景距离图归一化后相加def generate_weight_matrix(gt_bin): fg_dist ndimage.distance_transform_edt(gt_bin) bg_dist ndimage.distance_transform_edt(1 - gt_bin) fg_dist fg_dist / (fg_dist.max() 1e-8) bg_dist bg_dist / (bg_dist.max() 1e-8) weight fg_dist bg_dist return weight使用这个权重矩阵替换掉之前基于距离的简单版本加权 F 对边缘定位的灵敏度会明显提升但计算开销也更高。5.3 通过指标交叉定位模型缺陷一个实用的诊断方法是绘制 E-measure 与 S-measure 的散点图。若某个模型位于左下方说明它无论是在边缘还是区域结构上都偏离真值若 E 高 S 低说明边缘不错但区域内部有空洞E 低 S 高则代表整体结构相似但边缘模糊。把这些交叉比较做成二维表格可以快速筛出需要针对性调整的模型。模型E-measureS-measureMAE可能的缺陷A0.8930.8610.041结构稳定边界锐利整体良好B0.9120.7420.063边缘不错但区域内部偏薄C0.7210.8840.052区域完整但边界不锐利D0.6550.6020.098整体偏离真值需检查对齐5.4 确保结果可复现的三个硬性条件最后提醒三点。第一所有指标计算前必须将预测图和真值图转为灰度图并用相同插值方式 resize 到统一尺寸推荐使用最近邻插值避免引入额外灰度平滑。第二PR 曲线扫描的阈值步长要保持一致论文报告时注明 256 个阈值或 1024 个阈值否则不同来源的结果无法对比。第三加权 F 的实现版本众多有的用 4 邻域距离有的用 8 邻域务必在代码注释中记录权重生成方式方便他人复现你的实验。把 E-measure、S-measure、加权 F 与常规 F-measure、MAE 和 PR 曲线组合在同一套评估流程中最大的好处是能同时覆盖像素、区域、轮廓和综合分布四个层级。下一次拿到一组预测结果时不妨先跑一遍批量脚本生成所有指标再根据异常值去定位问题所在——这比单纯盯着一个 F-measure 数字要有效得多。本文还有配套的精品资源点击获取