OpenCV中HoughLinesP红线检测冗余问题:如何合并为单线
问题与解决方案
问题背景
现有基于OpenCV的代码可检测视频中的红色胶带线,但单条红线会被检测出约40条冗余线段,需将其合并为单条线,以便计算线段交点与红色正方形角点。原代码如下:
import cv2 import numpy as np def detect_grid(frame): hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) lower_red = np.array([0, 100, 100]) upper_red = np.array([10, 255, 255]) mask1 = cv2.inRange(hsv, lower_red, upper_red) lower_red = np.array([140, 20, 20]) upper_red = np.array([180, 255, 255]) mask2 = cv2.inRange(hsv, lower_red, upper_red) red_mask = mask1 + mask2 kernel = np.ones((5, 5), np.uint8) red_mask = cv2.morphologyEx(red_mask, cv2.MORPH_OPEN, kernel) red_mask = cv2.morphologyEx(red_mask, cv2.MORPH_CLOSE, kernel) lines = cv2.HoughLinesP(red_mask, 1, np.pi / 180, threshold=50, minLineLength=50, maxLineGap=5) if lines is not None: for line in lines: x1, y1, x2, y2 = line[0] cv2.line(frame, (x1, y1), (x2, y2), (0, 255, 0), 5) cap = cv2.VideoCapture('redline_rectified.mp4') while cap.isOpened(): ret, frame = cap.read() if not ret: break detect_grid(frame) cv2.imshow('Grid Detection', frame) if cv2.waitKey(1) & 0xFF == ord(' '): break cap.release() cv2.destroyAllWindows()
解决思路与代码实现
核心是对HoughLinesP输出的冗余线段进行聚类合并:先将斜率、截距相近的线段归为一组,再对每组线段拟合出一条最优直线。
关键步骤
- 计算每条线段的极坐标参数(θ, ρ):避免垂直直线斜率无穷大的问题,用角度和到原点的距离描述直线
- 聚类相似线段:设定角度、距离阈值,将参数相近的线段归为同一类
- 直线拟合:对每类中的所有线段端点,用最小二乘法拟合出一条覆盖图像范围的直线
修改后的完整代码
import cv2 import numpy as np def calculate_line_params(x1, y1, x2, y2): # 计算线段的极坐标参数θ(角度)和ρ(到原点的距离) dx = x2 - x1 dy = y2 - y1 theta = np.arctan2(dy, dx) rho = (x1 * dy - y1 * dx) / np.sqrt(dx**2 + dy**2) if dx**2 + dy**2 !=0 else 0 return theta, rho def cluster_lines(lines, angle_threshold=0.1, rho_threshold=20): if lines is None: return [] clusters = [] for line in lines: x1, y1, x2, y2 = line[0] theta, rho = calculate_line_params(x1, y1, x2, y2) added = False # 遍历现有聚类,判断是否属于同一类 for cluster in clusters: avg_theta, avg_rho, points = cluster # 处理角度周期性,比如θ和θ+π是同一条线 angle_diff = min(abs(theta - avg_theta), abs(theta - avg_theta + np.pi), abs(theta - avg_theta - np.pi)) rho_diff = abs(rho - avg_rho) if angle_diff < angle_threshold and rho_diff < rho_threshold: points.append((x1, y1)) points.append((x2, y2)) # 更新聚类的平均参数 cluster[0] = np.mean([avg_theta, theta]) cluster[1] = np.mean([avg_rho, rho]) added = True break if not added: clusters.append([theta, rho, [(x1, y1), (x2, y2)]]) return clusters def fit_line_from_points(points, frame_shape): points = np.array(points, dtype=np.float32) # 用OpenCV拟合直线 [vx, vy, x0, y0] = cv2.fitLine(points, cv2.DIST_L2, 0, 0.01, 0.01) rows, cols = frame_shape[:2] # 计算直线在图像边界的两个端点 lefty = int((-x0 * vy / vx) + y0) righty = int(((cols - x0) * vy / vx) + y0) return (0, lefty), (cols - 1, righty) def detect_grid(frame): hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) lower_red = np.array([0, 100, 100]) upper_red = np.array([10, 255, 255]) mask1 = cv2.inRange(hsv, lower_red, upper_red) lower_red = np.array([140, 20, 20]) upper_red = np.array([180, 255, 255]) mask2 = cv2.inRange(hsv, lower_red, upper_red) red_mask = mask1 + mask2 kernel = np.ones((5, 5), np.uint8) red_mask = cv2.morphologyEx(red_mask, cv2.MORPH_OPEN, kernel) red_mask = cv2.morphologyEx(red_mask, cv2.MORPH_CLOSE, kernel) lines = cv2.HoughLinesP(red_mask, 1, np.pi / 180, threshold=50, minLineLength=50, maxLineGap=5) if lines is not None: line_clusters = cluster_lines(lines) frame_shape = frame.shape for cluster in line_clusters: _, _, points = cluster pt1, pt2 = fit_line_from_points(points, frame_shape) cv2.line(frame, pt1, pt2, (0, 255, 0), 5) cap = cv2.VideoCapture('redline_rectified.mp4') while cap.isOpened(): ret, frame = cap.read() if not ret: break detect_grid(frame) cv2.imshow('Grid Detection', frame) if cv2.waitKey(1) & 0xFF == ord(' '): break cap.release() cv2.destroyAllWindows()
参数调整提示
angle_threshold:角度阈值(弧度),建议范围0.05~0.2,值越小对角度相似度要求越高rho_threshold:距离阈值(像素),建议范围10~30,值越小对直线位置相似度要求越高
内容的提问来源于stack exchange,提问作者Sjaantje Markov
相关产品推荐
相关产品推荐

