从 PyTorch 到 ncnn:纤维表面缺陷检测的端侧部署链路
本文目录
把一个训练好的检测模型放进 C++ 程序,真正容易出错的通常不是某一行推理 API,而是模型导出、输入预处理、输出张量约定、后处理和目标设备之间的契约。这个公开仓库把 YOLO 风格的纤维缺陷检测模型,经 ONNX 与 PNNX 转换为 ncnn 格式,最后由 C++/OpenCV 程序批量读取图像并输出检测框。
本文依据默认分支固定提交的文件树、main.cpp、CMake 配置、转换与对比脚本做静态复盘。仓库没有 README;Git 树中没有模型权重,且 .gitignore 排除了根目录 models/。因此本文能说明代码组织和验证意图,不能证明模型可直接下载、工程能在当前机器构建,或检测效果达到某个准确率。
工程分层
convert_pt2onnx.py:调用仓库内的 Ultralytics Python 包将.pt导出成固定尺寸的 ONNX;脚本注明此分支带有 GAM 修改。convert_onnx2ncnn.py:调用 PNNX 生成 ncnn 的.param与.bin,可选择 fp16,并提供可选数值对比。main.cpp:加载 ncnn 网络,用 OpenCV 预处理图片、解析检测输出、执行类别内 NMS,并绘制结果或遍历目录。compare_onnx_ncnn.py:抽样真实图片,分别调用 ONNX Runtime 和编译后的 C++ 可执行文件,对比检测数量、类别和框位置。benchmark_onnx_ncnn.py:分别测 ONNX 和 ncnn 的推理耗时,并尝试采集 CPU 和内存信息。ultralytics/:随仓库提交的训练/导出代码树;其修改与上游版本之间的差异需要通过具体提交审阅,不能仅凭目录名推断改动范围。
推理数据路径
C++ 入口把输入缩放到 640×640 方形画布:按宽高比等比缩放,剩余区域以 114 灰度补边;随后将 OpenCV BGR 像素转成 RGB,并按 1/255 归一化。网络输入 blob 名称固定为 in0,输出固定为 out0。这些名字、尺寸、色彩顺序和归一化系数必须与导出模型保持一致,任何一处不匹配都可能表现为“程序运行成功但检测结果异常”。
输出按每个候选框的中心点和宽高读取,并从其余通道选最高类别分数。当前代码默认置信度阈值 0.25、NMS IoU 阈值 0.45,NMS 只抑制相同类别;坐标再按 letterbox 的比例与偏移映射回原图。类别表是 12 个纤维纹理/缺陷名称。若修改类别数、模型导出头或 blob 命名,C++ 解码器和 Python 对比脚本也必须一起更新。
目录模式只处理该目录的直接子文件,不递归扫描;支持的后缀包括 JPG、PNG、BMP、TIFF 和 WebP。结果目录创建在输入目录旁边,原始图片会被画框后写入结果目录。模型路径则固定为 models/best.ncnn.param 与 models/best.ncnn.bin,并非命令行参数。
导出与验证链
建议把验证分成三个层级,而不是只看最终程序有没有输出图片:
- PyTorch → ONNX:核对输出形状及数值差异。当前脚本用随机输入张量检查最大绝对误差,小于
1e-3才打印 PASS;这只验证一组随机输入上的导出数值,不验证真实纤维图片上的检测质量。 - ONNX → ncnn:PNNX 脚本允许用图片比较输出张量,默认寻找当前目录 JPG;更完整的
compare_onnx_ncnn.py会将真实图片分别送入 ONNX Runtime 和 C++ 程序,对比检测框。后者更接近部署验收,但还要留意样本代表性、框匹配规则、阈值和输出文件是否可写。 - 目标设备验收:在实际 CPU/指令集、ncnn 构建选项和 OpenCV 版本上测端到端延迟、峰值内存、模型加载时间与漏检/误检。仓库中的 Windows 默认数据目录和已编译可执行文件约定表明测试脚本需要按本机环境调整;源码没有提供目标设备的实测结果。
需要特别谨慎看待两个“验证成功”信号:导出脚本在缺少可选验证依赖时会打印跳过并返回成功;ONNX/ncnn 验证在没有测试图片时也会跳过并返回成功。因此命令退出码为 0 不足以证明数值比较实际执行。check_consistency.py 也只打印 PASS/MISMATCH,没有把差异结果转成失败退出码。CI 应检查明确的验证状态和样本数,不能只看进程退出码。
代码审阅发现与改进方向
- 转换产物保护:PNNX 转换前会先删除同名
.ncnn.param/.ncnn.bin。如果新转换失败,原来可用的模型产物已经被移除。更稳妥的流程是在临时目录生成、验证完整后再原子替换,并保留上一版产物。 - 模型资产契约:
models/被 Git 忽略,运行还依赖best.pt、ONNX、PNNX、ncnn 权重之间的来源关系。建议提供只含公开可分发信息的模型清单、转换参数、SHA-256、类别映射和权重获取/授权说明;不要把大型或受限权重误当成源码的一部分。 - 路径与失败信号:模型文件、输入输出 blob、输入尺寸和阈值主要写死在 C++;
cv::imwrite的布尔结果没有被检查。可增加明确配置、阈值范围校验、模型文件存在检查,以及写入失败时的非零退出状态。 - 复现门槛:CMake 依赖 ncnn 与 OpenCV 的 core/imgproc/highgui 组件,但仓库没有面向新使用者的 README、依赖版本锁定或自动化测试工作流。应补充平台构建步骤、编译选项、权重获取说明和最小回归图片集,同时说明数据集许可与脱敏要求。
- 性能解释:批量程序只初始化一次模型;benchmark 的 C++ 调用则通过反复启动独立进程,并把进程启动、模型加载与推理耗时分开报告。报告对比时要区分纯推理、单图端到端和整批吞吐,不能把一种口径当成另一种。
这些是从当前可见源码得出的维护建议,不是已复现的线上缺陷,也没有改动原 GitHub 仓库。仓库未提供可公开读取的权重和样本,因此本文不虚构精度、FPS、延迟或硬件兼容性结论。
可复现检查清单
- 固定源码提交、Python/Ultralytics/ONNX/PNNX/ncnn/OpenCV 与编译器版本。
- 为原始权重、ONNX、ncnn 参数/权重及类别表记录来源、许可与 SHA-256。
- 用标注验证集报告 precision、recall、mAP,并分别检查每类误检/漏检。
- 对同一批真实图片比较 PyTorch、ONNX Runtime 和 C++ ncnn 的类别、置信度与框坐标。
- 在部署设备记录冷启动、热推理、P50/P95 延迟、峰值内存与持续运行稳定性。
- 转换失败、模型缺失、坏图、空目录和结果目录无写权限时,都应有可读错误及非零退出码。
小结
这个项目展示了一条完整但尚未完全自描述的视觉模型部署链:以 Ultralytics 负责训练/ONNX 导出,以 PNNX 转为 ncnn,再用 C++ 与 OpenCV 落地预处理、推理和 NMS。其主要工程价值是把模型转换与目标端运行拆开,并提供跨运行时对比工具;要把它变成可交接、可复现的交付,还需要保护旧转换产物、让验证跳过状态显式失败、公开权重与依赖契约,并补齐目标设备与真实数据的验收记录。
源仓库
本文核对的是默认分支提交 0cfdddd(2026-08-24);上游内容后续可能变化:cdyforever/fiber-ncnn。
Live Notes
留言提交后会先进入审核,审核通过后对其他读者公开。