cv::getPerspectiveTransform要求输入点严格按左上→右上→右下→左下顺序排列,否则导致翻转或拉伸;需用y-x双排序或顺时针验证确保顺序正确。

cv::getPerspectiveTransform 输入点顺序必须严格按左上→右上→右下→左下
透视变换矫正文档的核心是找到四个角点并映射到目标矩形,但 cv::getPerspectiveTransform 对输入点顺序极其敏感——它不识别“哪个是左上”,只按你传入的 std::vector<:point2f> 的索引位置硬编码对应:索引 0 → 目标左上,1 → 右上,2 → 右下,3 → 左下。如果检测出的四边形顶点顺序错乱(比如把右上当成了左上),变换后文档会翻转、镜像或严重拉伸。
实操建议:
- 用
cv::approxPolyDP提取轮廓后,务必对四个顶点做排序:先按 y 坐标粗分上下行,再在每行内按 x 排序;避免依赖轮廓点原始遍历顺序 - 简单验证法:画出四点并标注索引(0/1/2/3),叠加到原图看是否符合“顺时针绕一圈”;不是就重排
- 若用霍夫线检测交点得角点,交点坐标可能含浮点误差,排序前先加
cv::Point2f(round(p.x), round(p.y))防止因精度导致跨行误判
目标尺寸选错会导致文字被压缩或留白过多
调用 cv::warpPerspective 时传入的目标图像尺寸(cv::Size)直接决定输出文档的宽高比和分辨率。常见错误是直接用检测到的四边形最大最小坐标差(max_x - min_x 等)作为目标宽高——这忽略了原始四边形的倾斜角度,算出来的宽高往往偏小,导致文字横向挤压、字迹变粗。
正确做法是基于角点计算**仿射不变的文档长宽估计**:
- 用角点两两距离估算真实长宽:例如左上→右上距离 ≈ 文档宽度,左上→左下距离 ≈ 文档高度;取平均或中位数更鲁棒
- 目标尺寸宽高比应尽量贴近 A4(≈ 1.414)或实际文档类型,避免纯靠几何推算;可设固定宽为 800,高 = round(800 / 1.414)
- 务必开启插值:使用
cv::INTER_LANCZOS4或cv::INTER_CUBIC,别用默认的cv::INTER_LINEAR,否则小字体边缘发虚
cv::findContours + cv::approxPolyDP 容易漏掉文档边缘
直接对灰度图做 cv::findContours 往往找不到完整文档四边形——光照不均、阴影、背景纹理会让边缘断裂,cv::approxPolyDP 就拟合不出四个点。这不是算法问题,而是预处理没到位。
关键预处理步骤:
- 先用
cv::GaussianBlur(核大小 5×5)降噪,再用cv::adaptiveThreshold(blockSize=21, C=10)替代全局阈值,适应局部明暗变化 - 对二值图做闭运算:
cv::morphologyEx(img, img, cv::MORPH_CLOSE, cv::getStructuringElement(cv::MORPH_RECT, {3,3})),连通断开的边缘 -
cv::findContours时用cv::RETR_EXTERNAL+cv::CHAIN_APPROX_SIMPLE,只取最外层轮廓,减少干扰 - 筛选轮廓时,不仅看边数(4),还要加约束:
cv::contourArea(contour) > img.rows * img.cols * 0.2且cv::isContourConvex(contour)必须为 true
OpenCV 4.5+ 中 cv::warpPerspective 的 alpha 通道支持需手动处理
如果你输入图是带透明通道的 PNG(比如扫描件有阴影渐变),cv::warpPerspective 默认不保留 alpha,输出会变黑底。这不是 bug,是 OpenCV 默认把多通道图当作 BGR 处理,alpha 被忽略。
解决方法只有手动拆通道:
- 先用
cv::split把四通道图拆成std::vector<:mat>,单独对 alpha 通道做相同变换(用同一个M矩阵) - 再用
cv::merge合并 BGR 和新 alpha;注意目标图尺寸要一致,否则cv::merge报错Assertion failed (mv[i].size == mv[0].size && mv[i].depth() == mv[0].depth()) - 如果只是矫正后保存,更简单:输出用
cv::IMWRITE_PNG_COMPRESSION保存为 PNG,但前提是输入 Mat 类型为CV_8UC4且 alpha 已参与 warp
真正难的不是写几行代码,而是角点检测的鲁棒性——同一张倾斜发票,在不同光照下,cv::approxPolyDP 可能返回 3、4、5 个点,甚至空向量。这时候与其调参,不如加一层 RANSAC 式投票:多次轻微扰动二值化参数,统计高频出现的四边形,再取其中面积最大者。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











