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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 12:45:42