# libstereo_bolt 交付说明(.so + 头文件) 你拿到的是**预编译的 aarch64 动态库 + C 头文件**,不含算法源码。 OpenCV 4.5.4、RKNN runtime 及全部传递依赖(约 131 个 `.so`)**已随包内置于 `lib/`**, 无需在目标板上安装任何软件包(NPU 内核驱动由固件提供,不在用户态包内)。 > **本包标定 = 12MP 生产相机(4096×3000)。** 喂进来的左右图必须是该相机的 **4096×3000** 帧 > (`config` 里 `image.width=4096 / height=3000` 已配好,指向 `calib/stereo_calib_20260607_211135.xml`)。 > **包内不含测试图** —— 请用你自己这套 12MP 相机拍的左右图。 > ✅ **已在板上实测**:本 12MP 标定**加载 + rectify 通过**(4096×3000,stereo_rms=0.373、baseline=200.19、f_rect=3330)。 > ⚠️ 但**检测 + 测量在该相机上还没用真实螺杆图验过**(包内无螺杆图)。 > ⚠️ 本标定单相机 RMS=**0.373**(5MP 那套是 0.19),已把 `calibration.max_single_rms` **放宽到 0.40** 才放行; > 0.373 偏松会折损测量精度,建议后续**重标到 <0.20** 再收回此闸。首次上机务必用真实螺杆图核对结果。 ## 包内容 | 路径 | 说明 | |---|---| | `lib/libstereo_bolt.so` | 预编译共享库(aarch64) | | `lib/librknnrt.so` 等(共 ~131 个) | OpenCV 4.5.4 + 全部传递依赖(**已随包,无需 apt**) | | `include/stereo_bolt/c_api.h` | C ABI 头文件(唯一对外接口) | | `weights/best_1280_20260621.rknn` | NPU 模型(精测 + 测距共用,imgsz 1280) | | `calib/stereo_calib_20260621_202857_ascii.xml` | 出厂标定(0621 装配,baseline 482 mm) | | `config/config.rk3588.12mp.yaml` | 配置文件(兼容 `sb_create` 接口用) | | `example/` | 最小 C 示例(精测 + 测距) + CMakeLists | | `INSTALL_3588.md` | 安装与运行说明(**从这里开始**) | | `DEPENDENCIES.md` | 依赖库详细清单及说明 | | `ABI_FINGERPRINT.txt` | 二进制 ABI 指纹 | --- ## 你什么时候需要什么(编译期 vs 运行期) **重点:你不需要重编这个库**(它是预编译好的成品,你也没有源码)。你只编译*你自己* 调用它的小程序,那一步几乎零依赖。OpenCV / librknnrt 是**运行那一刻**才需要的。 | 东西 | 编译你自己的调用程序 | 在板上**运行** | |---|:---:|:---:| | `c_api.h` | ✅ | — | | `libstereo_bolt.so` | ✅(链接,带 `--allow-shlib-undefined`) | ✅ | | OpenCV 4.5.4 | ❌ 不需要(C ABI 不暴露 cv 类型) | ✅ **已随包 `lib/`,无需安装** | | `librknnrt.so`(RKNN 运行时) | ❌ | ✅ **已随包 `lib/`** | | NPU 驱动 + `/dev/rknpu` | ❌ | ✅ 必须(内核态) | | `.rknn` 模型 + 标定 XML + `config` | ❌ | ✅ 必须(**已在本包** weights/ config/ calib/) | > **“YOLO 依赖”在板上很轻**:就是 `librknnrt.so` + NPU 驱动 + `.rknn` 文件。 > **不需要** Python / PyTorch / ultralytics / ONNXRuntime / rknn-toolkit2 —— 那些是 PC 上把 > `.onnx` 转成 `.rknn` 用的,转完板上就用不到。 --- ## ⚠️ 必须匹配的 ABI 指纹(动态链接的硬约束) 这个 `.so` 在某个具体环境里编出来,二进制里**写死了**它依赖的 so 名字和 glibc 版本。 **“自己准备 OpenCV” 不等于随便哪个 OpenCV 都行——必须是指纹里那个 major.minor。** 打开 `ABI_FINGERPRINT.txt`,确保你的板子满足: - CPU 架构 = **aarch64** - **glibc ≥ 指纹里的版本**(低于它,加载即 `GLIBC_x.xx not found`) - **OpenCV soname 与指纹一致**(本包指纹 = `libopencv_core.so.4.5d` ⇒ 装 **OpenCV 4.5.x**;其它版本 soname 不通用) - **librknnrt 版本** = 转换 `.rknn` 所用 toolkit 版本(**2.3.x**) 一条命令自检(应当**没有任何** `not found`): ```bash ldd lib/libstereo_bolt.so | grep "not found" ``` --- ## 板子上需要准备的 | 依赖 | 状态 | |---|---| | OpenCV 4.5.4 + 传递依赖(131 个 .so) | ✅ **已随包 `lib/`**,无需 apt | | `librknnrt.so`(RKNN 运行时 2.3.2) | ✅ **已随包 `lib/`** | | `.rknn` 模型 + 标定 XML + config | ✅ **已随包** `weights/` `calib/` `config/` | | NPU 内核驱动 + `/dev/rknpu` | ⚠️ **板子固件提供**,出厂 RK3588 一般已有 | | glibc ≥ 2.32(Ubuntu 22.04 自带 2.35) | ✅ 系统自带,无需操作 | > 完整依赖说明见 `DEPENDENCIES.md`。 --- ## 怎么用(C ABI) 完整示例见 `example/main.c`。有**两个**喂帧入口,生产用内存版: **① 生产路径 —— 内存版(不落盘,直接喂相机帧):** ```c StereoBoltCtx* ctx = sb_create("config/config.rk3588.yaml"); // 一次:载配置/标定/RKNN 模型 while (抓帧) { const uint8_t* L = camera_left_frame(); // 相机 SDK 给的 Mono8 缓冲(W*H 字节,在内存) const uint8_t* R = camera_right_frame(); StereoBoltModuleResultC out; sb_process_bolt_module_buffers(ctx, L, W, H, 0, // stride=0 表示行紧凑排列(每行 W 字节) R, W, H, 0, 1, // channels: 1=Mono8(灰度), 3=BGR 也接受(内部转灰度) 8, &out); // 8 = 期望螺栓数 /* out.success==1: out.data.bolts[i].height_mm / foot_xyz / top_xyz + adjacent_distances + ground_plane out.success==0: out.failure.reason + left_rois/right_rois/pairs(诊断用) */ sb_free_bolt_module_result(&out); } sb_destroy(ctx); // 一次 ``` > `W`/`H` **必须等于标定分辨率**(`config` 的 `image.width`/`image.height`),否则返回 `SB_ERR_IO`。 > 内存版**全程不碰磁盘**(已实测:删掉源文件后内存版照常出结果)。 **② 离线/测试路径 —— 文件版(读磁盘图,用你自己的 12MP 左右图):** ```c sb_process_bolt_module_files(ctx, "your_left.bmp", "your_right.bmp", 8, &out); ``` > 图必须是本套 12MP 相机的 4096×3000(灰度或彩色均可),格式 .bmp/.tiff/.png。本包不含测试图。 错误码:`SB_OK=0` / `SB_ERR_INVALID_CONFIG=-1` / `SB_ERR_IO=-2` / `SB_ERR_ALGORITHM_REJECTED=-3` / `SB_ERR_INTERNAL=-99`。 > `SB_ERR_ALGORITHM_REJECTED` 不是崩溃,是算法判定该帧不可测,`out.failure` 里有原因 + ROI + 配对状态。 --- ## 输入 / 输出(契约) **输入** - 一次性(`sb_create`):`config/config.rk3588.yaml`(内部指向 `calib/*.xml` 与 `weights/best.rknn`)。 - 每帧(生产用 `sb_process_bolt_module_buffers`):左/右 Mono8 内存缓冲 + 宽/高/stride、通道数、期望螺栓数(int)、`&out`。 - 每帧(离线用 `sb_process_bolt_module_files`):左图路径、右图路径、期望螺栓数(int)、`&out`。 **输出**(`StereoBoltModuleResultC out` + 返回码) - 成功 `out.success==1` → `out.data`: - `bolts[]` 每颗:`bolt_id` / `left_roi`,`right_roi`(x,y,width,height,score) / **`height_mm`** / **`foot_xyz`,`top_xyz`**(底/顶 3D 点) / `centerline`(3D 轴 = point+direction) - `adjacent_distances[]`:`bolt_id_i`,`bolt_id_j`,**`distance_mm`** - `ground_plane`:`model[4]`(ax+by+cz+d=0)、`rms_mm`、`inlier_count` - 失败 `out.success==0` → `out.failure`:`reason` + `left_rois[]`/`right_rois[]` + `pairs[]`(配对诊断) **单位 / 坐标系**:高度、间距 = **mm**;3D 坐标(foot/top/centerline) = **mm,校正后左相机坐标系** (x 右 / y 下 / z=深度,典型 z≈1600–2200mm)。`score`=YOLO 置信度。 输出格式示例(**仅示意字段结构**,数值来自早先 5MP 验证机,非本包 12MP 相机): ``` success=1 bolts=8 distances=8 bolt 0 height=151.2mm top=(-191.9,-122.9,1995.3) foot=(-204.3,2.9,2078.3) ... dist 0-1=268.3mm dist 1-2=254.4mm ... dist 7-0=335.8mm ``` > 一句话:**入 = 一对左右图 + 期望螺栓数(配置/标定/模型初始化时给一次); > 出 = 每颗螺栓 高度 + 顶/底 3D + 3D 中心轴 + 相邻间距 + 地平面,或失败原因 + 诊断。** --- ## 编译你自己的调用程序 + 运行(**都在包根目录执行**) ```bash # 在包根目录(与 lib/ config/ calib/ 同级)编译 cmake -S example -B example/build -DSB_ROOT="$(pwd)" cmake --build example/build # 在包根目录运行(config 里 calib/、weights/ 是相对路径,按当前目录解析) # example 走【内存接口】: 读 raw Mono8 裸数据(W*H 字节)喂 sb_process_bolt_module_buffers, # 库全程不见文件路径。your_left/right.raw = 你 12MP 相机帧 dump 的 Mono8(4096*3000 字节) LD_LIBRARY_PATH=lib:/usr/lib:$LD_LIBRARY_PATH \ ./example/build/bolt_demo config/config.rk3588.yaml your_left.raw your_right.raw 4096 3000 8 ```