🏆本文收录于专栏 《YOLOv26实战:从入门到深度优化》。 本专栏围绕 YOLOv26 的改进、训练、部署与工程优化 展开,系统梳理并复现当前主流的 YOLOv26 实战案例与优化方案,内容目前已覆盖 分类、检测、分割、追踪、关键点、OBB 检测 等多个方向。 整体坚持 持续更新 + 深度解析 + 工程导向 的写作思路,不仅关注模型结构本身,也关注训练策略、损失函数设计、推理加速、部署适配以及真实项目中的问题排查。部分章节还会结合国内外前沿论文与 AIGC 大模型技术,对主流改进方案进行重构与再设计。
🎯当前专栏限时优惠中:一次订阅,终身有效,后续更新内容均可免费解锁 👉 点此查看专栏详情 👈️ 🎉本专栏还不够过瘾?别急,好戏才刚刚开始!我已经为你准备了一整套 YOLO 进阶实战大礼包🎁:
👉《YOLOv8实战》 👉《YOLOv9实战》 👉《YOLOv10实战》 👉《YOLOv11实战》 👉《YOLOv12实战》 👉以及最新上线的 《YOLOv26实战》
想一次搞定所有版本?直接冲 《YOLO全栈实战合集》,一站式涵盖 YOLO 各版本实战教学!
🚀想学哪个版本?直接找 bug 菌“许愿”,安排!必须安排!🚀
🎯 本文定位:目标检测 × YOLOv26 导出、部署与边缘优化篇 📅 预计阅读时间:约50~60 分钟 ⭐ 难度等级:⭐⭐⭐⭐☆(高级) 🔧 技术栈:Ultralytics YOLO26 | Python v3.10–3.12 | PyTorch v2.5+ | torchvision v0.20+ | Ultralytics v8.4.0+ | CUDA v11.8 / v12.1+
全文目录:
-
- 📌 上期回顾
- 🎯 本节目标
- 一、为什么 YOLOv26 部署需要 Docker
-
- 1.1 "在我机器上能跑"的魔咒
- 1.2 YOLOv26 的部署特点与 Docker 的契合度
- 1.3 Docker 在推理部署中的三大核心价值
- 二、Docker 核心概念速查(部署视角)
-
- 2.1 镜像(Image)与容器(Container)
- 2.2 Dockerfile
- 2.3 Volumes 与 Bind Mounts
- 2.4 网络模式
- 三、基础镜像选型:CPU vs GPU
-
- 3.1 CPU 推理路线
- 3.2 GPU 推理路线
- 3.3 版本对应关系表
- 四、完整 Dockerfile 编写(CPU 路线)
-
- 4.1 项目目录结构
- 4.2 .dockerignore 文件
- 4.3 requirements.txt
- 4.4 完整 Dockerfile(CPU,含详细注释)
- 4.5 核心推理脚本 inference.py
- 五、Docker 镜像构建与运行
-
- 5.1 构建镜像
- 5.2 运行容器
- 5.3 交互式调试
- 六、GPU 版 Dockerfile
-
- 6.1 Dockerfile.gpu
- 6.2 GPU 容器运行命令
- 七、多阶段构建:压缩镜像体积
-
- 7.1 多阶段构建的原理
- 八、Docker Compose 多服务编排
-
- 8.1 为什么需要 Docker Compose
- 8.2 docker-compose.yml
- 8.3 Nginx 配置(结果展示服务)
- 8.4 Docker Compose 常用命令
- 九、推理流程与架构图
-
- 9.1 Docker 镜像构建流程图
- 9.2 容器内推理数据流图
- 9.3 多服务 Compose 架构图
- 十、ARM 架构与 Apple Silicon 适配
-
- 10.1 构建多架构镜像
- 10.2 Apple Silicon(M1/M2/M3)本地运行
- 10.3 NVIDIA Jetson 系列适配
- 十一、镜像推送与私有仓库管理
-
- 11.1 推送到 Docker Hub
- 11.2 搭建私有镜像仓库(Harbor / Registry)
- 11.3 镜像版本管理策略
- 十二、容器化部署的性能调优
-
- 12.1 CPU 推理性能优化
- 12.2 批处理优化
- 12.3 容器资源限制配置
- 十三、CI/CD 集成:自动化构建与部署
-
- 13.1 GitHub Actions 工作流
- 十四、常见问题排查手册
-
- 14.1 libGL.so.1 / libgthread 相关错误
- 14.2 ONNX 模型加载失败
- 14.3 CUDA out of memory(GPU 场景)
- 14.4 容器内权限问题
- 14.5 网络问题:镜像拉取失败
- 14.6 推理结果为空(无检测框)
- 十五、安全加固:生产环境的最佳实践
-
- 15.1 不以 root 身份运行容器
- 15.2 模型文件的安全存储
- 十六、完整配置文件:infer_config.yaml
- 十七、完整使用手册:从零到运行
- 总结与思考
- 🔭 下期预告:YOLOv26 FastAPI 检测接口开发
- 🧧🧧 文末福利,等你来拿!🧧🧧
- 🫵 Who am I?
📌 上期回顾
在上期《YOLOv26【第十章:导出、部署与边缘优化篇·第12节】YOLOv26 C++ OpenCV DNN 推理实践》内容中,我们深入探讨了如何在 C++ 环境下借助 OpenCV 的 DNN 模块完成 YOLOv26 的推理部署。整个流程从模型导出开始——需要将训练好的 .pt 权重导出为 ONNX 格式,并注意 opset 版本与 simplify 选项的配合。在 C++ 侧,我们详细讲解了图像预处理的等比缩放(letterbox)逻辑,解释了为什么直接 resize 会破坏目标比例从而影响检测精度。
后处理部分是上节的重点难点。针对 end2end=True 的导出,输出 Tensor 已经不含冗余框,直接读取即可;而 end2end=False 的导出则需要手动进行置信度过滤与 NMS(非极大值抑制)。我们提供了完整的 C++ 代码实现,包含 cv::dnn::NMSBoxes 的调用方式,以及输出 Tensor shape 解析的注意事项([1, 84, 8400] 转置为 [1, 8400, 84] 的处理)。
最终效果是:在 CPU 环境下,OpenCV DNN + ONNX 的推理链路无需任何深度学习框架依赖,编译出来的二进制可以直接移植到大多数 Linux 生产环境,具备很强的通用性。如果你当时跟着操作过,相信对 ONNX 输出结构已经有了相当清晰的认识——这个认识在今天的 Docker 容器化部署中同样用得上。
🎯 本节目标
本节我们要完成的事情,用一句话概括就是:把 YOLOv26 的推理环境打包进 Docker 镜像,让它能在任何支持 Docker 的机器上一键启动、稳定运行。
具体来说,我们会覆盖以下内容:
一、为什么 YOLOv26 部署需要 Docker
1.1 "在我机器上能跑"的魔咒
做过一段时间工程落地的人,大概都碰到过这类场景:模型训练完,本地推理一切正常,信心满满地交给运维同学部署,结果对方反馈各种奇怪的报错——numpy 版本不对、onnxruntime 找不到某个动态库、CUDA 版本不匹配……这不是谁的失误,而是软件依赖的本质复杂性使然。
深度学习推理环境的依赖链条其实相当长:
Python 版本
└── PyTorch / ONNXRuntime 版本
└── CUDA 版本(如果用 GPU)
└── cuDNN 版本
└── 系统 GLIBC 版本
└── 驱动版本
任何一环出现版本偏差,都可能导致推理失败。而 Docker 的核心价值,就是把这整条链路"封装"起来,让运行时环境跟着代码走,而不是让代码去适配不同的运行时环境。
1.2 YOLOv26 的部署特点与 Docker 的契合度
YOLOv26 官方明确定位为"更适合部署的版本",这体现在几个方面:精简的模型结构、对 ONNX 导出的良好支持、end2end 模式减少后处理依赖。这些特点恰好与 Docker 容器化部署形成很好的互补:
- ONNX 格式本身就是跨平台的,容器内的 onnxruntime 版本固定后,推理行为可以完全确定。
- end2end=True 导出的模型输出结构简单,后处理逻辑少,容器内的代码量更小,镜像更干净。
- YOLOv26 对 Python 版本的要求相对明确,与 Docker 镜像的版本锁定机制高度吻合。
1.3 Docker 在推理部署中的三大核心价值
一致性(Consistency):开发环境、测试环境、生产环境使用完全相同的镜像,消除"环境差异"导致的问题。
可移植性(Portability):一个镜像可以在任何支持 Docker 的机器上运行——无论是本地工作站、云服务器、边缘计算节点还是 CI/CD 流水线。
隔离性(Isolation):推理服务与宿主机环境完全隔离,不会因为宿主机升级 Python 或某个系统库而影响推理结果,也不会污染宿主机环境。
二、Docker 核心概念速查(部署视角)
在开始写 Dockerfile 之前,我们先用部署工程师的视角梳理几个核心概念。如果你已经熟悉 Docker,可以跳过这部分;如果你是第一次用 Docker 做模型部署,这部分值得多看一眼。
2.1 镜像(Image)与容器(Container)
镜像是一个静态的、只读的文件系统快照,包含了运行某个应用所需的所有内容——操作系统层、运行时、依赖库、代码文件。容器是镜像的运行实例,类似于"从模板创建出来的虚拟机实例",但比虚拟机轻量得多。
对于 YOLOv26 推理部署来说:镜像 = 推理环境的完整配方;容器 = 真正在跑推理的进程。
2.2 Dockerfile
Dockerfile 是构建镜像的"食谱",每一行指令对应镜像的一层(Layer)。Docker 的层缓存机制意味着:如果某一层没有变化,构建时会直接复用缓存,大幅加速构建速度。理解这一点对于合理编写 Dockerfile 非常重要——变化频繁的指令应该放在后面。
2.3 Volumes 与 Bind Mounts
容器本身是无状态的——容器停止后,写入容器内的数据默认消失。对于 YOLOv26 推理来说,模型文件、输入图像、输出结果通常不应该打包进镜像(否则每次更换模型都要重新构建镜像)。
解决方案是挂载(Mount):
- Bind Mount:将宿主机的某个目录直接映射到容器内的某个路径,适合开发阶段的模型文件管理。
- Volume:Docker 管理的持久化存储,适合生产环境的数据持久化。
2.4 网络模式
容器内的服务(比如后面章节会讲的 FastAPI 接口)默认不对外暴露端口。需要通过 -p host_port:container_port 映射才能从宿主机访问。
三、基础镜像选型:CPU vs GPU
这是容器化部署的第一个关键决策,选错了后面全白费。
3.1 CPU 推理路线
如果你的推理场景是:轻量级实时检测、边缘设备、没有 GPU 的云服务器、CI/CD 自动化测试——选 CPU 路线。
基础镜像推荐:
# 选项1:官方 Python 镜像(最通用,体积适中)
FROM python:3.10-slim-bookworm
# 选项2:更轻量,但需要手动安装更多依赖
FROM python:3.10-alpine
# 选项3:Ubuntu 基础,适合需要 apt 安装大量系统依赖的场景
FROM ubuntu:22.04
对于 YOLOv26 ONNX 推理,python:3.10-slim-bookworm 是最平衡的选择:基于 Debian Bookworm,slim 变体删除了文档和缓存但保留了完整的 apt 包管理能力,对 onnxruntime 的依赖兼容性最好。
3.2 GPU 推理路线
如果你的推理场景是:高并发批量推理、TensorRT 加速、需要 FP16/INT8 量化——选 GPU 路线。
GPU 路线的基础镜像必须来自 NVIDIA 官方的 nvidia/cuda 系列:
# CUDA 12.1 + cuDNN 8 + Ubuntu 22.04(适配主流 GPU 推理)
FROM nvidia/cuda:12.1.1-cudnn8-runtime-ubuntu22.04
# 或者直接使用 NGC 的 PyTorch 镜像(已预装 CUDA + PyTorch + cuDNN)
FROM nvcr.io/nvidia/pytorch:24.01-py3
GPU 路线有一个前提条件:宿主机必须安装 NVIDIA 驱动,并且安装了 nvidia-container-toolkit。否则容器内无法访问 GPU 硬件。
验证命令:
# 宿主机执行,确认驱动与 container toolkit 正常
nvidia-smi
docker run –rm –gpus all nvidia/cuda:12.1.1-base-ubuntu22.04 nvidia-smi
3.3 版本对应关系表
| CPU 推理 | python:3.10-slim-bookworm | onnxruntime==1.18.0 | 3.10 |
| GPU 推理(ONNX) | nvidia/cuda:12.1.1-cudnn8-runtime-ubuntu22.04 | onnxruntime-gpu==1.18.0 | 3.10 |
| GPU 推理(TensorRT) | nvcr.io/nvidia/tensorrt:24.01-py3 | — | 3.10 |
| 边缘设备(ARM) | arm64v8/python:3.10-slim-bookworm | onnxruntime==1.18.0 | 3.10 |
⚠️ 注意:onnxruntime 与 onnxruntime-gpu 不能同时安装,二者是互斥的。CPU 环境装了 -gpu 版本不会报错但会有警告,GPU 环境装了非 -gpu 版本则无法利用 GPU 加速。
四、完整 Dockerfile 编写(CPU 路线)
我们先从最实用、覆盖面最广的 CPU 路线开始,一行一行地讲清楚每个指令的用意。
4.1 项目目录结构
在写 Dockerfile 之前,先把项目结构规划好:
yolov26–docker/
├── Dockerfile # CPU 推理镜像构建文件
├── Dockerfile.gpu # GPU 推理镜像构建文件(后面讲)
├── docker–compose.yml # 多服务编排文件
├── .dockerignore # 排除不需要打包的文件
├── requirements.txt # Python 依赖列表
├── src/
│ ├── inference.py # 推理主脚本
│ ├── preprocess.py # 图像预处理模块
│ └── postprocess.py # 后处理模块
├── models/ # 模型文件目录(通过挂载注入,不打包进镜像)
│ └── .gitkeep
├── data/
│ ├── input/ # 输入图像目录
│ └── output/ # 推理结果输出目录
└── configs/
└── infer_config.yaml # 推理配置文件
这个结构设计有几个原则:
- 模型文件(.onnx)不打包进镜像,通过 Volume 挂载注入,方便更换模型版本
- 源代码打包进镜像,保证推理逻辑版本固定
- 输入输出目录通过挂载暴露给宿主机,方便查看推理结果
4.2 .dockerignore 文件
构建镜像时,Docker 会把整个构建上下文发送给 Docker daemon。如果目录里有大型模型文件、训练数据集,会拖慢构建速度。.dockerignore 的作用就是告诉 Docker:“这些文件不用打包进去”。
# 模型文件不打包进镜像(通过挂载注入)
models/
*.onnx
*.pt
*.engine
# 数据集和测试图像
data/
datasets/
# Python 缓存
__pycache__/
*.pyc
*.pyo
*.pyd
.Python
*.egg-info/
dist/
build/
# 虚拟环境
.venv/
venv/
env/
# Git 相关
.git/
.gitignore
# IDE 配置
.vscode/
.idea/
*.code-workspace
# 日志文件
*.log
logs/
# 测试文件
tests/
*.test.py
4.3 requirements.txt
# YOLOv26 核心依赖
ultralytics>=8.3.0
# ONNX 推理运行时(CPU 版本)
onnxruntime==1.18.0
# ONNX 模型工具
onnx==1.16.0
# 图像处理
opencv-python-headless==4.10.0.84
Pillow==10.4.0
numpy==1.26.4
# 配置文件解析
PyYAML==6.0.1
# 日志
loguru==0.7.2
# 进度条(可选)
tqdm==4.66.4
关于 opencv-python-headless:容器环境通常没有图形界面(X11 显示服务器),使用 opencv-python 会报 libGL.so.1: cannot open shared object file 这类错误。headless 版本去掉了 GUI 相关的依赖,专门为无头服务器环境设计,体积也更小。这是容器化部署中最常见的踩坑点之一。
4.4 完整 Dockerfile(CPU,含详细注释)
# ============================================================
# YOLOv26 CPU 推理 Docker 镜像
# 基础镜像:Python 3.10 Slim (Debian Bookworm)
# 用途:ONNX 格式 YOLOv26 的 CPU 推理部署
# ============================================================
# ———————————————————–
# 第一阶段:依赖安装层
# 使用独立阶段安装依赖,利用 Docker 层缓存机制
# 当 requirements.txt 不变时,此层会被缓存复用
# ———————————————————–
FROM python:3.10-slim-bookworm AS base
# 设置环境变量
# PYTHONDONTWRITEBYTECODE=1:不生成 .pyc 字节码文件,减少镜像体积
# PYTHONUNBUFFERED=1:Python 输出不缓冲,确保日志实时输出到 Docker logs
# DEBIAN_FRONTEND=noninteractive:apt 安装时不弹出交互提示
ENV PYTHONDONTWRITEBYTECODE=1 \\
PYTHONUNBUFFERED=1 \\
DEBIAN_FRONTEND=noninteractive \\
PIP_NO_CACHE_DIR=1 \\
PIP_DISABLE_PIP_VERSION_CHECK=1
# 安装系统级依赖
# libglib2.0-0:OpenCV 的底层依赖(headless 版本仍需要)
# libgomp1:OpenMP 支持,onnxruntime 的并行推理依赖
# libgl1-mesa-glx:部分 OpenCV 操作需要(即使 headless)
# ca-certificates:HTTPS 证书,pip 下载包需要
# wget curl:调试工具(可在生产镜像中去掉)
RUN apt-get update && apt-get install -y –no-install-recommends \\
libglib2.0-0 \\
libgomp1 \\
libsm6 \\
libxext6 \\
libxrender-dev \\
libgl1-mesa-glx \\
ca-certificates \\
wget \\
curl \\
&& rm -rf /var/lib/apt/lists/*
# 注意:rm -rf /var/lib/apt/lists/* 清除 apt 缓存,减小镜像体积
# 这个清理操作必须和 apt-get install 在同一个 RUN 指令中执行
# 否则即使清理了,前一层的缓存依然存在于最终镜像中
# ———————————————————–
# 第二阶段:Python 依赖安装
# ———————————————————–
FROM base AS dependencies
# 设置工作目录
WORKDIR /app
# 先复制 requirements.txt(而不是整个代码目录)
# 这样当代码变更时,只要 requirements.txt 不变,这一层会被缓存
# 避免每次代码变更都重新安装依赖(安装依赖通常是最耗时的步骤)
COPY requirements.txt .
# 安装 Python 依赖
# –no-cache-dir:不缓存 pip 下载的包,减小镜像体积
# 使用清华镜像源加速(国内环境推荐)
RUN pip install –no-cache-dir \\
-i https://pypi.tuna.tsinghua.edu.cn/simple \\
-r requirements.txt
# ———————————————————–
# 第三阶段:最终运行镜像
# ———————————————————–
FROM dependencies AS runtime
# 设置工作目录
WORKDIR /app
# 创建必要的目录结构
# models/:模型文件挂载点(容器启动时通过 -v 挂载)
# data/input/:推理输入图像目录
# data/output/:推理结果输出目录
# logs/:日志文件目录
RUN mkdir -p models data/input data/output logs
# 复制应用源代码
# 注意:这一步在依赖安装之后,利用层缓存
# 代码变更时只重新执行这一层,不重新安装依赖
COPY src/ ./src/
COPY configs/ ./configs/
# 创建非 root 用户运行推理服务(安全最佳实践)
# 在生产环境中,容器以 root 身份运行存在安全风险
RUN groupadd -r yolov26user && useradd -r -g yolov26user yolov26user
RUN chown -R yolov26user:yolov26user /app
USER yolov26user
# 声明挂载点
# 这是一种文档声明,告诉使用者这些路径应该被挂载
VOLUME ["/app/models", "/app/data/input", "/app/data/output"]
# 健康检查
# 每 30 秒检查一次,超时 10 秒,失败 3 次认为不健康
# 这里用 python -c 检查推理依赖是否正常加载
HEALTHCHECK –interval=30s –timeout=10s –start-period=5s –retries=3 \\
CMD python -c "import onnxruntime; import cv2; print('healthy')" || exit 1
# 暴露端口(预留,FastAPI 服务用)
EXPOSE 8000
# 设置默认启动命令
# 容器启动时默认运行 inference.py,处理 data/input 下的图像
ENTRYPOINT ["python", "src/inference.py"]
CMD ["–input", "data/input", "–output", "data/output", "–model", "models/yolov26n.onnx"]
4.5 核心推理脚本 inference.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
YOLOv26 ONNX 推理脚本(容器化部署版本)
支持:单图推理、批量推理、目录批量推理
兼容:end2end=True 和 end2end=False 两种导出模式
"""
import argparse
import os
import sys
import time
from pathlib import Path
from typing import List, Tuple, Optional
import cv2
import numpy as np
import onnxruntime as ort
from loguru import logger
# ============================================================
# 配置日志格式
# ============================================================
logger.remove() # 移除默认处理器
logger.add(
sys.stdout,
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{line}</cyan> – <level>{message}</level>",
level="INFO"
)
logger.add(
"logs/inference_{time:YYYY-MM-DD}.log",
rotation="1 day", # 每天轮换日志文件
retention="7 days", # 保留7天的日志
level="DEBUG"
)
# ============================================================
# COCO 80 类别名称(YOLOv26 默认训练集)
# ============================================================
COCO_CLASSES = [
'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train',
'truck', 'boat', 'traffic light', 'fire hydrant', 'stop sign',
'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep',
'cow', 'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella',
'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard',
'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard',
'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup', 'fork',
'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich', 'orange',
'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair',
'couch', 'potted plant', 'bed', 'dining table', 'toilet', 'tv',
'laptop', 'mouse', 'remote', 'keyboard', 'cell phone', 'microwave',
'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase',
'scissors', 'teddy bear', 'hair drier', 'toothbrush'
]
# 为每个类别生成固定颜色(BGR 格式)
# 使用 HSV 色彩空间均匀分布,确保颜色区分度
np.random.seed(42)
COLORS = {
cls_name: tuple(np.random.randint(0, 255, 3).tolist())
for cls_name in COCO_CLASSES
}
class YOLOv26ONNXInference:
"""
YOLOv26 ONNX 推理类
支持两种导出模式:
– end2end=True:输出已经包含 NMS 结果,直接读取检测框
– end2end=False:输出原始预测,需要手动 NMS
Args:
model_path: ONNX 模型文件路径
conf_threshold: 置信度阈值(默认 0.25)
iou_threshold: NMS IOU 阈值(默认 0.45)
input_size: 模型输入尺寸(默认 640)
num_threads: ONNX 推理线程数(默认使用 CPU 核心数)
"""
def __init__(
self,
model_path: str,
conf_threshold: float = 0.25,
iou_threshold: float = 0.45,
input_size: int = 640,
num_threads: int = 0 # 0 表示使用系统默认(自动检测 CPU 核心数)
):
self.model_path = model_path
self.conf_threshold = conf_threshold
self.iou_threshold = iou_threshold
self.input_size = input_size
# 初始化 ONNX Runtime 推理会话
self._init_session(num_threads)
# 解析模型输入输出信息
self._parse_model_info()
logger.info(f"YOLOv26 ONNX 推理引擎初始化完成")
logger.info(f"模型路径: {model_path}")
logger.info(f"输入尺寸: {self.input_size}x{self.input_size}")
logger.info(f"置信度阈值: {self.conf_threshold}")
logger.info(f"NMS IOU 阈值: {self.iou_threshold}")
logger.info(f"End2End 模式: {self.is_end2end}")
def _init_session(self, num_threads: int):
"""初始化 ONNX Runtime 推理会话,配置执行提供者"""
# 配置会话选项
sess_options = ort.SessionOptions()
# 设置推理线程数
# 在容器中,可以通过环境变量 OMP_NUM_THREADS 控制,也可以直接设置
if num_threads > 0:
sess_options.intra_op_num_threads = num_threads
sess_options.inter_op_num_threads = num_threads
# 设置图优化级别(ORT_ENABLE_ALL 启用所有优化)
sess_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
# 设置执行模式(SEQUENTIAL 适合单图推理;PARALLEL 适合多图并发)
sess_options.execution_mode = ort.ExecutionMode.ORT_SEQUENTIAL
# 选择执行提供者(Provider)
# CPUExecutionProvider 是最通用的,任何环境都支持
# 如果有 GPU,可以添加 CUDAExecutionProvider 或 TensorrtExecutionProvider
available_providers = ort.get_available_providers()
logger.info(f"可用的执行提供者: {available_providers}")
# 按优先级选择提供者
providers = []
if 'CUDAExecutionProvider' in available_providers:
providers.append(('CUDAExecutionProvider', {
'device_id': 0,
'arena_extend_strategy': 'kNextPowerOfTwo',
'gpu_mem_limit': 2 * 1024 * 1024 * 1024, # 限制 GPU 内存 2GB
'cudnn_conv_algo_search': 'EXHAUSTIVE',
}))
logger.info("检测到 CUDA,启用 GPU 推理加速")
providers.append('CPUExecutionProvider')
# 创建推理会话
self.session = ort.InferenceSession(
self.model_path,
sess_options=sess_options,
providers=providers
)
logger.info(f"实际使用的执行提供者: {self.session.get_providers()}")
def _parse_model_info(self):
"""解析模型的输入输出信息,判断是否为 end2end 模式"""
# 获取输入节点信息
input_info = self.session.get_inputs()
self.input_name = input_info[0].name
self.input_shape = input_info[0].shape # 通常为 [1, 3, 640, 640]
# 获取输出节点信息
output_info = self.session.get_outputs()
self.output_names = [out.name for out in output_info]
self.output_shapes = [out.shape for out in output_info]
logger.debug(f"模型输入: {self.input_name}, shape={self.input_shape}")
logger.debug(f"模型输出: {self.output_names}, shapes={self.output_shapes}")
# 判断是否为 end2end 模式
# end2end=True 的导出通常有 3 个输出节点:num_dets, boxes, scores, labels
# end2end=False 的导出通常只有 1 个输出节点:predictions [1, 84, 8400]
self.is_end2end = len(output_info) >= 3
if self.is_end2end:
logger.info("检测到 End2End 模式(含内置 NMS),直接读取检测结果")
else:
logger.info("检测到标准模式(不含 NMS),将执行后处理 NMS")
def preprocess(self, image: np.ndarray) –> Tuple[np.ndarray, float, Tuple[int, int]]:
"""
图像预处理:letterbox 缩放 + 归一化
letterbox 的核心思想:保持图像宽高比,用灰色填充空白区域
这样可以避免直接 resize 导致目标变形,影响检测精度
Args:
image: 原始图像,BGR 格式,shape=[H, W, C]
Returns:
blob: 模型输入 Tensor,shape=[1, 3, H, W]
scale: 缩放比例(用于还原坐标)
pad: 填充量 (pad_w, pad_h)(用于还原坐标)
"""
orig_h, orig_w = image.shape[:2]
target_size = self.input_size
# 计算等比缩放比例(取较小的缩放比,确保长边不超出目标尺寸)
scale = min(target_size / orig_h, target_size / orig_w)
# 缩放后的实际尺寸
new_h = int(orig_h * scale)
new_w = int(orig_w * scale)
# 执行等比缩放
resized = cv2.resize(image, (new_w, new_h), interpolation=cv2.INTER_LINEAR)
# 计算填充量(使缩放后图像居中)
pad_w = (target_size – new_w) / 2
pad_h = (target_size – new_h) / 2
# 创建目标尺寸的灰色画布(114 是 YOLO 系列的标准填充值)
padded = np.full((target_size, target_size, 3), 114, dtype=np.uint8)
# 将缩放后的图像复制到画布中心
top = int(pad_h)
left = int(pad_w)
padded[top:top+new_h, left:left+new_w] = resized
# BGR -> RGB(YOLO 训练使用 RGB 格式)
rgb = cv2.cvtColor(padded, cv2.COLOR_BGR2RGB)
# HWC -> CHW(Tensor 格式:[C, H, W])
chw = rgb.transpose(2, 0, 1)
# 归一化到 [0, 1]
normalized = chw.astype(np.float32) / 255.0
# 添加 batch 维度 -> [1, C, H, W]
blob = np.expand_dims(normalized, axis=0)
return blob, scale, (pad_w, pad_h)
def postprocess_end2end(
self,
outputs: List[np.ndarray],
scale: float,
pad: Tuple[float, float],
orig_shape: Tuple[int, int]
) –> List[dict]:
"""
End2End 模式后处理
End2End 模式输出通常包含:
– num_dets: [1, 1],检测到的目标数量
– boxes: [1, max_dets, 4],检测框坐标(xyxy格式)
– scores: [1, max_dets],置信度分数
– labels: [1, max_dets],类别索引
Args:
outputs: ONNX 推理的原始输出列表
scale: 预处理中的缩放比例
pad: 预处理中的填充量 (pad_w, pad_h)
orig_shape: 原始图像尺寸 (H, W)
Returns:
检测结果列表,每个元素为 dict:
{'box': [x1, y1, x2, y2], 'score': float, 'class_id': int, 'class_name': str}
"""
orig_h, orig_w = orig_shape
pad_w, pad_h = pad
# 解析 end2end 输出
# 不同导出配置的输出节点顺序可能有差异,这里按常见顺序处理
num_dets = int(outputs[0][0, 0]) # 实际检测数量
boxes = outputs[1][0] # shape: [max_dets, 4]
scores = outputs[2][0] # shape: [max_dets]
labels = outputs[3][0] # shape: [max_dets]
results = []
for i in range(num_dets):
score = float(scores[i])
# 过滤低置信度检测结果
if score < self.conf_threshold:
continue
# 获取检测框(在 letterbox 处理后的坐标系中)
x1, y1, x2, y2 = boxes[i]
# 坐标变换:从 letterbox 坐标系还原到原始图像坐标系
# 步骤1:减去填充偏移
x1 = (x1 – pad_w) / scale
y1 = (y1 – pad_h) / scale
x2 = (x2 – pad_w) / scale
y2 = (y2 – pad_h) / scale
# 步骤2:裁剪到原始图像范围内
x1 = max(0, min(int(x1), orig_w))
y1 = max(0, min(int(y1), orig_h))
x2 = max(0, min(int(x2), orig_w))
y2 = max(0, min(int(y2), orig_h))
class_id = int(labels[i])
class_name = COCO_CLASSES[class_id] if class_id < len(COCO_CLASSES) else f"class_{class_id}"
results.append({
'box': [x1, y1, x2, y2],
'score': score,
'class_id': class_id,
'class_name': class_name
})
return results
def postprocess_standard(
self,
outputs: List[np.ndarray],
scale: float,
pad: Tuple[float, float],
orig_shape: Tuple[int, int]
) –> List[dict]:
"""
标准模式后处理(end2end=False)
标准模式输出:[1, 84, 8400]
– 84 = 4(边框)+ 80(类别分数)
– 8400 = 三个检测头的预测框数量之和(80×80 + 40×40 + 20×20 = 8400)
需要手动执行:
1. 置信度过滤
2. 坐标转换(xywh -> xyxy)
3. NMS
Args:
outputs: ONNX 推理的原始输出列表
scale: 预处理中的缩放比例
pad: 预处理中的填充量 (pad_w, pad_h)
orig_shape: 原始图像尺寸 (H, W)
Returns:
检测结果列表
"""
orig_h, orig_w = orig_shape
pad_w, pad_h = pad
# 原始输出 shape: [1, 84, 8400]
# 转置为 [8400, 84] 方便逐框处理
predictions = outputs[0][0].T # shape: [8400, 84]
# 分离坐标和类别分数
boxes_xywh = predictions[:, :4] # [8400, 4] -> cx, cy, w, h(letterbox坐标系)
class_scores = predictions[:, 4:] # [8400, 80] -> 各类别分数
# 计算每个预测框的最高类别分数和对应类别
max_scores = np.max(class_scores, axis=1) # [8400]
class_ids = np.argmax(class_scores, axis=1) # [8400]
# 置信度过滤(第一轮粗过滤,减少后续处理量)
mask = max_scores >= self.conf_threshold
if not np.any(mask):
return []
boxes_xywh = boxes_xywh[mask]
max_scores = max_scores[mask]
class_ids = class_ids[mask]
# xywh -> xyxy 坐标转换
# YOLO 输出的是中心点坐标 + 宽高,需要转换为左上右下格式
cx, cy, w, h = boxes_xywh[:, 0], boxes_xywh[:, 1], boxes_xywh[:, 2], boxes_xywh[:, 3]
x1 = cx – w / 2
y1 = cy – h / 2
x2 = cx + w / 2
y2 = cy + h / 2
boxes_xyxy = np.stack([x1, y1, x2, y2], axis=1)
# 执行 NMS(非极大值抑制)
# OpenCV 的 NMSBoxes 需要 [x, y, w, h] 格式
boxes_for_nms = np.stack([x1, y1, w, h], axis=1).tolist()
scores_for_nms = max_scores.tolist()
indices = cv2.dnn.NMSBoxes(
boxes_for_nms,
scores_for_nms,
self.conf_threshold,
self.iou_threshold
)
if len(indices) == 0:
return []
# 处理 NMS 后的检测结果
results = []
# 注意:cv2.dnn.NMSBoxes 的返回值格式在不同 OpenCV 版本中有差异
# 较新版本(4.x)返回 [idx] 或 [[idx]] 格式
if isinstance(indices, np.ndarray):
indices = indices.flatten()
for idx in indices:
bx1, by1, bx2, by2 = boxes_xyxy[idx]
score = float(max_scores[idx])
class_id = int(class_ids[idx])
# 坐标还原:letterbox 坐标系 -> 原始图像坐标系
bx1 = max(0, min(int((bx1 – pad_w) / scale), orig_w))
by1 = max(0, min(int((by1 – pad_h) / scale), orig_h))
bx2 = max(0, min(int((bx2 – pad_w) / scale), orig_w))
by2 = max(0, min(int((by2 – pad_h) / scale), orig_h))
class_name = COCO_CLASSES[class_id] if class_id < len(COCO_CLASSES) else f"class_{class_id}"
results.append({
'box': [bx1, by1, bx2, by2],
'score': score,
'class_id': class_id,
'class_name': class_name
})
return results
def infer(self, image: np.ndarray) –> Tuple[List[dict], float]:
"""
执行单张图像推理
Args:
image: 原始图像,BGR 格式
Returns:
(检测结果列表, 推理耗时(ms))
"""
orig_shape = image.shape[:2] # (H, W)
# 预处理
blob, scale, pad = self.preprocess(image)
# ONNX 推理
start_time = time.perf_counter()
outputs = self.session.run(self.output_names, {self.input_name: blob})
infer_time = (time.perf_counter() – start_time) * 1000 # 转换为毫秒
# 后处理
if self.is_end2end:
results = self.postprocess_end2end(outputs, scale, pad, orig_shape)
else:
results = self.postprocess_standard(outputs, scale, pad, orig_shape)
return results, infer_time
def draw_results(self, image: np.ndarray, results: List[dict]) –> np.ndarray:
"""
在图像上绘制检测结果
Args:
image: 原始图像(会被复制,不修改原图)
results: 检测结果列表
Returns:
带检测框的图像
"""
vis_image = image.copy()
for det in results:
x1, y1, x2, y2 = det['box']
score = det['score']
class_name = det['class_name']
color = COLORS.get(class_name, (0, 255, 0))
# 绘制检测框
cv2.rectangle(vis_image, (x1, y1), (x2, y2), color, 2)
# 绘制标签背景和文字
label = f"{class_name}: {score:.2f}"
font_scale = 0.6
thickness = 1
(text_w, text_h), baseline = cv2.getTextSize(
label, cv2.FONT_HERSHEY_SIMPLEX, font_scale, thickness
)
# 标签背景矩形
cv2.rectangle(
vis_image,
(x1, y1 – text_h – baseline – 4),
(x1 + text_w, y1),
color, –1 # -1 表示填充
)
# 标签文字(白色)
cv2.putText(
vis_image, label,
(x1, y1 – baseline – 2),
cv2.FONT_HERSHEY_SIMPLEX, font_scale,
(255, 255, 255), thickness,
cv2.LINE_AA
)
return vis_image
def process_images(
inferencer: YOLOv26ONNXInference,
input_path: str,
output_path: str,
save_visualization: bool = True
):
"""
批量处理图像目录
Args:
inferencer: 推理引擎实例
input_path: 输入目录或单张图像路径
output_path: 输出目录
save_visualization: 是否保存可视化结果
"""
input_path = Path(input_path)
output_path = Path(output_path)
output_path.mkdir(parents=True, exist_ok=True)
# 收集要处理的图像列表
supported_formats = {'.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.webp'}
if input_path.is_file():
image_paths = [input_path]
elif input_path.is_dir():
image_paths = [
p for p in input_path.iterdir()
if p.suffix.lower() in supported_formats
]
image_paths.sort()
else:
logger.error(f"输入路径不存在: {input_path}")
return
if not image_paths:
logger.warning(f"在 {input_path} 中未找到支持的图像文件")
return
logger.info(f"找到 {len(image_paths)} 张图像,开始推理…")
total_time = 0
total_detections = 0
for i, img_path in enumerate(image_paths):
# 读取图像
image = cv2.imread(str(img_path))
if image is None:
logger.warning(f"无法读取图像: {img_path}")
continue
# 执行推理
results, infer_time = inferencer.infer(image)
total_time += infer_time
total_detections += len(results)
logger.info(
f"[{i+1}/{len(image_paths)}] {img_path.name} | "
f"检测到 {len(results)} 个目标 | "
f"推理耗时: {infer_time:.1f}ms"
)
# 输出检测结果的详细信息
for det in results:
logger.debug(
f" – {det['class_name']}: {det['score']:.3f} @ "
f"[{det['box'][0]}, {det['box'][1]}, {det['box'][2]}, {det['box'][3]}]"
)
# 保存可视化结果
if save_visualization:
vis_image = inferencer.draw_results(image, results)
out_img_path = output_path / f"result_{img_path.name}"
cv2.imwrite(str(out_img_path), vis_image)
# 打印统计信息
if image_paths:
avg_time = total_time / len(image_paths)
avg_fps = 1000 / avg_time if avg_time > 0 else 0
logger.info(f"\\n{'='*50}")
logger.info(f"推理完成!统计信息:")
logger.info(f" 处理图像总数: {len(image_paths)}")
logger.info(f" 总检测目标数: {total_detections}")
logger.info(f" 平均推理耗时: {avg_time:.1f}ms/张")
logger.info(f" 平均推理速度: {avg_fps:.1f} FPS")
logger.info(f" 结果已保存至: {output_path}")
logger.info(f"{'='*50}")
def main():
parser = argparse.ArgumentParser(
description="YOLOv26 ONNX 推理脚本(Docker 容器化版本)",
formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument('–model', type=str, required=True, help='ONNX 模型文件路径')
parser.add_argument('–input', type=str, required=True, help='输入图像路径或目录')
parser.add_argument('–output', type=str, default='data/output', help='输出目录')
parser.add_argument('–conf', type=float, default=0.25, help='置信度阈值')
parser.add_argument('–iou', type=float, default=0.45, help='NMS IOU 阈值')
parser.add_argument('–imgsz', type=int, default=640, help='模型输入尺寸')
parser.add_argument('–threads', type=int, default=0, help='推理线程数(0=自动)')
parser.add_argument('–no-vis', action='store_true', help='不保存可视化结果')
args = parser.parse_args()
# 检查模型文件是否存在
if not os.path.exists(args.model):
logger.error(f"模型文件不存在: {args.model}")
logger.info("提示:请确保已通过 -v 挂载模型目录,或将模型文件放置在正确路径")
sys.exit(1)
# 初始化推理引擎
inferencer = YOLOv26ONNXInference(
model_path=args.model,
conf_threshold=args.conf,
iou_threshold=args.iou,
input_size=args.imgsz,
num_threads=args.threads
)
# 执行推理
process_images(
inferencer=inferencer,
input_path=args.input,
output_path=args.output,
save_visualization=not args.no_vis
)
if __name__ == '__main__':
main()
五、Docker 镜像构建与运行
5.1 构建镜像
# 在项目根目录执行(注意最后的 . 表示当前目录为构建上下文)
docker build -t yolov26-cpu:latest .
# 如果需要指定 Dockerfile 名称(比如有多个 Dockerfile)
docker build -f Dockerfile -t yolov26-cpu:latest .
# 构建时强制不使用缓存(适合依赖版本更新后的完整重建)
docker build –no-cache -t yolov26-cpu:latest .
# 查看构建完成的镜像信息
docker images | grep yolov26
构建过程中你会看到类似下面的输出,每一步对应 Dockerfile 中的一个指令:
[+] Building 180.3s (12/12) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> [internal] load .dockerignore 0.0s
=> [base 1/3] FROM python:3.10-slim-bookworm 15.2s
=> [base 2/3] RUN apt-get update && apt-get install -y … 25.8s
=> [dependencies 1/2] COPY requirements.txt . 0.0s
=> [dependencies 2/2] RUN pip install –no-cache-dir … 130.5s
=> [runtime 1/4] RUN mkdir -p models data/input data/output logs 0.3s
=> [runtime 2/4] COPY src/ ./src/ 0.1s
=> [runtime 3/4] COPY configs/ ./configs/ 0.0s
=> [runtime 4/4] RUN groupadd -r yolov26user … 0.4s
=> exporting to image 7.2s
首次构建大约需要 3-5 分钟(主要时间在 pip install)。第二次构建如果 requirements.txt 没变化,会跳过依赖安装步骤,只需要几秒钟。
5.2 运行容器
最基础的运行方式:
# 挂载模型和数据目录,执行推理
docker run –rm \\
-v /path/to/your/models:/app/models \\ # 挂载模型目录
-v /path/to/your/images:/app/data/input \\ # 挂载输入图像
-v /path/to/your/output:/app/data/output \\ # 挂载输出目录
yolov26-cpu:latest \\
–model models/yolov26n.onnx \\
–input data/input \\
–output data/output \\
–conf 0.3
关于 -v 挂载路径的注意事项:
- 左边是宿主机路径(必须是绝对路径)
- 右边是容器内路径(对应 Dockerfile 中 VOLUME 声明的路径)
- –rm 参数表示容器退出后自动删除,避免留下大量停止的容器
5.3 交互式调试
推理报错时,不要盲目重建镜像,先进容器里看看:
# 启动交互式 bash 会话(不执行默认的推理命令)
docker run –rm -it \\
-v /path/to/models:/app/models \\
-v /path/to/images:/app/data/input \\
yolov26-cpu:latest \\
bash
# 进入容器后,手动测试各个组件
# 1. 检查 Python 环境
python –version
# 2. 检查依赖是否正确安装
python -c "import onnxruntime; print(ort.__version__)"
python -c "import cv2; print(cv2.__version__)"
# 3. 检查挂载是否成功
ls /app/models/
ls /app/data/input/
# 4. 手动执行推理
python src/inference.py \\
–model models/yolov26n.onnx \\
–input data/input/test.jpg \\
–output data/output
六、GPU 版 Dockerfile
6.1 Dockerfile.gpu
# ============================================================
# YOLOv26 GPU 推理 Docker 镜像
# 基础镜像:NVIDIA CUDA 12.1 + cuDNN 8 + Ubuntu 22.04
# 前提条件:宿主机安装了 NVIDIA 驱动 + nvidia-container-toolkit
# ============================================================
FROM nvidia/cuda:12.1.1-cudnn8-runtime-ubuntu22.04 AS base
# 基础环境变量
ENV PYTHONDONTWRITEBYTECODE=1 \\
PYTHONUNBUFFERED=1 \\
DEBIAN_FRONTEND=noninteractive \\
PIP_NO_CACHE_DIR=1
# 安装 Python 3.10 和系统依赖
# Ubuntu 22.04 默认 Python 版本是 3.10,直接安装即可
RUN apt-get update && apt-get install -y –no-install-recommends \\
python3.10 \\
python3.10-dev \\
python3-pip \\
python3-setuptools \\
python3-wheel \\
libglib2.0-0 \\
libgomp1 \\
libsm6 \\
libxext6 \\
libxrender-dev \\
libgl1-mesa-glx \\
ca-certificates \\
wget \\
&& rm -rf /var/lib/apt/lists/*
# 创建 python/python3/pip 软链接(部分脚本依赖 python 命令)
RUN update-alternatives –install /usr/bin/python python /usr/bin/python3.10 1 && \\
update-alternatives –install /usr/bin/pip pip /usr/bin/pip3 1
WORKDIR /app
# GPU 版本的 requirements(注意:onnxruntime-gpu 替换了 onnxruntime)
COPY requirements-gpu.txt .
RUN pip install –no-cache-dir \\
-i https://pypi.tuna.tsinghua.edu.cn/simple \\
-r requirements-gpu.txt
# 复制源码
COPY src/ ./src/
COPY configs/ ./configs/
# 创建目录结构
RUN mkdir -p models data/input data/output logs
# 创建非 root 用户
RUN groupadd -r yolov26user && useradd -r -g yolov26user yolov26user
RUN chown -R yolov26user:yolov26user /app
USER yolov26user
VOLUME ["/app/models", "/app/data/input", "/app/data/output"]
HEALTHCHECK –interval=30s –timeout=15s –start-period=10s –retries=3 \\
CMD python -c "import onnxruntime; sess = onnxruntime.get_available_providers(); print(sess)" || exit 1
EXPOSE 8000
ENTRYPOINT ["python", "src/inference.py"]
CMD ["–model", "models/yolov26n.onnx", "–input", "data/input", "–output", "data/output"]
requirements-gpu.txt:
# GPU 版本依赖(替换 onnxruntime 为 onnxruntime-gpu)
ultralytics>=8.3.0
onnxruntime-gpu==1.18.0 # 替换为 GPU 版本!
onnx==1.16.0
opencv-python-headless==4.10.0.84
Pillow==10.4.0
numpy==1.26.4
PyYAML==6.0.1
loguru==0.7.2
tqdm==4.66.4
6.2 GPU 容器运行命令
# 构建 GPU 镜像
docker build -f Dockerfile.gpu -t yolov26-gpu:latest .
# 运行 GPU 容器(关键:–gpus all 参数)
docker run –rm \\
–gpus all \\ # 允许容器访问所有 GPU
-v /path/to/models:/app/models \\
-v /path/to/images:/app/data/input \\
-v /path/to/output:/app/data/output \\
yolov26-gpu:latest \\
–model models/yolov26n.onnx \\
–input data/input
# 如果只想分配特定 GPU(多卡服务器)
docker run –rm \\
–gpus '"device=0"' \\ # 只使用 GPU 0
-v /path/to/models:/app/models \\
yolov26-gpu:latest
七、多阶段构建:压缩镜像体积
单阶段构建的镜像往往体积偏大(CPU 镜像可能达到 2-3GB),主要原因是构建工具、编译器、缓存文件都被留在了最终镜像中。多阶段构建(Multi-stage Build)可以有效解决这个问题。
7.1 多阶段构建的原理
多阶段构建允许在一个 Dockerfile 中定义多个 FROM 阶段,最终镜像只包含最后一个阶段的内容。前面的阶段只用于构建和编译,不会出现在最终镜像中。
# ============================================================
# YOLOv26 CPU 推理镜像(多阶段构建优化版)
# 目标:将最终镜像体积压缩到 800MB 以内
# ============================================================
# ———————————————————–
# 构建阶段:安装所有依赖(包括编译工具)
# 这个阶段的内容不会进入最终镜像
# ———————————————————–
FROM python:3.10-slim-bookworm AS builder
ENV PIP_NO_CACHE_DIR=1 \\
PYTHONDONTWRITEBYTECODE=1
# 安装构建依赖(编译某些包需要 gcc 等)
RUN apt-get update && apt-get install -y –no-install-recommends \\
gcc \\
g++ \\
build-essential \\
&& rm -rf /var/lib/apt/lists/*
WORKDIR /install
# 将所有依赖安装到指定目录(而不是系统目录)
# 这样可以在后续阶段直接复制这个目录
COPY requirements.txt .
RUN pip install –no-cache-dir \\
-i https://pypi.tuna.tsinghua.edu.cn/simple \\
–target=/install/packages \\
-r requirements.txt
# ———————————————————–
# 运行阶段:只包含运行时所需的最小内容
# ———————————————————–
FROM python:3.10-slim-bookworm AS runtime
ENV PYTHONDONTWRITEBYTECODE=1 \\
PYTHONUNBUFFERED=1 \\
# 将安装的包目录添加到 Python 路径
PYTHONPATH=/app/packages:$PYTHONPATH
# 只安装运行时系统依赖(不包括 gcc 等编译工具)
RUN apt-get update && apt-get install -y –no-install-recommends \\
libglib2.0-0 \\
libgomp1 \\
libsm6 \\
libxext6 \\
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# 从 builder 阶段复制已安装的 Python 包(这是多阶段构建的核心)
COPY –from=builder /install/packages /app/packages
# 复制应用代码
COPY src/ ./src/
COPY configs/ ./configs/
# 创建目录和用户
RUN mkdir -p models data/input data/output logs && \\
groupadd -r yolov26user && \\
useradd -r -g yolov26user yolov26user && \\
chown -R yolov26user:yolov26user /app
USER yolov26user
VOLUME ["/app/models", "/app/data/input", "/app/data/output"]
HEALTHCHECK –interval=30s –timeout=10s –start-period=5s –retries=3 \\
CMD python -c "import onnxruntime; import cv2" || exit 1
ENTRYPOINT ["python", "src/inference.py"]
CMD ["–model", "models/yolov26n.onnx", "–input", "data/input", "–output", "data/output"]
多阶段构建后,最终镜像不包含 gcc、g++、build-essential 等编译工具,体积可以减少 400-800MB。
八、Docker Compose 多服务编排
8.1 为什么需要 Docker Compose
单个容器的推理脚本适合批处理场景,但实际生产中往往需要多个服务协作:
- 推理服务:跑 YOLOv26 推理的主服务
- 文件监控服务:监控输入目录,有新文件就触发推理
- 结果展示服务:提供 Web 页面查看推理结果
- 日志聚合服务:收集和展示推理日志
Docker Compose 允许用一个 docker-compose.yml 文件定义和管理所有这些服务。
8.2 docker-compose.yml
version: '3.8'
# ============================================================
# YOLOv26 推理系统服务编排
# 包含:推理服务、文件监控、日志查看
# ============================================================
services:
# ———————————————————-
# 核心推理服务(CPU 模式)
# ———————————————————-
yolov26-infer-cpu:
image: yolov26–cpu:latest
container_name: yolov26_infer_cpu
# 构建配置(如果镜像不存在,自动构建)
build:
context: .
dockerfile: Dockerfile
target: runtime
# 挂载配置
volumes:
# 模型文件(只读挂载,防止意外修改)
– type: bind
source: ./models
target: /app/models
read_only: true
# 输入图像目录
– type: bind
source: ./data/input
target: /app/data/input
# 推理结果输出目录
– type: bind
source: ./data/output
target: /app/data/output
# 日志目录
– type: bind
source: ./logs
target: /app/logs
# 配置文件
– type: bind
source: ./configs/infer_config.yaml
target: /app/configs/infer_config.yaml
read_only: true
# 默认推理命令
command: >
–model models/yolov26n.onnx
–input data/input
–output data/output
–conf 0.25
–iou 0.45
# 资源限制(防止推理服务占满 CPU)
deploy:
resources:
limits:
cpus: '4.0' # 最多使用 4 个 CPU 核心
memory: 2G # 最多使用 2GB 内存
reservations:
cpus: '1.0' # 保证至少有 1 个 CPU 核心
memory: 512M # 保证至少有 512MB 内存
# 环境变量
environment:
– OMP_NUM_THREADS=4 # OpenMP 线程数
– PYTHONPATH=/app
# 重启策略(批处理任务通常不需要自动重启)
restart: "no"
# 网络配置
networks:
– yolov26_net
# ———————————————————-
# GPU 推理服务(需要宿主机有 NVIDIA GPU)
# ———————————————————-
yolov26-infer-gpu:
image: yolov26–gpu:latest
container_name: yolov26_infer_gpu
build:
context: .
dockerfile: Dockerfile.gpu
# GPU 访问配置
deploy:
resources:
reservations:
devices:
– driver: nvidia
count: 1 # 使用 1 块 GPU
capabilities: [gpu]
volumes:
– type: bind
source: ./models
target: /app/models
read_only: true
– type: bind
source: ./data/input
target: /app/data/input
– type: bind
source: ./data/output
target: /app/data/output
– type: bind
source: ./logs
target: /app/logs
command: >
–model models/yolov26n.onnx
–input data/input
–output data/output
restart: "no"
networks:
– yolov26_net
# 仅在 GPU 可用时启用(默认注释掉)
profiles:
– gpu
# ———————————————————-
# 推理结果展示服务(简单的静态文件服务器)
# 用于在浏览器中查看推理结果图像
# ———————————————————-
result-viewer:
image: nginx:alpine
container_name: yolov26_result_viewer
ports:
– "8080:80" # 宿主机 8080 端口 -> 容器 80 端口
volumes:
– type: bind
source: ./data/output
target: /usr/share/nginx/html/results
read_only: true
– type: bind
source: ./configs/nginx.conf
target: /etc/nginx/conf.d/default.conf
read_only: true
depends_on:
– yolov26–infer–cpu
restart: unless–stopped
networks:
– yolov26_net
# ============================================================
# 网络配置
# ============================================================
networks:
yolov26_net:
driver: bridge
name: yolov26_network
# ============================================================
# 命名卷(可选,用于持久化数据)
# ============================================================
volumes:
model_data:
driver: local
output_data:
driver: local
8.3 Nginx 配置(结果展示服务)
# configs/nginx.conf
server {
listen 80;
server_name localhost;
# 推理结果图像目录
location /results/ {
alias /usr/share/nginx/html/results/;
autoindex on; # 开启目录列表
autoindex_exact_size off;
autoindex_localtime on;
# 允许浏览器直接显示图片
types {
image/jpeg jpg jpeg;
image/png png;
image/gif gif;
}
}
# 首页重定向到结果目录
location / {
return 301 /results/;
}
}
8.4 Docker Compose 常用命令
# 启动所有服务(后台运行)
docker-compose up -d
# 只启动 CPU 推理服务(不启动 GPU 服务)
docker-compose up -d yolov26-infer-cpu result-viewer
# 启动 GPU 服务(需要指定 profile)
docker-compose –profile gpu up -d yolov26-infer-gpu
# 查看服务状态
docker-compose ps
# 查看推理服务的实时日志
docker-compose logs -f yolov26-infer-cpu
# 停止并删除所有服务
docker-compose down
# 重新构建镜像并启动
docker-compose up -d –build
# 进入运行中的容器调试
docker-compose exec yolov26-infer-cpu bash
# 查看资源占用
docker stats
九、推理流程与架构图
了解了代码和 Dockerfile 之后,我们用 Mermaid 图来梳理整个容器化推理的架构。
9.1 Docker 镜像构建流程图
相关示意图绘制如下,仅供参考:
9.2 容器内推理数据流图
#mermaid-svg-VbGrG86lVn90pYyw{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-VbGrG86lVn90pYyw .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-VbGrG86lVn90pYyw .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-VbGrG86lVn90pYyw .error-icon{fill:#552222;}#mermaid-svg-VbGrG86lVn90pYyw .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-VbGrG86lVn90pYyw .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-VbGrG86lVn90pYyw .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-VbGrG86lVn90pYyw .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-VbGrG86lVn90pYyw .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-VbGrG86lVn90pYyw .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-VbGrG86lVn90pYyw .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-VbGrG86lVn90pYyw .marker{fill:#333333;stroke:#333333;}#mermaid-svg-VbGrG86lVn90pYyw .marker.cross{stroke:#333333;}#mermaid-svg-VbGrG86lVn90pYyw svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-VbGrG86lVn90pYyw p{margin:0;}#mermaid-svg-VbGrG86lVn90pYyw .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-VbGrG86lVn90pYyw .cluster-label text{fill:#333;}#mermaid-svg-VbGrG86lVn90pYyw .cluster-label span{color:#333;}#mermaid-svg-VbGrG86lVn90pYyw .cluster-label span p{background-color:transparent;}#mermaid-svg-VbGrG86lVn90pYyw .label text,#mermaid-svg-VbGrG86lVn90pYyw span{fill:#333;color:#333;}#mermaid-svg-VbGrG86lVn90pYyw .node rect,#mermaid-svg-VbGrG86lVn90pYyw .node circle,#mermaid-svg-VbGrG86lVn90pYyw .node ellipse,#mermaid-svg-VbGrG86lVn90pYyw .node polygon,#mermaid-svg-VbGrG86lVn90pYyw .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-VbGrG86lVn90pYyw .rough-node .label text,#mermaid-svg-VbGrG86lVn90pYyw .node .label text,#mermaid-svg-VbGrG86lVn90pYyw .image-shape .label,#mermaid-svg-VbGrG86lVn90pYyw .icon-shape .label{text-anchor:middle;}#mermaid-svg-VbGrG86lVn90pYyw .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-VbGrG86lVn90pYyw .rough-node .label,#mermaid-svg-VbGrG86lVn90pYyw .node .label,#mermaid-svg-VbGrG86lVn90pYyw .image-shape .label,#mermaid-svg-VbGrG86lVn90pYyw .icon-shape .label{text-align:center;}#mermaid-svg-VbGrG86lVn90pYyw .node.clickable{cursor:pointer;}#mermaid-svg-VbGrG86lVn90pYyw .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-VbGrG86lVn90pYyw .arrowheadPath{fill:#333333;}#mermaid-svg-VbGrG86lVn90pYyw .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-VbGrG86lVn90pYyw .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-VbGrG86lVn90pYyw .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VbGrG86lVn90pYyw .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-VbGrG86lVn90pYyw .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VbGrG86lVn90pYyw .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-VbGrG86lVn90pYyw .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-VbGrG86lVn90pYyw .cluster text{fill:#333;}#mermaid-svg-VbGrG86lVn90pYyw .cluster span{color:#333;}#mermaid-svg-VbGrG86lVn90pYyw div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-VbGrG86lVn90pYyw .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-VbGrG86lVn90pYyw rect.text{fill:none;stroke-width:0;}#mermaid-svg-VbGrG86lVn90pYyw .icon-shape,#mermaid-svg-VbGrG86lVn90pYyw .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VbGrG86lVn90pYyw .icon-shape p,#mermaid-svg-VbGrG86lVn90pYyw .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-VbGrG86lVn90pYyw .icon-shape .label rect,#mermaid-svg-VbGrG86lVn90pYyw .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VbGrG86lVn90pYyw .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-VbGrG86lVn90pYyw .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-VbGrG86lVn90pYyw :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
Docker 容器
宿主机
推理流程
挂载层
Yes
No
bind mount
bind mount
bind mount
models/yolov26n.onnx
data/input/*.jpg
data/output/result_*.jpg
app/models
app/data/input
app/data/output
读取图像 cv2.imread
Letterbox 预处理
BGR→RGB 归一化
ONNX Runtime 推理
End2End?
直接读取结果
置信度过滤 + NMS
坐标还原
绘制检测框
保存结果图
9.3 多服务 Compose 架构图
相关示意图绘制如下,仅供参考:
十、ARM 架构与 Apple Silicon 适配
随着 Apple M1/M2/M3 芯片的普及,以及 ARM-based 边缘设备(树莓派、Jetson 等)的广泛应用,容器化部署的跨架构支持变得越来越重要。
10.1 构建多架构镜像
Docker Buildx 支持构建可以在多种 CPU 架构上运行的镜像:
# 创建并启用支持多架构的 builder
docker buildx create –name mybuilder –use
docker buildx inspect –bootstrap
# 构建并推送支持 amd64 和 arm64 的多架构镜像
docker buildx build \\
–platform linux/amd64,linux/arm64 \\
-t yourusername/yolov26-cpu:latest \\
–push \\
.
# 验证多架构镜像
docker buildx imagetools inspect yourusername/yolov26-cpu:latest
10.2 Apple Silicon(M1/M2/M3)本地运行
在 Apple Silicon Mac 上,Docker Desktop 会自动处理架构转换:
# Apple Silicon 上构建 arm64 镜像(本机架构,性能最佳)
docker build –platform linux/arm64 -t yolov26-arm64:latest .
# 运行 arm64 容器
docker run –rm \\
–platform linux/arm64 \\
-v $(pwd)/models:/app/models \\
-v $(pwd)/data/input:/app/data/input \\
-v $(pwd)/data/output:/app/data/output \\
yolov26-arm64:latest \\
–model models/yolov26n.onnx \\
–input data/input
10.3 NVIDIA Jetson 系列适配
Jetson 设备是 ARM + NVIDIA GPU 的组合,有专门的基础镜像:
# Jetson 专用 Dockerfile
# 注意:必须使用 NVIDIA 提供的 L4T(Linux for Tegra)镜像
FROM nvcr.io/nvidia/l4t-pytorch:r35.2.1-pth2.0-py3 AS jetson-base
# Jetson 环境的特殊处理
# JetPack 已经预装了 CUDA、cuDNN、TensorRT
# 不需要单独安装这些组件
ENV PYTHONDONTWRITEBYTECODE=1 \\
PYTHONUNBUFFERED=1
# Jetson 上的 onnxruntime 需要从源码构建或使用专用版本
# NVIDIA 提供了针对 JetPack 的预编译 onnxruntime wheel
RUN pip install –no-cache-dir \\
https://nvidia.box.com/shared/static/pmsqsiaw4pg9frbeckcbymho6c01jj4z.whl
WORKDIR /app
COPY requirements-jetson.txt .
RUN pip install –no-cache-dir -r requirements-jetson.txt
COPY src/ ./src/
COPY configs/ ./configs/
RUN mkdir -p models data/input data/output logs
ENTRYPOINT ["python", "src/inference.py"]
CMD ["–model", "models/yolov26n.onnx", "–input", "data/input", "–output", "data/output"]
十一、镜像推送与私有仓库管理
11.1 推送到 Docker Hub
# 登录 Docker Hub
docker login
# 给镜像打标签(格式:用户名/镜像名:版本号)
docker tag yolov26-cpu:latest yourusername/yolov26-cpu:v1.0.0
docker tag yolov26-cpu:latest yourusername/yolov26-cpu:latest
# 推送镜像
docker push yourusername/yolov26-cpu:v1.0.0
docker push yourusername/yolov26-cpu:latest
11.2 搭建私有镜像仓库(Harbor / Registry)
生产环境通常需要私有仓库,避免将企业内部模型推送到公网:
# 使用官方 Registry 镜像搭建私有仓库(简单版)
docker run -d \\
-p 5000:5000 \\
–name private-registry \\
–restart=always \\
-v /data/registry:/var/lib/registry \\
registry:2
# 向私有仓库推送镜像
docker tag yolov26-cpu:latest localhost:5000/yolov26-cpu:latest
docker push localhost:5000/yolov26-cpu:latest
# 从私有仓库拉取镜像(在部署机器上执行)
docker pull your-registry-server:5000/yolov26-cpu:latest
11.3 镜像版本管理策略
对于生产环境的模型部署,建议采用以下版本标签策略:
# 格式:镜像名:模型版本–推理框架版本–构建日期
yolov26–cpu:yolov26n–ort1.18.0–20250101
yolov26–cpu:yolov26s–ort1.18.0–20250115
yolov26–gpu:yolov26m–cuda12.1–20250201
# 同时维护 latest 和稳定版标签
yolov26–cpu:latest –> 最新版本
yolov26–cpu:stable –> 经过测试的稳定版本
yolov26–cpu:dev –> 开发版本
十二、容器化部署的性能调优
12.1 CPU 推理性能优化
# 推理脚本中的性能优化配置
import os
# 方法1:通过环境变量控制 OpenMP 线程数(容器启动时设置)
# docker run -e OMP_NUM_THREADS=4 yolov26-cpu:latest
# 方法2:在代码中显式设置
os.environ['OMP_NUM_THREADS'] = str(os.cpu_count() or 4)
os.environ['MKL_NUM_THREADS'] = str(os.cpu_count() or 4)
# 方法3:通过 ONNX Runtime Session Options 精细控制
import onnxruntime as ort
sess_options = ort.SessionOptions()
# 设置图内并行度(矩阵乘法等操作的线程数)
sess_options.intra_op_num_threads = 4
# 设置图间并行度(多个独立操作的并行线程数)
sess_options.inter_op_num_threads = 1 # 通常设为1,避免调度开销
# 启用内存优化
sess_options.enable_mem_pattern = True
sess_options.enable_mem_reuse = True
# 图优化(生产环境推荐 ORT_ENABLE_ALL)
sess_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
# 将优化后的模型保存(可以节省后续推理的优化时间)
sess_options.optimized_model_filepath = "/app/models/yolov26n_optimized.onnx"
12.2 批处理优化
对于批量图像推理,可以通过增大 batch size 提高吞吐量(GPU 场景效果更明显):
import numpy as np
from typing import List
import cv2
def batch_infer(
session: ort.InferenceSession,
images: List[np.ndarray],
input_name: str,
output_names: List[str],
input_size: int = 640,
batch_size: int = 8 # 批处理大小
) –> List[np.ndarray]:
"""
批量推理:将多张图像合并为一个 batch 送入模型
对于 GPU 推理,batch_size=8~32 通常能显著提升吞吐量
对于 CPU 推理,batch_size 带来的收益有限,通常保持=1
"""
all_results = []
for batch_start in range(0, len(images), batch_size):
batch_images = images[batch_start:batch_start + batch_size]
# 预处理:将每张图像转换为 Tensor 并堆叠
batch_blobs = []
for img in batch_images:
blob, _, _ = preprocess_single(img, input_size)
batch_blobs.append(blob[0]) # 去掉 batch 维度
# 堆叠为 [N, C, H, W] 格式
batch_input = np.stack(batch_blobs, axis=0)
# 批量推理
batch_outputs = session.run(output_names, {input_name: batch_input})
all_results.extend(batch_outputs)
return all_results
def preprocess_single(image: np.ndarray, input_size: int):
"""单张图像预处理(复用前面定义的 letterbox 逻辑)"""
orig_h, orig_w = image.shape[:2]
scale = min(input_size / orig_h, input_size / orig_w)
new_h, new_w = int(orig_h * scale), int(orig_w * scale)
resized = cv2.resize(image, (new_w, new_h))
pad_w = (input_size – new_w) / 2
pad_h = (input_size – new_h) / 2
padded = np.full((input_size, input_size, 3), 114, dtype=np.uint8)
padded[int(pad_h):int(pad_h)+new_h, int(pad_w):int(pad_w)+new_w] = resized
rgb = cv2.cvtColor(padded, cv2.COLOR_BGR2RGB)
blob = rgb.transpose(2, 0, 1).astype(np.float32) / 255.0
blob = np.expand_dims(blob, axis=0)
return blob, scale, (pad_w, pad_h)
12.3 容器资源限制配置
# 生产环境中合理限制容器资源,防止单个推理服务耗尽宿主机资源
# CPU 限制:最多使用 4 个 CPU 核心
docker run –rm \\
–cpus="4.0" \\ # 最大 CPU 配额
–cpu-shares=512 \\ # CPU 调度权重(默认1024)
-v $(pwd)/models:/app/models \\
-v $(pwd)/data:/app/data \\
yolov26-cpu:latest
# 内存限制:最多使用 2GB,超出后 OOM kill
docker run –rm \\
–memory="2g" \\ # 内存上限
–memory-swap="4g" \\ # 内存+SWAP 上限
–memory-reservation="512m" \\ # 软限制(内存紧张时的最低保障)
yolov26-cpu:latest
# 同时限制 CPU 和内存
docker run –rm \\
–cpus="4.0" \\
–memory="2g" \\
–memory-swap="2g" \\ # swap=memory 表示禁用 swap(推荐)
-v $(pwd)/models:/app/models \\
-v $(pwd)/data:/app/data \\
yolov26-cpu:latest
十三、CI/CD 集成:自动化构建与部署
13.1 GitHub Actions 工作流
真正的工程化部署,应该把镜像构建集成到 CI/CD 流水线中,实现"代码提交→自动测试→自动构建镜像→自动部署"的完整链路。
# .github/workflows/docker-build-deploy.yml
name: YOLOv26 Docker Build and Deploy
on:
push:
branches: [ main, develop ]
tags: [ 'v*.*.*' ]
pull_request:
branches: [ main ]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}/yolov26–cpu
jobs:
# ———————————————————–
# 阶段1:推理代码测试
# ———————————————————–
test:
runs-on: ubuntu–latest
steps:
– uses: actions/checkout@v4
– name: Set up Python 3.10
uses: actions/setup–python@v4
with:
python-version: '3.10'
– name: Install test dependencies
run: |
pip install -r requirements.txt
pip install pytest pytest-cov
– name: Run unit tests
run: |
pytest tests/ -v –cov=src –cov-report=xml
# ———————————————————–
# 阶段2:构建并推送 Docker 镜像
# ———————————————————–
build-and-push:
needs: test
runs-on: ubuntu–latest
permissions:
contents: read
packages: write
steps:
– name: Checkout code
uses: actions/checkout@v4
# 设置 Docker Buildx(支持多架构构建)
– name: Set up Docker Buildx
uses: docker/setup–buildx–action@v3
# 登录到 GitHub Container Registry
– name: Log in to Container Registry
uses: docker/login–action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# 提取镜像元数据(标签、标注)
– name: Extract Docker metadata
id: meta
uses: docker/metadata–action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix=sha-
# 构建并推送多架构镜像
– name: Build and push Docker image
uses: docker/build–push–action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# 利用 GitHub Actions 缓存加速构建
cache-from: type=gha
cache-to: type=gha,mode=max
# ———————————————————–
# 阶段3:部署到测试环境(仅 main 分支)
# ———————————————————–
deploy-staging:
needs: build–and–push
runs-on: ubuntu–latest
if: github.ref == 'refs/heads/main'
steps:
– name: Deploy to staging server
uses: appleboy/ssh–action@v1.0.0
with:
host: ${{ secrets.STAGING_HOST }}
username: ${{ secrets.STAGING_USER }}
key: ${{ secrets.STAGING_SSH_KEY }}
script: |
# 拉取最新镜像
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:main
# 停止旧容器
docker stop yolov26_infer_cpu || true
docker rm yolov26_infer_cpu || true
# 启动新容器
docker run –d \\
––name yolov26_infer_cpu \\
––restart=unless–stopped \\
––cpus="4.0" \\
––memory="2g" \\
–v /data/models:/app/models:ro \\
–v /data/input:/app/data/input \\
–v /data/output:/app/data/output \\
–v /data/logs:/app/logs \\
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:main
十四、常见问题排查手册
这是从实际踩坑经验中总结出来的,专门针对 YOLOv26 Docker 容器化部署的常见问题。
14.1 libGL.so.1 / libgthread 相关错误
症状:
ImportError: libGL.so.1: cannot open shared object file: No such file or directory
原因:使用了 opencv-python(带 GUI 版本)在无头环境中的缺少共享库问题。
解决方案:
# 方案1(推荐):改用 headless 版本
# 在 requirements.txt 中
opencv-python-headless==4.10.0.84
# 而不是
# opencv-python==4.10.0.84
# 方案2:安装缺失的系统库
RUN apt-get update && apt-get install -y –no-install-recommends \\
libgl1-mesa-glx \\
libglib2.0-0 \\
&& rm -rf /var/lib/apt/lists/*
14.2 ONNX 模型加载失败
症状:
onnxruntime.capi.onnxruntime_pybind11_state.InvalidGraph:
[ONNXRuntimeError] : 10 : INVALID_GRAPH : Load model failed
排查步骤:
# 1. 确认模型文件是否正确挂载
docker run –rm \\
-v $(pwd)/models:/app/models \\
yolov26-cpu:latest \\
bash -c "ls -la /app/models/"
# 2. 验证模型文件完整性(在宿主机执行)
python -c "
import onnx
model = onnx.load('models/yolov26n.onnx')
onnx.checker.check_model(model)
print('模型验证通过')
"
# 3. 检查 ONNX opset 版本
python -c "
import onnx
model = onnx.load('models/yolov26n.onnx')
print('opset version:', model.opset_import[0].version)
"
# 4. 检查 onnxruntime 版本兼容性
python -c "import onnxruntime; print(onnxruntime.__version__)"
常见兼容性矩阵:
| opset 17 | 1.14.0 |
| opset 18 | 1.15.0 |
| opset 19 | 1.16.0 |
| opset 20 | 1.17.0 |
14.3 CUDA out of memory(GPU 场景)
症状:
onnxruntime.capi.onnxruntime_pybind11_state.RuntimeException:
CUDA error: out of memory
解决方案:
# 在创建 session 时限制 GPU 内存使用
providers = [
('CUDAExecutionProvider', {
'device_id': 0,
'gpu_mem_limit': 1 * 1024 * 1024 * 1024, # 限制 1GB GPU 内存
'arena_extend_strategy': 'kSameAsRequested', # 按需申请内存,不预分配
}),
'CPUExecutionProvider'
]
# 或者在 docker run 时限制 GPU 内存
docker run –rm \\
–gpus '"device=0,memory=2g"' \\ # 某些版本支持此语法
yolov26-gpu:latest
14.4 容器内权限问题
症状:
PermissionError: [Errno 13] Permission denied: '/app/data/output/result.jpg'
原因:宿主机目录权限与容器内用户权限不匹配。
解决方案:
# 方法1:以 root 用户运行容器(不推荐用于生产)
docker run –rm -u root \\
-v $(pwd)/data:/app/data \\
yolov26-cpu:latest
# 方法2:修改宿主机目录权限
chmod 777 data/output
# 方法3:指定容器用户与宿主机用户 UID 一致
# 获取当前用户 UID
id -u # 假设返回 1000
docker run –rm \\
-u 1000:1000 \\ # 使用宿主机用户 UID
-v $(pwd)/data:/app/data \\
yolov26-cpu:latest
# 方法4(推荐):在 Dockerfile 中创建与宿主机 UID 匹配的用户
ARG USER_ID=1000
ARG GROUP_ID=1000
RUN groupadd -g ${GROUP_ID} appuser && \\
useradd -u ${USER_ID} -g ${GROUP_ID} appuser
# 构建时传入 UID
docker build –build-arg USER_ID=$(id -u) –build-arg GROUP_ID=$(id -g) -t yolov26-cpu:latest .
14.5 网络问题:镜像拉取失败
在中国大陆服务器上,直接从 Docker Hub 或 NVIDIA NGC 拉取镜像可能超时。
# 配置 Docker 镜像加速(/etc/docker/daemon.json)
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": [
"https://mirror.ccs.tencentyun.com",
"https://hub-mirror.c.163.com",
"https://registry.docker-cn.com"
]
}
EOF
# 重启 Docker daemon
sudo systemctl daemon-reload
sudo systemctl restart docker
# 验证配置
docker info | grep "Registry Mirrors" -A 5
14.6 推理结果为空(无检测框)
排查清单:
# 调试用脚本:打印原始模型输出,帮助定位问题
import onnxruntime as ort
import numpy as np
import cv2
session = ort.InferenceSession("models/yolov26n.onnx")
input_name = session.get_inputs()[0].name
# 创建一个简单的测试输入(纯色图像)
test_input = np.random.rand(1, 3, 640, 640).astype(np.float32)
outputs = session.run(None, {input_name: test_input})
print(f"输出节点数量: {len(outputs)}")
for i, out in enumerate(outputs):
print(f"输出[{i}] shape: {out.shape}, dtype: {out.dtype}")
print(f" 最大值: {out.max():.4f}, 最小值: {out.min():.4f}")
print(f" 均值: {out.mean():.4f}")
常见原因:
十五、安全加固:生产环境的最佳实践
15.1 不以 root 身份运行容器
前面的 Dockerfile 中已经演示了创建非 root 用户,这里补充几个额外的安全配置:
# 以只读文件系统运行容器(防止容器内恶意写操作)
docker run –rm \\
–read-only \\ # 容器文件系统只读
–tmpfs /tmp:rw,noexec,nosuid,size=128m \\ # 允许写临时文件
-v $(pwd)/models:/app/models:ro \\
-v $(pwd)/data/input:/app/data/input:ro \\
-v $(pwd)/data/output:/app/data/output \\ # 输出目录可写
yolov26-cpu:latest
# 禁用特权模式
docker run –rm \\
–security-opt no-new-privileges:true \\ # 禁止获取新权限
–cap-drop ALL \\ # 删除所有 Linux capabilities
yolov26-cpu:latest
15.2 模型文件的安全存储
模型文件(.onnx)通常包含企业核心资产,在 Docker 部署中应注意:
# 方案1:使用 Docker Secrets(适合 Docker Swarm)
echo "/path/to/yolov26n.onnx" | docker secret create model_file –
docker service create \\
–name yolov26_service \\
–secret model_file \\
yolov26-cpu:latest
# 方案2:通过加密存储挂载(使用 HashiCorp Vault 等)
# 这适合对模型文件有严格保密要求的场景
# 方案3:在容器启动时从 S3/OSS 下载(带鉴权)
# 在 entrypoint 脚本中先下载再推理
十六、完整配置文件:infer_config.yaml
# configs/infer_config.yaml
# YOLOv26 推理服务配置文件
model:
path: "models/yolov26n.onnx" # 模型文件路径(相对于工作目录)
input_size: 640 # 模型输入尺寸
end2end: true # 是否为 end2end 导出模式
inference:
conf_threshold: 0.25 # 置信度阈值
iou_threshold: 0.45 # NMS IOU 阈值
num_threads: 0 # 推理线程数(0=自动检测)
batch_size: 1 # 批处理大小(CPU 推理建议=1)
input:
dir: "data/input" # 输入目录
supported_formats: # 支持的图像格式
– ".jpg"
– ".jpeg"
– ".png"
– ".bmp"
– ".tiff"
– ".webp"
output:
dir: "data/output" # 输出目录
save_visualization: true # 是否保存可视化结果
result_prefix: "result_" # 结果文件名前缀
logging:
level: "INFO" # 日志级别:DEBUG/INFO/WARNING/ERROR
dir: "logs" # 日志目录
rotation: "1 day" # 日志轮换周期
retention: "7 days" # 日志保留时间
classes: # 自定义类别名称(可选,不填则使用 COCO 默认)
# 0: person
# 1: bicycle
# 2: car
# …
十七、完整使用手册:从零到运行
为了让读者能够直接上手,这里提供一份完整的"傻瓜式"操作指南:
#!/bin/bash
# YOLOv26 Docker 推理环境从零搭建脚本
# ============================================================
# 步骤1:准备目录结构
# ============================================================
mkdir -p yolov26-deploy/{models,data/{input,output},src,configs,logs}
cd yolov26-deploy
# ============================================================
# 步骤2:导出 YOLOv26 ONNX 模型(在有 ultralytics 的环境中执行)
# ============================================================
# 如果你已经有 .onnx 文件,跳过这步
python -c "
from ultralytics import YOLO
# 加载预训练权重
model = YOLO('yolov8n.pt') # 使用 yolov26 对应版本
# 导出为 ONNX(end2end=True 模式)
model.export(
format='onnx',
imgsz=640,
opset=17,
simplify=True,
dynamic=False,
)
print('模型导出完成:yolov8n.onnx')
"
# 将导出的模型复制到 models 目录
cp yolov8n.onnx models/yolov26n.onnx
# ============================================================
# 步骤3:准备测试图像
# ============================================================
# 下载 COCO 验证集的一张示例图像作为测试
wget -P data/input/ https://ultralytics.com/images/bus.jpg
wget -P data/input/ https://ultralytics.com/images/zidane.jpg
# ============================================================
# 步骤4:构建 Docker 镜像
# ============================================================
# 确保前面的 Dockerfile、requirements.txt、src/ 已经创建好
docker build -t yolov26-cpu:latest .
# 查看镜像大小
docker images yolov26-cpu
# ============================================================
# 步骤5:运行推理
# ============================================================
docker run –rm \\
-v $(pwd)/models:/app/models \\
-v $(pwd)/data/input:/app/data/input \\
-v $(pwd)/data/output:/app/data/output \\
-v $(pwd)/logs:/app/logs \\
yolov26-cpu:latest \\
–model models/yolov26n.onnx \\
–input data/input \\
–output data/output \\
–conf 0.25
# ============================================================
# 步骤6:查看结果
# ============================================================
ls data/output/
# 应该看到 result_bus.jpg 和 result_zidane.jpg
echo "部署完成!在 data/output/ 目录中查看推理结果"
总结与思考
回顾这一节走过的内容,从"为什么需要 Docker"出发,到选择基础镜像、编写 Dockerfile、构建运行镜像、多服务 Compose 编排、跨架构适配、CI/CD 集成,再到常见问题排查——算是把 YOLOv26 容器化部署的完整链路走了一遍。
有几点想特别强调一下。
第一,层缓存是 Docker 构建效率的核心。把变化频繁的指令(COPY 源码)放在不怎么变化的指令(pip install 依赖)之后,能让 90% 的重复构建在 10 秒内完成,而不是 3 分钟。这不是什么"技巧",是真正理解了 Docker 工作原理之后的自然选择。
第二,opencv-python-headless 是容器环境的唯一正确选项。每次看到有人在 Dockerfile 里装了完整版的 opencv-python 然后又去 apt 装一堆 libGL 依赖,我都想直接告诉他:直接换 headless,省事。
第三,模型文件不应该打包进镜像。镜像是代码逻辑的载体,模型是数据资产,二者应该分开管理。通过 Volume 挂载注入模型,可以让同一个镜像支持不同版本的模型文件,极大提升灵活性。
第四,非 root 用户运行是生产环境的基本要求。很多人图省事用 root 跑,在安全审计时就麻烦了。多加三行 RUN groupadd && useradd 的成本,和安全合规的收益相比,根本不值一提。
容器化部署本质上是一种工程思维的体现:把复杂的依赖关系显式化、把运行环境代码化、把部署流程自动化。YOLOv26 本身已经在模型结构层面为部署做了优化,Docker 则在工程落地层面为部署提供了保障。两者结合,才是真正生产可用的部署方案。
🔭 下期预告:YOLOv26 FastAPI 检测接口开发
下一节,我们把 YOLOv26 推理能力包装成 HTTP API 接口。
如果说这一节解决的是"怎么在容器里跑推理",下一节解决的就是"怎么让外部系统调用推理能力"。
FastAPI 是目前 Python 生态中性能最好、开发体验最佳的异步 Web 框架,内置 Pydantic 数据验证和自动 OpenAPI 文档生成,非常适合作为模型推理的 API 层。
下节主要内容预告:
接口设计层面,我们会设计两种检测接口:同步接口适合单次调用场景(上传图像,立即返回检测结果),异步接口适合批量任务场景(提交任务→轮询状态→获取结果)。结合本节的 Docker 容器,下节的 FastAPI 服务会直接以容器形式部署,端口通过 -p 8000:8000 暴露给外部。
性能优化层面,我们会讲 YOLOv26 推理引擎的全局单例化(避免每次请求都重新加载模型),以及异步任务队列的设计(避免推理阻塞 HTTP 线程池)。
工程化层面,完整的 Pydantic 请求/响应模型定义、错误处理、推理日志记录、Swagger UI 自动文档,以及如何在 docker-compose.yml 中把 FastAPI 服务与推理引擎服务一起编排。
如果你做过模型接口化部署,你知道"让模型能被调用"和"让模型好被调用"之间差距有多大——下节我们重点解决后者。
互动环节:
如果你有任何问题或想法,欢迎在评论区留言:
我会认真阅读每一条评论,并在后续章节中回应大家的关切。你的反馈,是这个专栏不断进步的动力!
资源链接汇总:
为了方便你的学习,这里汇总了本节提到的所有重要资源:
官方资源:
- YOLOv26 官方文档:https://docs.ultralytics.com
- GitHub 仓库:https://github.com/ultralytics/ultralytics
- 官方论坛:https://github.com/ultralytics/ultralytics/discussions
学习资源:
- 深度学习课程:吴恩达 Coursera 课程
- 计算机视觉:CS231n 斯坦福课程
- PyTorch 教程:https://pytorch.org/tutorials/
工具资源:
- 数据标注:LabelImg、CVAT、Roboflow
- 云 GPU:AutoDL、恒源云、Colab
- 模型转换:ONNX、TensorRT、OpenVINO
社区资源:
- CSDN:搜索"YOLOv26"
- 知乎:关注"目标检测"话题
- B 站:搜索"YOLO 教程"
- GitHub:搜索"yolov26 projects"
硬件购买建议:
- 树莓派 4B:官方授权店铺
- Jetson Nano:NVIDIA 官方或京东
- USB 摄像头:罗技 C270/C920
- 移动电源:小米、Anker 等品牌
最后的最后:
技术的学习没有捷径,但有方法。希望这个专栏能成为你学习 YOLOv26 的良师益友。无论你是初学者还是资深开发者,都能在这里找到有价值的内容。
记住:每一个大神,都曾是小白。 不要因为暂时的困难而放弃,坚持下去,你一定能掌握 YOLOv26,甚至成为这个领域的专家。
加油!我们下节课见!
彩蛋:一个真实的故事
最后,我想分享一个真实的故事。去年,我帮一个农民朋友部署了基于 YOLOv8 的病虫害检测系统。系统运行了一个月后,他打电话给我说:“小伙子,你这系统挺好,就是太费电了,我的太阳能板带不动。”
那一刻,我意识到,技术不仅要先进,更要实用。再高的精度,如果无法在实际场景中稳定运行,也只是纸上谈兵。
后来,YOLOv26 发布了。我第一时间帮他升级了系统。现在,系统已经稳定运行了 6 个月,电池续航从 3 小时延长到了 15 小时,病虫害检出率提升了 40%,农药使用减少了 30%。
他上次见到我,拉着我的手说:“这才是真正有用的技术!”
这就是我写这个专栏的初心:让技术真正服务于实际需求,让 AI 走进千家万户。
希望你也能用 YOLOv26,创造出属于你的精彩故事!
最后,希望本文围绕 YOLOv26 的实战讲解,能在以下几个方面对你有所帮助:
- 🎯 模型精度提升:通过结构改进、损失函数优化、数据增强策略等方案,尽可能提升检测效果与任务表现;
- 🚀 推理速度优化:结合量化、裁剪、蒸馏、部署加速等手段,帮助模型在实际业务场景中跑得更快、更稳;
- 🧩 工程级落地实践:从训练、验证、调参到部署优化,提供可直接复用或稍作修改即可迁移的完整思路与方案。
PS:如果你按文中步骤对 YOLOv26 进行优化后,仍然遇到问题,请不必焦虑或灰心。 YOLOv26 作为新一代目标检测模型,最终效果往往会受到 硬件环境、数据集质量、任务定义、训练配置、部署平台 等多重因素共同影响,因此不同任务之间的最优方案也并不完全相同。 如果你在实践过程中遇到:
- 新的报错 / Bug
- 精度难以提升
- 推理速度不达预期 欢迎把 报错信息 + 关键配置截图 / 代码片段 粘贴到评论区,我们可以一起分析原因、定位瓶颈,并讨论更可行的优化方向。 同时,如果你有更优的调参经验、结构改进思路,或者在实际项目中验证过更有效的方案,也非常欢迎分享出来,大家互相启发、共同完善 YOLOv26 的实战打法 🙌
- 当然,部分章节还会结合国内外前沿论文与 AIGC 大模型技术,对主流改进方案进行重构与再设计,内容更贴近真实工程场景,适合有落地需求的开发者深入学习与对标优化。
🧧🧧 文末福利,等你来拿!🧧🧧
文中涉及的多数技术问题,来源于我在 YOLOv26 项目中的一线实践,部分案例也来自网络与读者反馈;如有版权相关问题,欢迎第一时间联系,我会尽快处理(修改或下线)。 部分思路与排查路径参考了全网技术社区与人工智能问答平台,在此也一并致谢。如果这些内容尚未完全解决你的问题,还请多一点理解——YOLOv26 的优化本身就是一个高度依赖场景与数据的工程问题,不存在“一招通杀”的方案。 如果你已经在自己的任务中摸索出更高效、更稳定的优化路径,非常鼓励你:
- 在评论区简要分享你的关键思路;
- 或者整理成教程 / 系列文章。 你的经验,可能正好就是其他开发者卡关许久所缺的那一环 💡
OK,本期关于 YOLOv26 优化与实战应用 的内容就先聊到这里。如果你还想进一步深入:
- 了解更多结构改进与训练技巧;
- 对比不同场景下的部署与加速策略;
- 系统构建一套属于自己的 YOLOv26 调优方法论; 欢迎继续查看专栏:《YOLOv26实战:从入门到深度优化》。 也期待这些内容,能在你的项目中真正落地见效,帮你少踩坑、多提效,下期再见 👋
码字不易,如果这篇文章对你有所启发或帮助,欢迎给我来个 一键三连(关注 + 点赞 + 收藏),这是我持续输出高质量内容的核心动力 💪
同时也推荐关注我的技术号 「猿圈奇妙屋」:
- 第一时间获取 YOLOv26 / 目标检测 / 多任务学习 等方向的进阶内容;
- 不定期分享与视觉算法、深度学习相关的最新优化方案与工程实战经验;
- 以及 BAT 等大厂面试题、技术书籍 PDF、工程模板与工具清单等实用资源。 期待在更多维度上和你一起进步,共同提升算法与工程能力 🔧🧠
🫵 Who am I?
我是专注于 计算机视觉 / 图像识别 / 深度学习工程落地 的讲师 & 技术博主,笔名 bug菌:
- 热活于 CSDN | 稀土掘金 | InfoQ | 51CTO | 华为云开发者社区 | 阿里云开发者社区 | 腾讯云开发者社区 | 开源中国 | 博客园 | 墨天轮 等各大技术社区;
- CSDN 博客之星 Top30、华为云多年度十佳博主&卓越贡献奖、掘金多年度人气作者 Top40;
- CSDN、掘金、InfoQ、51CTO 等平台签约及优质作者;
- 全网粉丝累计 30w+。
更多高质量技术内容及成长资料,可查看这个合集入口 👉 点击查看 👈️
硬核技术号 「猿圈奇妙屋」 期待你的加入,一起进阶、一起打怪升级。
– End –
网硕互联帮助中心



评论前必须登录!
注册