引言

学校在四月份准备了一场校赛让我们提前适应电赛
题目为小车
在这里插入图片描述

大致规则:

从B跑到A,不能踩线,视觉识别数字再跑对应的道,加个弯道A到A
限高限宽限长202015

难点:

从B开始到跑道上有一段距离如何进入并且不踩到线,双线如何转大弯,定点停车,并且稳定出错率少。

解决方案

一.数字识别:
1.数字识别本质是多物体检测,我们可以使用目标检测算法yolov11模型来实现,我们的硬件中有对神经网路算法的硬件加速,因此使用时不会减缓摄像头其他算法以及对下位机的操控。
数字识别相对的视觉我们有成熟的解决方案
神经网络秒杀

二.双道识别巡线与大弯

1.双道巡线

,第一时间我们团队想到智能驾驶上存在双线行驶,通过过滤视野中不需要的部分,再对图像帧中双线进行双滑动窗口拟合曲线,运用曲率来控制驱动程序跑动,但是由于小车限制高度,摄像头高位视野中双线视野过窄,容易丢失单线,造成算法误差大,以及大弯处距离短,速度快,曲率瞬时变化过大,下位pid难度巨大以及参数难以线性拟合等问题我们暂定放弃该方案。

第一种解决方案设计是参考网上的开源程序

链接: https://github.com/kemfic/Curved-Lane-Lines/blob/master/P4.ipynb
在这里插入图片描述

2.单线算法

双线都检测,在转弯处大概率会丢失内线,因此我们团队,运用斜沿算法,锁定外道,来进行弯道以及入道以后的巡线,经过测试,该算法有较为良好的线性变化,适合下位机操作,但是该算法计算量较大,帧率仅仅2~~3帧,经过优化也只有5~10帧,因此存在实时性滞后的风险,因此我们将方案放在备选方案中。
在这里插入图片描述在这里插入图片描述
核心思路二值化后寻找左下角点向上爬线
想到这个方法是看到别人智能车代码里有八邻域方法 感觉效果嘎嘎好
但是我们追求稳定没有太多的时间调所以没有深入修改,从而放弃了这个方法

#------------------------------------
'''
斜沿线法
'''
def find_black_points(binary):
    height, width = binary.shape
    black_points = []
    x_time= time()
    # 从左下角开始找第一个x坐标小于320的黑点
    for y in range(height - 1, -1, -1):
        for x in range(100):
            if binary[y, x] == 0:
                black_points.append((x, y))
                break
        if black_points:
            break
    y_time = time()
    print(y_time-x_time)        
    if not black_points:
        print("没有找到黑点")
        return np.array([])

    # 继续找其他黑点
    while True:
        x, y = black_points[-1]
        new_x = x
        new_y = y - 1
        min_x = width
        found = False

        # 扩大搜索范围到左右各5个像素
        for offset_x in range(-1, 6):
            check_x = new_x + offset_x
            if 0 <= check_x < width and 0 <= new_y < height:
                if binary[new_y, check_x] == 0:
                    found = True
                    min_x = min(min_x, check_x)

        if found:
            black_points.append((min_x, new_y))
        else:
            break
    return np.array(black_points)

3.侧摄像头巡外线

由于我们团队在开始时调整pid过程中发现单线巡线算法耗时低且控制简单,出错率低,在对前置摄像头单线方案不满意时我们试了试侧摄像头巡线,不需要多复杂的算法,简单的对黑线中点的拟合,对数据处理以及小车控制的稳定性,都有极大提升,大弯甚至都不需要修改任何参数,轻轻松松通过。

**这个思路就最简单仅仅取黑线的某个x轴比如500行时的值看他与中间这条线的偏移量 **
还有强调这个时我们装侧面的摄像头,他的x和y对于前面的摄像头是反的,也就是说你要用到前置摄像头就好把他的y和x改过来

#-------------------------------------------------------------------------------------------------
#测摄像头寻线
def black_line_y(cnm):
    global mode
    global rechange
    global end_flag
    global double_kill_flag
    huidu_img = cv2.cvtColor(cnm, cv2.COLOR_RGB2GRAY)
    #threshold_value = 30
    #_, black_regions = cv2.threshold(huidu_img, threshold_value, 255, cv2.THRESH_BINARY)
    #retval, dst = cv2.threshold(huidu_img, 0, 255, cv2.THRESH_OTSU)
    #二值化
    retval, dst = cv2.threshold(huidu_img, 170, 255, cv2.THRESH_BINARY)
    #print("数值")
    #print(retval)
    # 膨胀,白区域变大
    dst = cv2.dilate(dst, None, iterations=2)
    # # 腐蚀,白区域变小
    # dst = cv2.erode(dst, None, iterations=6)
    # 显示结果
    # cv2.imshow('Black Regions', dst)
    # 对调x与y,这里取第350列的数据
    color = dst[:, 500]
    # 找到黑色的像素点个数
    black_count = np.sum(color == 0)
    # 找到黑色的像素点索引
    black_index = np.where(color == 0)
    # 防止black_count=0的报错
    if black_count == 0:
        black_count = 1
    if black_index[0].size == 0:
        # 你可以设置默认返回的值
        black_index = (np.array([240]), np.array([240]))  # 假设图像高度为480,默认返回图像垂直中心位置
    # 找到黑色像素的中心点位置
    center = (black_index[0][black_count - 1] + black_index[0][0]) / 2
    # 计算出center与标准中心点的偏移量,因为图像高度是480,因此标准中心是240
    direction = (center - 240)+280
    #param = str(direction)+'\r\n'
    print(int(direction))
    Serial_port_sending_xy(int(direction), 0, 0)
    # ser.write(param.encode('utf-8'))
    # print(center)
    color = dst[:,639]
    black_head = np.sum(color == 0)
    print("black_head",black_head)
    if black_head == 0:
        double_kill_flag+=1
    if double_kill_flag == 2:
        mode = 0
        end_flag= 1
        rechange = 0
        #Serial_port_sending_xy(320,0,4)
    return dst  
#-------------------------------------------------------------------------

三.起步,进入双道
我们团队在起步阶段发现,开环进入双线道路极有可能压线,以及侧摄像头无法得到正确的图像(小车偏移过多)的问题,因此需要对进入赛道时进行一个身位调整。

1.视野投影+最长白列法,调整小车入道时的身位。

#-------------------------------------------------------------------------
#视野变化从平视变成垂直看
def perspective_warp(img, 
                     dst_size=(640,480),
                     src=np.float32([(0.0,0),(1,0),(0.0,0.5),(1,0.5)]),
                     dst=np.float32([(0,0), (1, 0), (0,1), (1,1)])):
    img_size = np.float32([(img.shape[1],img.shape[0])])
    src = src* img_size
    # For destination points, I'm arbitrarily choosing some points to be
    # a nice fit for displaying our warped result 
    # again, not exact, but close enough for our purposes
    dst = dst * np.float32(dst_size)
    # Given src and dst points, calculate the perspective transform matrix
    M = cv2.getPerspectiveTransform(src, dst)
    # Warp the image using OpenCV warpPerspective()
    warped = cv2.warpPerspective(img, M, dst_size)
    return warped
#-------------------------------------------------------------------------
#-------------------------------------------------------------------------
#最长白列算法
def longest_white_column_from_middle(image):
    # 1. 图像预处理
    # 将图像转换为灰度图
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    # 进行二值化处理,将像素值大于 170 的设为 255,小于等于 170 的设为 0
    retval, binary = cv2.threshold(gray, 170, 255, cv2.THRESH_BINARY)
    # 膨胀,白区域变大
    #cv2.imshow('nb',binary)
    #binary = perspective_warp(binary)
    binary = cv2.dilate(binary, None, iterations=2)
    binary = cv2.erode(binary, None, iterations=6)
    height, width = binary.shape
    middle_col = width // 2

    # 3. 从中间向两边统计白色像素数量
    left_counts = []
    right_counts = []

    # 向左统计
    for col in range(middle_col, -1, -1):
        white_count = np.count_nonzero(binary[:, col] == 255)
        left_counts.append(white_count)

    # 向右统计
    for col in range(middle_col, width):
        white_count = np.count_nonzero(binary[:, col] == 255)
        right_counts.append(white_count)

    # 4. 找出左右两侧的最长白列
    left_longest_index = np.argmax(left_counts)
    right_longest_index = np.argmax(right_counts)

    left_longest_col = middle_col - left_longest_index
    right_longest_col = middle_col + right_longest_index

    left_longest_count = left_counts[left_longest_index]
    right_longest_count = right_counts[right_longest_index]

    return binary,left_longest_col, left_longest_count, right_longest_col, right_longest_count
#------------------------------------------------------------------------------

四.单线巡线

1.前置摄像头过滤二值化后拟合黑线(和前面的那个侧摄像头大差不差)

#--------------------------------------------------------------------------------------------------------------------
#直线单线寻线
def black_line(frame):
    global rechange
    global shibie_flag
    huidu_img = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
    threshold_value = 30
    _, black_regions = cv2.threshold(huidu_img, threshold_value, 255, cv2.THRESH_BINARY)
    retval, dst = cv2.threshold(huidu_img, 0, 255, cv2.THRESH_OTSU)
    #二值化其实只用了下面这个
    retval, dst = cv2.threshold(huidu_img, 170,255 , cv2.THRESH_BINARY)
    
    # 膨胀,白区域变大
    dst = cv2.dilate(dst, None, iterations=2)
    # # 腐蚀,白区域变小
    # dst = cv2.erode(dst, None, iterations=6)
    # 显示结果
    #cv2.imshow('Black Regions', dst)
    color = dst[479]
    # 找到白色的像素点个数,如寻黑色,则改为0
    white_count = np.sum(color == 0)
    # 找到白色的像素点索引,如寻黑色,则改为0
    white_index = np.where(color == 0)
    # 防止white_count=0的报错
    if white_count == 0:
        shibie_flag =3

    if white_count != 0 : 
        if white_index[0].size == 0:
        # 你可以设置默认返回的值
            white_index = (np.array([320]), np.array([320]))  # 默认返回 -1
        # 找到黑色像素的中心点位置
        # 计算方法应该是边缘检测,计算黑色边缘的位置和/2,即是白色的中央位置。
        center = (white_index[0][white_count - 1] + white_index[0][0]) / 2
        # 计算出center与标准中心点的偏移量,因为图像大小是640,因此标准中心是320,因此320不能改。
        direction = center - 320
        param = str(direction)+'\r\n'
        Serial_port_sending_xy(int(center),0,1 )
        #ser.write(param.encode('utf-8'))
        print(center)
#------------------------------------------------------------------------------------------------

五.出道停车
由于单下位机出道停车存在一定的偏移,因此需要通过视觉来简单的纠正。
同四的代码

总结

还得靠队友的超强PID

在这里插入图片描述

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐