- pose_test: verify the geometry chain (4 corners -> distance/yaw/pitch)
before any detector exists; modes: camera + mouse clicks, --image,
--corners (headless), --selftest (synthetic round-trip, no camera needed)
- solvePose: undistort corners first, then solvePnPGeneric(IPPE) and pick
the best candidate; reject solutions behind the camera (z<=0) or with
reprojection error > 8px
- fixes IPPE + distortion + in-plane rotation returning garbage
- 4 coplanar points: cyclic/mirrored orderings give the same quad and
the same pose (harmless); crossing orderings blow up reprojection
and are rejected
- camera_set: implement setAeState/setExposureTime/setAnalogGain(+-Times)/
setWbMode/setFrameSpeed; configs/camera_runtime.yaml drives them
- selftest passes all 6 synthetic cases; verified on 6 real photos
(chessboard outer rect 180x120mm): distance 0.44-0.69m, reproj <2.5px
8.6 KiB
27RM
RoboMaster 视觉工程(MindVision 相机 + OpenCV)。
构建
xmake # 产出四个程序(见下)
xmake run 27RM # 跑主程序
VSCode + clangd
compile_commands.json 含本机绝对路径,不入库,克隆后自己生成一次:
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,含合成自检) |
配置
configs/
├── camera.yaml # 相机内参 + 畸变 + 标定元信息(由 calibrate 生成,需入库)
└── camera_runtime.yaml # 运行时参数:曝光/增益/白平衡/帧率(手工维护,需入库)
代码里这样用(inc/camera_calib.hpp):
#include "camera_calib.hpp"
CameraCalib calib;
if (!calib.load("configs/camera.yaml")) return -1; // 启动时加载一次
cv::Mat undist;
calib.undistort(frame, undist); // 每帧去畸变
// 装甲板四角点(左下、左上、右上、右下)→ 位姿
std::array<cv::Point2f, 4> 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 之后调用):
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 解算」: 拿真的装甲板(或打印 135mm 矩形)摆在相机前,鼠标点 4 个角, 输出的距离/yaw/pitch 与卷尺、量角器核对。之后接检测器出问题必是检测的锅。
./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,与卷尺真值一致
两个踩过的坑(写检测器时别重蹈)
- IPPE + 畸变 + 板内旋转 = 垃圾解(OpenCV 4.x/5.x 都中招)。
解法:角点先去畸变再喂 IPPE,重投影回原图算(
solvePose已内置)。 - 4 个共面点的顺序错误查不出来:循环/镜像顺序 = 同一个四边形 =
同一个解(无害);交叉乱序会被重投影门限拦下。真正需要 roll 门限的是
「检测器给出假四边形」场景,等写检测器时照搬 jlu 的
roll_thres 15°。
相机标定流程
本工程标定板规格:10 × 7 内角点、方格 20.0 mm(卡尺实测值,不是打印标称值) 相机:MV-SUA131GC(1/3" CMOS,3.75μm 像元)+ MV-LD-M12-2.8MM-C 镜头,输出 1280×1024 ⚠️ M12 镜头调焦会改变有效焦距,标定和使用时必须保持对焦环位置一致
1. 采集标定图
# 用 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. 计算内参
./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(见上方「配置」一节),它已经处理了配置路径、去畸变缓存和位姿门限。
若要手动读:
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. 距离验证(改完参数一定要做)
~/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 插图)要提交时,用强制添加:
git add -f docs/xxx.png
⚠️ 标定图请自己留备份
照片不入库 = 只有本机一份。重装系统前记得把 calib_images/ 拷到网盘或 U 盘,
否则将来想重新标定(换镜头、调焦后)就没有原始数据了。
标定结果在 configs/camera.yaml 里,但结果无法替代原始照片。