三点三角方案有一个痛点:点完 3 次后,可能停留在仿射阶段,透视校正要碰运气(guided search 能不能搜到额外星位)。四点角方案用 4 个角星位,多 1 次点击,直接拿到 8 个约束 → direct
findHomography,始终输出完整的透视矫正。
1. 选点 & 点击顺序(顺时针)
CLICK 1 CLICK 2
● (3,3) ─────────── ● (3,15)
│ │
│ 棋盘区域 │
│ │
│ ★ 天元 │
│ │
│ │
● (15,3) ────────── ● (15,15)
CLICK 4 CLICK 3
| 点击 | 坐标 | 位置 | 作用 |
|---|---|---|---|
| 🖱️ 1 | (3, 3) | 左上角星位 | 基准锚点 |
| 🖱️ 2 | (3, 15) | 右上角星位 | 水平方向 + 上边界 |
| 🖱️ 3 | (15, 15) | 右下角星位 | 对角线方向 |
| 🖱️ 4 | (15, 3) | 左下角星位 | 垂直方向 + 下边界 |
2. 与三点三角的核心区别
🔺 3-point: 仿射(6-DOF) → guided search → 可能升级单应(8-DOF)
└── 升级失败 → 仅仿射, 无透视校正
🔲 4-point: 4 角直接 → findHomography(4点, 精确解) → 全单应(8-DOF)
└── 无需仿射中转, 始终全透视校正
└── guided search 可选: 补搜其余5点 → 超定RANSAC
| 维度 | 🔺 3-point | 🔲 4-point |
|---|---|---|
| 点击次数 | 3 | 4 (+1) |
| 最小变换 | 仿射 (无透视) | 全单应 (含透视) |
| 透视(梯形)校正 | 需升级, 不可靠 | ✅ 始终 |
| guided search 依赖 | 强依赖 | 不依赖 |
| 数学适定性 | 欠定→仿射→需升级 | ✅ 精确 4 点解 |
| RANSAC 精度 | 4-9 点 (超定) | 4-9 点 (超定) |
3. 为什么 4 点直接优于 3 点中转
3.1 直接求解
4 个点 → 8 个约束 → 恰好确定 8-DOF 单应矩阵:
vector<Point2f> src = {
{3.0f/18, 3.0f/18}, // 左上 (3,3)
{3.0f/18, 15.0f/18}, // 右上 (3,15)
{15.0f/18, 15.0f/18}, // 右下 (15,15)
{15.0f/18, 3.0f/18} // 左下 (15,3)
};
vector<Point2f> dst = {mark1, mark2, mark3, mark4};
Mat H = findHomography(src, dst, 0);
// 4 点精确解: 非迭代, 无 RANSAC 开销
3.2 无中间态
3 点方案先走仿射(6-DOF),再从仿射预测位置搜星位升级到单应(8-DOF)。这个中间态有两个问题:
- 仿射预测没有透视分量 → guided search 的 ROI 可能对不准
- 如果 guided search 找不到足够额外星位 → 永远停在仿射,梯形畸变不矫正
4 点方案不存在这个问题——从一开始就是全单应。
3.3 全面透视矫正
梯形畸变、近大远小、倾斜旋转——一次性全部纠正:
// 直接透视矫正,无中间步骤
Mat H = findHomography(src, dst, 0);
Mat output;
warpPerspective(input, output, H, Size(outSize, outSize));
4. 算法流程
flowchart TD
A["🖱️ 用户点击 4 个角星位<br/>(3,3)→(3,15)→(15,15)→(15,3)"] --> B["cv::findHomography<br/>4 点精确解, 8-DOF"]
B --> C{"可选: 搜索剩余 5 星位?"}
C -->|"是"| D["Guided HoughCircles<br/>在 5 个未标记位置搜"]
C -->|"否"| E["4 点单应, 直接输出"]
D --> F{"找到额外星位?"}
F -->|"是"| G["5-9 点超定 RANSAC<br/>精度提升, outlier rejection"]
F -->|"否"| E
G --> H["warpPerspective → 正方形输出"]
E --> H
5. 实现
class QuadRectifier {
public:
void setCorners(cv::Point2f topLeft, // (3,3) 左上
cv::Point2f topRight, // (3,15) 右上
cv::Point2f bottomRight, // (15,15) 右下
cv::Point2f bottomLeft) // (15,3) 左下
{
corners[0] = topLeft;
corners[1] = topRight;
corners[2] = bottomRight;
corners[3] = bottomLeft;
}
void rectify(const cv::Mat& input, cv::Mat& output,
int outSize = 600, bool refine = true)
{
// 源点: 4 个角星位的归一化棋盘坐标
cv::Point2f src[4] = {
{3.0f/18, 3.0f/18},
{3.0f/18, 15.0f/18},
{15.0f/18, 15.0f/18},
{15.0f/18, 3.0f/18}
};
// 直接求解单应
cv::Mat H = cv::findHomography(
std::vector<cv::Point2f>(src, src + 4),
std::vector<cv::Point2f>(corners, corners + 4),
0 // 4 点精确解, 非迭代
);
if (refine) {
// 可选: 用 H 预测其余 5 个星位 → guided search → 超定 RANSAC
auto extra = guidedSearchRemaining(input, H);
if (!extra.empty()) {
auto allSrc = mergeAll(src, extra);
auto allDst = mergeAll(corners, extra);
H = cv::findHomography(allSrc, allDst,
cv::RANSAC, 3.0);
}
}
cv::warpPerspective(input, output, H,
cv::Size(outSize, outSize));
}
private:
cv::Point2f corners[4];
};
6. 四点 vs 三点 vs 九点最终对比
| 维度 | 🔺 3-point | 🔲 4-point | 📐 9-point |
|---|---|---|---|
| 点击次数 | 3 | 4 (+1) | 9 |
| 数学最小保证 | 仿射 (无透视) | 全单应 (有透视) | 全单应 (超定) |
| 透视校正 | 需升级, 可能失败 | ✅ 始终 | ✅ 超定高精度 |
| 推荐场景 | 快速预览, 接近正面 | 生产默认 | 要求最高精度 |
| 用户体验 | 良好 | 最佳 | 繁琐 |
7. 常见问题
Q: 3 点仿射升级到单应,和 4 点直达单应,结果有区别吗?
有。3 点的仿射中间态会导致 guided search 窗口位置基于仿射(无透视)预算,可能与真实星位有偏移。4 点直接单应的预测位置包含透视信息,guided search 更准。
Q: 如果只点 4 个角,没搜到其余 5 个星位,效果如何?
4 点精确解已包含完整透视变换。5 个额外点只是让 RANSAC 有更多内点可用于 outlier rejection——但 4 个人工标记本身就是精确的,不需要 outlier rejection。
Q: 为什么推荐 4 点作为生产默认?
多 1 次点击(3→4),换来"始终全单应"的确定性。用户不需要理解"升级/兜底"的区别——4 个点 = 完整矫正,简单直接。
8. 总结
各方案的选择指南:
点击成本
↑ 📐 9-point
│ (最高精度, 最慢)
│
│ 🔲 4-point ← 推荐生产默认
│ (全单应, 始终透视矫正)
│
│ 🔺 3-point
│ (快速预览, 可能无透视)
│
│ ⚡ 2-point
│ (已废弃: 共线退化)
└─────────────────────→ 矫正能力
4 点角星位 = 最少点击量 × 全透视矫正保证 = 生产环境最优解。