当前位置: 首页 > news >正文

在Unity中运行Yolo推理

在Unity中运行Yolo推理

今天研究了一整天,在Unity中推理Yolo,路子终于已经搞通了。

关于YOLO

我是在WSL中研究YOLO的,WSL真是个好东西。

安装环境:
sudo apt update
sudo apt upgrade# cuda
sudo apt install nvidia-cuda-toolkit# miniconda
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
bash ./Miniconda3-latest-Linux-x86_64.sh
source ~/miniconda3/bin/activate   # sudo reboot
conda init --all# 创建环境
conda config --set ssl_verify false
conda create -n yolo
conda activate yolo# 安装pytorch
pip install torch torchvision
# pip install torchaudio# 安装Yolo
pip install ultralytics# 安装Jupyter
conda install jupyter notebook
jupyter notebook
训练
from ultralytics import YOLO
model = YOLO('yolo11n.pt')
model.train(data='xxx.yaml', epochs=300, batch=16)
# data: 数据集yaml文件
# epochs: 训练的总轮数。每个epoch代表对整个数据集的一次完整遍历。调整此值会影响训练时长和模型性能。
# batch: 批次大小,具有三种模式:设置为整数(例如,batch=16),自动模式,GPU 内存利用率为 60%(batch=-1),或具有指定利用率分数的自动模式(batch=0.70)。## 继续训练(接着上次的训练结果继续训练)
from ultralytics import YOLO
model = YOLO('path/to/last.pt')
model.train(resume=True)

其中,训练时需要用到Yaml配置文件,很简单,就几个路径而已,参考如下:

path: project_name	# dataset root dir 相对于datasets
train: train/images  # train images 相对于path
val: valid/images # val images 相对于path
test: test/images # test images 相对于path  测试集可以为空names:0: class11: class22: class33: class4download: url # 下载地址,可省略
推理测试
from ultralytics import YOLO
yolo = YOLO(model='yolo11n.pt', task='detect')
result = yolo(source='a.jpg', save=True)

导出ONNX

from ultralytics import YOLO# 加载训练好的模型
model = YOLO("path/to/best.pt")  # 您的训练权重# 导出到 ONNX,支持矩形输入
model.export(format="onnx",             # 格式imgsz=(480, 640),          # 矩形大小,匹配 640x480(注意顺序:height, width)
#   dynamic=True,              # 启用动态形状half=True,                 # FP16 量化(可选)
#   simplify=True,             # 简化模型(推荐)opset=9                   # ONNX 版本(兼容 Unity 等)
)

imgsz,其他他推荐的是一个方形的尺寸,只有一个数,比如 imgsz=640这样,因为我摄像头是640*480的,所以弄成这个尺寸,但是这个不是很重要。
重要的是:opset=9必须是9。我从13一路降到9进行测试,只有9,导入到unity之后,没有告警,9以上的版本,会给出“MaxPool: Unsupported attribute ceil_mode. Value will be ignored and defaulted to [0].”的警告。

Unity端,比较麻烦

一开始,查资料,看到了有个Barrcuda的库,开始研究,后来发现,从Unity2022左右,这个库升级了,改成了Sentis,我在Unity2022版本中,Package Manager中可以找到这个库,并可以顺利安装,但是, 在Unity6中,死活就是找不到这个库。郁闷了很久,最后发现,它又改名字了,这是Grok给我的回答:

在 Unity 6 中,Sentis 库并未“消失”,而是被正式重命名为 Inference Engine(推理引擎)。这是 Unity 在 2025 年对 AI 工具栈的重大更新之一,旨在更好地整合和优化运行时 AI 功能。重命名从 Sentis 版本 2.2 开始生效(对应 Unity 6.2 Beta 及更高版本),原包名 com.unity.sentis 被替换为 com.unity.ai.inference。Inference Engine 本质上是 Sentis 的延续和升级版,保留了核心功能(如 ONNX 模型导入、跨平台推理、GPU/CPU 支持),但进行了优化,包括更好的模型兼容性、性能提升和与 Unity 6 运行时的自动集成。

安装好之后,测试也并不太顺利,因为它的API极不稳定,版本改来改去,文档也跟不太上,好多API都有过时的标记。比如:

[Obsolete("`Tensor<float> ToTensor(Texture texture, int width = -1, int height = -1, int channels = -1)` is deprecated, please use `void ToTensor(Texture texture, Tensor<float> tensor, TextureTransform transform)` instead.")]
[Obsolete("`TextureTransform SetDimensions(int width = -1, int height = -1, int channels = -1)` is deprecated, dimensions of the target tensor or texture are used.")]

好在,最后测通了:

using System;
using System.Collections.Generic;
using Unity.InferenceEngine;
using UnityEngine;public class SentisTest : MonoBehaviour
{[SerializeField] private Texture2D inputTexture;[SerializeField] private ModelAsset _modelAsset;private Model _model;private Worker _worker;private const int inputWidth = 640;private const int inputHeight = 480;private void Start(){try{_model = ModelLoader.Load(_modelAsset);//_model = ModelLoader.Load(Path.Combine(Application.streamingAssetsPath, "my-yolo.onnx"));_worker = new Worker(_model, BackendType.GPUCompute);Debug.Log(_model);RunInference(inputTexture);}catch (Exception e){Debug.LogError(e.Message);}}private void RunInference(Texture2D texture){// 步骤1: 预处理输入// 按比例调整图像到 480x640,保持 aspect ratio 并添加 padding 如果需要Texture2D resized = ResizeTexture(texture, inputWidth, inputHeight);var inputTensor = new Tensor<float>(new TensorShape(1, 3, inputHeight, inputWidth));// TextureConverter.ToTensor(resized, inputHeight, inputWidth, 3);  // [1, 3, height, width]// 新:创建 TextureTransform 并设置维度(width, height, channels)var trans = new TextureTransform().SetTensorLayout(TensorLayout.NCHW);       // 可选:明确设置布局(默认 NCHW)// 新:使用 ToTensor 填充 tensorTextureConverter.ToTensor(resized, inputTensor, trans);// 步骤2: 执行推理_worker.Schedule(inputTensor);// 步骤3: 获取输出// YOLOv8 输出通常为 "output0" 或类似(用 Netron 检查您的模型输出名称)if (_worker.PeekOutput("output0") is not Tensor<float> outputTensor){inputTensor.Dispose();Debug.Log("Out put Error");return;}// 同步输出到 CPU 以读取//outputTensor.MakeReadable();// 步骤4: 后处理输出var results = PostProcess(outputTensor, inputHeight, inputWidth);// 显示结果(例如绘制边界框)DisplayResults(resized, results);//displayImage.texture = resized;  // 更新 UI// 清理inputTensor.Dispose();outputTensor.Dispose();}// 调整纹理大小(支持矩形,保持 aspect ratio)private static Texture2D ResizeTexture(Texture2D source, int newWidth, int newHeight){// 计算缩放比例以保持 aspect ratiofloat scale = Mathf.Min((float)newWidth / source.width, (float)newHeight / source.height);int scaledWidth = Mathf.RoundToInt(source.width * scale);int scaledHeight = Mathf.RoundToInt(source.height * scale);// 创建临时 RenderTexture 并 Blit(居中放置,添加黑边如果不匹配)RenderTexture rt = RenderTexture.GetTemporary(newWidth, newHeight);RenderTexture.active = rt;Graphics.Blit(source, rt, new Vector2(scale, scale), new Vector2((newWidth - scaledWidth) / 2f, (newHeight - scaledHeight) / 2f));Texture2D result = new Texture2D(newWidth, newHeight);result.ReadPixels(new Rect(0, 0, newWidth, newHeight), 0, 0);result.Apply();RenderTexture.ReleaseTemporary(rt);RenderTexture.active = null;return result;}// 后处理函数(YOLO 输出解析示例,假设 4 个边界框值 + 1 置信度 + 类数)private static List<DetectionResult> PostProcess(Tensor<float> output, int ih, int iw){float[] data = output.DownloadToArray();  // 获取浮点数据List<DetectionResult> detections = new List<DetectionResult>();int numDetections = output.shape[2];  // 如 8400int numClasses = output.shape[1] - 5; // 假设前 5 是 x,y,w,h,conffor (int i = 0; i < numDetections; i++){float conf = data[i * output.shape[1] + 4];  // 置信度if (conf > 0.5f)  // 阈值过滤{float cx = data[i * output.shape[1] + 0] / iw;  // 归一化float cy = data[i * output.shape[1] + 1] / ih;float w = data[i * output.shape[1] + 2] / iw;float h = data[i * output.shape[1] + 3] / ih;// 转换为边界框 (x1,y1,x2,y2)float x1 = cx - w / 2;float y1 = cy - h / 2;float x2 = cx + w / 2;float y2 = cy + h / 2;// 找到最高类分数int bestClass = -1;float bestScore = 0;for (int c = 0; c < numClasses; c++){float score = data[i * output.shape[1] + 5 + c] * conf;if (score > bestScore){bestScore = score;bestClass = c;}}if (bestScore > 0.5f){detections.Add(new DetectionResult { box = new Rect(x1 * iw, y1 * ih, w * iw, h * ih), classId = bestClass, score = bestScore });}}}// 应用 NMS 去除重叠框detections = ApplyNMS(detections, 0.45f);  // IoU 阈值 0.45return detections;}// 简单 NMS 实现(可优化)private static List<DetectionResult> ApplyNMS(List<DetectionResult> detections, float iouThreshold){// 按分数排序detections.Sort((a, b) => b.score.CompareTo(a.score));List<DetectionResult> final = new List<DetectionResult>();while (detections.Count > 0){final.Add(detections[0]);detections.RemoveAt(0);for (int i = detections.Count - 1; i >= 0; i--){if (CalculateIoU(final[^1].box, detections[i].box) > iouThreshold){detections.RemoveAt(i);}}}return final;}private static float CalculateIoU(Rect a, Rect b){float interX = Mathf.Max(0, Mathf.Min(a.xMax, b.xMax) - Mathf.Max(a.xMin, b.xMin));float interY = Mathf.Max(0, Mathf.Min(a.yMax, b.yMax) - Mathf.Max(a.yMin, b.yMin));float interArea = interX * interY;float unionArea = a.width * a.height + b.width * b.height - interArea;return interArea / unionArea;}// 显示结果(绘制边界框)private static void DisplayResults(Texture2D texture, List<DetectionResult> results){foreach (var res in results){// 使用 Graphics.DrawTexture 或 LineRenderer 绘制框(简化示例)Debug.Log($"Detected class {res.classId} at {res.box} with score {res.score}");// 实际中:用 Texture2D.SetPixels 绘制红色框}}private struct DetectionResult{public Rect box;  // 基于输入大小的像素坐标public int classId;public float score;}private void OnDestroy(){_worker?.Dispose();}
}

待续

待后续深入研究…

http://www.dtcms.com/a/557138.html

相关文章:

  • php+redis基本操作及操作说明
  • 神经网络补充知识
  • 怎么在网站后台删除图片vps搭建网站教程
  • MySQL主从数据一致性校验工具:pt-table-checksum 详解
  • 源码分享:AI照片风格化小程序
  • 哪些行业做网站的多如何做二级域名子目录网站
  • jQuery Mobile 面板
  • Vulfocus---shiro反序列化漏洞
  • 搭建以Node-RED为基础的“小快灵”智能工厂OT底座的实现框架
  • 三防平板三防是指哪三防?适合应用在什么场景?
  • <数据集>yolo织物缺陷识别数据集<目标检测>
  • ubuntu中安装多版本esp-idf环境
  • 网站建设与管理相关工作岗位智慧团建在线登录
  • 从依赖地狱到仓库中枢:Nexus让包管理像逛超市一样简单
  • 用pw后缀的网站今天广西紧急通知最新
  • 果合gohe网站建设网站的互动功能
  • php做网站模板wordpress 顶部导航条
  • 集成RabbitMQ+MQ常用操作
  • element-ui源码阅读-开篇
  • 11月1日
  • SSM基于个性化推荐的新房信息服务平台xr73q(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
  • 2025年11月1日-AI 驱动教学革命:3 分钟生成专业级动画课件,还能导出视频 GIF!
  • 网站项目开发的制作流程网站管理方案
  • Robot Framework Log关键字:实时日志输出的强大力量
  • 合泰单片机之定时器
  • No049:诗意的栖居——当DeepSeek开始在逻辑之外触摸真理
  • 从穿孔卡片到云原生:批处理系统的不朽演进与核心思想
  • 技术准备五:protoBuf
  • 做网站找不到客户有什么网站可以免费搭建网址
  • 算法题(251):最短路计数