# Interface_AI
**Repository Path**: dfhhdq/interface_-ai
## Basic Information
- **Project Name**: Interface_AI
- **Description**: 推理段代码, 包含了yolo, yolo-master, rf-detr等,有目标检测,旋转框,分割等
- **Primary Language**: C++
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2026-08-19
- **Last Updated**: 2026-09-04
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# InterfaceAI
TensorRT 10.8 + YOLO11 / YOLO-Master / RF-DETR 推理接口库(detect / pose / segment / obb / master-detect / rfdetr-detect / rfdetr-segment)。
纯虚接口 + 智能指针 + 工厂导出的 DLL 设计,调用方只依赖一个头文件;函数级 tools 归纳公共前后处理,任务差异各自实现,无套娃继承。
## 特性
- **仅推理**:加载用户提供的 `.engine` 文件,库内不做 onnx → engine 构建
- **七种任务**:YOLO11 detect / pose(COCO 17 关键点)/ segment(实例分割)/ obb(旋转框)+ YOLO-Master detect / segment(腾讯 CVPR2026, MoE 加速)+ RF-DETR detect / segment(Roboflow, DETR 架构, 无 NMS),统一输出结构
- **规范接口**:纯虚类 `IYoloDetector` + 工厂函数返回 `std::shared_ptr`,DLL 边界零实现暴露,跨编译器安全
- **函数级 tools**:letterbox、decode、NMS、坐标映射等公共逻辑归纳到 `src/tools/`,任务差异各自实现
- **自动适配**:engine 输出内存布局(`[C,N]` / `[N,C]`)、类别数、标准/带obj 布局自动探测,换模型无需改代码
- **指定 GPU 运行**:构造时 `opt.gpuId` 选择目标显卡,engine 加载与推理都在该卡上
- **动态 batch(固定批 + 黑图补全)**:构造时 `opt.batch` 指定运行时批大小(如 4),推理时**每批固定 = 4**,输入不足用黑图补全(推 2 张 → 2 真+2 黑),黑图结果强制丢弃不污染输出;任意图片数量均可推理,无下限报错
- **同步推理**:单模型对象,内部单 CUDA stream,简单可靠
- **日志输出**:spdlog header-only(xmake 包管理,无动态库),模型加载过程 + 推理结果/耗时自动打日志
- **零 PATH 配置**:构建时自动把 OpenCV / TensorRT 运行时 dll 拷贝到 exe 同目录
## 架构总览
```mermaid
flowchart LR
subgraph 调用方
APP[你的程序]
end
subgraph interface_ai.dll
API[IYoloDetector 纯虚接口
+ 工厂函数]
DET[YoloDetectImpl]
POSE[YoloPoseImpl]
SEG[YoloSegmentImpl]
MSTD[YoloMasterDetectImpl]
RFD[RfDetrDetectImpl / RfDetrSegmentImpl]
TOOLS[src/tools 函数级工具
letterbox / rfdetrResize / decode / NMS / 映射]
ENG[TRTEngine
加载 + enqueueV3]
API --> DET & POSE & SEG & MSTD & MSSG & RFD
DET & POSE & SEG & MSTD & MSSG & RFD --> TOOLS
DET & POSE & SEG & MSTD & MSSG & RFD --> ENG
end
APP -->|"createYolo*() -> shared_ptr"| API
ENG -->|"反序列化 .engine"| TRT[TensorRT 10.8]
```
**分层职责**:
| 层 | 文件 | 职责 |
|----|------|------|
| 接口层 | `include/interface_ai/` | 对外契约:统一数据结构 + 纯虚接口 + 工厂声明(唯一 ABI 边界) |
| 引擎层 | `src/engine/` | TRT 生命周期管理、tensor 绑定、同步推理、内存布局判定 |
| 工具层 | `src/tools/` | 与任务无关的公共函数:前处理(letterbox/归一化)、后处理(decode/NMS/映射) |
| 任务层 | `src/yolo/` | 各任务实现,只做"编排":调 tools 前处理 → 引擎推理 → 调 tools 后处理 → 填输出 |
| 工厂 | `src/factory.cpp` | DLL 唯一导出点,组装 engine + 任务实现 |
## 推理数据流
```mermaid
flowchart TD
A[输入 cv::Mat
任意尺寸 BGR] --> B[letterbox
等比缩放+灰边114]
B --> C[RGB 转换
+ HWC2CHW
+ 归一化 /255]
C --> D[H2D 拷贝]
D --> E[enqueueV3 推理]
E --> F[stream 同步]
F --> G[D2H 拷贝]
G --> H[decode
候选框+置信度]
H --> I[NMS 去重]
I --> J[坐标映射回原图
scale/padX/padY]
J --> K[InferResult
统一输出]
H -. pose: 关键点解码 .-> I
H -. segment: mask系数×原型图加权和
按框裁剪(proto系) -> bilinear放大
acc>0 二值 -> 贴回原图 .-> I
A -. RF-DETR: 拉伸缩放(bilinear) + ImageNet归一化
/255 -> (x-mean)/std .-> D
G -. RF-DETR: top-Q 多分类解码(无 NMS)
labels [Q, C+1] 跳过 no-object 槽 .-> J
G -. RF-DETR seg: mask logits bilinear放大原图
>0 二值(无 sigmoid) .-> K
```
## 环境要求
| 依赖 | 版本 | 说明 |
|------|------|------|
| Windows + MSVC | VS 2022+ | xmake 自动探测 |
| CUDA | 12.4 (nvcc) | `set_config("cuda_sdkver", "12.4")` |
| TensorRT | 10.8.0.43 | 链接 `nvinfer_10` / `nvinfer_plugin_10` / `nvonnxparser_10` |
| OpenCV | 4.8.1 | `E:/03__3rdparty/opencv_4.8.1/...` |
| spdlog | 1.17.0 (header-only) | xmake 自动下载安装,无需动态库 |
| nlohmann/json | 3.x (header-only) | xmake 自动下载安装,测试配置解析 |
| GPU | RTX 5070 (sm_120) | 驱动 595.97+ (CUDA 13.2) |
> **sm_120 说明**:RTX 50 系列需要 CUDA 12.8+ 才能原生编译,本机只有 CUDA 12.4,故 cu 文件的 gencode 采用 `compute_90` 的 PTX 模式,由驱动 JIT 到 sm_120。当前实现预处理在 CPU(OpenCV),暂无 cu 文件;后续加 GPU kernel 时沿用此策略。
## 模型转换 (ONNX → engine)
`Scripts/convert_onnx2trt.ps1` 基于 `trtexec` 批量转换,内置 onnx protobuf 解析器(免 Python),自动读取每个模型的**输入名和实际维度**——模型尺寸变化(如 640→1280)无需改脚本。
```powershell
.\Scripts\convert_onnx2trt.ps1 # 全量转换 (默认并行 2, 已存在跳过)
.\Scripts\convert_onnx2trt.ps1 -Name yolo11_detect_dynamic_m_8x3x640x640_20260813091015 # 只转指定模型 (onnx 完整名)
.\Scripts\convert_onnx2trt.ps1 -Parallel 1 -Force # 串行强制重转
.\Scripts\convert_onnx2trt.ps1 -MaxBatch 16 # 动态 batch 上限改为 16
```
- 扫描 `models/test/*.onnx`(根目录平铺),engine 与日志**沿用 onnx 文件名**(`.engine` / `.engine.log` 同级);目录内不建二级子目录;不符合命名规范的 onnx 警告跳过
- **目录约定**:`models/test/pt/` 存原始 pt 权重(命名 `架构_类型_大小.pt`);`models/test/` 根目录存 onnx + engine + log 平铺;模型命名六段 `架构_类型_动静_大小_尺寸_时间`(全小写连字符,时间戳 = onnx 导出时 mtime,engine/log 沿用 onnx 名,**重转不更名**)
- **dynamic**:仅 batch 维动态,profile = min/opt/max = `2x3x640x640 / 4x3x640x640 / 8x3x640x640`(batch 可 `-MinBatch/-OptBatch/-MaxBatch` 调,其余维度跟随模型实际值;若非 batch 维动态直接报错)
- **static**:直接用模型固定 shape
- 并行用后台 job,每任务独立 trtexec 进程,单任务失败不影响其他;汇总 `ok=failed=skipped=` 并以非零退出码标记失败
## 构建与测试
```bash
xmake # 构建全部 (interface_ai.dll / test_interface_ai.exe / engine_info.exe)
xmake build # 同上 (增量)
xmake f -m debug # 切换到 debug 模式 (默认 release)
# 打印 engine 输入输出布局 (换模型第一步, 确认结构)
xmake run engine_info
# 推理测试 (config/config.json 驱动, 输出 PASSED/FAILED)
# 配置: config/config.json 的 tests 数组, 每组 {task, enabled, engine, image, mode, gpu, batch, ...}
# enabled=true 执行, false 跳过; engine/image 相对项目根; config 从 exe 目录向上定位, 不依赖 cwd
# 用例: detect/pose/segment/obb (创建校验/逐图/批量/结构校验) + conf (热更新) + err (错误路径) + loop (循环推理)
xmake run -w . test_interface_ai
```
运行时 dll 由 `rule_copy_runtime_dll` 自动拷贝到 exe 同目录,无需手动配 PATH。
## 使用指南
### 最小示例
```cpp
#include
// 1. 工厂创建 (返回 shared_ptr, 自动释放; nullptr = engine 加载失败)
auto det = iai::createYoloDetect("person.engine");
if (!det) { /* engine 加载失败 */ }
// 2. 同步推理 (内部自动 letterbox, 输入任意尺寸 BGR 图)
cv::Mat img = cv::imread("test.jpg");
iai::InferResult r = det->infer(img);
// 3. 读结果 (坐标已映射回原图像素系)
if (r.code != iai::IAI_OK) { printf("error: %s\n", r.msg.c_str()); return; }
for (const auto& b : r.boxes) {
printf("label=%d conf=%.3f box=(%.1f,%.1f,%.1f,%.1f)\n",
b.label, b.conf, b.x1, b.y1, b.x2, b.y2);
// pose: b.kpts (17 个 Kpt{x,y,conf})
// segment: b.mask (原图尺寸 CV_8UC1 二值图)
// obb: b.rotRect (cv::RotatedRect) — points() 取 4 角点; x1y1x2y2 为外接矩形
}
```
### 工厂函数
```cpp
struct YoloOptions {
int gpuId = 0; // 目标 GPU (默认 0 号卡)
int batch = 1; // 运行时批大小 (构造时固定): 1 = 不启用; >1 按此值分批推理
};
```
| 函数 | 任务 | engine 输出要求 |
|------|------|----------------|
| `iai::createYoloDetect(path, conf = 0.5, opt = {})` | 检测 | `[b, 4+nc, N]` 或 `[b, 5+nc, N]` |
| `iai::createYoloPose(path, conf = 0.5, opt = {})` | 姿态 | `[b, 4+nc+3*17, N]` (17 关键点) |
| `iai::createYoloSegment(path, conf = 0.5, opt = {})` | 分割 | `[b, 4+nc+32, N]` + `[b, 32, 160, 160]` 两个输出 |
| `iai::createYoloObb(path, conf = 0.5, opt = {})` | 旋转框 | `[b, 4+nc+1, N]` (angle 在最后, 弧度) |
| `iai::createYoloMasterDetect(path, conf = 0.5, opt = {})` | 检测 (YOLO-Master) | 同 createYoloDetect (原生导出格式一致) |
| `iai::createRfDetrDetect(path, conf = 0.5, opt = {})` | 检测 (RF-DETR) | `dets[b, Q, 4]` + `labels[b, Q, C+1]` 两个输出 |
| `iai::createRfDetrSegment(path, conf = 0.5, opt = {})` | 分割 (RF-DETR) | `dets[b, Q, 4]` + `labels[b, Q, C+1]` + `masks[b, Q, H, W]` 三个输出 |
> RF-DETR 模型必须用其官方仓库导出 ONNX(`scripts/export_onnx.py`, 输出 `dets/labels[/masks]`,
> labels 末列是 no-object 槽, 解码时跳过)。前处理为**拉伸缩放**(非 letterbox)+ ImageNet 归一化
> (mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225]), 后处理为 per-class sigmoid + top-Q 多分类
> (无 NMS)。类别数为 labels 通道数 - 1(官方 COCO-90 类)。
> YOLO-Master 模型必须用其仓库自带 ultralytics **原生导出**(`model.export(format="onnx")`, 非 end2end 模式),
> 输出格式与 yolo11 完全一致, 后处理全量复用; 若用 trtyolo-export 插件导出 (engine 内 NMS), 输出为
> `[1, 1, N, 7]` 已 NMS 格式, 与本库后处理不兼容。
> `opt` 不指定(默认 `{}`)时行为与旧版本完全一致:GPU 0 + 单张推理。
> 指定 `opt.batch=4`(engine 范围 [2..8]):每批固定 4 张,推 2 张 → 2真+2黑 一次推理(黑图无结果),推 6 张 → 两批 4+2(第二批 2真+2黑)。
### 接口方法
```cpp
class IYoloDetector {
virtual InferResult infer(const cv::Mat& image) = 0; // 单图推理
virtual std::vector infer(const std::vector&) = 0; // 批量 (当前串行)
virtual void inputSize(int& width, int& height) const = 0; // 模型输入尺寸
virtual int numClasses() const = 0; // 类别数 (自动推断)
virtual void setConf(float conf) = 0; // 热更新置信度阈值 (下一帧生效)
};
```
### 输出结构字段
```cpp
struct Box { // 四类任务共用
float x1, y1, x2, y2; // 检测框, 原图像素坐标 (OBB 时为外接矩形)
float conf; // 置信度 [0,1]
int label; // 类别 id (与训练标签对应)
cv::RotatedRect rotRect; // OBB 旋转框 (原图像素系, angle 为度, 用 points() 取角点)
std::vector kpts; // pose: 17 关键点 {x,y,conf}
cv::Mat mask; // segment: CV_8UC1 二值图 (0/255), 原图尺寸
};
struct InferResult {
int code; // 0 = 成功, 非 0 见错误码枚举
std::string msg; // 失败原因
YoloTask task; // Detect / Pose / Segment / Obb / MasterDetect
std::vector boxes; // 统一出口: 检测框 / 关键点 / 掩码 / 旋转框
};
```
## 日志
基于 spdlog header-only(xmake 包管理,无动态库依赖),进程级单例 `IAI_LOGGER()`。
**输出位置**:运行目录(exe 所在目录)下的 `iai.log`,追加模式,**不输出到控制台**。日志文件不可写时自动降级丢弃,不影响主流程。
```cpp
#include // 库内部使用; 使用方如需要可自行 include spdlog
IAI_LOGGER()->info("..."); // 级别: trace/debug/info/warn/error
```
已埋日志点:
| 位置 | 内容 | 级别 |
|------|------|------|
| 模型加载 | 文件路径/大小、输入输出 shape、内存布局判定结果、成功/失败 | info/error |
| 工厂创建 | 任务类型 + 类别数 | info |
| 推理结果 | 目标数 + 耗时拆分(pre/infer/post/total) | info |
| 逐框明细 | label/conf/box (pose 含点数, segment 含 mask 前景像素) | debug |
> debug 级别默认关闭;需要逐框明细时在 `src/tools/logger.h` 把 `set_level` 改为 `spdlog::level::debug`。
**异常安全**:所有对外入口(工厂创建 / 引擎加载 / 推理)均有 try/catch 兜底,任何异常转为错误码/错误日志返回,不跨 DLL 边界抛出。
## 核心原理
### 0. 指定 GPU 与动态 batch
- **GPU 选择**:`TRTEngine::setDevice(gpuId)` 在加载前调用(校验设备号),`load`/`infer` 内部都会 `cudaSetDevice` 切回目标卡——即使调用线程的 CUDA 上下文被外部修改也能正确运行。
- **动态 batch(固定批 + 黑图补全)**:engine 输入 `[b, 3, H, W]` 的 batch 维为 `-1`(profile min≠max)时判定为动态。构造时 `opt.batch=N` 校验 N ∈ [minBatch, maxBatch] 并固定为运行时批大小;推理时每批恒为 N,输入不足用全 0 黑图补全到满批,回调只处理真实图片(补全部分结果强制丢弃),批大小恒定所以 `setBatch` 只在构造时调用一次、推理期零重分配。
- **不指定就没有**:`opt.batch` 默认 1——固定 engine 走串行单张,动态 engine 用最小批;只有显式指定 `>1` 才固定批大小,且 engine 不支持时创建直接失败(日志明确报错)。
### 1. yolo 输出内存布局(最重要的坑)
engine 输出 shape 有两种可能,内存排列完全不同:
| shape | 含义 | 候选 i 第 ch 个值的位置 |
|-------|------|------------------------|
| `[1, C, N]` | 通道在前 (ultralytics 默认导出) | `data[ch * N + i]` |
| `[1, N, C]` | 候选在前 (onnx 未 transpose) | `data[i * C + ch]` |
判定:比较 `dims[1]` 与 `dims[2]`——类别通道数 C(如 6)远小于候选数 N(8400),所以 `dims[1] < dims[2]` 即为通道在前。此标志由 TRTEngine 加载时判定并存入 `TensorInfo::channelFirst`,decode 必须透传。
### 2. 通道语义与布局探测
- 标准 yolo8/11: `[cx, cy, w, h, cls0..clsN-1]`,通道数 = 4 + nc
- yolo5 风格: `[cx, cy, w, h, obj, cls0..clsN-1]`,通道数 = 5 + nc,置信度 = obj × max(cls)
当通道数无法区分两种解释时(如 6 = 4+2 或 5+1),首次推理自动探测:标准布局解出有效框则采用,否则切换带 obj 布局。
### 3. 坐标映射
letterbox 后模型输出坐标位于 640x640 的 letterbox 坐标系,映射回原图:
```
原图坐标 = (letterbox坐标 - pad) / scale
```
`scale` / `padX` / `padY` 由 `tools::letterbox` 产生,后处理结束时统一映射并 clamp 到图像范围。
### 4. segment mask 生成(踩坑记录,与 ultralytics 逐像素对齐)
mask 由第二个输出 `[b, 32, 160, 160]` 原型图(proto)生成,流程与官方 `process_mask` 完全一致:
```
coefs @ protos(160x160 加权和) -> 按框裁剪 -> bilinear 放大到框尺寸 -> acc>0 二值(等价 sigmoid>0.5)
```
**关键陷阱:proto 系裁剪区域不能减 pad**。proto 是 letterbox 全图(640x640, 含灰边)的 160x160 缩略图,
裁剪区域 = **letterbox 框坐标 × ratio(160/640),减 pad 会使 crop 上移 padY×ratio**——
曾致 mask 内容相对框整体偏下、人物缺头部(偏移约 26.5px @ 竖图)。坐标处理顺序:decode(letterbox 系)
→ 算 protoRects(letterbox 框×ratio) → 再 mapToOriginal,顺序不能反。
其他对齐细节:proto 不过 sigmoid(加权和直接裁剪);阈值(>0)在 resize 之后,与官方 `gt_(0.0)` 同语义;
批量按帧裁剪用 INTER_LINEAR(非 NEAREST)以贴合官方 bilinear 边缘质量。
**与官方逐像素对比方法**:官方 `r.masks.data` 是 predict 默认 auto-letterbox 后的输入系坐标(非 640x640),
需先映射回原图;质心对比要用**像素加权质心**(非空行均值 `ys.mean()` 不可比,数值差异可达数十像素)。
本机对照环境:`E:\00__demo\01__py\20260703_test\.venv`(ultralytics 8.4.118)。实测 person mask 像素加权质心
C++ = 281.1 vs 官方 = 281.2(差 0.1px),逐像素 IoU 0.96+(残余来自 fp16 vs fp32)。
## 常见问题 (FAQ)
| 症状 | 原因 | 处理 |
|------|------|------|
| 大量 conf≈0.3 的假框 | 输入未归一化 (/255) | 检查 `toModelInput` 是否被改动 |
| 输出框坐标全为 0 或乱值 | 内存布局判错 | 跑 `engine_info` 确认输出 shape, 检查 `channelFirst` |
| 同目标输出两个类 | 标准/带obj 布局判错 | 首次推理会自动探测; 若探测失效检查 numClasses_ |
| engine 加载失败 | TRT 版本不兼容 | engine 必须由 TRT 10.8 构建 (序列化格式跨版本不兼容) |
| 检测结果有偏差 | 输入通道顺序 (BGR/RGB) 或拉伸 vs letterbox | 与训练时前处理保持一致 |
| segment mask 相对框偏下、缺头部 | proto 系裁剪区域减了 pad | proto 是 letterbox 全图缩略图,裁剪 = letterbox 框 × ratio,**不减 pad** (见"核心原理 4") |
## 扩展指南:新增任务类型(如 OBB)
1. `src/yolo/` 新增 `YoloObb.h/.cpp`,实现 `IYoloDetector`(不继承其他任务类,复用 tools 函数)
2. 差异后处理写在自己类里(如 OBB 的角度解码),公共部分调 `tools::`
3. `src/factory.cpp` 增加 `createYoloObb()`,复用 `loadEngine()`
4. `include/interface_ai/IYoloDetector.h` 声明新工厂函数
5. 输出结构字段不足时,在 `Box` 上按任务追加字段(注意 DLL 使用方兼容)
## 项目结构
```
Interface_AI/
├── xmake.lua # 构建配置 (3 个 target + dll 拷贝规则)
├── opencode.json # 项目级 MCP 配置 (codebase-memory 索引指向项目根)
├── AGENTS.md # AI 协作指南 (关键陷阱 / 修改约定)
├── Scripts/
│ └── convert_onnx2trt.ps1 # ONNX → engine 批量转换 (trtexec, 自动探测输入, 并行)
├── include/interface_ai/ # ── 对外接口 (唯一 ABI 边界)
│ ├── DataStruct.h # 统一输出结构 + 错误码
│ └── IYoloDetector.h # 纯虚接口 + 工厂函数 + 导出宏
├── src/
│ ├── engine/TRTEngine.* # TRT 10.8 封装: 加载 / 绑定 / enqueueV3 / 布局判定 / 动态batch
│ ├── tools/ # ── 函数级公共工具
│ │ ├── preprocess.* # letterbox + BGR2RGB + HWC2CHW + 归一化
│ │ ├── postprocess.* # decode (双布局/OBB) + NMS/ProbIoU + IoU + 坐标映射
│ │ ├── batch.h # 批量调度: 黑图补全 + 分批 + 批量前处理
│ │ └── logger.h # 日志单例 (spdlog header-only, 写运行目录)
│ ├── yolo/ # ── 各任务独立实现 (无基类继承)
│ │ ├── YoloDetect.* # 检测 (布局自动探测)
│ │ ├── YoloPose.* # 姿态 (17 关键点)
│ │ ├── YoloSegment.* # 分割 (mask 生成)
│ │ ├── YoloObb.* # 旋转框 (cv::RotatedRect 输出)
│ │ ├── YoloMasterDetect.* # YOLO-Master 检测 (原生导出, 格式同 yolo11)
│ │ ├── RfDetrDetect.* # RF-DETR 检测 (DETR, top-Q 解码, 无 NMS)
│ │ └── RfDetrSegment.* # RF-DETR 分割 (mask logits 放大 >0 二值)
│ └── factory.cpp # 工厂实现 (DLL 唯一导出)
├── test/
│ ├── test_main.cpp # 测试入口: 读 config/config.json + 按 enabled 分发
│ ├── test_common.h/.cpp # 公共: config 加载 / 图片加载 / 结果打印+保存 / 用例声明
│ ├── test_detect.cpp # Detect 用例 (单图 + batch 满批/黑图补全 + conf 热更新)
│ ├── test_pose.cpp # Pose 用例 (校验 17 关键点)
│ ├── test_segment.cpp # Segment 用例 (校验 mask 尺寸/类型)
│ ├── test_obb.cpp # OBB 用例 (校验 RotatedRect 合法性)
│ ├── test_master.cpp # YOLO-Master 用例 (detect, 复用公共流程)
│ ├── test_rfdetr.cpp # RF-DETR 用例 (detect/segment, static/dynamic)
│ └── test_loop.cpp # Loop 用例 (循环推理 N 次, 长时间运行稳定性)
├── config/
│ └── config.json # 测试配置: tests 数组 + enabled 开关 (engine/image 相对项目根)
└── tools/engine_info/main.cpp # engine IO 布局打印工具
```
## 已知限制
- 同步推理、单模型对象;异步 / 多模型管理后续按需扩展
- 输入尺寸固定(engine 决定,当前模型 640x640 / RF-DETR 576x576 / 432x432);动态 shape 未支持
- pose 关键点数默认 17(COCO),不同训练改 `YoloPose.h` 的 `numPoints_`
- 预处理在 CPU(OpenCV);GPU kernel 优化待性能需求出现时加入(gencode 已备好)
- segment 的 mask 逐框生成原图尺寸二值图,多目标时内存开销随框数增长(简单直观优先)