车道线检测-从单车道到多车道的车道线检测(三)

车道线检测

····这里是车道线检测第三篇,也是最后一篇,睿智小编,在线码字。
····车道线检测,从易到难:单直线车道检测–>单弯道车道检测–>多直线车道检测–>多弯道车道检测
····我更希望用例子去说明,多按图说话。本篇分三个内容:

···· 1.讲解Udacity的CarND-LaneLines-P1-master项目
···· 2.讲解Udacity的CarND-Advanced-Lane-Lines-master项目
···· 3.讲解我在这基础上改进的multi-lane-lines-detection项目

Multi-Lane-lines-detection

···基于前两篇文章的思路进行改进,得到实时多车道检测算法。
详细代码见 https://github.com/wisdom-bob/multi-lane-lines-detection
在这里插入图片描述
····如图所示,为项目结果图,边缘拟合度高,基于hough变换,实时性高,效果稳定,这里直接设定最高检测三车道,满足变道功能需求,偏航预警需求,车道跟随需求。
····主要思路:对输入图像通过灰度化、高斯滤波、canny边缘检测;再框选出感兴趣区域,对感兴趣区域内的二值化点集进行hough变换,得到目标直线集;基于车道宽度和车道线斜率筛选数据,再对直线集进行分类、合并得到目标车道线,设计滚动平滑器,输出平滑变化车道线。

思路讲解

····我们通过一帧图片来说明处理过程。
在这里插入图片描述
····如图[image]所示为图像原图,通过灰度化处理、高斯滤波,基于Canny算子对图像进行边缘检测二值化,关于canny的相关理论可以见车道线检测(一),基于ROI得到我们需要的目标检测区域,到这里就完成了车道线的图像预处理。

#canny
gray = grayscale(image)
blur_gray = gaussian_blur(gray,7)
edges = canny(blur_gray, 65, 170)
masked_edges = region_of_interest(edges,[interest])

#  img, rho, theta, threshold, min_line_len, max_line_gap
lines = hough_lines(masked_edges, 1, np.pi/180, 15, 1, 130)
result = weighted_img(image, lines, 0.9, 0.9)

····针对[roi]图像,进行hough变换,得到结果如[lines]所示,针对这些直线根据角度值进行筛选,合并和剔除,得到最终车道线,如图[line],完成多车道检测,不过从图像检测结果也可以看出算法并没有检测出右侧车道,虽然二值化已经得到明显的右侧道结果,但由于点之间间隔较大且均匀,而被忽略掉了。

重要函数解释

基于车道线检测(一)、(二),主要进行改变的地方就是增加了平滑器,改进了hough_lines函数,实现了多车道检测功能,下面针对改进的这两个函数,hough_linesSmooth进行讲解说明。

hough_lines

执行Hough变换,基于输出结果lines,进行角度筛选,分类合并,输出结果(当然中间还要经过Smooth平滑器)。
在这里插入图片描述
我们明确针对三车道检测,根据车道宽度3.7m,可以算出四条车道线的角度范围,如上图所示,我们可以发现|k|>0.2,基于角度条件筛选车道线;转化车道线特征为斜截式(y=530为底线),对车道线进行分类,把其中截距和斜率近似的车道线归为一类;基于分类结果,剔除匹配目标太少的目标,得到当帧的稳定检测结果,传入Smooth进行数据记录;Smooth,针对记录数据,输出平滑结果,作为当帧输出结果。

	def hough_lines(img, rho, theta, threshold, min_line_len, max_line_gap):
    # 基于houghLinesP的结果进行特征处理,转化直线特征成斜截式,基于相似度函数对直线进行分类,
    # 剔除数量过少的目标,把车道线均值传入平滑器
    
    # a为声明全局Smooth类,用法下文介绍
    global a
    
    # 预处理后的图像传入houghlinesP,完成hough变换
    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)
    lines2 = []
    try:
	    # 遍历检测结果,根据k取值,筛选车道线结果
        for line in lines:
            for x1,y1,x2,y2 in line:
                if abs(y1-y2) < 10:#10
                    continue
                k = float(y2-y1)/(x2-x1)
                if abs(1/k)>5:
                    continue
                # 输出结果统一,转化为斜截式
                if y1 > y2:
                    extend = int(x2 + (height-y2)/k)
                    lines2.append([x2-x1, y2, k, extend])
                elif y1 < y2:
                    extend = int(x1 + (height-y1)/k)
                    lines2.append([x2-x1, y1, k, extend])
        lines3 = [[lines2[0]]]
        
		# 基于相似性函数,对车道线进行分类,把同一条车道线数据分成一类,这里需要调参mindif
        for j in range(len(lines2)):
            mindif=10
            tempid=0
            side=lines2[j]
            for ID in range(len(lines3)):
                temp = SimularMea1(lines3[ID][0][2],lines3[ID][0][3],side[2],side[3])# 相似度计算函数
                if temp < mindif:
                    mindif = temp
                    tempid = ID    
            if mindif > 7:
                lines3.append([side])
            else:
                lines3[tempid].append(side)
        
        # 基于分类结果,取平均值,得到lines4,并把结果传输给smooth,这里对+=操作重载操作
        lines4=[]
        for side in lines3:
            if len(side)<3:
                continue
            side=np.array(side)
            h2 = side[:, 1].min()
            side[:,0] /= side[:,0].min()
            k1 = np.average(side[:,2], weights=side[:,0])
            x1 = np.average(side[:,3], weights=side[:,0])
            lines4.append([int(x1), height, int(x1-(height-h2)/k1), int(h2)])
        a += np.array(lines4)
    except:
        pass
    # 从smooth中取出平滑结果,作为当帧输出。
    lines5 = a.data.mean(axis=0)
    draw_lines(line_img, [lines5], thickness=5)
    return line_img

下面对平滑器进行讲解说明

# 滚动平滑器,通过滚动平滑,不断更新记录的车道线,每条车道线保留window_size个数据,输出为平均值,
# 使车道线显示平滑更新,结果更稳定,鲁棒性更高。
class Smooth:
    def __init__(self, windowsize=10):
        # 初始化,window_size代表同一根车道线最多保留多少帧结果,实际输出为平均值!
        # index记录帧数,lenth记录有效车道数,threshold需要调参,time记录每条车道线的检测数,VP记录灭点像素坐标
        # data,保留window_size帧数据,最多10条车道线,每条车道线有4个参数
        self.window_size = windowsize
        self.data = np.zeros((self.window_size, 10, 4), dtype=np.float32)
        self.index = 0
        self.lenth = 0
        self.threshold = 300
        self.time = np.array([0,0,0,0,0,0,0,0,0,0],dtype=np.int8)
        self.VP = None
    
    def __iadd__(self, x):# 对接重载的+=操作
        if self.index == 0:
		# 初始化
            self.lenth = len(x)
            self.data[:,0:self.lenth] = x 
            self.time[0:self.lenth] = 2
        index = self.index % self.window_size
        
        # 当帧车道线与跟踪数据对比,判断是否属于,是则加入data中作为更新值,否则丢弃数据或者作为新跟踪目标;当有灭点时,
        # 判断是否经过灭点,去掉不经过的车道线;实时更新time值,>0为有效目标,否则无效;每3帧更新一次灭点和跟踪数目。
        for data in x:
            mindif = self.threshold
            tempid = 0
            for ID in range(self.lenth):
                Data = self.data[index][ID]
                temp = abs(data[0]-Data[0])# 另外构建SimularMea2代入
                if temp < mindif:
                    mindif = temp
                    tempid = ID
            if self.VP is not None:
                if disP2L(self.VP,data)>15:# 灭点到线距离,大于阈值15,则认为不经过灭点,需要调参。
                    continue                
            # 若未检测到,则增设为新的跟踪车道线,否则作为某条车道线在平滑器中记录的当帧更新值
            if mindif == self.threshold:
                self.data[:,self.lenth] = data
                self.time[self.lenth] = 3
                self.lenth += 1
            else:
                self.data[index][tempid] = data
                self.time[tempid] += 2
        self.time[self.time>0] -=1
		
		# 每3帧,计算一次灭点,剔除无效车道线。
        if self.index % 3 == 0:
            targetid=np.nonzero(self.time)
            templen=len(targetid[0])
            
            if templen != self.lenth:
                temp=self.data[:,targetid,:].mean(axis=0)
                self.data=np.zeros((self.window_size, 10, 4), dtype=np.float32)
                self.data[:,0:templen,:] = temp
                
                temp=self.time[targetid]
                self.time = np.array([0,0,0,0,0,0,0,0,0,0],dtype=np.int8)
                self.time[0:templen]=temp
            
                self.lenth=templen

            temp=self.data.mean(axis=0)
            if self.lenth >2:
                self.VP=CatchVanishP(temp,self.lenth)        
 
        self.index += 1
        return self

总结和拓展

····至此,完成基于Hough变换的改进直线车道多车道检测,只在cpu上跑可达到50帧,满足实时性要求,算是非常好的结果,基于滚动平滑器,车道线检测结果非常稳定,只是偶尔会有错帧,效果好了许多,完整代码见github。
····从优化角度去考虑,还有以下可改进地方:hough变换效果有待提高,偶尔良好的二值化结果却无法实现变换,是个糟糕的事情;相似度函数可以进一步改进,提高匹配度,进而有更好的结果,偶尔的错帧偏移都是由于匹配不够好,分类效果不够好导致的。
····快去试试吧~~

  • 5
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值