👀🎉📜系列文章目录
【yolov5小技巧(1)】—可视化并统计目标检测中的TP、FP、FN
🚀🚀🚀前言
在论文中我们经常可以见到提取的物体特征以热力图的形式展示出来,首先我们要了解为什么要将特征图以热力图的形式进行可视化,将特征图以热力图的方式进行可视化在深度学习中有几个重要的原因:
- 强调激活区域 :热力图可以帮助我们直观地理解哪些区域对于模型的决策是最重要的。通过热力图,我们可以看到特征图中哪些区域具有较高的激活值,从而了解模型对于不同特征的关注程度。
- 可视化激活模式 :热力图可以展示模型在特定任务或数据集上学到的激活模式。例如,在图像分类任务中,热力图可以显示模型对于不同物体或区域的激活情况,从而帮助我们理解模型的决策依据。
- 可视化网络学习的过程 :通过在训练过程中可视化特征图的热力图,我们可以观察模型随着训练的进行而学习到的特征的变化。这有助于我们理解模型是如何逐步改进并适应数据的。
- 提高解释性和可解释性 :热力图可以帮助解释深度学习模型的决策过程。通过展示模型在输入数据上的激活情况,我们可以更好地理解模型是如何基于输入数据做出预测或分类的。
- 直观的可视化方式 :热力图是一种直观的可视化方式,易于理解和解释。通过将特征图表示为颜色密度较高的区域,我们可以更直观地理解模型对于不同区域的关注程度。
一、1️⃣ 将特征图可视化的文章CFPNet
论文源码:Centralized Feature Pyramid for Object Detection
CFPNet代码链接:https://github.com/QY1994-0919/CFPNet
在特征图可视化方面可以参考CFPNet这篇文章,下图是不同的网络特征进行热力图可视化的一个结果。
🔥🔥🔥这幅图是视觉识别任务中图像特征演化的可视化。对于(a)中的输入图像,(b)中的CNN模型只定位那些判别性最强的区域;虽然©中的递进模型在注意机制或变压器transformer的帮助下可以看到更宽的范围,但它通常忽略了对密集预测任务很重要的角落线索;(d)中的模型通过将集中约束附加到具有高级远程依赖关系的特征上,不仅可以看到更广泛,而且更全面,更适合于密集预测任务。
二、2️⃣yolov5自带的特征图可视化工具
📌其实在yolov5中的detect.py推理代码中只需要修改一个参数,就可以将输入图片经过每一层网络的特征可视化图保存下来。该参数是visualize
,只需要将visualize参数默认为True,修改如下:
parser.add_argument('--visualize', default=True, action='store_true', help='visualize features')
☀️☀️☀️将该参数设置默认为True之后,再对目标图片进行推理,在runs文件夹下的detect文件夹下除了会生成一个检测的图片,还会再单独生成一个文件夹,该文件保存的是每一层网络对该图片的特征可视化图。
三、3️⃣如何将特征图转换成热力图
那么如何将可视化的特征图以热力图的形式进行保存。首先找到utils
文件夹下的plots.py
文件,在该文件中找到feature_visualization
函数,将该函数内容全部注释掉,用下面这段代码代替原代码:
def feature_visualization(x, module_type, stage, n=32, save_dir=Path('runs/detect/exp')):
"""
x: Features to be visualized
module_type: Module type
stage: Module stage within model
n: Maximum number of feature maps to plot
save_dir: Directory to save results
"""
if 'Detect' not in module_type: # 如果不是检测模块
batch, channels, height, width = x.shape # 获取特征张量的形状信息(批量大小、通道数、高度、宽度)
if height > 1 and width > 1: # 如果特征图的高度和宽度都大于1
f = save_dir / f"stage{stage}_{module_type.split('.')[-1]}_features.png" # 生成保存结果的文件名
blocks = torch.chunk(x[0].cpu(), channels, dim=0) # 将特征张量按通道数分块,选择批量索引为0的部分
n = min(n, channels) # 确定要绘制的特征图数量
fig, ax = plt.subplots(math.ceil(n / 8), 8, tight_layout=True) # 创建子图网格,以8列的形式排列绘制的特征图
ax = ax.ravel() # 将子图数组展平为一维
plt.subplots_adjust(wspace=0.05, hspace=0.05) # 设置子图之间的水平和垂直间距
for i in range(n): # 遍历要绘制的特征图数量
block = blocks[i].squeeze().detach().numpy() # 提取并转换为NumPy数组
block = (block - np.min(block)) / (np.max(block) - np.min(block)) # 归一化处理
temp = np.array(block * 255.0, dtype=np.uint8) # 将数据缩放到0-255,并转换为8位无符号整数
temp = cv2.applyColorMap(temp, cv2.COLORMAP_JET) # 应用热力图颜色映射
ax[i].imshow(temp, cmap=plt.cm.jet) # 绘制热力图
ax[i].axis('off') # 关闭坐标轴显示
LOGGER.info(f'Saving {f}... ({n}/{channels})') # 记录保存结果的信息
plt.savefig(f, dpi=300, bbox_inches='tight') # 保存结果图像
plt.close() # 关闭图形
np.save(str(f.with_suffix('.npy')), x[0].cpu().numpy()) # 将结果保存为NumPy数组
这段代码与yolov5原代码的主要区别是在for循环里面,在循环里面添加了热力图颜色映射。
🚀🚀接下来我们重新运行detect.py代码,发现经过卷积神经网络之后的特征图全部都以热力图的形式展示出来。下面第一幅图展示的是经过第一层网络之后的特征热力图;随着网络的加深,特征图所展现出来的特征就越难区分,例如第二幅图是经过最后一层网络之后的特征热力图,已经无法用肉眼去区分疵点的位置和特征。