# 27RM RoboMaster 视觉工程(MindVision 相机 + OpenCV)。 ## 构建 ```bash xmake # 产出四个程序(见下) xmake run 27RM # 跑主程序 ``` ### VSCode + clangd `compile_commands.json` 含本机绝对路径,**不入库**,克隆后自己生成一次: ```bash xmake project -k compile_commands .vscode ``` (`.vscode/settings.json` 里已配置 `clangd.arguments: ["--compile-commands-dir=.vscode"]`) ## 程序清单 | 程序 | 用途 | |---|---| | `27RM` | 自瞄主程序 | | `capture_calib` | **相机标定图采集工具** | | `calibrate` | **标定计算工具**(照片 → 内参/畸变 → `configs/camera.yaml`) | | `pose_test` | **几何链路验证工具**(4 角点 → 距离/yaw/pitch,含合成自检) | | `detect_test` | **灯条检测验证工具**(检测 → PnP 一条龙,含合成自检) | ## 配置 ``` configs/ ├── camera.yaml # 相机内参 + 畸变 + 标定元信息(由 calibrate 生成,需入库) ├── camera_runtime.yaml # 运行时参数:曝光/增益/白平衡/帧率(手工维护,需入库) └── light_bar.yaml # 灯条检测参数:HSV 范围/配对门限/PCA(手工维护,需入库) ``` 代码里这样用(`inc/camera_calib.hpp`): ```cpp #include "camera_calib.hpp" CameraCalib calib; if (!calib.load("configs/camera.yaml")) return -1; // 启动时加载一次 cv::Mat undist; calib.undistort(frame, undist); // 每帧去畸变 // 装甲板四角点(左下、左上、右上、右下)→ 位姿 std::array corners{lb, lt, rt, rb}; if (auto pose = calib.solvePose(corners, ArmorSize::small())) { double yaw, pitch; CameraCalib::poseToYawPitch(pose->tvec, yaw, pitch); // pose->distance_mm / pose->reproj_error_px 可用于过滤坏帧 } ``` `solvePose` 内部已处理:去畸变 → `solvePnPGeneric`(IPPE) 取回全部候选解 → 筛掉「相机后方 (z<=0)」「重投影 > 8px」→ 取最优。详情见源码注释。 相机参数设置(`inc/camera_set.hpp`,`initializeCamera` 之后调用): ```cpp cam.setAeState(false); // 手动曝光(比赛必须,否则画面亮度漂移) cam.setExposureTime(3000.0); // 曝光时间,微秒 cam.setAnalogGainTimes(2.0); // 模拟增益,倍率(内部按 fAnalogGainStep 换算) cam.setWbMode(false); // 关自动白平衡(颜色阈值才稳定) cam.setFrameSpeed(2); // 帧率档位 0/1/2 ``` 也可以直接由配置驱动:`applyRuntimeParams` 的示例见 `src/pose_test.cpp`。 > 程序必须在工程根目录运行(`configs/` 是相对路径),或自行传绝对路径。 ## pose_test:几何链路验证 写检测器之前,先用它验证「标定 + 坐标约定 + PnP 解算」: 拿真的装甲板(或打印 131.56×55mm 矩形)摆在相机前,鼠标点 4 个角, 输出的距离/yaw/pitch 与卷尺、量角器核对。之后接检测器出问题必是检测的锅。 ```bash ./build/linux/x86_64/debug/pose_test # 相机 + 鼠标点角 ./build/linux/x86_64/debug/pose_test --image 照片.jpg # 图片模式 ./build/linux/x86_64/debug/pose_test --selftest # 合成自检(无需相机/显示器) # 脚本/无窗口:直接给 4 个角点(左下 左上 右上 右下),--size 自定义矩形(毫米) ./build/linux/x86_64/debug/pose_test --image 照片.jpg --size 180x120 \ --corners x1,y1 x2,y2 x3,y3 x4,y4 ``` - 相机模式按键:`r` 清除重选 | `b` 切换大小装甲 | `u` 切换去畸变显示 | `s` 截图 | `q` 退出 - `--selftest` 自检已通过:正前方/右偏/上仰/复合/板内滚转均精确恢复, 交叉乱序被重投影门限正确拒绝 - 真实照片验证:6 张标定图(棋盘格外圈 180×120mm)距离 0.44–0.69m、 重投影 0.3–2.5px,与卷尺真值一致 ### 两个踩过的坑(写检测器时别重蹈) 1. **OpenCV 4.x 的 IPPE 有两个数值雷区**(实测): ①「畸变 + 板内旋转」→ 解出垃圾解;②「完美轴对称矩形」→ 返回 NaN。 解法:IPPE 全候选逐个筛(有限值/z>0/重投影),**全被筛掉就自动退回 `SOLVEPNP_ITERATIVE`**——两条退化路径都稳(`solvePose` 已内置)。 2. **4 个共面点的顺序错误查不出来**:循环/镜像顺序 = 同一个四边形 = 同一个解(无害);交叉乱序会被重投影门限拦下。真正需要 roll 门限的是 「检测器给出假四边形」场景,等写检测器时照搬 jlu 的 `roll_thres 15°`。 ### 装甲板尺寸(2026-09-16 实测) | 板型 | W 灯条中心距 | L 灯条长度 | 来源 | |---|---|---|---| | 小装甲(2/3/4/5 号、哨兵) | **131.56 mm** | **55.0 mm** | 实测 3 号板,与官方规格 131.5mm 一致 | | 大装甲(英雄 1 号) | 230 mm(名义值) | 55.0 mm | 待实测校正 W | ⚠️ 尺寸误差 1% = 距离误差 1%;拿 135mm 模型解 230mm 大板,距离会差 0.59 倍。 检测器将来必须同时输出「4 角点 + 大小装甲分类」。 ## detect_test:灯条检测 + PnP 一条龙 传统 CV 管线(借鉴 jlu_vision_26):HSV 颜色掩膜 → 轮廓 → LightBar (minAreaRect+质心+端点)→ PCA 角点修正(灰度矩对称轴 + 亮度梯度找端点) → 左右配对五道门 → 大小装甲分类 → 4 角点 → `solvePose`。 ```bash ./build/linux/x86_64/debug/detect_test # 相机实时 ./build/linux/x86_64/debug/detect_test --image 照片.jpg # 静态图 ./build/linux/x86_64/debug/detect_test --selftest # 合成自检(无需相机) ``` - 按键:`q` 退出 | `s` 截图 | `c` 切换敌方颜色(红方打蓝,默认蓝) | `r` 重载参数 - 参数都在 `configs/light_bar.yaml`,现场按 `r` 热重载调参 - 敌方颜色默认 `blue`(主人是红方);拿自己红板测试时改 `red` 或现场按 `c` - `--selftest` 自检已通过 6/6:正前方/右偏/上仰/复合/板内滚转/大装甲, 距离误差 ≤3.1%、yaw/pitch 误差 ≤0.05°,大小分类全部正确 ### 调参时踩过的三个坑(已在默认参数里避开) 1. **形态学闭运算别开 2 次**:远景灯条只有 2~3px 宽,腐蚀两轮整个抹掉 2. **长宽比门限别设太高**(默认 1.5):远景灯条模糊+阈值后掩膜被撑到 10px 宽,实测比只剩 1.7;防误检主要靠配对五道门 3. **角点顺序的镜像坑**:板子朝向相机时,板的左条出现在画面右侧 (面对面镜像),输出角点已按模型约定「板左下→板左上→板右上→板右下」处理 ### 渲染自检的教训(写自检时别重蹈) 圆管式画灯条会让端弧超出模型端点 4px+,距离直接偏 17%;用填充四边形、 端点严格对齐模型点。 ## 相机标定流程 > **本工程标定板规格**:10 × 7 内角点、**方格 20.0 mm**(卡尺实测值,不是打印标称值) > **相机**:MV-SUA131GC(1/3" CMOS,3.75μm 像元)+ MV-LD-M12-2.8MM-C 镜头,输出 1280×1024 > ⚠️ M12 镜头**调焦会改变有效焦距**,标定和使用时必须保持对焦环位置一致 ### 1. 采集标定图 ```bash # 用 MindVision 相机(机器人上那台) ./build/linux/x86_64/debug/capture_calib --square 20.0 # 或改用 USB 摄像头 ./build/linux/x86_64/debug/capture_calib --usb 0 --square 20.0 ``` 按键: | 键 | 功能 | |---|---| | 空格 | 保存当前帧(默认要求检测到棋盘格) | | `f` | 强制保存(未检测到也存) | | `d` | 删除上一张 | | `c` | 开关实时角点检测显示 | | `g` | 灰度预览(排查过曝/反光) | | `q` / ESC | 退出 | 拍摄要求: - 标定板出现在画面**中心、四角、边缘**(2.8mm 广角镜头边缘畸变最大) - 姿态多样:正对、倾斜、旋转、俯仰 - 清晰不糊、光照均匀、无反光、板子贴平硬板 - **20~30 张**,保存于 `calib_images/`(已加入 .gitignore,不会入库) ### 2. 计算内参 ```bash ./build/linux/x86_64/debug/calibrate --dir calib_images --square 20.0 # 只算顶层 ./build/linux/x86_64/debug/calibrate --dir calib_all --square 20.0 # 全部 31 张(推荐) ./build/linux/x86_64/debug/calibrate --square 20.0 --show # 逐张查看角点与重投影 ``` 工具会输出重投影误差 RMS、内参 K、畸变系数、每张图误差排行,并写入 `camera_calib.yml`。 | 重投影误差 | 评价 | |---|---| | < 0.3 px | 很好 | | 0.3 ~ 0.5 px | 可接受 | | > 0.5 px | 建议重拍 | > **当前标定结果**(`calib_all/` 31 张,20mm 方格): > fx=657.47, fy=658.25, cx=625.64, cy=509.00,RMS **0.483 px** > **距离验证**:卷尺 700mm vs 程序 687~697mm,误差 **< 2%** ✓ ### ⚠️ 标定图必须同时具备「三种多样性」 | 维度 | 作用 | 不满足的后果 | |---|---|---| | **画面位置**(九宫格覆盖) | 约束**畸变系数** | 边缘畸变解不出来,RMS 偏高 | | **倾角**(正对/上下倾/左右倾/大角度) | 约束**焦距与主点** | 参数耦合,精度下降 | | **距离**(近/中/远,板子占画面 40%/20%/8%) | 约束**尺度** ⭐ | **fx 无法确定**:所有图同距离时,"焦距×距离"可整体缩放而图像不变,优化器会落到错误值,**且 RMS 依然很低**(最阴险的坑) | > 实测教训:一套 19 张、覆盖 9/9 但**距离相近**的图,标出的 fx=883(实际 659,偏差 34%),距离误差直接 +32%; > 与旧的 12 张(距离 24~65cm)合并后 fx 回到 657,误差 <2%。**距离多样性是 fx 的锚点,不可省。** ### 3. 使用内参 推荐直接用封装好的 `CameraCalib`(见上方「配置」一节),它已经处理了配置路径、去畸变缓存和位姿门限。 若要手动读: ```cpp cv::Mat K, D; cv::FileStorage fs("configs/camera.yaml", cv::FileStorage::READ); fs["camera_matrix"] >> K; fs["dist_coeffs"] >> D; ``` 之后用 `cv::undistort` / `cv::solvePnP` 做去畸变与位姿解算。 ### 4. 距离验证(改完参数一定要做) ```bash ~/yolo-env/bin/python ~/rm_vision/verify_distance.py 图片.jpg --square 20.0 --distance 647 ``` 用卷尺量出真实距离,与程序算出的距离对比:误差 < 2% 说明标定可用。 ## 提交规则(.gitignore) | 内容 | 是否入库 | 原因 | |---|---|---| | `src/` `inc/` `xmake.lua` `README.md` | ✅ 入库 | 源码与构建脚本 | | `configs/camera.yaml` | ✅ **必须入库** | 相机内参,全队共用 | | `calib_images/` `calib_all/` | ❌ 不入库 | 标定照片,20MB+ 且随时重拍 | | `*.jpg` `*.png` `*.bmp` `*.avi` `*.mp4` | ❌ 不入库 | 图片视频一律不入库 | | `build/` `.xmake/` `Camera/` `*.log` | ❌ 不入库 | 编译产物、SDK 运行时日志 | | `.vscode/compile_commands.json` | ❌ 不入库 | xmake 生成,含本机绝对路径 | 确有少量配图(例如 README 插图)要提交时,用强制添加: ```bash git add -f docs/xxx.png ``` ### ⚠️ 标定图请自己留备份 照片不入库 = 只有本机一份。重装系统前记得把 `calib_images/` 拷到网盘或 U 盘, 否则将来想重新标定(换镜头、调焦后)就没有原始数据了。 标定**结果**在 `configs/camera.yaml` 里,但结果无法替代原始照片。