基于YOLOv8的热力图生成与可视化:支持自定义模型与置信度阈值的多维度分析

news2025/4/5 19:11:57

目标检测是计算机视觉领域的重要研究方向,而YOLO(You Only Look Once)系列算法因其高效性和准确性成为该领域的代表性方法。YOLOv8作为YOLO系列的最新版本,在目标检测任务中表现出色。然而,传统的目标检测结果通常以边界框和类别标签的形式呈现,缺乏对模型决策过程的直观解释。热力图作为一种可视化工具,能够直观地展示模型在图像中的关注区域,为模型的可解释性提供了重要支持。本文将探讨基于YOLOv8的热力图生成与可视化方法,并支持自定义模型与置信度阈值的多维度分析。

1. YOLOv8与热力图生成技术概述

1.1 YOLOv8的核心特性

YOLOv8在YOLO系列的基础上进行了多项优化,包括:

  1. 更高效的网络结构:采用CSP(Cross Stage Partial)架构,提升特征提取能力。
  2. 更精确的检测性能:通过改进的损失函数和训练策略,提高目标检测的准确性。
  3. 更灵活的部署支持:支持多种硬件平台和推理框架,便于实际应用。

1.2 热力图生成的基本原理

热力图是一种通过颜色梯度表示数据分布的可视化方法。在目标检测中,热力图通常用于展示模型对图像中不同区域的关注程度。生成热力图的核心步骤包括:

  1. 特征提取:从YOLOv8的中间层提取特征图。
  2. 权重计算:根据模型的输出计算每个像素的权重。
  3. 可视化映射:将权重映射为颜色梯度,生成热力图。

2. 项目源代码

import warnings
warnings.filterwarnings('ignore')
warnings.simplefilter('ignore')
import torch, yaml, cv2, os, shutil
import numpy as np
np.random.seed(0)
import matplotlib.pyplot as plt
from tqdm import trange
from PIL import Image
from ultralytics.nn.tasks import DetectionModel as Model
from ultralytics.utils.torch_utils import intersect_dicts
from ultralytics.utils.ops import xywh2xyxy
from pytorch_grad_cam import GradCAMPlusPlus, GradCAM, XGradCAM
from pytorch_grad_cam.utils.image import show_cam_on_image
from pytorch_grad_cam.activations_and_gradients import ActivationsAndGradients
 
def letterbox(im, new_shape=(640, 640), color=(114, 114, 114), auto=True, scaleFill=False, scaleup=True, stride=32):
    # Resize and pad image while meeting stride-multiple constraints
    shape = im.shape[:2]  # current shape [height, width]
    if isinstance(new_shape, int):
        new_shape = (new_shape, new_shape)
 
    # Scale ratio (new / old)
    r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
    if not scaleup:  # only scale down, do not scale up (for better val mAP)
        r = min(r, 1.0)
 
    # Compute padding
    ratio = r, r  # width, height ratios
    new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
    dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1]  # wh padding
    if auto:  # minimum rectangle
        dw, dh = np.mod(dw, stride), np.mod(dh, stride)  # wh padding
    elif scaleFill:  # stretch
        dw, dh = 0.0, 0.0
        new_unpad = (new_shape[1], new_shape[0])
        ratio = new_shape[1] / shape[1], new_shape[0] / shape[0]  # width, height ratios
 
    dw /= 2  # divide padding into 2 sides
    dh /= 2
 
    if shape[::-1] != new_unpad:  # resize
        im = cv2.resize(im, new_unpad, interpolation=cv2.INTER_LINEAR)
    top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
    left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
    im = cv2.copyMakeBorder(im, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color)  # add border
    return im, ratio, (dw, dh)
 
class yolov8_heatmap:
    def __init__(self, weight, cfg, device, method, layer, backward_type, conf_threshold, ratio):
        device = torch.device(device)
        ckpt = torch.load(weight)
        model_names = ckpt['model'].names
        csd = ckpt['model'].float().state_dict()  # checkpoint state_dict as FP32
        model = Model(cfg, ch=3, nc=len(model_names)).to(device)
        csd = intersect_dicts(csd, model.state_dict(), exclude=['anchor'])  # intersect
        model.load_state_dict(csd, strict=False)  # load
        model.eval()
        print(f'Transferred {len(csd)}/{len(model.state_dict())} items')
        
        target_layers = [eval(layer)]
        method = eval(method)
 
        colors = np.random.uniform(0, 255, size=(len(model_names), 3)).astype(np.int32)
        self.__dict__.update(locals())
    
    def post_process(self, result):
        logits_ = result[:, 4:]
        boxes_ = result[:, :4]
        sorted, indices = torch.sort(logits_.max(1)[0], descending=True)
        return torch.transpose(logits_[0], dim0=0, dim1=1)[indices[0]], torch.transpose(boxes_[0], dim0=0, dim1=1)[indices[0]], xywh2xyxy(torch.transpose(boxes_[0], dim0=0, dim1=1)[indices[0]]).cpu().detach().numpy()
    
    def draw_detections(self, box, color, name, img):
        xmin, ymin, xmax, ymax = list(map(int, list(box)))
        cv2.rectangle(img, (xmin, ymin), (xmax, ymax), tuple(int(x) for x in color), 2)
        cv2.putText(img, str(name), (xmin, ymin - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.8, tuple(int(x) for x in color), 2, lineType=cv2.LINE_AA)
        return img
 
    def __call__(self, img_path, save_path):
        # remove dir if exist
        if os.path.exists(save_path):
            shutil.rmtree(save_path)
        # make dir if not exist
        os.makedirs(save_path, exist_ok=True)
 
        # img process
        img = cv2.imread(img_path)
        img = letterbox(img)[0]
        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        img = np.float32(img) / 255.0
        tensor = torch.from_numpy(np.transpose(img, axes=[2, 0, 1])).unsqueeze(0).to(self.device)
 
        # init ActivationsAndGradients
        grads = ActivationsAndGradients(self.model, self.target_layers, reshape_transform=None)
 
        # get ActivationsAndResult
        result = grads(tensor)
        activations = grads.activations[0].cpu().detach().numpy()
 
        # postprocess to yolo output
        post_result, pre_post_boxes, post_boxes = self.post_process(result[0])
        for i in trange(int(post_result.size(0) * self.ratio)):
            if float(post_result[i].max()) < self.conf_threshold:
                break
 
            self.model.zero_grad()
            # get max probability for this prediction
            if self.backward_type == 'class' or self.backward_type == 'all':
                score = post_result[i].max()
                score.backward(retain_graph=True)
 
            if self.backward_type == 'box' or self.backward_type == 'all':
                for j in range(4):
                    score = pre_post_boxes[i, j]
                    score.backward(retain_graph=True)
 
            # process heatmap
            if self.backward_type == 'class':
                gradients = grads.gradients[0]
            elif self.backward_type == 'box':
                gradients = grads.gradients[0] + grads.gradients[1] + grads.gradients[2] + grads.gradients[3]
            else:
                gradients = grads.gradients[0] + grads.gradients[1] + grads.gradients[2] + grads.gradients[3] + grads.gradients[4]
            b, k, u, v = gradients.size()
            weights = self.method.get_cam_weights(self.method, None, None, None, activations, gradients.detach().numpy())
            weights = weights.reshape((b, k, 1, 1))
            saliency_map = np.sum(weights * activations, axis=1)
            saliency_map = np.squeeze(np.maximum(saliency_map, 0))
            saliency_map = cv2.resize(saliency_map, (tensor.size(3), tensor.size(2)))
            saliency_map_min, saliency_map_max = saliency_map.min(), saliency_map.max()
            if (saliency_map_max - saliency_map_min) == 0:
                continue
            saliency_map = (saliency_map - saliency_map_min) / (saliency_map_max - saliency_map_min)
 
            # add heatmap and box to image
            cam_image = show_cam_on_image(img.copy(), saliency_map, use_rgb=True)
            "不想在图片中绘画出边界框和置信度,注释下面的一行代码即可"
            cam_image = self.draw_detections(post_boxes[i], self.colors[int(post_result[i, :].argmax())], f'{self.model_names[int(post_result[i, :].argmax())]} {float(post_result[i].max()):.2f}', cam_image)
            cam_image = Image.fromarray(cam_image)
            cam_image.save(f'{save_path}/{i}.png')
 
def get_params():
    params = {
        'weight': 'yolov8n.pt',   # 训练出来的权重文件
        'cfg': 'ultralytics/cfg/models/v8/yolov8n.yaml',  # 训练权重对应的yaml配置文件
        'device': 'cuda:0',
        'method': 'GradCAM', # GradCAMPlusPlus, GradCAM, XGradCAM , 使用的热力图库文件不同的效果不一样可以多尝试
        'layer': 'model.model[9]',  # 想要检测的对应层
        'backward_type': 'all', # class, box, all
        'conf_threshold': 0.01, # 0.6  # 置信度阈值,有的时候你的进度条到一半就停止了就是因为没有高于此值的了
        'ratio': 0.02 # 0.02-0.1
    }
    return params
 
if __name__ == '__main__':
    model = yolov8_heatmap(**get_params())
    model(r'ultralytics/assets/10.jpg', 'result')  # 第一个是检测的文件, 第二个是保存的路径
参数名参数类型解释
weightstr训练出来的权重文件路径,用于加载训练好的模型。
cfgstr训练权重对应的YAML配置文件路径,定义了模型的结构和参数。
devicestr指定运行设备,例如 cuda:0 表示使用第一个GPU,cpu 表示使用CPU。
methodstr使用的热力图生成方法,例如 GradCAM、GradCAMPlusPlus、XGradCAM。
layerstr想要检测的模型层,例如 model.model[11],表示检测第11层。
backward_typestr反向传播类型,可选 class(类别)、box(边界框)、all(全部)。
conf_thresholdfloat置信度阈值,过滤低于该值的检测结果,默认值为 0.9。
ratiofloat热力图与原图的叠加比例,取值范围为 0.02-0.1,默认值为 0.3。

在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2327780.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Design Compiler:库特征分析(ALIB)

相关阅读 Design Compilerhttps://blog.csdn.net/weixin_45791458/category_12738116.html?spm1001.2014.3001.5482 简介 在使用Design Compiler时&#xff0c;可以对目标逻辑库进行特征分析&#xff0c;并创建一个称为ALIB的伪库&#xff08;可以被认为是缓存&#xff09;&…

便携式雷达信号模拟器 —— 打造实战化电磁环境的新利器

在现代战争中&#xff0c;雷达信号的侦察与干扰能力直接关系到作战的成败。为了提升雷达侦察与干扰装备的实战能力&#xff0c;便携式雷达信号模拟器作为一款高性能设备应运而生&#xff0c;为雷达装备的训练、测试和科研提供了不可或缺的支持。 核心功能 便携式雷达信号模拟…

CentOS Linux升级内核kernel方法

目录 一、背景 二、准备工作 三、升级内核 一、背景 某些情况需要对Linux发行版自带的内核kernel可能版本较低&#xff0c;需要对内核kernel进行升级。例如&#xff1a;CentOS 7.x 版本的系统默认内核是3.10.0&#xff0c;该版本的内核在Kubernetes社区有很多已知的Bug&#…

【C++】多态功能细节问题分析

多态是在不同继承关系的类对象去调用同一函数&#xff0c;产生了不同的行为。值得注意的是&#xff0c;虽然多态在功能上与隐藏是类似的&#xff0c;但是还是有较大区别的&#xff0c;本文也会进行多态和隐藏的差异分析。 在继承中要构成多态的条件 1.1必须通过基类的指针或引用…

EIP-712:类型化结构化数据的哈希与签名

1. 引言 以太坊 EIP-712: 类型化结构化数据的哈希与签名&#xff0c;是一种用于对类型化结构化数据&#xff08;而不仅仅是字节串&#xff09;进行哈希和签名 的标准。 其包括&#xff1a; 编码函数正确性的理论框架&#xff0c;类似于 Solidity 结构体并兼容的结构化数据规…

基于S函数的simulink仿真

基于S函数的simulink仿真 S函数可以用计算机语言来描述动态系统。在控制系统设计中&#xff0c;S函数可以用来描述控制算法、自适应算法和模型动力学方程。 S函数中使用文本方式输入公式和方程&#xff0c;适合复杂动态系统的数学描述&#xff0c;并且在仿真过程中可以对仿真…

每日一题洛谷P8664 [蓝桥杯 2018 省 A] 付账问题c++

P8664 [蓝桥杯 2018 省 A] 付账问题 - 洛谷 (luogu.com.cn) 思路&#xff1a;要使方差小&#xff0c;那么钱不能一下付的太多&#xff0c;可以让钱少的全付玩&#xff0c;剩下还需要的钱再让钱多的付&#xff08;把钱少的补上&#xff09;。 将钱排序&#xff0c;遍历一遍&…

迅饶科技X2Modbus网关-GetUser信息泄露漏洞

免责声明&#xff1a;本号提供的网络安全信息仅供参考&#xff0c;不构成专业建议。作者不对任何由于使用本文信息而导致的直接或间接损害承担责任。如涉及侵权&#xff0c;请及时与我联系&#xff0c;我将尽快处理并删除相关内容。 漏洞描述 该漏洞的存在是由于GetUser接口在…

关于inode,dentry结合软链接及硬链接的实验

一、背景 在之前的博客 缺页异常导致的iowait打印出相关文件的绝对路径-CSDN博客 里 2.2.3 一节里&#xff0c;我们讲到了file&#xff0c;fd&#xff0c;inode&#xff0c;dentry&#xff0c;super_block这几个概念&#xff0c;在这篇博客里&#xff0c;我们针对inode和dentr…

PandasAI:当数据分析遇上自然语言处理

数据科学的新范式 在数据爆炸的时代&#xff0c;传统的数据分析工具正面临着前所未有的挑战。数据科学家们常常需要花费70%的时间在数据清洗和探索上&#xff0c;而真正的价值创造时间却被大幅压缩。PandasAI的出现&#xff0c;正在改变这一现状——它将生成式AI的强大能力注入…

Unity网络开发基础 (3) Socket入门 TCP同步连接 与 简单封装练习

本文章不作任何商业用途 仅作学习与交流 教程来自Unity唐老狮 关于练习题部分是我观看教程之后自己实现 所以和老师写法可能不太一样 唐老师说掌握其基本思路即可,因为前端程序一般不需要去写后端逻辑 1.认识Socket的重要API Socket是什么 Socket&#xff08;套接字&#xff0…

做题记录:和为K的子数组

来自leetcode 560 前言 自己只会暴力&#xff0c;这里就是记录一下前缀和哈希表的做法&#xff0c;来自灵神的前缀和哈希表&#xff1a;从两次遍历到一次遍历&#xff0c;附变形题 正文 首先&#xff0c;这道题无法使用滑动窗口&#xff0c;因为滑动窗口需要满足单调性&am…

VMware虚拟机卡顿、CPU利用率低、编译Linux内核慢,问题解决与实验对比

目录 一、总结在前面&#xff08;节约时间就只看这里&#xff09;0 环境说明1 遇到的问题&#xff1a;2 问题的原因&#xff1a;3 解决办法&#xff1a;4 实验验证&#xff1a;5 关于虚拟机内核数量设置6 关于强行指定Vm能用的CPU内核 二、管理员启动&#xff0c;实验对比实验1…

【7】数据结构的队列篇章

目录标题 队列的定义顺序队列的实现初始化入队出队顺序队列总代码与调试 循环队列的实现初始化入队出队获取队首元素循环队列总代码与调试 链式队列的实现链式队列的初始化入队出队获取队首元素链式队列总代码与调试 队列的定义 定义&#xff1a;队列&#xff08;Queue&#x…

颜色归一化操作

当我们不太关注图像具体细节&#xff0c;只关注图像大致的内容时&#xff0c;为了避免光照角度、光照强度对图像的影响&#xff0c;可以采用下面进行归一化操作。这种颜色系统具有通道对表面方向、照明方向具有鲁棒性的特性&#xff0c;适用于图像分割等领域&#xff0c;在机器…

深度学习处理文本(6)

理解词嵌入 重要的是&#xff0c;进行one-hot编码时&#xff0c;你做了一个与特征工程有关的决策。你向模型中注入了有关特征空间结构的基本假设。这个假设是&#xff1a;你所编码的不同词元之间是相互独立的。事实上&#xff0c;one-hot向量之间都是相互正交的。对于单词而言…

STL-vector的使用

1.STL-vector 向量是可以改变其大小的线性序列容器。向量使用连续的空间存储元素&#xff0c;表明向量可以像数组通过下标来访问元素&#xff0c;但是向量的大小可以动态变化。向量的容量可能大于其元素需要的实际容量&#xff0c;向量通过消耗更多的内存来换取存储管理效率。…

MySQL深入

体系结构 连接层&#xff1a;主要处理客户端的连接进行授权认证、校验权限等相关操作 服务层&#xff1a;如sql的接口、解析、优化在这里完成&#xff0c;所有跨存储引擎的操作在这里完成 引擎层&#xff1a;索引是在存储引擎层实现的&#xff0c;所以不同的存储引擎他的索引…

Genspark:重新定义搜索体验的AI智能体引擎

关于我们 飞书-华彬智融知识库 由前百度高管景鲲&#xff08;Eric Jing&#xff09;和朱凯华&#xff08;Kay Zhu&#xff09;联合创立的AI搜索引擎Genspark&#xff0c;正以革命性的技术架构和用户导向的设计理念&#xff0c;为全球用户带来一场搜索体验的范式革命。本文将基…

从零实现Json-Rpc框架】- 项目实现 - 服务端主题实现及整体封装

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;博客仓库&#xff1a;https://gitee.com/JohnKingW/linux_test/tree/master/lesson &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &…