3.6 外接矩形与透视变换
总目录:1.1 OpenCV介绍与学习路线 | 第三章:边缘、轮廓与几何 | 编号:3.6
← 上一节:3.5 多边形逼近与形状识别 | 下一节:3.7 凸包凸缺陷与Solidity →
3.6 外接矩形与透视变换
找到轮廓后,我们常想把目标框起来,甚至把斜着拍的卡片拉成正视图。这一篇从“框”一路走到“鸟瞰图”。
一、boundingRect:轴对齐外接框
x, y, w, h = cv2.boundingRect(contour)
cv2.rectangle(result, (x, y), (x + w, y + h), (0, 255, 0), 2)
它返回与图像 x/y 轴平行的外接矩形,速度快,适合:
- 获取 ROI;
- 做粗略位置和尺寸统计;
- 目标基本不旋转的场景。
它是“最小的轴对齐矩形”,不是所有方向中面积最小的矩形。

二、minAreaRect:允许旋转的外接框
rect = cv2.minAreaRect(contour)
center, size, angle = rect
box = cv2.boxPoints(rect)
box = box.astype(np.int32)
cv2.drawContours(result, [box], 0, (0, 0, 255), 2)
rect 大致是:
((中心 x, 中心 y), (宽, 高), 角度)

旧代码常写 np.int0(box),这个别名已不推荐使用;明确转换为 np.int32 更清楚。
三、不要死记 minAreaRect 的原始角度
原笔记收集了不同版本中 angle 范围的多种说法。真正麻烦的是:矩形旋转时,返回的 width、height 可能互换,角度也会随所选边发生跳变。
如果项目需要稳定的“长轴方向”,更可靠的做法是从 boxPoints 的边向量计算:
box = cv2.boxPoints(rect).astype(np.float32)
edges = np.roll(box, -1, axis=0) - box
lengths = np.linalg.norm(edges, axis=1)
long_edge = edges[np.argmax(lengths)]
angle_deg = np.degrees(np.arctan2(long_edge[1], long_edge[0]))
即使这样,方向仍可能相差 180°。若任务需要有正反方向,还要用目标纹理或上一帧方向消除歧义。
四、四点排序
透视变换需要知道哪个点对应左上、右上、右下、左下。不要假设 boxPoints 永远按你想要的顺序返回。
一种明确的排序方法:先按 y 分成上面两个和下面两个,再按 x 排:
def order_quad(points):
points = np.asarray(points, dtype=np.float32).reshape(4, 2)
by_y = points[np.argsort(points[:, 1])]
top = by_y[:2][np.argsort(by_y[:2, 0])]
bottom = by_y[2:][np.argsort(by_y[2:, 0])]
tl, tr = top
bl, br = bottom
return np.array([tl, tr, br, bl], dtype=np.float32)
强透视或菱形姿态下,仅靠 y 分组也可能不稳定。工程中应结合轮廓方向、凸包顺序或更鲁棒的四边形排序方法验证。
五、透视变换:把斜着拍的平面拉正
已知原图四点和目标四点:
src = order_quad(approx)
width = 500
height = 300
dst = np.array([
[0, 0],
[width - 1, 0],
[width - 1, height - 1],
[0, height - 1],
], dtype=np.float32)
matrix = cv2.getPerspectiveTransform(src, dst)
warped = cv2.warpPerspective(image, matrix, (width, height))
原图上的斜四边形
↓ 3×3 透视变换矩阵
目标图上的正矩形
它适合平面物体,例如卡片、试卷、屏幕和电赛靶面。不是平面的物体会产生拉伸,不能靠一张单应矩阵完全校正。
六、对角线交点是不是“中心”
一般四边形的对角线交点可以通过两条参数直线求交:
def line_intersection(p1, p2, p3, p4):
p1, p2, p3, p4 = map(lambda p: np.asarray(p, dtype=float),
(p1, p2, p3, p4))
matrix = np.column_stack((p2 - p1, p3 - p4))
vector = p3 - p1
try:
t, _ = np.linalg.solve(matrix, vector)
except np.linalg.LinAlgError:
return None
return p1 + t * (p2 - p1)
但它只是投影几何中的对角线交点,不一定等于:
- 轮廓面积质心;
- 真实物理平面的欧氏中心;
- 外接矩形中心。
如果目标是平面矩形,先做透视校正,再在校正图上取中心,含义通常更明确。
七、参考
← 上一节:3.5 多边形逼近与形状识别 | 下一节:3.7 凸包凸缺陷与Solidity →
留言区
不收集任何个人信息;昵称只绑定浏览器里的随机 Cookie。