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

目录

一、图像二值化处理

1.Sobel算子+绝对值

2.Sobel算子

3.倾斜角度

4.HLS颜色空间

5.二值图结合

二、车道线分割

1.仿射变换

2.车道线直方图

3.滑动窗口寻找车道线

4.车道线拟合

5.车道线区域标注

一、图像二值化处理

主要目的是通过二值化图像,使得车道线那部分图像被尽可能多地显示出来。这里使用4中方式得到不同的二值图。

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

1.Sobel算子+绝对值

转为灰度图后,将求得的x,y方向上的Sobel梯度(64位浮点数)取他们的绝对值,并进行归一化后,转为8位无符号数,根据设定的阈值筛选。

原因:标准的计算梯度的方式是下面第二种,通常,为了计算方便,也会直接取绝对值。

def abs_sobel_threshold(img, orient='x', thresh_min=2, thresh_max=1000000):
    gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
    abs_sobel = None
    if orient == 'x':
        abs_sobel = np.absolute(cv2.Sobel(gray, cv2.CV_64F, 1, 0))
    if orient == 'y':
        abs_sobel = np.absolute(cv2.Sobel(gray, cv2.CV_64F, 0, 1))
    scaled_sobel = np.uint8(255 * abs_sobel / np.max(abs_sobel))
    binary_output = np.zeros_like(scaled_sobel)
    binary_output[(scaled_sobel >= thresh_min) & (scaled_sobel

2.Sobel算子

这里是计算的实际的xy方向上的梯度大小,公式是G = np.sqrt( G(x)2 + G(y)2 )。同样也是进行归一化,转为8位无符号数。

def mag_threshold(img, sobel_kernel=3, mag_threshold=(50, 1000)):
    gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
    sobel_x = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=sobel_kernel)
    sobel_y = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=sobel_kernel)
    gradmag = np.sqrt(sobel_x ** 2 + sobel_y ** 2)
    scale_factor = np.max(gradmag) / 255
    gradmag = (gradmag / scale_factor).astype(np.uint8)
    binary_out = np.zeros_like(gradmag)
    binary_out[(gradmag >= mag_threshold[0]) & (gradmag

3.倾斜角度

求出梯度后,通过arctan求得y和x之间的角度,并根据设定的角度的阈值过滤。这里设定了0.7和1.3之间,大约是45°-80°,也就是y方向梯度比x方向的梯度要大,但不能过大,如果y方向上梯度变化很大,x方向上梯度变化很小,可能是图中明暗交界非常明显的地方,以及车道线白线和车道交界的地方。根据下面第三张图可以看出来。主要保留了左边的黄色车道线。

def dir_threshold(img, sobel_kernel=5, thresh=(0.7, 1.3)):
    '''根据倾斜角度过滤'''
    gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
    sobel_x = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=sobel_kernel)
    sobel_y = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=sobel_kernel)
    absgraddir = np.arctan(sobel_y / (sobel_x + 1e-7))
    binary_out = np.zeros_like(absgraddir)
    binary_out[(absgraddir >= thresh[0]) & (absgraddir

4.HLS颜色空间

将原来的RGB图像,转到HLS颜色空间,并取出S通道,根据阈值,对S通道进行筛选。

原因:车道线是白色以及黄色,训练图像是深色的路面以及其他标识,因此将颜色转换到HLS色域,该颜色空间的三种表示方式分别是:Hue(色相)、Saturation(饱和度)、Lightness(亮度)。饱和度越低,颜色越偏向于灰色。亮度越高,颜色越偏向于白色。色相越高,颜色越鲜艳。因此,由于训练图整体是偏灰色的,把饱和度高的这一部分的提取出来,会更容易取出车道线。

def hls_thresh(img, thresh=(100, 255)):
    hls = cv2.cvtColor(img, cv2.COLOR_RGB2HLS)
    _, _, s_channel = cv2.split(hls)
    binary_output = np.zeros_like(s_channel)
    binary_output[(s_channel >= thresh[0]) & (s_channel

5.二值图结合

以上四种方式,根据调整的阈值不同,可能有不同的效果,我们根据四个输出,对其进行融合,得到最终的二值图。

def combined_threshold(img):
    abs_bin = abs_sobel_threshold(img, orient='x', thresh_min=50, thresh_max=255)
    mag_bin = mag_threshold(img, mag_threshold=(50, 255))
    dir_bin = dir_threshold(img, sobel_kernel=5, thresh=(0.7, 1.3))
    hls_bin = hls_thresh(img, thresh=(170, 255))
    combined = np.zeros_like(dir_bin)
    combined[(abs_bin == 255) & (mag_bin == 255) & (dir_bin == 255) | (hls_bin == 255)] = 255
    return combined, abs_bin, mag_bin, dir_bin, hls_bin

以上四种以及二值图结合后效果如下:

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

二、车道线分割

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

1.仿射变换

SRC是实际图像的车道线所在的位置四点坐标,原图是1280*720,选出了一个梯形的车道线。DST是目标图像四点坐标,是一个矩形,要把车道线映射到这个矩形上。也就是说我们要把原图中,有畸变的车道线,变成一个俯视图的车道线。因此这里先获取到变换矩阵m以及逆变换矩阵m_inv。

import cv2
import numpy as np
import matplotlib.pyplot as plt

src = np.float32([[200, 720], [1100, 720], [595, 450], [685, 450]])
dst = np.float32([[300, 720], [980, 720], [300, 0], [980, 0]])
m_inv = cv2.getPerspectiveTransform(dst, src)
m = cv2.getPerspectiveTransform(src, dst)

2.车道线直方图

(这里首先把原图的1280720转为了7201280,也就是行和列的表示方式,以方便矩阵运算。)

由于已经转为了二值图,车道线在图的下半部分,于是我们直接把下半部分,沿着高的方向求和,就可以得到直方图了。从1280/2=640也就是图的中心位置,左右两侧分别从100开始,找到峰值,峰值所在的地方,就是车道线的地方。

传统方法车道线标注及相关知识
    histogram = np.sum(binary_warped[int(binary_warped.shape[0] / 2):, :], axis=0)
    midpoint = np.int(histogram.shape[0] / 2)
    leftx_base = np.argmax(histogram[100:midpoint]) + 100
    rightx_base = np.argmax(histogram[midpoint:binary_warped.shape[1] - 100]) + midpoint

3.滑动窗口寻找车道线

首先选出图像中的非零点,并返回非零点的x和y坐标,这张图里面有23819个非零点,那么x和y就全都是23819维的向量。 注意:矩阵的行,是坐标中的y,矩阵的列,是坐标中的x。

滑动窗口的方式是由上到下,窗口的起始位置,就是峰值所在的像素点的位置。窗口宽度这里设定为200,因此需要中心点左边-100,右边+100。高度就是根据窗口数量,用总高度除以数量所得。

有了窗口之后,只需要判断非零点的xy坐标全都在窗口内即可, 在的话,就记录他的索引,用这个索引可以在23819那么多的非零点里面,取得对应的点的坐标。对于左右车道线都进行同样的操作。当出现了车道线之后,根据索引,取得其像素坐标,根据均值作为新的窗口中心点,继续往下移动。

最后把两侧车道线的xy坐标都合并起来。

总结:这种滑动的方式寻找的车道线,只适合车道线比较清晰的路面,如果路面上很多车道线都磨没了,这种方式很可能就找不到车道线了,但是深度学习的方式可以自动脑补出来车道线。

滑动窗口查找车道线
    nwindows = 9
    window_height = np.int(binary_warped.shape[0] / nwindows)
    nonzero = binary_warped.nonzero()
    nonzeroy = np.array(nonzero[0])
    nonzerox = np.array(nonzero[1])
    leftx_current = leftx_base
    rightx_current = rightx_base
    margin = 100
    minpix = 10
    left_lane_inds = []
    right_lane_inds = []
    for window in range(nwindows):
        win_y_low = binary_warped.shape[0] - (window + 1) * window_height
        win_y_high = binary_warped.shape[0] - window * window_height
        win_xleft_low = leftx_current - margin
        win_xleft_high = leftx_current + margin
        win_xright_low = rightx_current - margin
        win_xright_high = rightx_current + margin

        # 非零y的索引在窗口内,非零x的索引也在窗口内,只记录行索引即可,后面通过索引寻找
        good_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]
        left_lane_inds.append(good_left_inds)
        right_lane_inds.append(good_right_inds)
        if 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]))
    left_lane_inds = np.concatenate(left_lane_inds)
    right_lane_inds = np.concatenate(right_lane_inds)

    leftx = nonzerox[left_lane_inds]
    lefty = nonzeroy[left_lane_inds]
    rightx = nonzerox[right_lane_inds]
    righty = nonzeroy[right_lane_inds]

4.车道线拟合

通过np.polyfit,对车道线进行拟合,根据多项式函数的特性,如果变量的幂比较高,是可以出现曲线的,因此这里设为最高3次幂,基本上就足够用来拟合了。返回的是四个值,分别是三次项参数、二次项参数、一次项参数、以及偏置。由于我们是通过x来计算y,因此方法中传入的是y和x。

有了参数,就可以创建y坐标了,这里的undist就是原图,读出来后是720*1280,因此y坐标就是0到719。根据得到的多项式参数,就可以算出左侧车道线以及右侧车道线的x坐标了。

    para_l = np.polyfit(lefty, leftx, deg=3)
    para_r = np.polyfit(righty, rightx, deg=3)
    ploty = np.linspace(0, undist.shape[0] - 1, undist.shape[0])
    left_fitx = para_l[0] * ploty ** 3 + para_l[1] * ploty ** 2 + para_l[2] * ploty + para_l[3]
    right_fitx = para_r[0] * ploty ** 3 + para_r[1] * ploty ** 2 + para_r[2] * ploty + para_r[3]

5.车道线区域标注

这里用一张黑色的图,把车道线的区域标出来。

    # 生成一张黑图,做mask,将车道线区域标注出来
    color_warp = np.zeros((720, 1280, 3), dtype='uint8')
    pts_left = np.array([np.transpose(np.vstack([left_fitx, ploty]))])
    pts_right = np.array([np.flipud(np.transpose(np.vstack([right_fitx, ploty])))])
    pts = np.hstack((pts_left, pts_right))
    cv2.fillPoly(color_warp, np.int_([pts]), (0, 255, 0))

对于左侧车道线,y是从0到719,也就是从上到下,因此,左侧车道线是从右到左的x坐标,由大到小排列,坐标顺序就是下图中的从上到下。

对于右侧也是一样,但是对于cv2.fillPoly来说,需要提供的是轮廓点,如果想生成下面 左图这样的填充好的图的话,顺序应当是左上,左下,右下,右上,这样才是一个完整的轮廓,因此需要把右侧的坐标上下反转,用来在画图时,和左侧进行连接。

填充完毕,就可以逆变换了,使用前面已经准备好的矩阵进行逆变换。最后通过addWeighted方法,把变换好的mask和原图融合在一起,得到结果。

newwarp = cv2.warpPerspective(color_warp, m_inv, imgOut_size, flags=cv2.INTER_LINEAR)
result = cv2.addWeighted(img, 0.5, newwarp, 0.5, 0)

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

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

Original: https://blog.csdn.net/Swayzzu/article/details/122357709
Author: Swayzzu
Title: 传统方法车道线标注及相关知识

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/641552/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球