高级车道线检测

article/2025/10/2 22:15:57

基于图像处理相关技术的高级车道线检测(可适用于弯道,车道线颜色不固定,路面阴影,亮光)
pipeline:
1.校准摄像头的畸变,使拍摄照片能够较完整的反映3D世界的情况
2.对每一帧图片做透视转换(perspective transform),将摄像头的照片转换到鸟瞰图视角(图片见正文),方便计算车道线曲率,从而控制车辆运动
3.对鸟瞰图二值化,通过二值的像素点进一步区分左右两条车道线,从而拟合出车道线曲线
4.用滑窗的方法检测第一帧的车道线像素点,然后拟合车道线曲线
5.从第一帧的曲线周围寻找接下来的车道线像素点,然后拟合车道线曲线,这样可以节约计算资源,加快检测速度
6.有了车道线曲线方程之后,可以计算斜率和车道线偏离中心的位置

正文
1.校准摄像头畸变
摄像头畸变主要分两种,径向畸变和切向畸变,径向畸变是由于光线经过摄像机的镜头时,边缘的光线会更多或更少的弯曲,所以边缘的物体成像时会有畸变。切向畸变主要是由于镜头和成像胶卷或传感器不平行导致的。
关于由5个参数矫正畸变,k1-k5,畸变越严重,所需参数越多,径向畸变矫正公式如下:在这里插入图片描述
切向畸变矫正公式如下:
在这里插入图片描述
其中x,y为原图任一点,x,y(corrected)为其对应的没有畸变的图像上的坐标,r为点到中心的距离,K1,K2,K3为径向畸变参数,P1,P2为切向畸变参数,矫正参数由opencv的API来获得。这里用棋盘图的原因是计算点坐标相对容易,一般计算要多拍一些棋盘图在不同角度的照片,找到每一张棋盘图的角点坐标(图中的全部(x,y)和(x,y)(corrected)坐标),和其对应的未畸变的角点坐标,然后计算矫正参数进行矫正。

#创建objpoints和imgpoints来接收来自无畸变图片和相机拍摄畸变图片的角点,存储多张图片角点增加矫正的准确率。
def calibrate_camera(nx,ny):objp = np.zeros((nx*ny,3), np.float32)objp[:,:2] = np.mgrid[0:nx,0:ny].T.reshape(-1,2)objpoints = []imgpoints = []images = glob.glob('camera_cal/calibration*.jpg')for idx, fname in enumerate(images):#convert image to gray that the 'cv2.findChessboardCorners' neededimg = mpimg.imread(fname)gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)#Finding chessboard corners (for an 9×️6 board)ret, corners = cv2.findChessboardCorners(gray, (nx,ny), None)if ret == True:objpoints.append(objp)imgpoints.append(corners)#mtx represents 3D to 2D transformation, dist represents undistortion coef, rvecs the spin of camera#and tvecs the offset(偏移量)of the camera in the real world.return objpoints, imgpoints

有了目标点和图像点的角点信息之后,利用opencv的API自动计算矫正系数来矫正每一帧图片,返回值是无畸变图像,图1为矫正前图像,图2为矫正后图像。

def undistort_image(img):objectpoints, imagepoints = calibrate_camera(9,6)img_size = img.shape[1::-1]#get the distortion coef and other parametersret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objectpoints, imagepoints, img_size, None, None)#Undistort a imagedst = cv2.undistort(img, mtx, dist, None, mtx)return dst

矫正前

矫正后

接着就是为了计算曲率,将摄像头的视角进行转换,转换的结果最好是转换成从上向下的视角,造成摄像机从空中垂直拍摄车道线的效果,使用转换前图片任意多边形的边界点和转换以后多边形的边界点作为输入,调用opencv的cv2.getPerspectiveTransform函数可以返回转换的矩阵M,Minv是反转换矩阵,warped就是转换后车道线鸟瞰图。

def perspective_transform(img):# Vertices extracted manually for performing a perspective transformleftupperpoint = [568, 470]rightupperpoint = [717, 470]leftlowerpoint = [260, 680]rightlowerpoint = [1043, 680]src = np.float32([leftupperpoint, leftlowerpoint, rightupperpoint, rightlowerpoint])dst = np.float32([[200, 0], [200, 680], [1000, 0], [1000, 680]])img_size = img.shape[1::-1]#Compute the perspective transform, M, given source and destination pointsM = cv2.getPerspectiveTransform(src, dst)#Compute the inverse perspective transformMinv = cv2.getPerspectiveTransform(dst, src)#Warp an image using the perspective transform, Mwarped = cv2.warpPerspective(img, M, img_size, flags=cv2.INTER_LINEAR)return M, Minv, warpeddef perspective_transform(img):

在这里插入图片描述
有了鸟瞰图,还是要让程序自己辨别左右两条车道线,车道线在图像上是两条垂直线,可以用边缘检测的方法检测出,关于Sobel边缘检测,用sobel算子在x方向求导数可以很好的检测出垂直的车道线,但是为了让车道线检测效果更鲁棒,这里还需要结合一些图像颜色空间的知识,实验表明,HLS颜色中的S(饱和度)空间对阴影,光照的结果很鲁棒,因为饱和度通常反应物体颜色的鲜艳程度,与物体颜色及颜色亮暗无关,所以采用x方向的sobel边缘检测和饱和度阈值结合的方法可以使车道线检测结果更鲁棒
在这里插入图片描述

def pipeline(img, s_thresh=(170, 255), sx_thresh=(20, 100)):img = np.copy(img)# Convert to HLS color space and separate the V channelhls = cv2.cvtColor(img, cv2.COLOR_RGB2HLS)l_channel = hls[:,:,1]s_channel = hls[:,:,2]# Sobel xsobelx = cv2.Sobel(l_channel, cv2.CV_64F, 1, 0) # Take the derivative in xabs_sobelx = np.absolute(sobelx) # Absolute x derivative to accentuate lines away from horizontalscaled_sobel = np.uint8(255*abs_sobelx/np.max(abs_sobelx))# Threshold x gradientsxbinary = np.zeros_like(scaled_sobel)sxbinary[(scaled_sobel >= sx_thresh[0]) & (scaled_sobel <= sx_thresh[1])] = 1# Threshold color channels_binary = np.zeros_like(s_channel)s_binary[(s_channel >= s_thresh[0]) & (s_channel <= s_thresh[1])] = 1# Stack each channelcolor_binary = np.dstack(( np.zeros_like(sxbinary), sxbinary, s_binary)) *255return color_binary

有了二进制的车道线图像,就可以进一步拟合车道线曲线,首先画车道线二进制图的像素直方图,取下半张图片,像素最多的位置作为车道线的起始位置,然后自定义窗口大小和个数向上做滑窗操作,求出每个窗口中像素点的x和y坐标作为车道线的x,y坐标,当前窗口像素的平均x坐标作为下一个滑窗的中心位置,有了全部滑窗和车道线坐标用cv2.fitpoly函数拟合车道线曲线方程
在这里插入图片描述

def find_line_pixels(binary_warped):# Take a histogram of the bottom half of the imagehistogram = np.sum(binary_warped[binary_warped.shape[0] // 2:, :], axis=0)# Find the peak of the left and right halves of the histogram# These will be the starting point for the left and right linesmidpoint = np.int(histogram.shape[0] / 2)leftx_base = np.argmax(histogram[:midpoint])rightx_base = np.argmax(histogram[midpoint:]) + midpoint# Choose the number of sliding windowsnwindows = 9# Set height of windowswindow_height = np.int(binary_warped.shape[0] / nwindows)# Identify the x and y positions of all nonzero pixels in the imagenonzero = binary_warped.nonzero()nonzeroy = np.array(nonzero[0])nonzerox = np.array(nonzero[1])# Current positions to be updated for each windowleftx_current = leftx_baserightx_current = rightx_base# Set the width of the windows +/- marginmargin = 100# Set minimum number of pixels found to recenter windowminpix = 50# Create empty lists to receive left and right lane pixel indicesleft_lane_inds = []right_lane_inds = []# Step through the windows one by onefor window in range(nwindows):# Identify window boundaries in x and y (and right and left)win_y_low = binary_warped.shape[0] - (window + 1) * window_heightwin_y_high = binary_warped.shape[0] - window * window_heightwin_xleft_low = leftx_current - marginwin_xleft_high = leftx_current + marginwin_xright_low = rightx_current - marginwin_xright_high = rightx_current + margin# Identify the nonzero pixels in x and y within the windowgood_left_inds = ((nonzeroy >= win_y_low) & (nonzeroy < win_y_high) &(nonzerox >= win_xleft_low) & (nonzerox < win_xleft_high)).nonzero()[0]good_right_inds = ((nonzeroy >= win_y_low) & (nonzeroy < win_y_high) &(nonzerox >= win_xright_low) & (nonzerox < win_xright_high)).nonzero()[0]# Append these indices to the listsleft_lane_inds.append(good_left_inds)right_lane_inds.append(good_right_inds)# If you found > minpix pixels, recenter next window on their mean positionif len(good_left_inds) > minpix:leftx_current = np.int(np.mean(nonzerox[good_left_inds]))if len(good_right_inds) > minpix:rightx_current = np.int(np.mean(nonzerox[good_right_inds]))# Concatenate the arrays of indicesleft_lane_inds = np.concatenate(left_lane_inds)right_lane_inds = np.concatenate(right_lane_inds)# Extract left and right line pixel positionsleftx = nonzerox[left_lane_inds]lefty = nonzeroy[left_lane_inds]rightx = nonzerox[right_lane_inds]righty = nonzeroy[right_lane_inds]# Fit a second order polynomial to eachleft_fit = np.polyfit(lefty, leftx, 2)right_fit = np.polyfit(righty, rightx, 2)return left_fit, right_fit, left_lane_inds, right_lane_inds, lefty, leftx, righty, rightx

滑窗的方法通常用于第一帧或者检测失败重新开始的检测,因为对计算资源浪费过多,检测时间长,由于连续帧图像之间相差不大,之后几帧的图像可以只对第一帧拟合的曲线周围检测,设置周围的margin,然后在该范围内寻找下一帧曲线的像素点从而拟合曲线。但是再最后处理每一帧图像时要设置标志位检测是否检测到拟合的曲线,检测到的话用search_from_previous方法,否则的话要用滑窗的方法重新寻找车道线。
在这里插入图片描述


def search_from_previous(binary_warped, left_fit, right_fit):nonzero = binary_warped.nonzero()nonzeroy = np.array(nonzero[0])nonzerox = np.array(nonzero[1])margin = 100left_lane_inds = ((nonzerox > (left_fit[0] * (nonzeroy ** 2) + left_fit[1] * nonzeroy +left_fit[2] - margin)) & (nonzerox < (left_fit[0] * (nonzeroy ** 2) +left_fit[1] * nonzeroy + left_fit[2] + margin)))right_lane_inds = ((nonzerox > (right_fit[0] * (nonzeroy ** 2) + right_fit[1] * nonzeroy +right_fit[2] - margin)) & (nonzerox < (right_fit[0] * (nonzeroy ** 2) +right_fit[1] * nonzeroy + right_fit[2] + margin)))# Again, extract left and right line pixel positionsleftx = nonzerox[left_lane_inds]lefty = nonzeroy[left_lane_inds]rightx = nonzerox[right_lane_inds]righty = nonzeroy[right_lane_inds]# Fit a second order polynomial to eachleft_fit = np.polyfit(lefty, leftx, 2)right_fit = np.polyfit(righty, rightx, 2)return left_fit, right_fit, left_lane_inds, right_lane_inds,lefty,leftx,righty,rightx

有了曲线的方程,则可以根据曲线曲率半径的公式计算曲率,dx/dy是因为我们拟合的是x对y的方程,因为车道线基本垂直,y对x的函数可能会存在一个y队形多个x的情况,这里还要注意自己计算的车道线曲线是根据像素值计算的,还要转换为其对应的实际道路距离(m)。
对于车辆的偏移,我们可以假设摄像头安装在车辆的正中心,那么道路中心就是检测到的图像中两条车道线的中点,车道线中心和图像中心的偏移就是车辆相对于车道线的偏移。同样记得把像素值转换为实际道路记录(m)。

在这里插入图片描述

def measure_radius_of_curvature(warped_img, lefty, leftx, righty, rightx):ym_per_pix = 20 / 720  # meters per pixel in y dimensionxm_per_pix = 3.7 / 800  # meters per pixel in x dimension# Fit a second order polynomial to pixel positions in each fake lane line# Fit new polynomials to x,y in world spaceleft_fit_cr = np.polyfit(lefty*ym_per_pix, leftx*xm_per_pix, 2)right_fit_cr = np.polyfit(righty*ym_per_pix, rightx*xm_per_pix, 2)ploty = np.linspace(0, warped_img.shape[0] - 1, warped_img.shape[0])# Define y-value where we want radius of curvature# We'll choose the maximum y-value, corresponding to the bottom of the imagey_eval = np.max(ploty)#Implement the calculation of R_curve (radius of curvature)left_curverad = ((1 + (2 * left_fit_cr[0] * y_eval * ym_per_pix + left_fit_cr[1]) ** 2) ** 1.5) / np.absolute(2 * left_fit_cr[0])right_curverad = ((1 + (2 * right_fit_cr[0] * y_eval * ym_per_pix + right_fit_cr[1]) ** 2) ** 1.5) / np.absolute(2 * right_fit_cr[0])left_lane_bottom = left_fit_cr[0] * (y_eval* ym_per_pix) ** 2 + left_fit_cr[0] * y_eval* ym_per_pix + left_fit_cr[2]right_lane_bottom = right_fit_cr[0] * (y_eval* ym_per_pix) ** 2 + right_fit_cr[0] * y_eval* ym_per_pix + right_fit_cr[2]# Lane center as mid of left and right lane bottomlane_center = (left_lane_bottom + right_lane_bottom) / 2.center_image = np.float(1280/2*xm_per_pix)center = lane_center - center_image  # Convert to metersposition = "left" if center < 0 else "right"center = "Vehicle is {:.2f}m {}".format(center, position)# Now our radius of curvature is in metersreturn left_curverad, right_curverad, center

这就是完整的车道线检测流程了,对于一些连续弯道导致车道线超出图像边界的情况日后解决了会更新。


http://chatgpt.dhexx.cn/article/QhKfYE6t.shtml

相关文章

传统方法车道线标注及相关知识

目录 一、图像二值化处理 1.Sobel算子绝对值 2.Sobel算子 3.倾斜角度 4.HLS颜色空间 5.二值图结合 二、车道线分割 1.仿射变换 2.车道线直方图 3.滑动窗口寻找车道线 4.车道线拟合 5.车道线区域标注 一、图像二值化处理 主要目的是通过二值化图像&#xff0c;使得车…

学习笔记之车道线相关记录

一. 车道线相关的知识 &&1.标线的分类 以下分类来自于百科&#xff1a; 按照道路交通标线的功能划分为&#xff1a;指示标线、警告标线和禁止标线。 按标划方法可分为&#xff1a;白色虚线、白色实线、黄色虚线、黄色实线、双白虚线、双白实线、双黄虚线和双黄实线…

关于python 最简单封装实例

一、 #定义一个类 class Person: #init是定义类实例初始化函数 ,没有返回return def __init__(self,name,area): self.name name self.area area #类里面定义方法 def run(self): print(self.name) …

Python软件封装打包

作者&#xff1a;Naples 链接&#xff1a;https://www.zhihu.com/question/32703639/answer/165326590 来源&#xff1a;知乎 著作权归作者所有。商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处。 Python Tkinter打包封装的方法有&#xff1a;PyInstaller, py…

Python封装、继承和多态

Python 语言在设计之初&#xff0c;就定位为一门面向对象的编程语言&#xff0c;“Python 中一切皆对象”。同时&#xff0c;Python 也支持面向对象的三大特征&#xff1a;封装、继承和多态。 一、封装 封装&#xff08;Encapsulation&#xff09;&#xff0c;即在设计类时&am…

python程序封装

python程序封装1 报错请执行pip install --upgrade setuptools 和 pip install --upgrade wheel 步骤如下&#xff1a; &#xff08;1&#xff09;安装pyinstaller&#xff0c;可以直接在cmd命令行中&#xff0c;输入命令“pip install pyinstaller”&#xff0c;安装pyinsta…

python封装程序

#终端/cmd命令下&#xff1a; 1.安装python Welcome to Python.org 2.安装pip pip PyPI 下载get-pip.py 在cmd窗口下执行&#xff0c;python */*/get-pip.py&#xff08;*为文件所在位置&#xff09; *如果安装好后出现 不是内部命令的情况 需要在环境中添加&#xff…

制作python包,封装成可用模块

制作python包&#xff0c;封装成可用模块 首先编写py程序: printtest.py #coding: utf-8 def test():print(print test)if __name__ __main__:test() 将以上.py文件做成python模块&#xff0c;需要在相同目录下创建setup.py文件&#xff0c;setup.py中输入配置信息: #cod…

Python 程序封装-打包成exe程序

Python 程序封装-打包成exe程序 前言一、 Python 打包工具—Pyinstaller二、打包具体过程1. 打包成仅包含一个独立的exe程序2. 打包成包含文件夹的程序&#xff0c;内有相关的依赖库&#xff08;推荐&#xff09;3. 其他的打包命令 三、注意事项 欢迎学习交流&#xff01; 邮箱…

怎么python程序封装?此文详解

python程序封装1 步骤如下: (1)安装pyinstaller,可以直接在cmd命令行中,输入命令“pip install pyinstaller”,安装pyinstaller (2)进入py代码的保存目录,这里py代码放在“E:\python学习\python_work” (3)cmd,输入命令:e:,进入e盘 (4)继续输入:E:\pytho…

Python封装

在用新电脑做python的封装的时候&#xff0c;出现了一系列的问题。在这里简单写一下Python的封装的一些流程以及可能出现的问题和解决方法吧。 封装我选择的是pyinstaller 首先是安装pyinstaller&#xff1a;Python 默认并不包含 PyInstaller 模块&#xff0c;因此需要自行安…

python之类的封装

博主简介&#xff1a;原互联网大厂tencent员工&#xff0c;网安巨头Venustech员工&#xff0c;阿里云开发社区专家博主&#xff0c;微信公众号java基础笔记优质创作者&#xff0c;csdn优质创作博主&#xff0c;创业者&#xff0c;知识共享者,欢迎关注&#xff0c;点赞&#xff…

Python入门——函数封装

当工程量比较大时&#xff0c;我们可以采取“函数封装”的方法实现函数的重复使用&#xff0c;避免“重复造轮子”。 步骤 手动创建一个包&#xff0c;只需进行以下 2 步操作&#xff1a; 新建一个文件夹&#xff0c;文件夹的名称就是新建包的包名&#xff1b;在该文件夹中&…

Python学习基础笔记五十八——封装

封装&#xff1a;广义上的面向对象封装。代码的保护。面向对象的思想本身就是一种封装&#xff0c;只让自己的对象能调用自己类中的方法。 狭义的封装概念&#xff1a;面向对象的三大特性之一&#xff1a;让属性和方法都藏起来&#xff0c;不让你看见。 例1&#xff1a; clas…

Python必备封装基本代码~Python函数

大家好&#xff0c;我是辣条 最近不少粉丝通过文末找到辣条让我分享一些代码封装这一块的内容&#xff0c;今天他来了~ 一遍看不懂就收起来慢慢看&#xff0c;我写的还是很详细的&#xff0c;一定是能轻松拿捏住Python函数的&#xff0c;不过还请记得多多支持辣条&#xff0c;…

PMP学习笔记顺口溜

区分几种组织结构 老板项目为系统&#xff1b; 只有职能为职能&#xff1b; 多个部门多部门&#xff0c; 多个项目为项目 职能项目为矩阵&#xff0c;项强为强&#xff0c;项弱为弱&#xff1b; 项目职能一般大 &#xff1a;平衡 网络分散为虚拟

PMP学习笔记 零 启动

PMP 学习笔记 零 启动 我为什么要学习PMP 我是一个程序猿&#xff0c;别人让我做什么&#xff0c;我就去做什么&#xff0c;提出什么需求我就完成&#xff0c;但是渐渐的我不满足成为一个工具人&#xff0c;就开始也开始设计功能&#xff0c;和抛开产品经理独自完成一些需求&…

我的PMP学习考试心得

01看书学习是基础&#xff0c;但更需要深化理解 备考期间&#xff0c;我通读了PMBOK&#xff0c;认真观看了视频教程和小红书&#xff0c;按照班主任的要求循序渐进&#xff0c;慢慢掌握了基本的知识点。因为缺少基础&#xff0c;所以只能通过反复记忆&#xff0c;特别是利用每…

PMP学习群沙龙+抽奖活动

3月考试临近&#xff0c;学员们都在积极备考。 老师们当然也不会闲着。 这不&#xff0c;就给大家带来福利了。 针对我们的PMP学习群的福利活动即将上线。 进群填写信息即可获得抽奖机会一个&#xff0c;邀请进群一人再赠送一个抽奖机会。 百分百中奖 奖品&#xff1a; 超…

PMP 学习记录

1.预算和估算 2.挣值分析 1&#xff09;三个指标&#xff1a; PV &#xff08;planned value&#xff09;&#xff0c;计划值&#xff1b; AC&#xff08;Actual Cost&#xff09;&#xff1a;实际成本&#xff0c; EV&#xff1a;Earned value&#xff0c;挣得的值&#xff1b…