可视化目标检测算法推理部署(二)YOLOv8模型图像推理

news2024/11/26 22:41:49

在先前的RT-DETR中,博主使用ONNX模型文件进行了视频、图像的推理,在本章节,博主打算使用YOLOv8模型进行推理,因此,我们除了需要获取YOLOv8ONNX模型文件外,还需要进行一些额外的操作,如NMS后处理过程,其详细实现过程如下:

YOLOv8模型导出

YOLOv8的官方项目中,新建export.py,写入如下代码即可导出yolov8n,onnx文件

from ultralytics import YOLO
model = YOLO("D:\graduate\programs\yolo8/ultralytics-main\yolov8n.pt")
model.export(format="onnx")

Gradio推理UI设计

这里,我们先使用Gradio进行推理界面的搭建,其输入与输出均为图像

import YOLODet
import gradio as gr
import cv2
model = 'yolov8n.onnx'
yolo_det = YOLODet.YOLODet(model, conf_thres=0.5, iou_thres=0.3)

def det_img(cv_src):
    yolo_det(cv_src)
    cv_dst = yolo_det.draw_detections(cv_src)
    return cv_dst

if __name__ == '__main__':
    img_input = gr.Image()
    img_output = gr.Image()
    app = gr.Interface(fn=det_img, inputs=img_input, outputs=img_output)
    app.launch()

YOLO目标检测推理

上面已经给出了YOLOv8模型推理的函数,那么其具体是如何实现的呢?
上述函数的具体实现分别在YOLODet.pyutils.py文件中,具体实现过程如下:

YOLODet实例化

首先是YOLODet对象的实例化,其通过__init__初始化置信度等参数,并生成InferenceSession的实例

yolo_det = YOLODet.YOLODet(model, conf_thres=0.5, iou_thres=0.3)

初始化参数代码如下:

def __init__(self, path, conf_thres=0.7, iou_thres=0.5):
        self.conf_threshold = conf_thres
        self.iou_threshold = iou_thres
        # Initialize model
        self.initialize_model(path)

生成InferenceSession的实例

def initialize_model(self, path):
        self.session = onnxruntime.InferenceSession(path,providers=onnxruntime.get_available_providers())
        # Get model info
        self.get_input_details()
        self.get_output_details()

读取模型的参数信息,YOLO模型在训练时设置图像为640*640,该信息作为参数保存在了ONNX模型文件中,此时通过读取模型文件中的相关参数来为前处理任务设置参数

def get_input_details(self):
        model_inputs = self.session.get_inputs()
        self.input_names = [model_inputs[i].name for i in range(len(model_inputs))]
        self.input_shape = model_inputs[0].shape
        self.input_height = self.input_shape[2]
        self.input_width = self.input_shape[3]

获取ONNX的输出值的名称

def get_output_details(self):
        model_outputs = self.session.get_outputs()
        self.output_names = [model_outputs[i].name for i in range(len(model_outputs))]

输出结果解析原理

这里的输出值为output0,即只有一个值,我们可以通过下面代码来查看这个onnx模型的输出结果具体是什么样子的

	import onnx
    # 加载模型
    model = onnx.load('D:\graduate\programs\yolo8/ultralytics-main/runs\detect/train2\weights/best.onnx')
    # 检查模型格式是否完整及正确
    onnx.checker.check_model(model)
    # 获取输出层,包含层名称、维度信息
    output = model.graph.output
    print(output)

可以看到其名称即为output0,其维度为3维,即(1,7,8400)

[name: "output0"
type {
  tensor_type {
    elem_type: 1
    shape {
      dim {
        dim_value: 1
      }
      dim {
        dim_value: 7
      }
      dim {
        dim_value: 8400
      }
    }
  }
}
]

这里为何是(7,8400)呢,首先解释一下7的原因,前4个元素是边界框的坐标(x, y, w, h)
剩下的3个元素(car,truck,bus)是类别得分。
博主也曾直接使用YOLOv8训练好的pt模型进行转换,得到的结果为(1,84,8400),其中84便是4个坐标加上COCO数据集中的80个类别
那么,这个8400是如何来的呢,这是由于YOLO的三个不同尺度的检测头的原因:

(80×80+40×40+20×20)=(6400+1600+400)=8400

每个网格点产生一个预测结果,即有8400个预测结果,同时,关于这一点我们可以通过后处理过程来证实

def postprocess(self, input_image, output):
    """
    对模型的输出进行后处理,以提取边界框、置信度分数和类别ID。
    参数:
        input_image (numpy.ndarray): 输入图像。
        output (numpy.ndarray): 模型的输出。
    返回值:
        numpy.ndarray: 带有绘制检测结果的输入图像。
    """
    # 转置并压缩输出以匹配预期的形状
    outputs = np.transpose(np.squeeze(output[0]))
    # 获取输出数组中的行数
    rows = outputs.shape[0]
    # 用于存储检测到的边界框、置信度分数和类别ID的列表
    boxes = []
    scores = []
    class_ids = []
    # 计算边界框坐标的缩放因子
    x_factor = self.img_width / self.input_width
    y_factor = self.img_height / self.input_height
    # 遍历输出数组中的每一行
    for i in range(rows):  # 选出大于置信度的检测结果
        # 从当前行中提取类别分数
        classes_scores = outputs[i][4:]
        # 找到类别分数中的最大值
        max_score = np.amax(classes_scores)
        # 如果最大值大于置信度阈值
        if max_score >= self.confidence_thres:
            # 获取具有最高分数的类别ID
            class_id = np.argmax(classes_scores)
            # 从当前行中提取边界框坐标
            x, y, w, h = outputs[i][0], outputs[i][1], outputs[i][2], outputs[i][3]

注意,该后处理过程通过 np.amax确定其所属类别,并将所有大于置信度的结果筛选出,但其数量依旧很多,因此需要进行非极大值抑制(NMS)操作。

YOLO推理代码

推理的代码很简单,只要把图像输入加载好的模型中即可:
得到输出结果output0

outputs = self.inference(input_tensor)
def inference(self, input_tensor):
        start = time.perf_counter()
        outputs = self.session.run(self.output_names, {self.input_names[0]: input_tensor})
        return outputs

在这里插入图片描述

后处理操作之结果解析

随后将输出结果进行后处理操作

self.boxes, self.scores, self.class_ids = self.process_output(outputs)

上述的后处理过程实现较为繁杂,因此可以采用如下处理方式:

def process_output(self, output):
        predictions = np.squeeze(output[0]).T#转为维度为(8400,7)
        # Filter out object confidence scores below threshold
        scores = np.max(predictions[:, 4:], axis=1)#选出预测概率最大的分数
        predictions = predictions[scores > self.conf_threshold, :]#选出大于置信度的预测结果
        scores = scores[scores > self.conf_threshold]#选出大于置信度的分数
        if len(scores) == 0:
            return [], [], []

        # Get the class with the highest confidence
        class_ids = np.argmax(predictions[:, 4:], axis=1)#根据每个类别最大的概率得到其对应的类别编号

        # Get bounding boxes for each object
        boxes = self.extract_boxes(predictions)#获取box

        # Apply non-maxima suppression to suppress weak, overlapping bounding boxes
        # indices = nms(boxes, scores, self.iou_threshold)
        indices = multiclass_nms(boxes, scores, class_ids, self.iou_threshold)

        return boxes[indices], scores[indices], class_ids[indices]

关于extract_boxes函数,其作用为处理 bounding box(预测框),主要分为两个过程,一个是将预测框结果恢复到与图像大小相匹配的大小(由于多尺度的关系,其输出的预测框的大小都是归一化后的)。
此外,还要将预测的(x,y,w,h)转换为(x y x y)形式

def extract_boxes(self, predictions):
        # Extract boxes from predictions
        boxes = predictions[:, :4]
        # Scale boxes to original image dimensions
        boxes = self.rescale_boxes(boxes)
        # Convert boxes to xyxy format
        boxes = xywh2xyxy(boxes)
        return boxes

该两部分代码如下:恢复预测框大小

def rescale_boxes(self, boxes):
        # Rescale boxes to original image dimensions
        input_shape = np.array([self.input_width, self.input_height, self.input_width, self.input_height])
        boxes = np.divide(boxes, input_shape, dtype=np.float32)
        boxes *= np.array([self.img_width, self.img_height, self.img_width, self.img_height])
        return boxes

(x,y,w,h)转换为(x y x y)

def xywh2xyxy(x):
    # Convert bounding box (x, y, w, h) to bounding box (x1, y1, x2, y2)
    y = np.copy(x)
    y[..., 0] = x[..., 0] - x[..., 2] / 2
    y[..., 1] = x[..., 1] - x[..., 3] / 2
    y[..., 2] = x[..., 0] + x[..., 2] / 2
    y[..., 3] = x[..., 1] + x[..., 3] / 2
    return y

后处理操作值NMS操作

经过上述类别置信度筛选后的结果还可以存在很多,为了让结果更加精确(为了防止一个目标有多个预测框),故进行非极大值抑制,即NMS操作,其原理如下:

  1. 对于每个类别,按照预测框的置信度进行排序,将置信度最高的预测框作为基准。
  2. 从剩余的预测框中选择一个与基准框的重叠面积最大的框,如果其重叠面积大于一定的阈值,则将其删除。
  3. 对于剩余的预测框,重复步骤2,直到所有的重叠面积都小于阈值,或者没有被删除的框剩余为止

当然,由于YOLO模型的性能较好,我们的置信度在设置为0.5时,筛选后的结果便不多了,8400个筛选完后仅还有21

indices = multiclass_nms(boxes, scores, class_ids, self.iou_threshold)

在这里插入图片描述

代码如下,其过程便是按照每个类别计算保存下的预测框

def multiclass_nms(boxes, scores, class_ids, iou_threshold):
    unique_class_ids = np.unique(class_ids)
    keep_boxes = []
    for class_id in unique_class_ids:
        class_indices = np.where(class_ids == class_id)[0]
        class_boxes = boxes[class_indices,:]
        class_scores = scores[class_indices]

        class_keep_boxes = nms(class_boxes, class_scores, iou_threshold)
        keep_boxes.extend(class_indices[class_keep_boxes])

    return keep_boxes

具体的预测框计算使用如下nms函数,这个是针对单个类别进行计算的

def nms(boxes, scores, iou_threshold):
    # Sort by score
    sorted_indices = np.argsort(scores)[::-1]#排序

    keep_boxes = []
    while sorted_indices.size > 0:
        # Pick the last box
        box_id = sorted_indices[0]#获取分值最高的box
        keep_boxes.append(box_id)#这个box保留

        # Compute IoU of the picked box with the rest
        ious = compute_iou(boxes[box_id, :], boxes[sorted_indices[1:], :])
		#通过计算IOU来判断是否保留,计算的IOU是与最大框的分数
        # Remove boxes with IoU over the threshold
        keep_indices = np.where(ious < iou_threshold)[0]
		#去除与最大框的IOU大于阈值的预测框,这些预测框可以认为是预测的同一个目标
        # print(keep_indices.shape, sorted_indices.shape)
        sorted_indices = sorted_indices[keep_indices + 1]
        #提取出保留的预测框的坐标

    return keep_boxes

在这里插入图片描述
对于下面的代码

sorted_indices = sorted_indices[keep_indices + 1]

为何要加一呢,其实是由于前面计算IOU时算的是与最大框的IOU,经过keep_indices = np.where(ious < iou_threshold)[0]筛选后得到小于阈值的(预测的不是同一个目标),但此时这个id是去除了最大框的,要在sorted_indices中选择就需要加一。

计算IOU的代码如下:

def compute_iou(box, boxes):
    # Compute xmin, ymin, xmax, ymax for both boxes
    xmin = np.maximum(box[0], boxes[:, 0])
    ymin = np.maximum(box[1], boxes[:, 1])
    xmax = np.minimum(box[2], boxes[:, 2])
    ymax = np.minimum(box[3], boxes[:, 3])

    # Compute intersection area
    intersection_area = np.maximum(0, xmax - xmin) * np.maximum(0, ymax - ymin)

    # Compute union area
    box_area = (box[2] - box[0]) * (box[3] - box[1])
    boxes_area = (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1])
    union_area = box_area + boxes_area - intersection_area

    # Compute IoU
    iou = intersection_area / union_area

    return iou

可视化操作

至此,便可以通过NMS找出最终的预测框了,随后便是在图像上标注出目标了:

cv_dst = yolo_det.draw_detections(cv_src)
def draw_detections(self, image, draw_scores=True, mask_alpha=0.4):

        return detections_dog(image, self.boxes, self.scores,
                              self.class_ids, mask_alpha)
def detections_dog(image, boxes, scores, class_ids, mask_alpha=0.3):
    det_img = image.copy()

    img_height, img_width = image.shape[:2]
    font_size = min([img_height, img_width]) * 0.0006
    text_thickness = int(min([img_height, img_width]) * 0.001)

    # det_img = draw_masks(det_img, boxes, class_ids, mask_alpha)

    # Draw bounding boxes and labels of detections

    for class_id, box, score in zip(class_ids, boxes, scores):

        color = colors[class_id]

        draw_box(det_img, box, color)
        label = class_names[class_id]
        caption = f'{label} {int(score * 100)}%'
        draw_text(det_img, caption, box, color, font_size, text_thickness)

    return det_img

最终的检测效果如下:

在这里插入图片描述
事实上,这套处理流程不仅可以应对YOLOv8的检测,博主还曾测试过YOLOv9的模型,依旧是可用的。

最终完整代码博主将在完成视频推理设计后公布在github,尽情期待。

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

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

相关文章

PHP在线加密系统源码

历时半年&#xff0c;它再一次迎来更新[飘过] 刚刚发的那个有点问题&#xff0c;重新修了一下 本次更新内容有点多 1. 更新加密算法&#xff08;这应该是最后一次更新加密算法了&#xff0c;以后主要更新都在框架功能上面了&#xff09; 2. 适配php56-php74 3. 取消批量加…

【机器学习西瓜书学习笔记——神经网络】

机器学习西瓜书学习笔记【第四章】 第五章 神经网络5.1神经元模型5.2 感知机与多层网络学习感知机学习率成本/损失函数梯度下降 5.3 BP神经网络&#xff08;误差逆传播&#xff09;5.4 全局最小与局部极小5.5 其他常见神经网络RBF网络RBF 与 BP 最重要的区别 ART网络 第五章 神…

【Linux】进程间通信 —— 管道与 System V 版本通信方式

目录 为什么有进程间通信&#xff1f;进程间通信的目的是什么&#xff1f; 管道 匿名管道 父子进程共享管道 命名管道 共享内存 概念 原理 共享内存和内存映射&#xff08;文件映射&#xff09;的区别 使用 消息队列 概念 使用 信号量 概念 使用 IPCS 命令 S…

【人工智能专栏】Cross Entropy 交叉熵损失解析

Cross Entropy 交叉熵 信息熵 在信息世界中我们所有的信息都可以抽象为“情况”,用二进制 bit 来表达,正因为每个 bit 都有 0 1 两种“情况”,所以 n n n 个 bit 可以编码 2 n 2^n 2

Java----代理

什么是代理&#xff1f; 在Java中&#xff0c;代理是一种用于创建一个或多个服务的中间层&#xff0c;它可以拦截并处理程序对实际服务对象的请求。代理模式是一种设计模式&#xff0c;属于结构型模式&#xff0c;它允许程序员在不修改实际对象代码的情况下&#xff0c;增强或控…

PHP反序列化漏洞从入门到深入8k图文介绍,以及phar伪协议的利用

文章参考&#xff1a;w肝了两天&#xff01;PHP反序列化漏洞从入门到深入8k图文介绍&#xff0c;以及phar伪协议的利用 前言 本文内容主要分为三个部分&#xff1a;原理详解、漏洞练习和防御方法。这是一篇针对PHP反序列化入门者的手把手教学文章&#xff0c;特别适合刚接触PH…

杭州等保测评的备案流程

杭州等级保护备案和测评&#xff0c;构筑了一座坚实的数字安全桥梁&#xff0c;其过程和条件清楚而又重要。这篇文章会详细介绍一些必要的步骤&#xff0c;以帮助你顺利地完成信息系统的安全和合规。 1. 系统识别与自评 在此基础上&#xff0c;首先要明确信息系统所承载的业务…

Zabbix配置监控参考

1 添加host 配置-主机-创建主机 添加主机名&#xff0c;IP&#xff0c;端口 2 添加监控项 配置-主机-监控项 打开后&#xff0c;点击右上角添加监控项&#xff08;进去后。配置想要的监控项目&#xff09; 3 添加CPU监控项 需求&#xff1a;CPU使用率 实现&#xff1…

【基础篇】Docker 容器操作 FOUR

嘿&#xff0c;小伙伴们&#xff01;我是小竹笋&#xff0c;一名热爱创作的工程师。在上一篇文章中&#xff0c;我们探讨了 Docker 镜像管理的相关知识。今天&#xff0c;让我们一起深入了解一下 Docker 容器的操作吧&#xff01; &#x1f4e6; 运行、停止和删除容器 Docker…

归并排序 python C C++ 代码及解析

一&#xff0c;概念及其介绍 归并排序&#xff08;Merge sort&#xff09;是建立在归并操作上的一种有效、稳定的排序算法&#xff0c;该算法是采用分治法(Divide and Conquer&#xff09;的一个非常典型的应用。将已有序的子序列合并&#xff0c;得到完全有序的序列&#xff…

商家转账到零钱开通最快捷径

商家转账到零钱存在一定的捷径&#xff0c;这一捷径将放在文章最后。如果商家希望自行开通&#xff0c;可以按照以下步骤进行申请&#xff1a; 1. 确认主体资格&#xff1a;申请主体必须是公司性质&#xff08;有限公司类型&#xff09;&#xff0c;个体工商户暂不支持申请&…

企业级Linux系统防护

一、企业级Linux系统防护概述 一&#xff09;企业级Linux系统安全威胁 企业级Linux系统安全威胁列表 解决的主要安全威胁安全威胁牵涉到的人员及操作文件系统防护避免有意/无意的文件篡改、越权访问&#xff0c;根用户&#xff08;root&#xff09;权限泛滥企业内部用户误操作、…

【Golang 面试 - 基础题】每日 5 题(九)

✍个人博客&#xff1a;Pandaconda-CSDN博客 &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/UWz06 &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 Golang 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞&#x1f44d;收藏…

Linux虚拟化技术KVM

文章目录 虚拟化基础什么是虚拟化虚拟化优势虚拟机虚拟机的主要特征Hypervisor类型类型1&#xff1a;裸金属型类型2&#xff1a;宿主型 KVM概述KVM体系结构KVM模块载入后的系统运行模式KVM集中管理和控制宿主机环境准备 安装KVM工具包libvirt包功能libvirt结构图安装KVM相关包C…

SEO优化 prerender-spa-plugin工具使用 踩坑记录

安装prerender-spa-plugin yarn add prerender-spa-plugin 或 npm install prerender-spa-plugin初始配置 后面记录踩的坑 配置路由 const routes [{path: /,redirect: {path: /HomeView},},{path: /home,redirect: {path: /HomeView},},{ path: /HomeView,component: HomeV…

postgresql密码复杂度验证和有效期

前言 为了数据库安全以及应对等保测评等要求&#xff0c;我们需要设置密码复杂度。我们通过passwordcheck模块实现复杂度检测功能。 启用密码复杂度验证 找到自己安装pg库的配置文件目录&#xff0c;修改postgresql.conf vim postgresql.conf修改如下内容 shared_preload_…

2023版IDEA安装通义灵码屡遭挫败:重复尝试,安装依旧失败

目录 背景: 过程: 第一步: 第二步: 第三步: 安装成功: 总结: 通义灵码的优点: 背景: 小编使用的是2023版本IDEA&#xff0c;在安装通义灵码的时候出现了一件很让人头痛的问题&#xff0c;我在IEDA中的插件中心里面去下载&#xff0c;但是当我我安装的进度条加载完成之…

命令行创建git仓库

方法1&#xff1a;初始化自己的仓库 git init创建完成之后可以用ls -a查看是否存在.git文件 如果不想要git仓库&#xff0c;可以使用rm -rf .git删除仓库 方法2&#xff1a;克隆别人的仓库 git clone [http][http]是仓库网址 总体流程 可以看到文件分为四种状态&#xff0c…

windows无法打开添加打印机原因分析及解决方法

在日常办公和生活中&#xff0c;打印机是不可或缺的重要设备。然而&#xff0c;有时在添加打印机的过程中&#xff0c;经常会遇各种问题。今天有个小伙伴问我windows无法打开添加打印机怎么回事&#xff1f;今天就教大家windows无法打开添加打印机原因分析及解决方法。 添加打打…

氧传感器在码头油气回收船岸安全装置中的重要作用

随着全球对环境保护和安全生产要求的日益提升&#xff0c;石化码头的油气回收问题已成为行业关注的焦点。在汽油、航煤、苯、对二甲苯等油品和化学品的装船过程中&#xff0c;大量油气挥发不仅加剧了大气污染&#xff0c;还潜藏着对人体健康的严重威胁。因此&#xff0c;推广和…