如何从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
相关产品推荐
相关产品推荐

