Add camera calibration toolchain and armor pose solver

- capture_calib: capture calibration images (MindVision / USB) with a live
  quality gate (Laplacian sharpness, 3x3 coverage, board auto-detect)
- calibrate: batch intrinsics from calib_images/, writes configs/camera.yaml
- camera_calib: CameraCalib loader (intrinsics + distortion, cached
  undistort remap) and solvePose() via IPPE with a reprojection gate
- configs/camera.yaml: MV-SUA131GC intrinsics, RMS 0.483 px over 31 images,
  ruler-verified distance error < 2%
- xmake: three targets sharing an rm_deps rule, C++17
- gitignore: keep calibration photos/videos and generated
  compile_commands.json out of the repo
This commit is contained in:
2026-09-15 23:33:01 +08:00
parent 14d7f9e571
commit ca6891eef2
12 changed files with 1183 additions and 110 deletions
+131
View File
@@ -0,0 +1,131 @@
# 27RM
RoboMaster 视觉工程(MindVision 相机 + OpenCV)。
## 构建
```bash
xmake # 产出两个程序(见下)
xmake run 27RM # 跑主程序
```
## 程序清单
| 程序 | 用途 |
|---|---|
| `27RM` | 自瞄主程序 |
| `capture_calib` | **相机标定图采集工具** |
| `calibrate` | **标定计算工具**(照片 → 内参/畸变 → `configs/camera.yaml`) |
## 配置
```
configs/
└── camera.yaml # 相机内参 + 畸变 + 标定元信息(由 calibrate 生成,需入库)
```
代码里这样用(`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<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 可用于过滤坏帧
}
```
> 程序必须在工程根目录运行(`configs/` 是相对路径),或自行传绝对路径。
## 相机标定流程
> **本工程标定板规格**: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. 使用内参
```cpp
cv::Mat K, D;
cv::FileStorage fs("camera_calib.yml", 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% 说明标定可用。