contribute上次修改:2026-09-29

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;
  • 做粗略位置和尺寸统计;
  • 目标基本不旋转的场景。

它是“最小的轴对齐矩形”,不是所有方向中面积最小的矩形。

3.6-image-001

二、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), (宽, 高), 角度)
3.6-image-003

旧代码常写 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。