一文详解YOLOv8多模态目标检测(可见光+红外图像,基于Ultralytics官方代码实现),轻松入门多模态检测领域!

目录

  • 1. 文章主要内容
  • 2. 相关说明
  • 3. 基于YOLOv8的多模态目标检测
    • 3.1 启动运行YOLOv8多模态代码
    • 3.2 详解代码流程(重点)
      • 3.2.1 train.py文件(入口)
      • 3.2.2 engine\model.py文件
      • 3.2.3 engine\trainer.py文件
      • 3.2.4 models\yolo\detect\train.py文件
      • 3.2.5 nn\tasks.py文件
      • 3.2.6 再次回到engine\trainer.py文件
    • 3.3 总结

1. 文章主要内容

       本文主要是详解YOLOv8实现多模态,包括如何启动,以及详细代码部分如何改进,从而让单模态的检测支持为多模态的检测!。基于YOLO的单模态检测赛道已经非常卷,很难出好的论文,这个时候入门多模态检测是非常有必要的!所以,本篇代码分析论文则是入门基于YOLOv8的多模态目标检测的基础之一。

2. 相关说明

       本篇博文代码来源于原博客:YOLOV8多模态(可见光+红外光目标检测任务,基于Ultralytics官方代码实现)。
       原博客中使用的是基于Ultralytics的YOLOv8模型,使用的数据集是DroneVehicle,这里的DroneVehicle数据集是红外-可见光两种模态的无人机目标检测数据集。需要注意的是原数据集中的DroneVehicle数据图像有白边,需要对原数据集进行去白边进行处理。另外,我自己在这里把目标框从旋转框改为水平框,也就是仅仅进行水平框的检测,后续再考虑出一篇旋转框的检测内容。
       注意到:以下代码的相关内容分析需要前提知识:已经了解单模态的YOLOv8代码相关的知识点。

3. 基于YOLOv8的多模态目标检测

       这一块分为两个部分,第一块是启动运行部分,第二块是多模态代码的分析(中期融合,也叫做特征融合)第二块是重点,因为后续想改进代码必须搞懂如何进行模型改进和前向传播的改进等。

3.1 启动运行YOLOv8多模态代码

       从上面原博客中找到代码的地址,或者直接点击这个链接TwoStream_Yolov8源代码,进去看相关README部分,配置好相关环境,然后准备好相关的数据集DroneVehicle以及对应的格式摆放。另外在安装环境的时候一定要运行这个代码pip install -e .,这个代码的作用是将这个项目TwoStream_Yolov8的本地的Ultralytics文件夹进行编译,而不是用环境nn下对应的包。不然就会出现No module Ultralytics相关错误。另外上述编译代码是在项目的根目录进行运行的,大家别搞错。

3.2 详解代码流程(重点)

       这一块的内容主要是从train函数部分,一步步去分析如何构造多模态目标检测的(这里是中期融合,后续的前期融合我准备再出一篇分析),这里先给出一张函数的流程图,下面的内容就是根据这张图来说明,注意我不会讲所有的代码,只会讲牵扯多模态相关需要修改的代码部分。
在这里插入图片描述

3.2.1 train.py文件(入口)

       说明:作者也提供了一份train.py文件,我是用自己的train.py,大差不差。源代码加载yaml文件使用的是绝对路径,我这里通过sys.path.append(“/home/project/TwoStream_Yolov8-main/”),将根目录设定为项目的根目录路径,所以下面加载yaml文件使用相对路径即可!然后,device部分我这里是多卡训练(使用Linux环境,建议使用Linux服务器),如果你是单卡的话改成0即可!
       代码分析:首先加载YOLO模型,这个YOLO模型只是ultralytics/model/yolo/model.py文件中的一个类,这个类继承了Model基类,也就是engine/model.py(这个类是重点)。所以你可以理解第一行代码model只是将yaml文件加载到了基类engine/model.py中的cfg变量当中
       后面第二行代码,调用model.train函数,其中带了data属性,也就是数据集的yaml文件!如下面第二幅图所示:train、train_ir分别为可见光和红外图像的训练集路径。
       注意这里和源代码不同,我这里改了相关代码,主要在路径中的imgRGB和IR部分,如果你也想改为自定义的路径,需要修改ultralytics/data/base.py中的load_image代码,如第三幅图所示。其主要的作用是根据可见光的路径获取红外文件的路径,然后再加载数据。

import warnings
import sys
sys.path.append("/home/project/TwoStream_Yolov8-main/")
warnings.filterwarnings('ignore')
from ultralytics import YOLOif __name__ == '__main__':# 加载模型model = YOLO('yaml/ADDyolov8n.yaml') # .load('yolov8n.pt')  # 从YAML构建并转移权重# 训练模型results = model.train(data='data/drone2.yaml', epochs=200, batch=32, device=[0,1])

在这里插入图片描述
在这里插入图片描述

3.2.2 engine\model.py文件

       进入到train这个类中,就跳转到了engine\model.py,然后我们找到在这里self.trainer.train()代码,继续进入,这里注意到我们进入的是下面这个类。
在这里插入图片描述

3.2.3 engine\trainer.py文件

       进入到engine\trainer.py类,找到这一行代码:self._do_train(world_size),进入这个方法,需要注意到world_size是判断训练是几张卡,如果有两张,那么假设你batch_size设置为16,每张卡就是batch_size为8.
       然后再找到这一行代码:self._setup_train(world_size),再次进入找到 ckpt = self.setup_model()方法,然后再次进入可以看到self.model = self.get_model(cfg=cfg, weights=weights, verbose=RANK == -1) 代码,这里的cfg就是我们的yaml/ADDyolov8n.yaml

3.2.4 models\yolo\detect\train.py文件

       然后再次进入跳转到models\yolo\detect\train.py文件,可以看到model = DetectionModel(cfg, nc=self.data["nc"], verbose=verbose and RANK == -1)这一行代码,说明我们的model使用的是DetectionModel类。

3.2.5 nn\tasks.py文件

       通过DetectionModel进入到这个 nn\tasks.py文件,然后找到这行代码self.model, self.save = parse_model(deepcopy(self.yaml), ch=ch, verbose=verbose),再进入到parse_model这个函数中,这个parse_model就是通过yaml文件去构造model的结构。
       第一:我们可以看到这两行代码:tx的list表里存放的是输入的通道数,接近着会讲,ty为索引,初始值为0.

  tx=[3,256,256,512,512,max_channels,max_channels] # TODO....ty=0

       第二:然后可以看到如下几行的代码:结合上面tx和ty的定义可以知道,这是对输入通道数c1的改变。当yaml文中from也就是f值为-4的时候,输入的通道数要从tx当中去取出,并且当ty不等于0的时候需要乘于width因子。

c1, c2 = ch[f], args[0]if f==-4:c1=tx[ty]if ty!=0:c1=c1*widthc1=int(c1)ty+=1

       第三:来结合ADDyolov8n.yaml文件中的代码结合来看,如下所示。可以看出,第一次出现f=-4的时候,是在IR也就是红外分支Conv的时候,如果这个时候我们不用特殊分支进行判断,按照YOLOv8的原逻辑就会从上一层也就是f=-1处理,此时的c1=256,也就是RGB第四层Conv的输出,明显不对。因为IR分支的第一层输入应该也是3,所以我们就搞懂了上述逻辑的代码,这里是一个重点!,这样我们就构造了通过yaml新建model的逻辑。

# Ultralytics YOLO 🚀, AGPL-3.0 license
# YOLOv8 object detection model with P3-P5 outputs. For Usage examples see https://docs.ultralytics.com/tasks/detect# Parameters
nc: 5 # number of classes
scales: # model compound scaling constants, i.e. 'model=yolov8n.yaml' will call yolov8.yaml with scale 'n'# [depth, width, max_channels]n: [0.33, 0.25, 1024] # YOLOv8n summary: 225 layers,  3157200 parameters,  3157184 gradients,   8.9 GFLOPss: [0.33, 0.50, 1024] # YOLOv8s summary: 225 layers, 11166560 parameters, 11166544 gradients,  28.8 GFLOPsm: [0.67, 0.75, 768] # YOLOv8m summary: 295 layers, 25902640 parameters, 25902624 gradients,  79.3 GFLOPsl: [1.00, 1.00, 512] # YOLOv8l summary: 365 layers, 43691520 parameters, 43691504 gradients, 165.7 GFLOPsx: [1.00, 1.25, 512] # YOLOv8x summary: 365 layers, 68229648 parameters, 68229632 gradients, 258.5 GFLOPs# YOLOv8.0n backbone
backbone:# [from, repeats, module, args]# RGB- [-1, 1, Conv, [64, 3, 2]] # 0-P1/2- [-1, 1, Conv, [128, 3, 2]] # 1-P2/4- [-1, 3, C2f, [128, True]] #2- [-1, 1, Conv, [256, 3, 2]] # 3-P3/8# IR- [-4, 1, Conv, [64, 3, 2]] # 4 3- [-1, 1, Conv, [128, 3, 2]] # 5- [-1, 3, C2f, [128, True]] # 6- [-1, 1, Conv, [256, 3, 2]] # 7# Fusion1 使用时记得修改文件block文件将RIFusion置为空- [-3,1,RIFusion,[64]] #8# RGB- [-4, 6, C2f, [256, True]] #9 256- [-1, 1, Conv, [512, 3, 2]] # 10# IR- [-4, 6, C2f, [256, True]] # 11- [-1, 1, Conv, [512, 3, 2]] # 12# Fusion2- [-3,1,RIFusion,[128]]  #13# RGB- [-4, 6, C2f, [512, True]] #14- [-1, 1, Conv, [1024, 3, 2]] # 15# IR- [-4, 6, C2f, [512, True]] #16- [-1, 1, Conv, [1024, 3, 2]] # 17# Fusion3- [-3,1,RIFusion,[256]] #18# RGB- [-4, 3, C2f, [1024, True]] #19- [-1, 1, SPPF, [1024, 5]] # 20# IR- [-4, 3, C2f, [1024, True]] #21- [-1, 1, SPPF, [1024, 5]] # 22- [[9,11], 1, ADD, [1]] # 23- [[14,16], 1, ADD, [1]] # 24- [[20,22],1, ADD, [1]] # 25# YOLOv8.0n head
head:- [-1, 1, nn.Upsample, [None, 2, "nearest"]] #26- [[-1, 24], 1, Concat, [1]] # 27- [-1, 3, C2f, [512]] # 28- [-1, 1, nn.Upsample, [None, 2, "nearest"]] #29- [[-1, 23], 1, Concat, [1]] # 30- [-1, 3, C2f, [256]] # 31- [-1, 1, Conv, [256, 3, 2]] #32- [[-1, 28], 1, Concat, [1]] # 33- [-1, 3, C2f, [512]] # 34- [-1, 1, Conv, [512, 3, 2]] # 35- [[-1, 25], 1, Concat, [1]] # 36- [-1, 3, C2f, [1024]] # 37- [[31, 34, 37], 1, Detect, [nc]] # 38

3.2.6 再次回到engine\trainer.py文件

       新建完模型之后,回到trainer.py的代码中,我们看到这行代码:这是读取数据集loader的代码,我们先去看看self.trainset, self.trainirset怎么获取的,可以看到在同一个文件中有这样一行代码:self.trainset, self.testset,self.trainirset,self.testirset = self.get_dataset(),再去看看get_dataset()这个代码,可以看到这两行代码:其中的data就是我们最开始train.py中传入的data='data/drone2.yaml',然后获取其中的相关数据集路径,并返回。

self.data = datareturn data["train"], data.get("val"),data["train_ir"],data.get("val_ir") or data.get("test")
   # 读取数据集self.train_loader = self.get_dataloader(self.trainset, self.trainirset,batch_size=batch_size, rank=RANK, mode="train")

       再回到self.get_dataloader代码部分,我们进去这个方法,注意是进去第一个get_dataloader。然后可以看到这行代码:dataset = self.build_dataset(dataset_path, datasetir_path,mode, batch_size),再进去这个函数可以看到这行代码, return build_yolo_dataset(self.args, img_path, imgir_path,batch, self.data, mode=mode, rect=mode == "val", stride=gs)。然后再进去build_yolo_dataset这个函数,可以看到我们使用的是YOLODataset这个函数,它需要支持 img_path=img_path和 imgir_path=imgir_path两个路径的输入。然后YOLODataset又是继承了BaseDataset,我们可以看到这行self.cache_images()这行代码,继续进入到这个函数。可以看到一行代码:fcn, storage = (self.cache_images_to_disk, "Disk") if self.cache == "disk" else (self.load_image, "RAM")。**我们再次进入到load_image这个函数中,这个函数通过imir=cv2.imread(f)来从路径中读取数据集。**另外要注意到load_image中的这行代码:im = np.dstack((im, imir)) ,说明输入的数据是以六通道的数据存在的,后续要进行分开处理!当然这里面还有数据增强部分,是牵扯self.transforms = self.build_transforms(hyp=hyp)这一块的代码,大家可以自己看看!
在这里插入图片描述
       好,我们回到get_dataloader的build_dataloader方法,刚刚我们得到了dataset,然后传到build_dataloader中即可得到加载器。

    def get_dataloader(self, dataset_path, datasetir_path,batch_size=16, rank=0, mode="train"):"""Construct and return dataloader."""assert mode in {"train", "val"}, f"Mode must be 'train' or 'val', not {mode}."with torch_distributed_zero_first(rank):  # init dataset *.cache only once if DDPdataset = self.build_dataset(dataset_path, datasetir_path,mode, batch_size)shuffle = mode == "train"if getattr(dataset, "rect", False) and shuffle:LOGGER.warning("WARNING ⚠️ 'rect=True' is incompatible with DataLoader shuffle, setting shuffle=False")shuffle = Falseworkers = self.args.workers if mode == "train" else self.args.workers * 2return build_dataloader(dataset, batch_size, workers, shuffle, rank)  # return dataloader

       在trainer.py文件中可以看到 self.loss, self.loss_items = self.model(batch),意思就是将数据以batch批次传进去,我们这里还有一个问题,model的结构构造好了,数据怎么传进去的?我们现在的数据从load_image中是以六通道的形式存在,所以我们得去看BaseModel类的前向传播算法。这个前向传播算法大家可以理解为数据进入到BaseModel中必须执行的一个函数,也就是在task.py中,可以看到def forward中 return self.predict(x, *args, **kwargs)这行代码,然后进去可以看到这行代码: return self._predict_once(x, profile, visualize, embed)。这个_predict_once函数才是处理六通道输入的函数,一步一步来:

       第一:首先将六通道的输入切分开,分别获得rgb和ir的输入,随后将rgb先赋值给x,这是因为在yaml文件中RGB的网络结构在IR的前面。

 y, dt, embeddings = [], [], []  # outputsrgb,ir=torch.chunk(x,chunks=2,dim=1) # 红外# rgb=x[:, :3, :, :] # 可见光x=rgb

       第二:当f==-4的时候,也就是要切换输入了,第一次这个时候将rgb切换为ir。

 if m.f==-4:# 跳转另外一个分支if isR:x= m(ir)ir=xisR=Falseelse :x = m(rgb)  # runrgb=xisR=True

       还有一段代码,这段代码就是正常的执行相关yaml文件,也就是在backbone的ADD融合之前:

            elif m.i<23:if isR:x= m(rgb)rgb=xelse :x = m(ir)  # runir=x

3.3 总结

       大概就是这些代码,可能有一些细节没讲解,这是属于中期融合,也就是特征级的融合,也是最常见的融合,希望大家能有收获,如果有任何疑问,可以评论区交流!如果可以的话,希望大家多多点赞,收藏,后续会更新相关代码和论文的解读!

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【顶刊TPAMI 2025】多头编码(MHE)之极限分类 Part 3:算法实现

目录 1 三种多头编码&#xff08;MHE&#xff09;实现1.1 多头乘积&#xff08;MHP&#xff09;1.2 多头级联&#xff08;MHC&#xff09;1.3 多头采样&#xff08;MHS&#xff09;1.4 标签分解策略 论文&#xff1a;Multi-Head Encoding for Extreme Label Classification 作者…

【AWS SDK PHP】This operation requests `sigv4a` auth schemes 问题处理

使用AWS SDK碰到的错误&#xff0c;其实很简单&#xff0c;要装个扩展库 保持如下 Fatal error: Uncaught Aws\Auth\Exception\UnresolvedAuthSchemeException: This operation requests sigv4a auth schemes, but the client currently supports sigv4, none, bearer, sigv4-…

LLM - 使用 LLaMA-Factory 部署大模型 HTTP 多模态服务 教程 (4)

欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://spike.blog.csdn.net/article/details/144881432 大模型的 HTTP 服务,通过网络接口,提供 AI 模型功能的服务,允许通过发送 HTTP 请求,交互大模型,通常基于云计算架构,无需在本地部署复杂的模型和硬件,…

【MATLAB】【Simulink仿真】向模型中添加自定义子系统

一、子系统的创建 1、启动Simulink&#xff0c;选择【新建】——【空白子系统】——【创建子系统】 2、选择【浏览组件库】&#xff0c;创建使能子系统。 3、保存至当前工作目录。 二、建立模型仿真 1、启动Simulink&#xff0c;选择【新建】——【空白子系统】——【创建子系…

HTML——56.表单发送

<!DOCTYPE html> <html><head><meta charset"UTF-8"><title>表单发送</title></head><body><!--注意&#xff1a;1.表单接收程序&#xff0c;放在服务器环境中(也就是这里的www文件目录中)2.表单发送地址&#x…

123.【C语言】数据结构之快速排序挖坑法和前后指针法

目录 1.挖坑法 执行流程 代码 运行结果 可读性好的代码 2.前后指针法(双指针法) 执行流程 单趟排序代码 将单趟排序代码改造后 写法1 简洁的写法 3.思考题 1.挖坑法 执行流程 "挖坑法"顾名思义:要有坑位,一开始将关键值放入临时变量key中,在数组中形成…

重庆大学软件工程复试怎么准备?

重大软件复试相对来说不算刁钻&#xff0c;关键是对自己的竞赛和项目足够了解&#xff0c;能应对老师的提问。专业课范围广&#xff0c;英文文献看个人水平&#xff0c;难度不算大&#xff0c;整体只要表现得得体从容&#xff0c;以及充分的准备&#xff0c;老师不会为难你。 …

【Rust自学】10.3. trait Pt.1:trait的定义、约束与实现

喜欢的话别忘了点赞、收藏加关注哦&#xff0c;对接下来的教程有兴趣的可以关注专栏。谢谢喵&#xff01;(&#xff65;ω&#xff65;) 题外话&#xff1a;trait的概念非常非常非常重要&#xff01;&#xff01;&#xff01;整个第10章全都是Rust的重难点&#xff01;&#x…

计算机网络——数据链路层-流量控制和可靠传输

一、流量控制 流量控制是指由接收方及时控制发送方发送数据的速率&#xff0c;使接收方来得及接受。 • 停止等待流量控制 • 滑动窗口流量控制 1、停止—等待流量控制 停止-等待流量控制的基本原理是发送方每发出一帧后&#xff0c;就要等待接收方的应答信号&#xff…

Linux运维相关基础知识(二)

系列文章目录 Linux常用命令 linux 账号管理与权限设定 Linux运维相关基础知识 文章目录 系列文章目录前言1. 自动任务执行at 与 atdcrontab 与 crond 2. SELinuxtty多任务管理与进程管理相关的命令/proc/* 文件的意义SELinux 3. 守护进程早期SystemV的init管理行为中daemon…

【CSS】第一天 基础选择器与文字控制属性

【CSS】第一天 1. CSS定义2. css引入方式2.1 内部样式2.2 外部样式2.3 行内样式 3. 选择器3.1 标签选择器3.2 类选择器3.3 id选择器3.4 通配符选择器 1. CSS定义 层叠样式表(CSS)是一种样式表语言&#xff0c;用来描述HTML文档的呈现(美化内容)。 书写位置&#xff1a;title标…

QT----------QT Data Visualzation

实现思路&#xff1a; 配置项目&#xff1a;在 .pro 文件中添加 QT datavisualization 以引入 QT Data Visualization 模块。创建主窗口&#xff1a;使用 QMainWindow 作为主窗口&#xff0c;添加 Q3DScatter、Q3DBars 和 Q3DSurface 等三维视图组件。初始化和创建三维图表&a…

连接Milvus

连接到Milvus 验证Milvus服务器正在侦听哪个本地端口。将容器名称替换为您自己的名称。 docker port milvus-standalone 19530/tcp docker port milvus-standalone 2379/tcp docker port milvus-standalone 192.168.1.242:9091/api/v1/health 使用浏览器访问连接地址htt…

AlphaPi相关硬件驱动提取

初涉硬件编程&#xff0c;在咸鱼上搞了几块AlphaPi和microbit的板鼓捣了一下&#xff0c;alphapi生态不完善&#xff0c;网上又无任何文档&#xff0c;搞封闭&#xff0c;可玩性实在有限&#xff0c;但貌似相关扩展板是可以插microbit的&#xff0c;于是想把这些扩展版用microb…

Nginx——静态资源部署(二/五)

目录 1.Nginx 服务器基础配置实例2.Nginx 服务操作的问题及解决方案2.1.Nginx 配置成系统服务2.2.Nginx 命令配置到系统环境 3.Nginx 静态资源部署3.1.Nginx 静态资源概述3.2.Nginx 静态资源的配置指令3.2.1.listen 指令3.2.2.server_name 指令3.2.2.1.配置方式3.2.2.2.匹配执行…

第0章 机器人及自动驾驶SLAM定位方法全解析及入门进阶学习建议

嗨&#xff0c;各位同学大家好&#xff01;笔者自985硕士毕业后&#xff0c;在机器人算法领域已经深耕 7 年多啦。这段时间里&#xff0c;我积累了不少宝贵经验。本专栏《机器人工程师带你从零入门SLAM》将结合下面的SLAM知识体系思维导图及多年的工作实战总结&#xff0c;将逐…

Vue 全局事件总线:Vue 2 vs Vue 3 实现

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

xml格式化(1):使用python的xml库实现自闭合标签

前言 最近一段时间一直想要写一个urdf格式化插件。 至于为什么嘛&#xff0c;因为使用sw2urdf插件&#xff0c;导出的urdf&#xff0c;同一标签的内容&#xff0c;是跨行的&#xff0c;这就导致&#xff0c;内容比较乱&#xff0c;而且行数比较多。影响阅读。 因此&#xff…

模型 九屏幕分析法

系列文章 分享 模型&#xff0c;了解更多&#x1f449; 模型_思维模型目录。九屏幕法&#xff1a;全方位分析问题的系统工具。 1 九屏幕分析法的应用 1.1 新产品研发的市场分析 一家科技公司计划开发一款新型智能手机&#xff0c;为了全面评估市场潜力和风险&#xff0c;他们…

基于开发/发布/缺陷分离模型的 Git 分支管理实践20250103

基于开发/发布/缺陷分离模型的 Git 分支管理实践 引言 在现代软件开发中&#xff0c;合理的分支管理策略是保证项目成功的关键因素之一。本文将详细介绍一种基于开发/发布/缺陷分离的 Git 分支管理模型&#xff0c;这种模型不仅能提升团队协作效率&#xff0c;还能确保代码质…