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

如何在OpenCV Python中对巡线轮廓进行水平分割?

解决OpenCV黑线巡线机器人急弯时质心偏离问题(水平分割轮廓方案)

当前基于OpenCV的黑线巡线逻辑通过识别黑线最大轮廓并计算质心,对比图像中心控制转向,但遇到黑线急弯时,整体轮廓的质心会偏离实际黑线区域。以下是通过水平分割图像为多个小区域,分别提取每个区域内的黑线轮廓并计算质心的解决方案,无需拉近相机。

核心思路

  • 将捕获的图像按高度平均分割为多个水平条带(比如3个,可根据实际需求调整数量)
  • 对每个水平条带单独处理:提取黑线掩码、寻找轮廓、计算该区域内的黑线质心
  • 优先参考靠近机器人的下方条带质心(更贴近当前需要转向的方向),或综合多个条带的偏移量来调整机器人转向

修改后的代码

import numpy as np
import cv2
import math

# HSV格式的颜色阈值(黑线)
lb = np.array([0,32,0])
ub = np.array([179, 255, 93])

# 忽略此函数(用于巡线的其他部分)
def percentage_calculator(boundaries, colour, image):
    for(lower, upper) in boundaries:
        lower = np.array(lower)
        upper = np.array(upper)
        mask = cv2.inRange(image, lower, upper)
        output = cv2.bitwise_and(image, image, mask = mask)
        tot_pixel = image.size
        pixel = np.count_nonzero(output)
        percentage = round(pixel * 100 / tot_pixel, 2)
        print(colour + " pixels: " + str(pixel))
        print("Total pixels: " + str(tot_pixel))
        print("Percentage of " + colour + " pixels: " + str(percentage) + "%")

green_boundaries = [([75, 52, 60], [106, 255, 255])]

cap = cv2.VideoCapture(1)

# 图像尺寸参数
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
Xmid = frame_width // 2
Ymid = frame_height // 2

# 水平分割的区域数量,可根据实际调整
split_count = 3
strip_height = frame_height // split_count

while True:
    _, img = cap.read()
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

    # 获取黑线掩码
    mask1 = cv2.inRange(hsv, lb, ub)
    black_result = cv2.bitwise_and(hsv, hsv, mask=mask1)
    
    # 存储每个分割区域的质心
    centroids = []
    
    # 遍历每个水平分割区域
    for i in range(split_count):
        # 计算当前条带的上下边界
        y_start = i * strip_height
        y_end = (i + 1) * strip_height if i != split_count-1 else frame_height
        
        # 提取当前条带的掩码
        strip_mask = mask1[y_start:y_end, :]
        
        # 在当前条带中寻找轮廓
        contours, _ = cv2.findContours(strip_mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
        if not contours:
            continue
        
        # 找到当前条带中最大的黑线轮廓
        max_contour = max(contours, key=cv2.contourArea)
        # 简化轮廓
        approx_contour = cv2.approxPolyDP(max_contour, 0.05 * cv2.arcLength(max_contour, True), True)
        
        # 计算质心(注意坐标要转换回原图坐标系)
        M = cv2.moments(approx_contour)
        if M["m00"] == 0:
            continue
        cX = int(M["m10"] / M["m00"])
        # 质心的y坐标需要加上条带的起始y值
        cY = int(M["m01"] / M["m00"]) + y_start
        
        centroids.append((cX, cY))
        # 在原图上标记当前条带的质心和分割线
        cv2.circle(img, (cX, cY), 5, (0, 0, 255) if i == split_count-1 else (0, 255, 0), -1)
        cv2.line(img, (0, y_start), (frame_width, y_start), (255, 255, 255), 1)
    
    # 图像中心标记
    cv2.circle(img, (Xmid, Ymid), 5, (255, 255, 0), -1)
    
    # 如果有质心,优先使用最下方区域的质心计算偏移(更贴近机器人当前位置)
    if centroids:
        # 取最后一个(最下方)的质心
        target_cX, target_cY = centroids[-1]
        cv2.line(img, (target_cX, target_cY), (Xmid, Ymid), (255, 0, 0), 2)
        # 计算偏移距离
        distance = math.sqrt((Xmid - target_cX) ** 2 + (Ymid - target_cY) ** 2)
        # 这里可以加入机器人转向控制逻辑,比如根据target_cX与Xmid的差值调整左右轮速度
    
    cv2.imshow('black', black_result)
    cv2.imshow('img', img)

    k = cv2.waitKey(30) & 0xff
    if k == 27:
        break

cap.release()
cv2.destroyAllWindows()

代码说明

  • split_count:控制水平分割的区域数量,建议从3开始测试,根据实际巡线效果调整
  • 每个水平条带单独处理轮廓,避免急弯时整体轮廓的质心偏离
  • 优先使用最下方条带的质心作为转向参考,因为该区域更接近机器人的当前位置,能更准确反映即时转向需求
  • 可扩展逻辑:如果多个条带都有质心,可通过加权平均(比如下方条带权重更高)计算综合偏移量,提升稳定性

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 14:03:17