You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从OpenCV轮廓数据提取矩形坐标并判断是否正拍

问题解决:提取矩形坐标、判断正面视角及优化边缘检测

1. 提取矩形的x/y坐标

你打印的doc_cnts是OpenCV通过approxPolyDP返回的近似轮廓,格式是形状为(4, 1, 2)的numpy数组——外层是4个点的集合,每个点被嵌套在1维数组里,最后一维是(x, y)坐标。

要提取出直观的(x,y)坐标,只需要用reshape把它转换成(4,2)的格式:

if doc_cnts is not None:
    # 转换坐标格式,得到4个(x,y)点
    rect_points = doc_cnts.reshape(4, 2)
    # 遍历输出每个坐标
    for idx, (x, y) in enumerate(rect_points):
        print(f"第{idx+1}个点: ({x}, {y})")

2. 判断矩形是否为正面视角

正面拍摄的矩形满足几个核心特征:对边长度近似相等、四个角接近90度、旋转角度接近0。可以通过以下方法实现判断:

代码实现

添加一个判断函数,并在主循环中调用:

def is_frontal_rect(points, length_tol=0.1, angle_tol=10):
    """判断矩形是否为正面视角"""
    # 计算两点间距离
    def get_distance(p1, p2):
        return np.linalg.norm(p1 - p2)
    
    # 验证对边长度近似相等
    edge_lengths = [
        get_distance(points[0], points[1]),
        get_distance(points[1], points[2]),
        get_distance(points[2], points[3]),
        get_distance(points[3], points[0])
    ]
    if abs(edge_lengths[0] - edge_lengths[2])/edge_lengths[0] > length_tol:
        return False
    if abs(edge_lengths[1] - edge_lengths[3])/edge_lengths[1] > length_tol:
        return False
    
    # 计算夹角,验证接近90度
    def get_angle(p1, p2, p3):
        v1 = p1 - p2
        v2 = p3 - p2
        cos_val = np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))
        # 避免数值误差导致的超出范围问题
        cos_val = np.clip(cos_val, -1.0, 1.0)
        angle = np.arccos(cos_val) * 180 / np.pi
        return min(angle, 180 - angle)
    
    angles = [
        get_angle(points[0], points[1], points[2]),
        get_angle(points[1], points[2], points[3]),
        get_angle(points[2], points[3], points[0]),
        get_angle(points[3], points[0], points[1])
    ]
    for angle in angles:
        if abs(angle - 90) > angle_tol:
            return False
    
    # 验证旋转角度接近0
    rect = cv2.minAreaRect(points)
    rot_angle = rect[2]
    # 处理角度大于45度的特殊情况
    if rot_angle > 45:
        rot_angle = 90 - rot_angle
    if abs(rot_angle) > angle_tol:
        return False
    
    return True

在主循环中调用该函数:

if doc_cnts is not None:
    rect_points = doc_cnts.reshape(4, 2)
    if is_frontal_rect(rect_points):
        print("当前是正面视角")
    else:
        print("当前不是正面视角")

3. 优化背景边缘干扰

针对地毯纹理带来的大量小边缘,可以通过两种方式过滤:

方式1:添加形态学操作

在Canny边缘检测后,用开闭运算消除小噪点和缺口:

edged = cv2.Canny(blur, 75, 200)
# 形态学闭运算消除小缺口,开运算消除小噪点
kernel = np.ones((3, 3), np.uint8)
edged = cv2.morphologyEx(edged, cv2.MORPH_CLOSE, kernel)
edged = cv2.morphologyEx(edged, cv2.MORPH_OPEN, kernel)

方式2:过滤小面积轮廓

在排序轮廓前,先剔除面积过小的轮廓:

contours, _ = cv2.findContours(edged, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
# 过滤面积小于500的轮廓(可根据实际画面调整阈值)
min_contour_area = 500
contours = [cnt for cnt in contours if cv2.contourArea(cnt) > min_contour_area]
contours = sorted(contours, key=cv2.contourArea, reverse=True)

完整修改后的代码

import cv2
import numpy as np

green = (0, 255, 0)  # 绘制轮廓的颜色

def is_frontal_rect(points, length_tol=0.1, angle_tol=10):
    """判断矩形是否为正面视角"""
    def get_distance(p1, p2):
        return np.linalg.norm(p1 - p2)
    
    edge_lengths = [
        get_distance(points[0], points[1]),
        get_distance(points[1], points[2]),
        get_distance(points[2], points[3]),
        get_distance(points[3], points[0])
    ]
    if abs(edge_lengths[0] - edge_lengths[2])/edge_lengths[0] > length_tol:
        return False
    if abs(edge_lengths[1] - edge_lengths[3])/edge_lengths[1] > length_tol:
        return False
    
    def get_angle(p1, p2, p3):
        v1 = p1 - p2
        v2 = p3 - p2
        cos_val = np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))
        cos_val = np.clip(cos_val, -1.0, 1.0)
        angle = np.arccos(cos_val) * 180 / np.pi
        return min(angle, 180 - angle)
    
    angles = [
        get_angle(points[0], points[1], points[2]),
        get_angle(points[1], points[2], points[3]),
        get_angle(points[2], points[3], points[0]),
        get_angle(points[3], points[0], points[1])
    ]
    for angle in angles:
        if abs(angle - 90) > angle_tol:
            return False
    
    rect = cv2.minAreaRect(points)
    rot_angle = rect[2]
    if rot_angle > 45:
        rot_angle = 90 - rot_angle
    if abs(rot_angle) > angle_tol:
        return False
    
    return True

cap = cv2.VideoCapture(1)
if not cap.isOpened():
    print("无法打开摄像头")
    exit()

while True:
    ret, frame = cap.read()
    if not ret:
        print("无法获取画面(流已结束)。退出...")
        break

    image = frame.copy()
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    blur = cv2.GaussianBlur(gray, (5, 5), 0)
    edged = cv2.Canny(blur, 75, 200)
    
    # 形态学操作优化边缘
    kernel = np.ones((3, 3), np.uint8)
    edged = cv2.morphologyEx(edged, cv2.MORPH_CLOSE, kernel)
    edged = cv2.morphologyEx(edged, cv2.MORPH_OPEN, kernel)

    contours, _ = cv2.findContours(edged, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
    # 过滤小轮廓
    min_contour_area = 500
    contours = [cnt for cnt in contours if cv2.contourArea(cnt) > min_contour_area]
    contours = sorted(contours, key=cv2.contourArea, reverse=True)
    cv2.drawContours(image, contours, -1, green, 3)

    doc_cnts = None
    if len(contours) >= 1:
        for contour in contours:
            peri = cv2.arcLength(contour, True)
            approx = cv2.approxPolyDP(contour, 0.05 * peri, True)
            if len(approx) == 4:
                doc_cnts = approx
                break

    if doc_cnts is not None:
        rect_points = doc_cnts.reshape(4, 2)
        # 打印坐标
        print("矩形坐标点:")
        for idx, (x, y) in enumerate(rect_points):
            print(f"点{idx+1}: ({x}, {y})")
        # 判断是否正面视角
        if is_frontal_rect(rect_points):
            print("状态:正面视角")
        else:
            print("状态:非正面视角")
        print("---")

    cv2.imshow('original', frame)
    cv2.imshow('changed', edged)

    if cv2.waitKey(1) == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()

内容的提问来源于stack exchange,提问作者shawnlg

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 00:12:09