YOLOX 的定位很明确:它是一个 anchor-free 的 YOLO 检测器 ,并把 decoupled head 和 SimOTA 这类当时更先进的检测技巧正式带进了 YOLO 系列。([GitHub][1])
1. YOLOX 是什么
一句话概括:YOLOX = CSPDarknet/PAFPN 主体 + anchor-free 检测头 + decoupled head + SimOTA 标签分配 。论文里作者直接写明,YOLOX 的核心改动是把 YOLO 切到 anchor-free ,再结合 decoupled head 和 SimOTA ,从而在不同模型规模上都拿到更强的精度-速度平衡。论文给出的代表性结果是:YOLOX-L 在 COCO 上达到 50.0% AP、68.9 FPS(Tesla V100) ;官方当前模型库里,YOLOX-S/M/L/X 的测试 mAP 分别约为 40.5 / 47.2 / 50.1 / 51.5。([ar5iv][2])
2. 网络结构图
适合记忆和讲解的简化结构图:
text
Input
└─ Backbone: CSPDarknet
├─ Focus stem
├─ dark2: Conv + CSPLayer
├─ dark3: Conv + CSPLayer -> 输出 P3 特征
├─ dark4: Conv + CSPLayer -> 输出 P4 特征
└─ dark5: Conv + SPPBottleneck + CSPLayer -> 输出 P5 特征
└─ Neck: YOLOPAFPN
├─ Top-down:
│ P5 上采样 + 与 P4 拼接 + CSPLayer
│ 再上采样 + 与 P3 拼接 + CSPLayer
└─ Bottom-up:
P3 下采样 + 与中间层拼接 + CSPLayer
再下采样 + 与高层拼接 + CSPLayer
└─ Head: YOLOXHead(3 个尺度)
├─ 每个尺度先过 1×1 stem
├─ 分类分支:两层 3×3 Conv -> cls
├─ 回归分支:两层 3×3 Conv -> box
└─ 目标分支:从回归分支输出 obj
输出尺度:stride = 8 / 16 / 32
这个图不是随便画的,它和官方实现是一一对应的:
官方 YOLOX 模型默认由 YOLOPAFPN() 和 YOLOXHead(80) 组成;Backbone 输出的是 [dark3, dark4, dark5],Neck 再把它们融合后交给 Head。([GitHub][3])
3. Backbone:CSPDarknet
官方代码里,YOLOX 的主干是 CSPDarknet。它的入口不是普通卷积,而是 Focus stem ;随后依次经过 dark2 / dark3 / dark4 / dark5。其中:
dark3、dark4、dark5是后续特征金字塔的主要输入;dark5里额外加入了 SPPBottleneck;dark2~dark5都大量使用 CSPLayer。([GitHub][4])
如果你从工程角度理解,Backbone 的作用就是:
dark3:分辨率较高,偏小目标;dark4:中尺度目标;dark5:语义最强,偏大目标。
这也是为什么后面 Neck 要把这三层做双向融合。官方实现里 CSPDarknet 默认只把 dark3/dark4/dark5 作为输出特征返回。([GitHub][4])
4. Neck:YOLOPAFPN
YOLOX 的 Neck 类名就叫 YOLOPAFPN 。论文里提到,当它切换到更先进的 YOLOv5 风格架构时,采用了 CSPNet backbone + additional PAN head ;而官方实现中,这一部分具体落地成了 YOLOPAFPN。([ar5iv][2])
从代码看,它的融合流程非常清楚:
-
先取出 Backbone 的
[x2, x1, x0] = [dark3, dark4, dark5]。 -
自顶向下:
dark5 -> lateral_conv0 -> upsample -> concat(dark4) -> C3_p4- 再
reduce_conv1 -> upsample -> concat(dark3) -> C3_p3
-
自底向上:
- 小尺度结果下采样后,与中尺度再拼接,过
C3_n3 - 再下采样,与高尺度拼接,过
C3_n4
- 小尺度结果下采样后,与中尺度再拼接,过
-
最终输出 3 个尺度
(pan_out2, pan_out1, pan_out0)。([GitHub][5])
这部分你可以把它理解成:
上采样路径把高层语义"送下来",下采样路径再把局部细节"送回去"。所以它既不是只做 FPN,也不是只做 PAN,而是更完整的双向特征融合。([GitHub][5])
5. Head:YOLOXHead
YOLOX 的 Head 是它最重要的改造之一。官方代码里:
-
Head 接 3 个尺度,
strides=[8,16,32]; -
每个尺度先过一个
1×1的 stem; -
然后分成 分类分支 和 回归分支 两路;
-
分类分支、回归分支各有两层
3×3 Conv; -
最后输出:
reg_preds: 4 通道边框回归obj_preds: 1 通道目标置信度cls_preds:num_classes通道分类。([GitHub][6])
官方实现还明确写了:推理时会把三个尺度的输出拼成 [batch, n_anchors_all, 85] 这种形式(COCO 80 类时就是 4 box + 1 obj + 80 cls),并且 decode_in_inference=True,也就是默认在推理阶段做解码;如果做部署导出,代码里提示可以把它设成 False。([GitHub][6])
6. 为什么 YOLOX 的 Head 比老 YOLO 更强
核心原因是:把分类和回归解耦了。
论文明确指出,分类和定位本来就是冲突任务,所以很多一阶段/二阶段检测器都已经用 decoupled head,但旧 YOLO Head 还长期是 coupled 的。YOLOX 的实验显示:
- decoupled head 能明显提升收敛速度;
- 最终精度也更好;
- 在 YOLOv3 基线上,单独把 head 改成 decoupled,就从 38.5 AP 提到 39.6 AP;
- 代价是推理延迟从 10.5 ms 增到 11.6 ms。([ar5iv][2])
这也是为什么很多人讲 YOLOX 时,会说它不是"仅仅换成 anchor-free",而是 把 YOLO 系列的头部设计现代化了。([ar5iv][2])
7. YOLOX 的关键创新点
7.1 Anchor-Free
YOLOX 最标志性的改动就是 anchor-free 。论文里写得很直接:传统 anchor 机制有两个主要问题,一是需要聚类找 anchor,泛化性差;二是会增加 Head 复杂度和预测数量,在一些边缘 AI 系统里,预测结果在设备间搬运还会拖慢总延迟。YOLOX 切到 anchor-free 后,把 每个位置的预测从 3 个降到 1 个 ,直接预测 4 个框参数,同时保留多尺度 FPN 分配逻辑。结果是:更简单、更快,而且精度反而更高 ,在它的路线图实验里这一步把 AP 从 42.0 提到 42.9。([ar5iv][2])
7.2 Multi Positives
如果只把目标中心点当正样本,会浪费很多高质量候选。YOLOX 在 anchor-free 基础上又加了 multi positives ,本质上就是类似 FCOS 的 center sampling :把目标中心区域内的一批位置都设为正样本,而不是只留 1 个中心点。这一步又把 AP 从 42.9 拉到 45.0。([ar5iv][2])
7.3 SimOTA
这是 YOLOX 训练端最有名的创新。论文先总结了先进标签分配应满足 4 个特征:loss/quality aware、center prior、dynamic top-k、global view ;原始 OTA 虽然强,但作者发现用 Sinkhorn-Knopp 解 OT 问题会带来 约 25% 的额外训练时间 ,所以他们做了一个近似化版本,叫 SimOTA 。它先算 prediction-gt 的匹配代价,再在固定中心区域内为每个 gt 动态选 top-k 正样本。路线图里这一步把 AP 又从 45.0 提到 47.3。([ar5iv][2])
7.4 强数据增强
YOLOX 明确把 Mosaic + MixUp 当成默认的重要增强,并且在最后 15 个 epoch 关闭 这些强增强。论文还指出,在加入这些强增强后,ImageNet 预训练不再明显有益 ,因此后续模型都直接从头训练。路线图上,"strong augmentation" 这一步带来 +2.4 AP。([ar5iv][2])
7.5 可选的 End-to-End / NMS-Free
YOLOX 论文还尝试了 NMS-free 的 end-to-end 版本,但作者最后把它作为可选模块,而没有放进最终默认模型,因为它会带来一点精度和速度下降。在路线图实验里,+NMS free (optional) 从 47.3 AP 降到 46.5 AP。([ar5iv][2])
8. YOLOX 的训练范式
YOLOX 论文中的默认训练设置是:
- 总共 300 epochs
- 前 5 epochs warm-up
- 优化器是 SGD
- 学习率采用 cosine schedule
- 默认总 batch size 128
- 输入尺寸做多尺度训练,在 448 到 832 之间按 32 的步长采样。([ar5iv][2])
这说明 YOLOX 并不是只靠一个"新结构"赢的,它实际上是 结构改造 + 标签分配 + 强增强 + 训练策略 一整套组合拳。([ar5iv][2])
9. 标准模型族怎么选
官方模型库分成两类:
- 标准模型 :
YOLOX-s / m / l / x - 轻量模型 :
YOLOX-nano / tiny。([GitHub][7])
如果你只想快速选型,可以这么记:
- YOLOX-S:9.0M 参数,26.8 GFLOPs,640 测试尺寸,约 40.5 mAP,适合普通单卡/边缘 GPU 原型。
- YOLOX-L:54.2M 参数,155.6 GFLOPs,约 50.1 mAP,适合更高精度场景。
- YOLOX-X:99.1M 参数,281.9 GFLOPs,约 51.5 mAP,适合追顶精度。
- Nano/Tiny:更偏移动端和极轻量部署。([GitHub][7])
10. 官方部署支持哪些后端
YOLOX 官方 README 和文档都明确写了,它提供或支持以下部署路线:
- ONNX
- TensorRT
- ncnn
- OpenVINO
- MegEngine
- Android ncnn。([GitHub][1])
从工程上看,你可以这样选:
- NVIDIA GPU 服务器/边缘盒子:优先 TensorRT
- 通用 Python/C++ 跨平台:优先 ONNX / ONNX Runtime
- Intel CPU:优先 OpenVINO
- 移动端/安卓/NPU 友好链路:优先 ncnn。([YOLOX Documentation][8])
11. ONNX 部署
官方 ONNX 导出命令是:
bash
python3 tools/export_onnx.py --output-name yolox_s.onnx -n yolox-s -c yolox_s.pth
自定义实验文件则用 -f:
bash
python3 tools/export_onnx.py --output-name your_yolox.onnx -f exps/your_dir/your_yolox.py -c your_yolox.pth
官方 ONNXRuntime Demo 也给了直接可跑的命令:
bash
cd <YOLOX_HOME>/demo/ONNXRuntime
python3 onnx_inference.py -m <ONNX_MODEL_PATH> -i <IMAGE_PATH> -o <OUTPUT_DIR> -s 0.3 --input_shape 640,640
文档还特别提醒:--input_shape 要和导出时一致。([YOLOX Documentation][9])
12. TensorRT 部署
官方 TensorRT Python 路线是通过 torch2trt 转换,命令大致是:
bash
python tools/trt.py -n yolox-s -c your_ckpt.pth
如果是自定义模型,就改用 -f 指定你的 exp 文件。转换后会在实验输出目录生成 TensorRT 模型和序列化的 engine 文件。官方 Python Demo 直接在原来的 demo 命令上加 --trt:
bash
python tools/demo.py image -n yolox-s --trt --save_result
官方还提供了 TensorRT C++ Demo ,做法是先准备好 model_trt.engine,再 cmake .. && make 编译;跑的时候类似:
bash
./yolox ../model_trt.engine -i ../../../../assets/dog.jpg
如果你训的是自定义数据集,还要改 C++ 里的 num_class。([YOLOX Documentation][8])
13. OpenVINO 部署
YOLOX 官方同时给了 OpenVINO Python 和 OpenVINO C++ 教程。它的典型流程是:
- 先导出 ONNX;
- 再把 ONNX 转 OpenVINO IR。([YOLOX Documentation][10])
这里有一个很容易忽略的点:
官方文档明确说,如果你后续要转 OpenVINO,导出 ONNX 时要把 opset 设成 10,否则下一步会失败。([YOLOX Documentation][9])
OpenVINO 的转换命令在文档里也给了:
bash
python3 mo.py --input_model <ONNX_MODEL> --input_shape <INPUT_SHAPE> [--data_type FP16]
Linux 环境下还需要先 source /opt/intel/openvino_2021/bin/setupvars.sh。([YOLOX Documentation][11])
14. ncnn 与 Android 部署
YOLOX 官方给了 C++ ncnn 和 Android ncnn 两条路。C++ ncnn 的基本流程是:
- 先导出 ONNX:
bash
python3 tools/export_onnx.py -n yolox-s
- 再用
onnx2ncnn转成param/bin:
bash
./onnx2ncnn yolox.onnx model.param model.bin
([YOLOX Documentation][12])
这里有个很重要的工程细节:
官方文档明确写了,ncnn 不支持 Focus 模块 ,所以转换时会出现 Unsupported slice step! 之类警告;不过 YOLOX 的 yolox.cpp 已经实现了 C++ 版 Focus 层,因此文档要求你进一步手动修改 model.param。这点说明 YOLOX 的 ncnn 部署不是"导出即跑",而是要处理 Focus 兼容问题。([YOLOX Documentation][12])
Android ncnn 的流程更简单一些:官方教程是下载 ncnn-android-vulkan.zip,放到 app/src/main/jni 或修改 ncnn_DIR,再把示例 param/bin 放进 app/src/main/assets,最后用 Android Studio 打开项目构建。([YOLOX Documentation][13])
15. 实际部署时你最该注意什么
第一,YOLOX 是 anchor-free,但不是"零后处理"。默认模型仍然是常规检测流程,输出 box/obj/cls,再做阈值过滤和 NMS;只有论文里的 optional end-to-end 版本才是往 NMS-free 方向走。([ar5iv][2])
第二,部署时一定统一输入尺寸、预处理和输出解码方式 。官方 ONNX 文档已经明确提醒 input_shape 要和导出一致;而 Head 代码也说明了推理端默认会做 decode。你如果自己改导出逻辑,往往就是在这一步最容易出错。([YOLOX Documentation][9])
第三,Focus 是一个典型兼容点。在 PyTorch 里它没问题,但到 ncnn 这类后端时就可能需要额外处理,这也是很多人第一次部署 YOLOX 会踩的坑。([YOLOX Documentation][12])
16. 总结
YOLOX 的本质,不只是"把 YOLO 改成 anchor-free",而是用 CSPDarknet + PAFPN 作为主体,再用 decoupled head、multi positives、SimOTA 和强增强,把 YOLO 系列整体推进到一个更现代的一阶段检测框架。 这也是它在学术和工业部署里都很有影响力的原因。([ar5iv][2])
参考链接:
1\]: https://github.com/Megvii-BaseDetection/YOLOX/blob/main/README.md?utm_source=chatgpt.com "README.md - Megvii-BaseDetection/YOLOX" \[2\]: https://ar5iv.labs.arxiv.org/html/2107.08430 "\[2107.08430\] YOLOX: Exceeding YOLO Series in 2021" \[3\]: https://github.com/Megvii-BaseDetection/YOLOX/blob/main/yolox/models/yolox.py "YOLOX/yolox/models/yolox.py at main · Megvii-BaseDetection/YOLOX · GitHub" \[4\]: https://github.com/Megvii-BaseDetection/YOLOX/blob/main/yolox/models/darknet.py "YOLOX/yolox/models/darknet.py at main · Megvii-BaseDetection/YOLOX · GitHub" \[5\]: https://github.com/Megvii-BaseDetection/YOLOX/blob/main/yolox/models/yolo_pafpn.py "YOLOX/yolox/models/yolo_pafpn.py at main · Megvii-BaseDetection/YOLOX · GitHub" \[6\]: https://github.com/Megvii-BaseDetection/YOLOX/blob/main/yolox/models/yolo_head.py "YOLOX/yolox/models/yolo_head.py at main · Megvii-BaseDetection/YOLOX · GitHub" \[7\]: https://github.com/Megvii-BaseDetection/YOLOX/blob/main/README.md "YOLOX/README.md at main · Megvii-BaseDetection/YOLOX · GitHub" \[8\]: https://yolox.readthedocs.io/en/latest/demo/trt_py_readme.html?utm_source=chatgpt.com "YOLOX-TensorRT in Python" \[9\]: https://yolox.readthedocs.io/en/latest/demo/onnx_readme.html "YOLOX-ONNXRuntime in Python --- YOLOX 0.2.0 documentation" \[10\]: https://yolox.readthedocs.io/en/latest/demo/openvino_py_readme.html "YOLOX-OpenVINO in Python --- YOLOX 0.2.0 documentation" \[11\]: https://yolox.readthedocs.io/en/latest/demo/openvino_cpp_readme.html "YOLOX-OpenVINO in C++ --- YOLOX 0.2.0 documentation" \[12\]: https://yolox.readthedocs.io/en/latest/demo/ncnn_cpp_readme.html?utm_source=chatgpt.com "YOLOX-CPP-ncnn" \[13\]: https://yolox.readthedocs.io/en/latest/demo/ncnn_android_readme.html?utm_source=chatgpt.com "YOLOX-Android-ncnn"