车道线检测

news2024/11/26 12:18:26

前言

目前,车道线检测技术已经相当成熟,主要应用在自动驾驶、智能交通等领域。下面列举一些当下最流行的车道线检测方法:

  • 基于图像处理的车道线检测方法。该方法是通过图像处理技术从摄像头传回的图像中提取车道线信息的一种方法,主要是利用图像处理算法进行车道线的检测和识别,并输出车道线的位置信息。

  • 基于激光雷达的车道线检测方法。该方法通过激光雷达扫描地面,获取车道线位置信息。这种方法对于在光照较弱、天气恶劣的情况下车道线能更加准确地被检测出来。

  • 基于雷达与摄像头的融合车道线检测方法。此方法是将雷达和摄像头两个传感器的数据进行融合,从而得到更加准确的车道线位置信息,检测的鲁棒性也得到了提高。

  • 基于GPS和地图的车道线检测方法。该方法主要是利用车辆上的GPS以及地图数据来检测车道线的位置信息。这种方法可以克服图像处理技术在某些特定情况下(比如光照不足或者环境光线无法反射)的不足。

以上这些方法均存在优缺点,不同方法的选择主要取决于具体的技术需求和场景应用。在该章节以基于图像处理的车道线检测方法进行介绍。分别从基础的入门级方法到深度学习的方法进行介绍。

传统图像方法
传统图像方法通过边缘检测滤波等方式分割出车道线区域,然后结合霍夫变换、RANSAC等算法进行车道线检测。这类算法需要人工手动去调滤波算子,根据算法所针对的街道场景特点手动调节参数曲线,工作量大且鲁棒性较差,当行车环境出现明显变化时,车道线的检测效果不佳。基于道路特征检测根据提取特征不同,分为:基于颜色特征、纹理特征、多特征融合;

例如:在车道图像中,路面与车道线交汇处的灰度值变换剧烈,利用边缘增强算子突出图像的局部边缘,定义像素的边缘强度,设置阈值方法提取边缘点;
常用的算子:Sobel算子、Prewitt算子、Log算子、Canny算子;
基于灰度特征检测结构简单,对于路面平整、车道线清晰的结构化道路尤为适用;但当光照强烈、有大量异物遮挡、道路结构复杂、车道线较为模糊时,检测效果受到很大的影响;

使用openCV的传统算法

  • Canny边缘检测
  • 高斯滤波
  • ROI和mask
  • 霍夫变换

openCV在图片和视频相关常用的代码
在这里插入图片描述

# 读取图片 cv2.imread第一个参数是窗口的名字,第二个参数是读取格式(彩色或灰度)
cv2.imread(const String & filename, int flags = IMREAD_COLOR)

#显示图片 cv2.imshow第一个参数是窗口的名字  第二个参数是显示格式,
cv2.imshow(name, img)	

#保持图片
cv2.imwrite(newfile_name, img)

#关闭所有openCV打开的窗口。
cv2.destroyAllWindows()

#-----------------------------#
#打开视频
capture = cv2.VideoCapture('video.mp4')

#按帧读取视频 
#capture.read()有两个返回值。其中ret是布尔值,如果读取帧是正确的则返回True,如果文件读取到结尾,它的返回值就为False。frame就是每一帧的图像,是个三维矩阵。
ret, frame = capture.read()

#视频编码格式设置
fourcc = cv2.VideoWriter_fourcc('X', 'V', 'I', 'D')

"""
补充:cv2.VideoWriter_fourcc(‘I’, ‘4’, ‘2’, ‘0’),该参数是YUV编码类型,文件名后缀为.avi
cv2.VideoWriter_fourcc(‘P’, ‘I’, ‘M’, ‘I’),该参数是MPEG-1编码类型,文件名后缀为.avi
cv2.VideoWriter_fourcc(‘X’, ‘V’, ‘I’, ‘D’),该参数是MPEG-4编码类型,文件名后缀为.avi
cv2.VideoWriter_fourcc(‘T’, ‘H’, ‘E’, ‘O’),该参数是Ogg Vorbis,文件名后缀为.ogv
cv2.VideoWriter_fourcc(‘F’, ‘L’, ‘V’, ‘1’),该参数是Flash视频,文件名后缀为.flv

"""

高斯滤波

Canny边缘检测

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

  1. 使用高斯模糊,去除噪音点(cv2.GaussianBlur)
  2. 灰度转换(cv2.cvtColor)
  3. 使用sobel算子,计算出每个点的梯度大小和梯度方向
  4. 使用非极大值抑制(只有最大的保留),消除边缘检测带来的杂散效应
  5. 应用双阈值,来确定真实和潜在的边缘
  6. 通过抑制弱边缘来完成最终的边缘检测
#color_img 输入图片
#gaussian_ksize 高斯核大小,可以为方形矩阵,也可以为矩形
#gaussian_sigmax X方向上的高斯核标准偏差
gaussian = cv2.GaussianBlur(color_img, (gaussian_ksize,gaussian_ksize), gaussian_sigmax)

#用于颜色空间转换。input_image为需要转换的图片,flag为转换的类型,返回值为颜色空间转换后的图片矩阵。
#flag对应:
#cv2.COLOR_BGR2GRAY BGR -> Gray
#cv2.COLOR_BGR2RGB BGR -> RGB
#cv2.COLOR_BGR2HSV BGR -> HSV

gray_img = cv2.cvtColor(input_image, flag)

输出结果:
在这里插入图片描述

#imag为所操作的图片,threshold1为下阈值,threshold2为上阈值,返回值为边缘图。
edge_img = cv2.Canny(gray_img,canny_threshold1,canny_threshold2)

#整成canny_edge_detect的方法
def 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

在这里插入图片描述

ROI and mask

在机器视觉或图像处理中,通过图像获取到的信息通常是一个二维数组或矩阵,这些信息中可能包含需要进一步处理的区域以及不需要处理的区域。为了提高图像处理的效率和准确性,通常会在需要处理的区域内定义一个感兴趣的区域(ROI),并对该区域进行下一步的处理。ROI可以通过方框、圆、椭圆、不规则多边形等方式勾勒出需要处理的区域。在机器视觉软件中,常常通过图像处理算子和函数来计算ROI。比如,在OpenCV中可以使用cv::Rect、cv::RotatedRect、cv::Point等进行ROI的类型定义和计算;在Matlab中可以使用imrect、imellipse、impoly等函数实现ROI的定义和计算。

处理ROI的目的是为了便于在图像的区域中进行目标检测、物体跟踪、边缘检测、图像分割等操作。通过使用ROI,可以将不需要处理的区域从原始图像中排除掉,从而减少图像处理的复杂度和耗时,提高计算效率和准确性。

在这里插入图片描述

#设置ROI和掩码
poly_pts = numpy.array([[[0,368],[300,210],[340,210],[640,368]]])

mask = np.zeros_like(gray_img)

cv2.fillPoly(mask, pts, color)

img_mask = cv2.bitwise_and(gray_img, mask)

霍夫变换

霍夫变换是一种常用的图像处理算法,用于在图像中检测几何形状(如直线、圆、椭圆等)。该算法最初是由保罗·霍夫于 1962 年提出的。简单来说,霍夫变换可以将在直角坐标系中表示的图形转换为极坐标系中的线或曲线,从而方便地进行形状的检测和识别。所以霍夫变换实际上一种由繁到简(类似降维)的操作。在应用中,霍夫变换的过程可以分为以下几个步骤:

  1. 针对待检测的形状,选择相应的霍夫曼变换方法。比如,如果要检测直线,可以使用标准霍夫变换;如果要检测圆形,可以使用圆霍夫变换。
  2. 将图像转换为灰度图像,并进行边缘检测,以获得待检测的形状目标的轮廓。
  3. 以一定的步长和角度范围在霍夫空间中进行投票,将所有可能的直线或曲线与它们可能在的极坐标空间中的位置相对应。
  4. 找到霍夫空间中的峰值,这些峰值表示形状的参数空间中存在原始图像中形状的可能性。
  5. 通过峰值位置在原始图像中绘制直线、圆等形状。

当使用 canny 进行边缘检测后图像可以交给霍夫变换进行简单图形(线、圆)等的识别。这里用霍夫变换在 canny 边缘检测结果中寻找直线。

# 示例代码,作者丹成学长:Q746876041
mask = np.zeros_like(edges)

    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):
        """
        edge_img: 要检测的图片矩阵
		参数2: 距离r的精度,值越大,考虑越多的线
		参数3: 距离theta的精度,值越大,考虑越多的线
		参数4: 累加数阈值,值越小,考虑越多的线
		minLineLength: 最短长度阈值,短于这个长度的线会被排除
		maxLineGap:同一直线两点之间的最大距离
        """
        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

深度学习方法

  • 基于基于分割的方法
  • 基于检测的方法
  • 基于关键点的方法
  • 基于参数曲线的方法

LaneNet+H-Net车道线检测

论文链接:Towards End-to-End Lane Detection: an Instance Segmentation Approach
LaneNet+H-Net的车道线检测方法,通过深度学习方法实现端到端的车道线检测,该方法包括两个主要组件,一个是实例分割网络,另一个是车道线检测网络。

该方法的主要贡献在于使用实例分割技术来区分不同车道线之间的重叠和交叉,并且应用多任务学习方法同时实现车道线检测和实例分割。具体来说,该方法将车道线检测问题转化为实例分割问题,使用 Mask R-CNN 实现车道线的分割和检测。通过融合两个任务的 loss 函数,同时对车道线检测和实例分割网络进行训练,实现端到端的车道线检测。

论文中的模型结构主要包括两部分:实例分割网络和车道线检测网络。实例分割网络采用 Mask R-CNN,由主干网络和 Mask R-CNN 网络两部分组成。车道线检测网络采用了 U-Net 结构,用于对掩码图像进行后处理,得到车道线检测结果。

  • LaneNet将车道线检测问题转为实例分割问题,即:每个车道线形成独立的实例,但都属于车道线这一类别;H-Net由卷积层和全连接层组成,利用转换矩阵H对同一车道线的像素点进行回归;
  • 对于一张输入图片,LaneNet负责输出实例分割结果,每条车道线一个标识ID,H-Net输出一个转换矩阵,对车道线像素点进行修正,并对修正后的结果拟合出一个三阶多项式作为预测的车道线;
    在这里插入图片描述
    在这里插入图片描述
    测试效果在这里插入图片描述
    代码有需要的朋友,可以私聊我。

未完待续!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【AI工具 收藏】

文章目录 miniGPT4&#xff1a;chatGPT&#xff0c;支持图片哔哔终结者 BibiGPT&#xff1a;自动总结视频内容Scribble Diffusion&#xff1a;草图绘画ChatGPT Shortcut&#xff1a;prompt提示词其他 博客总结的工具&#xff1a; miniGPT4&#xff1a;chatGPT&#xff0c;支持图…

ROS2下机械手的手眼标定

最近发现老是有人通过爬虫把文章爬去他们网站&#xff0c;因此先发一遍这个草稿&#xff0c;让他们先爬。爬完我再慢慢修改 0.前期准备 0.1机械手要先映射&#xff08;标定好&#xff09;世界坐标系与用户坐标系 这个基本应该可以通过机械手自带的坐标系建立完成 一般机械手…

若依代码生成器的使用方式

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、代码生成器是什么&#xff1f;二、使用步骤1.菜单目录2.子菜单3.代码生成的方式 总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1…

符合业务目标的数据战略建设

我们都知道&#xff0c;战略是通往目的的手段&#xff0c;是企业取得最佳商业成果的方法。处于当今的数字化社会&#xff0c;企业需要满足实现数字化转型及数字社会的需求&#xff0c;提升企业数据资产价值&#xff0c;推动行业甚至社会面的数据流通及数据价值变现。因此企业更…

【数据结构与算法】二、线性表的顺序表示【硬核】

文章目录 二、线性表2.1 线性表的定义和特点2.2 线性表的顺序表示和实现2.3 类C语言有关操作补充2.4 线性表基本操作的实现2.4.1 线性表的基本操作&#xff1a;2.4.2 线性表L的初始化2.4.3 销毁和清空线性表L2.4.4 求线性表L的长度以及判断线性表L是否为空2.4.5 顺序表的取值&a…

Docker --- Docker-Compose、镜像仓库

一、Docker-Compose Docker Compose可以基于Compose文件帮我们快速的部署分布式应用&#xff0c;而无需手动一个个创建和运行容器&#xff01; 1.1、初识DockerCompose Compose文件是一个文本文件&#xff0c;通过指令定义集群中的每个容器如何运行。格式如下&#xff1a; v…

手术机器人企业密集IPO 国产替代寻求突破

原创 | 文 BFT机器人 2023年或为手术机器人国产化元年&#xff0c;谁将成为中国的“达芬奇”&#xff1f;各路资本对中国手术机器人产业热情高涨。 今年以来&#xff0c;多家手术机器人企业启动首次公开发行&#xff08;IPO&#xff09;进程&#xff0c;精锋医疗冲刺港股&#…

09-Node.js—express框架

目录 1、express 介绍2、express 使用2.1 express 下载2.2 express 初体验 3、express 路由3.1 什么是路由3.2 路由的使用3.2.1使用Ajax发送一次post请求 3.3 获取请求参数3.4 获取路由参数3.5 路由参数练习 4、express 响应设置5、express 中间件5.1 什么是中间件5.2 中间件的…

echarts 环形图_圆环动画

Echarts 常用各类图表模板配置 注意&#xff1a; 这里主要就是基于各类图表&#xff0c;更多的使用 Echarts 的各类配置项&#xff1b; 以下代码都可以复制到 Echarts 官网&#xff0c;直接预览&#xff1b; 图标模板目录 Echarts 常用各类图表模板配置一、环形动画二、环形图…

(原创)Flutter基础入门:手把手教你搭建Flutter混合项目:AAR方式集成

前言 上一篇博客讲了如何用“模块代码依赖模式”这种方式去搭建Flutter混合项目 因为篇幅原因&#xff0c;AAR集成方式来搭建项目的步骤和注意点放到这篇博客来讲 如果你没看过上篇博客&#xff0c;建议先阅读上一篇&#xff1a; &#xff08;原创&#xff09;Flutter基础入门…

快来看看这些前端开发技巧你掌握多少吧

文章目录 一、代码整洁推荐1.1 三元(三目)运算符1.2 短路判断简写1.3 变量声明简写1.4 if真值判断简写1.5 For循环简写1.6 对象属性简写1.7 箭头函数简写1.8 隐式返回简写1.9 模板字符串1.10 默认参数值1.11 解构赋值简写1.12 多条件判断简写1.13 多变量赋值简写1.14 解构时重命…

easyExcel动态导出,合并指定单元格

如上图所示&#xff0c;需要使用easyExcel动态导出上述表格并指定合并其中的单元格&#xff0c;日期是动态的&#xff0c;每个月不相同&#xff0c;直接上实现代码&#xff0c;以demo形式展现&#xff0c;更好理解 /** * 考勤记录动态导出测试 */ GetMapping(&q…

【Kotlin学习】R2DBC与MyBatis性能对比

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、测试框架选取Spring Webflux VS Spring MVCSpring Data R2DBC VS MyBatis 二、测试代码编写1. 项目1核心代码1.1 引入依赖1.2 接口代码 2. 项目2核心代码2.…

Layui图片上传

前端代码&#xff1a; <div class"layui-upload"> <button type"button" class"layui-btn" id"test1">上传图片</button> <div class"layui-upload-list"> <img class"lay…

如何一次性生成大量结构相同、内容不同的二维码

使用 批量模板数据 的方式&#xff0c;可 一次性生成大量结构相同&#xff0c;内容不同的活码 &#xff0c;大幅提升制码效率。 模板的样式&#xff0c;关联的表单状态等所有内容均可进行修改&#xff0c;修改后所有子码都将批量更新&#xff0c;且模板可重复使用&#xff0c;让…

页面加载进度条(VUE3)

通常我们希望在页面跳转加载中&#xff0c;页面顶部出现进度条。 &#xff08;1&#xff09; 下载依赖 npm install nprogress --save &#xff08;2&#xff09;在router中得index.js中引入 import NProgress from nprogress import nprogress/nprogress.css &#xff08;…

C++——类和对象(2)

作者&#xff1a;几冬雪来 时间&#xff1a;2023年4月25日 内容&#xff1a;C类和对象讲解 目录 前言&#xff1a; 1.this指针&#xff1a; 2.默认成员函数&#xff1a; 3.构造函数&#xff1a; 4.析构函数&#xff1a; 5.构造函数的问题&#xff1a; 结尾&#xff…

Transformer 代码详细解析

Transformer 代码详细解析 文章目录 Transformer 代码详细解析一、Transformer 背景介绍1.1 Transformer 的诞生1.2 Transformer 的优势1.3 Transformer 的市场 二、Transformer架构解析2.1 认识 Transformer 架构2.1.1 Transformer模型的作用2.1.2 Transformer 总体架构图 2.2…

全国计算机等级三级网络技术试卷详解(一)

请点击↑关注、收藏&#xff0c;本博客免费为你获取精彩知识分享&#xff01;有惊喜哟&#xff01;&#xff01; 1.下列关于RPR技术的描述中&#xff0c;错误的是&#xff08;&#xff09;。 A) RPR与FDDI一样使用双环结构 B) 在RPR环中&#xff0c;源节点向目的节点成功发出…

2023常用的10款电力电子系统仿真分析软件推荐

市场上有许多电子仿真器&#xff0c;那么对于电力电子项目来说&#xff0c;哪种仿真器最好呢&#xff1f;或者因为期望任何软件包在各个方面都是最好的是不合理的&#xff0c;那么用户如何确定哪个软件是项目的最佳选择&#xff1f; PSIM PSIM是一种模块化封装&#xff0c;专为…