竞赛 车道线检测(自动驾驶 机器视觉)

0 前言

无人驾驶技术是机器学习为主的一门前沿领域,在无人驾驶领域中机器学习的各种算法随处可见,今天学长给大家介绍无人驾驶技术中的车道线检测。

1 车道线检测

在无人驾驶领域每一个任务都是相当复杂,看上去无从下手。那么面对这样极其复杂问题,我们解决问题方式从先尝试简化问题,然后由简入难一步一步尝试来一个一个地解决问题。车道线检测在无人驾驶中应该算是比较简单的任务,依赖计算机视觉一些相关技术,通过读取
camera 传入的图像数据进行分析,识别出车道线位置,我想这个对于 lidar
可能是无能为力。所以今天我们就从最简单任务说起,看看有哪些技术可以帮助我们检出车道线。

我们先把问题简化,所谓简化问题就是用一些条件限制来缩小车道线检测的问题。我们先看数据,也就是输入算法是车辆行驶的图像,输出车道线位置。

更多时候我们如何处理一件比较困难任务,可能有时候我们拿到任务时还没有任何思路,不要着急也不用想太多,我们先开始一步一步地做,从最简单的开始做起,随着做就会有思路,同样一些问题也会暴露出来。我们先找一段视频,这段视频是我从网上一个关于车道线检测项目中拿到的,也参考他的思路来做这件事。好现在就开始做这件事,那么最简单的事就是先读取视频,然后将其显示在屏幕以便于调试。

2 目标

检测图像中车道线位置,将车道线信息提供路径规划。

3 检测思路

  • 图像灰度处理
  • 图像高斯平滑处理
  • canny 边缘检测
  • 区域 Mask
  • 霍夫变换
  • 绘制车道线

4 代码实现

4.1 视频图像加载

    import cv2
​    import numpy as np
​    import sys
​    

    import pygame
    from pygame.locals import *
    
    class Display(object):
    
        def __init__(self,Width,Height):
            pygame.init()
            pygame.display.set_caption('Drive Video')
            self.screen = pygame.display.set_mode((Width,Height),0,32)
        def paint(self,draw):
            self.screen.fill([0,0,0])
    
            draw = cv2.transpose(draw)
            draw = pygame.surfarray.make_surface(draw)
            self.screen.blit(draw,(0,0))
            pygame.display.update()


​    
​    
​    if __name__ == "__main__":
​        solid_white_right_video_path = "test_videos/丹成学长车道线检测.mp4"
​        cap = cv2.VideoCapture(solid_white_right_video_path)
​        Width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
​        Height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
​    

        display = Display(Width,Height)
    
        while True:
            ret, draw = cap.read()
            draw = cv2.cvtColor(draw,cv2.COLOR_BGR2RGB)
            if ret == False:
                break
            display.paint(draw)
            for event in pygame.event.get():
                    if event.type == QUIT:
                        sys.exit()



上面代码学长就不多说了,默认大家对 python 是有所了解,关于如何使用 opencv 读取图片网上代码示例也很多,大家一看就懂。这里因为我用的是 mac
有时候显示视频图像可能会有些问题,所以我们用 pygame 来显示 opencv 读取图像。这个大家根据自己实际情况而定吧。值得说一句的是 opencv
读取图像是 BGR 格式,要想在 pygame 中正确显示图像就需要将 BGR 转换为 RGB 格式。

4.2 车道线区域

现在这个区域是我们根据观测图像绘制出来,

在这里插入图片描述

 def color_select(img,red_threshold=200,green_threshold=200,blue_threshold=200):
        ysize,xsize = img.shape[:2]
    

        color_select = np.copy(img)
    
        rgb_threshold = [red_threshold, green_threshold, blue_threshold]
    
        thresholds = (img[:,:,0] < rgb_threshold[0]) \
                | (img[:,:,1] < rgb_threshold[1]) \
                | (img[:,:,2] < rgb_threshold[2])
        color_select[thresholds] = [0,0,0]
    
        return color_select


效果如下:
在这里插入图片描述

4.3 区域

我们要检测车道线位置相对比较固定,通常出现车的前方,所以我们通过绘制,也就是仅检测我们关心区域。通过创建 mask 来过滤掉那些不关心的区域保留关心区域。

4.4 canny 边缘检测

有关边缘检测也是计算机视觉。首先利用梯度变化来检测图像中的边,如何识别图像的梯度变化呢,答案是卷积核。卷积核是就是不连续的像素上找到梯度变化较大位置。我们知道
sobal 核可以很好检测边缘,那么 canny 就是 sobal 核检测上进行优化。

# 示例代码,作者丹成学长:Q746876041def canny_edge_detect(img):
​        gray = cv2.cvtColor(img,cv2.COLOR_RGB2GRAY)
​        kernel_size = 5
​        blur_gray = cv2.GaussianBlur(gray,(kernel_size, kernel_size),0)
​    

        low_threshold = 180
        high_threshold = 240
        edges = cv2.Canny(blur_gray, low_threshold, high_threshold)
    
        return edges



在这里插入图片描述

4.5 霍夫变换(Hough transform)

霍夫变换是将 x 和 y 坐标系中的线映射表示在霍夫空间的点(m,b)。所以霍夫变换实际上一种由繁到简(类似降维)的操作。当使用 canny
进行边缘检测后图像可以交给霍夫变换进行简单图形(线、圆)等的识别。这里用霍夫变换在 canny 边缘检测结果中寻找直线。

    

        ignore_mask_color = 255 
        # 获取图片尺寸
        imshape = img.shape
        # 定义 mask 顶点
        vertices = np.array([[(0,imshape[0]),(450, 290), (490, 290), (imshape[1],imshape[0])]], dtype=np.int32)
        # 使用 fillpoly 来绘制 mask
        cv2.fillPoly(mask, vertices, ignore_mask_color)
        masked_edges = cv2.bitwise_and(edges, mask)
        # 定义Hough 变换的参数
        rho = 1 
        theta = np.pi/180
        threshold = 2
    
        min_line_length = 4 # 组成一条线的最小像素数
        max_line_gap = 5    # 可连接线段之间的最大像素间距
        # 创建一个用于绘制车道线的图片
        line_image = np.copy(img)*0 
    
        # 对于 canny 边缘检测结果应用 Hough 变换
        # 输出“线”是一个数组,其中包含检测到的线段的端点
        lines = cv2.HoughLinesP(masked_edges, rho, theta, threshold, np.array([]),
                                    min_line_length, max_line_gap)
    
        # 遍历“线”的数组来在 line_image 上绘制
        for line in lines:
            for x1,y1,x2,y2 in line:
                cv2.line(line_image,(x1,y1),(x2,y2),(255,0,0),10)
    
        color_edges = np.dstack((edges, edges, edges)) 
    
    import math
    import cv2
    import numpy as np
    
    """
    Gray Scale
    Gaussian Smoothing
    Canny Edge Detection
    Region Masking
    Hough Transform
    Draw Lines [Mark Lane Lines with different Color]
    """
    
    class SimpleLaneLineDetector(object):
        def __init__(self):
            pass
    
        def detect(self,img):
            # 图像灰度处理
            gray_img = self.grayscale(img)
            print(gray_img)
            #图像高斯平滑处理
            smoothed_img = self.gaussian_blur(img = gray_img, kernel_size = 5)
            #canny 边缘检测
            canny_img = self.canny(img = smoothed_img, low_threshold = 180, high_threshold = 240)
            #区域 Mask
            masked_img = self.region_of_interest(img = canny_img, vertices = self.get_vertices(img))
            #霍夫变换
            houghed_lines = self.hough_lines(img = masked_img, rho = 1, theta = np.pi/180, threshold = 20, min_line_len = 20, max_line_gap = 180)
            # 绘制车道线
            output = self.weighted_img(img = houghed_lines, initial_img = img, alpha=0.8, beta=1., gamma=0.)
            
            return output
        def grayscale(self,img):
            return cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
    
        def canny(self,img, low_threshold, high_threshold):
            return cv2.Canny(img, low_threshold, high_threshold)
    
        def gaussian_blur(self,img, kernel_size):
            return cv2.GaussianBlur(img, (kernel_size, kernel_size), 0)
    
        def region_of_interest(self,img, vertices):
            mask = np.zeros_like(img)   
        
            if len(img.shape) > 2:
                channel_count = img.shape[2]  
                ignore_mask_color = (255,) * channel_count
            else:
                ignore_mask_color = 255
                
            cv2.fillPoly(mask, vertices, ignore_mask_color)
            
            masked_image = cv2.bitwise_and(img, mask)
            return masked_image
        def draw_lines(self,img, lines, color=[255, 0, 0], thickness=10):
            for line in lines:
                for x1,y1,x2,y2 in line:
                    cv2.line(img, (x1, y1), (x2, y2), color, thickness)
    
        def slope_lines(self,image,lines):
            img = image.copy()
            poly_vertices = []
            order = [0,1,3,2]
    
            left_lines = [] 
            right_lines = [] 
            for line in lines:
                for x1,y1,x2,y2 in line:
    
                    if x1 == x2:
                        pass 
                    else:
                        m = (y2 - y1) / (x2 - x1)
                        c = y1 - m * x1
    
                        if m < 0:
                            left_lines.append((m,c))
                        elif m >= 0:
                            right_lines.append((m,c))
    
            left_line = np.mean(left_lines, axis=0)
            right_line = np.mean(right_lines, axis=0)


​    
​            for slope, intercept in [left_line, right_line]:
​    

                rows, cols = image.shape[:2]
                y1= int(rows) 
    
                y2= int(rows*0.6)
    
                x1=int((y1-intercept)/slope)
                x2=int((y2-intercept)/slope)
                poly_vertices.append((x1, y1))
                poly_vertices.append((x2, y2))
                self.draw_lines(img, np.array([[[x1,y1,x2,y2]]]))
            
            poly_vertices = [poly_vertices[i] for i in order]
            cv2.fillPoly(img, pts = np.array([poly_vertices],'int32'), color = (0,255,0))
            return cv2.addWeighted(image,0.7,img,0.4,0.)
    
        def hough_lines(self,img, rho, theta, threshold, min_line_len, max_line_gap):
            lines = cv2.HoughLinesP(img, rho, theta, threshold, np.array([]), minLineLength=min_line_len, maxLineGap=max_line_gap)
            line_img = np.zeros((img.shape[0], img.shape[1], 3), dtype=np.uint8)
            line_img = self.slope_lines(line_img,lines)
            return line_img
    
        def weighted_img(self,img, initial_img, alpha=0.1, beta=1., gamma=0.):
    
            lines_edges = cv2.addWeighted(initial_img, alpha, img, beta, gamma)
            return lines_edges
            
        def get_vertices(self,image):
            rows, cols = image.shape[:2]
            bottom_left  = [cols*0.15, rows]
            top_left     = [cols*0.45, rows*0.6]
            bottom_right = [cols*0.95, rows]
            top_right    = [cols*0.55, rows*0.6] 
            
            ver = np.array([[bottom_left, top_left, top_right, bottom_right]], dtype=np.int32)
            return ver



在这里插入图片描述

4.6 HoughLinesP 检测原理

接下来进入代码环节,学长详细给大家解释一下 HoughLinesP 参数的含义以及如何使用。


​ lines = cv2.HoughLinesP(cropped_image,2,np.pi/180,100,np.array([]),minLineLength=40,maxLineGap=5)

  • 第一参数是我们要检查的图片 Hough accumulator 数组
  • 第二个和第三个参数用于定义我们 Hough 坐标如何划分 bin,也就是小格的精度。我们通过曲线穿过 bin 格子来进行投票,我们根据投票数量来决定 p 和 theta 的值。2 表示我们小格宽度以像素为单位 。

在这里插入图片描述
我们可以通过下图划分小格,只要曲线穿过就会对小格进行投票,我们记录投票数量,记录最多的作为参数

在这里插入图片描述
在这里插入图片描述

  • 如果定义尺寸过大也就失去精度,如果定义格子尺寸过小虽然精度上来了,这样也会打来增长计算时间。
  • 接下来参数 100 表示我们投票为 100 以上的线才是符合要求是我们要找的线。也就是在 bin 小格子需要有 100 以上线相交于此才是我们要找的参数。
  • minLineLength 给 40 表示我们检查线长度不能小于 40 pixel
  • maxLineGap=5 作为线间断不能大于 5 pixel

4.6.1 定义显示车道线方法


​ def disply_lines(image,lines):
​ pass

通过定义函数将找到的车道线显示出来。


​ line_image = disply_lines(lane_image,lines)

4.6.2 查看探测车道线数据结构


​ def disply_lines(image,lines):
​ line_image = np.zeros_like(image)
​ if lines is not None:
​ for line in lines:
​ print(line)

先定义一个尺寸大小和原图一样的矩阵用于绘制查找到车道线,我们先判断一下是否已经找到车道线,lines 返回值应该不为 None
是一个矩阵,我们可以简单地打印一下看一下效果


​ [[704 418 927 641]]
​ [[704 426 791 516]]
​ [[320 703 445 494]]
​ [[585 301 663 381]]
​ [[630 341 670 383]]

4.6.3 探测车道线

看数据结构[[x1,y1,x2,y2]] 的二维数组,这就需要我们转换一下为一维数据[x1,y1,x2,y2]

def disply_lines(image,lines):
​        line_image = np.zeros_like(image)if liness is not None:for line in lines:
​                x1,y1,x2,y2 = line.reshape(4)
​                cv2.line(line_image,(x1,y1),(x2,y2),(255,0,0),10)return line_image
​    

line_image = disply_lines(lane_image,lines)
cv2.imshow('result',line_image)

在这里插入图片描述

4.6.4 合成

有关合成图片我们是将两张图片通过给一定权重进行叠加合成。

在这里插入图片描述

4.6.5 优化

在这里插入图片描述

探测到的车道线还是不够平滑,我们需要优化,基本思路就是对这些直线的斜率和截距取平均值然后将所有探测出点绘制到一条直线上。

  def average_slope_intercept(image,lines):
        left_fit = []
        right_fit = []
        for line in lines:
            x1, y1, x2, y2 = line.reshape(4)
            parameters = np.polyfit((x1,x2),(y1,y2),1)
            print(parameters)

这里学长定义两个数组 left_fit 和 right_fit 分别用于存放左右两侧车道线的点,我们打印一下 lines 的斜率和截距,通过 numpy
提供 polyfit 方法输入两个点我们就可以得到通过这些点的直线的斜率和截距。


​ [ 1. -286.]
​ [ 1.03448276 -302.27586207]
​ [ -1.672 1238.04 ]
​ [ 1.02564103 -299.



​ [ 1.02564103 -299.

def average_slope_intercept(image,lines):
    left_fit = []
    right_fit = []
    for line in lines:
        x1, y1, x2, y2 = line.reshape(4)
        parameters = np.polyfit((x1,x2),(y1,y2),1)
        # print(parameters)
        slope = parameters[0]
        intercept = parameters[1]
        if slope < 0:
            left_fit.append((slope,intercept))
        else:
            right_fit.append((slope,intercept))
        print(left_fit)
        print(right_fit)

我们输出一下图片大小,我们图片是以其左上角作为原点 0 ,0 来开始计算的,所以我们直线从图片底部 700 多向上绘制我们无需绘制全部可以截距一部分即可。

在这里插入图片描述

    def make_coordinates(image, line_parameters):
        slope, intercept = line_parameters
        y1 = image.shape[0]
        y2 = int(y1*(3/5)) 
        x1 = int((y1 - intercept)/slope)
        x2 = int((y2 - intercept)/slope)
        # print(image.shape)
        return np.array([x1,y1,x2,y2])

所以直线开始和终止我们给定 y1,y2 然后通过方程的斜率和截距根据y 算出 x。

    
​    averaged_lines = average_slope_intercept(lane_image,lines);
​    line_image = disply_lines(lane_image,averaged_lines)
​    combo_image = cv2.addWeighted(lane_image,0.8, line_image, 1, 1,1)
​    

cv2.imshow('result',combo_image)

在这里插入图片描述

5 最后

该项目较为新颖,适合作为竞赛课题方向,学长非常推荐!

🧿 更多资料, 项目分享:

https://gitee.com/dancheng-senior/postgraduate

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/129649.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vxe table - 基于Vue的宝藏级 table 组件

文章目录 前言一、Vxe-table功能点计划 二&#xff0c;安装三&#xff0c;引入四&#xff0c;示例用法 前言 对于表格来说&#xff0c;也许我们会遇到一个需求就是表格中的单元格可编辑&#xff0c;如果我们使用的是ElementUI也许不太好办&#xff0c;因为官方没有可编辑的这个…

Spring封装数据结果

Spring封装数据结果 POST请求JSON格式 基本数据类型 public class Demo {private byte aByte;private short aShort;private int anInt;private long aLong;private float aFloat;private double aDouble;private char aChar;private boolean aBoolean; }没有传键 封装时就会…

【Spring】SpringBoot配置文件

SpringBoot配置文件 配置文件作用SpringBoot配置文件配置文件快速入手配置文件的格式properties配置文件说明基本语法读取配置文件properties缺点分析 yml配置文件说明yml基本语法yml使用进阶yml配置读取配置对象配置集合配置Mapyml优缺点 配置文件作用 计算机上有数以千计的配…

Unity 一些内置宏定义

在Unity中&#xff0c;有一些内置的宏定义可用于不同的平台。以下是一些常见的平台内置宏定义&#xff1a; 1、UNITY_EDITOR&#xff1a;在Unity编辑器中运行。 2、UNITY_EDITOR_WIN&#xff1a;在Unity编辑器运行在Windows操作系统时被定义。 3、UNITY_STANDALONE&#xff1a…

Linux学习第37天:Linux I2C 驱动实验(一):哥俩好

Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长 世界上的很多事物都是成双成对出现的。也包括在驱动开发的过程中&#xff0c;比如I2C中其实就是数据线和时钟线的相互配合才能完成的。 I2C常用于连接各种外设、…

Ubuntu 22.04 安装水星无线 USB 网卡

我的 USB 网卡是水星 Mercury 的&#xff0c; 在 Ubuntu 22.04 下面没有自动识别。 没有无线网卡的时候只能用有线接到路由器上&#xff0c;非常不方便。 寻思着把无线网卡驱动装好。折腾了几个小时装好了驱动。 1.检查网卡类型 & 安装驱动 使用 lsusb 看到的不一定是准确…

node插件MongoDB(四)—— 库mongoose 的条件控制(三)

文章目录 前言一、运算符二、逻辑运算1. $or 逻辑或2. $and 逻辑与 三、正则匹配 前言 在mongodb 不能使用 > < > < ! 等运算符&#xff0c;需要使用替代符号。 一、运算符 > 使用 $gt< 使用 $lt> 使用 $gte< 使用 $lte! 使用 $ne 例子&#xff1a;获…

Mysql 一步到位实现插入或替换数据(REPLACE INTO语句)

单条数据插入/替换 比如有一个数据表叫test_table&#xff0c;包含: 主键&#xff1a;key_id数据&#xff1a;value 运行&#xff1a; REPLACE INTO test_table (key_id,value) VALUES ("id_1","value_1"); REPLACE INTO test_table (key_id,value) VAL…

Qt 各种数据类型

目录 1. 基础类型 2. log 输出 3. 字符串类型 3.2 QByteArray 构造函数 数据操作 子字符串查找和判断 遍历 查看字节数 类型转换 3.3 QString 4. QVariant 4.1 标准类型 4.2 自定义类型 5. 位置和尺寸 5.1 QPoint 5.2 QLine 5.3 QSize 5.4 QRect 6. 日期和…

gcc [linux]

目录 背景知识 gcc如何完成 格式 预处理&#xff08;进行宏替换&#xff09; 编译&#xff08;生成汇编&#xff09; 汇编&#xff08;生成机器可执行码&#xff09; 连接&#xff08;生成可执行文件或库文件&#xff09; 函数库 静态库 静态链接优势 动态库 动态链…

Wsl2 Ubuntu在不安装Docker Desktop情况下使用Docker

目录 1. 前提条件 2.安装Distrod 3. 常见问题 3.1.docker compose 问题无法使用问题 3.1. docker-compose up报错 参考文档 1. 前提条件 win10 WSL2 Ubuntu(截止202308最新版本是20.04.xx) 有不少的博客都是建议直接安装docker desktop&#xff0c;这样无论在windows…

C#开发的OpenRA游戏之世界存在的属性(1)

C#开发的OpenRA游戏之世界存在的属性(1) 在游戏里,由于存在雷达,那么每个物品就可以在雷达上显示出来,但是雷达上显示不同的部分物品时,会采用不同的颜色来显示,那么它又是怎么样实现这种不同物品进行不同的颜色显示呢? 可以仔细观看下图: 可以看到矿产显示为绿色,…

C语言之文件操作(剩余部分)

上篇博客字数到极限了&#xff0c;给大家把内容补充在这一篇&#xff0c;我们还剩下文件读取结束的判定和文件缓冲区的内容没有介绍&#xff0c;让我们开始下面的学习吧&#xff01; 目录 1.文件读取结束的判定 1.1feof函数 1.2ferror函数 代码示例 2.文件缓冲区 2.1fflu…

Android T 实现简易的 USB Mode Select 需求

Android T 实现 USB Mode Select 需求 一、实现效果 二、主要实现思路 在手机连接 USB 发生/取消通知的同时&#xff0c;控制弹窗 Dialog 的显示/消失。 三、主要代码实现 连接 USB 发送/取消的主要实现是在 UsbDeviceManager.java 类中。类路径如下&#xff1a; system/f…

《持续交付:发布可靠软件的系统方法》- 读书笔记(十二)

持续交付&#xff1a;发布可靠软件的系统方法&#xff08;十二&#xff09; 第 12 章 数据管理12.1 引言12.2 数据库脚本化12.3 增量式修改12.3.1 对数据库进行版本控制12.3.2 联合环境中的变更管理 12.4 数据库回滚和无停机发布12.4.1 保留数据的回滚12.4.2 将应用程序部署与数…

Vue集成海康websdk实现摄像头预览

选择以及下载相应的websdk&#xff1a; 从海康开放平台下载相应的sdk&#xff0c;web3.0不支持高版本浏览器&#xff0c;web3.2需要摄像头支持摄像头取流&#xff0c;web3.3支持高版本浏览器 我这选择的是3.3的。可以先测试下开发包是否可以成功访问&#xff0c;修改用ip、户名…

visual studio 启用DPI识别功能

在开发widow程序时&#xff0c;有时必须将电脑 设置-->显示-->缩放与布局-->更改文本、应用项目的大小-->100%后&#xff0c;程序的画面才能正确运行&#xff0c;居说这是锁定了dpi的原因&#xff0c;需要启dpi识别功能。设置方法如下&#xff1a; 或者

ubuntu 16.04.5 安装 vivado 2019.1 完整编译AD9361的环境

一、前期安装 1、安装ncurses库&#xff08;已经包含了&#xff0c;其他的os需要安装&#xff09; sudo apt install libncurses5二、安装 sudo ./xsetup使用lic进行激活。 三、安装后 输入指令 sudo gedit ~/.bashrc 末尾添加 source /opt/Xilinx/Vivado/2019.1/setti…

防火防盗防小人 使用 Jasypt 库来加密配置文件

⚔️ 项目配置信息存放在哪&#xff1f; 在日常开发工作中&#xff0c;我们经常需要使用到各种敏感配置&#xff0c;如数据库密码、各厂商的 SecretId、SecretKey 等敏感信息。 通常情况下&#xff0c;我们会将这些敏感信息明文放到配置文件中&#xff0c;或者放到配置中心中。…

论文速览 | arxiv 2023, 马氏距离感知训练在分布外检测中的应用

注1:本文系“最新论文速览”系列之一,致力于简洁清晰地介绍、解读最新的顶会/顶刊论文。 OOD论文速览 | arxiv 2023, Mahalanobis-Aware Training for Out-of-Distribution Detection 该论文旨在提出一种改进的深度学习模型训练方法,以提高对分布外(OOD)样本的检测能力。…