无人驾驶虚拟仿真(七)--图像处理之车道线识别2

简介:上一节中我们计算得到了前视图到俯视图的投影变换矩阵,在这一节中我们要完成车道线的检测。

当车辆摄像头获取到一张图像,我们大致需要经过以下步骤来得到车道线数据:

1、预设参数

在整体过程中,我们需要预设以下变量参数

#!/usr/bin/env python3

import cv2
import numpy as np
import math
import time
from matplotlib import pyplot as plt

class LineDetect():
    def __init__(self):
        #图像缩小比例,减少运算量
        self.scale = [160,120]
        #边缘检测参数设置
        self.canny_thresholds = [80,200]
        self.canny_aperture_size = 3
        self.dilation_kernel_size = 3
        #霍夫变换参数设置
        self.hough_threshold = 2
        self.hough_min_line_length = 3
        self.hough_max_line_gap = 1   
        #色彩平衡参数设置
        self.lower_thresholds = [53, 57, 59]
        self.higher_thresholds = [189, 189, 190]
        #投影变换参数设置
        self.M = np.array([-1.02032622e+00,-2.48368883e+00,6.44725345e+02,2.54332970e-16,-4.61731493e+00,9.22218319e+02,-8.37904170e-19,-7.77917189e-03,1.00000000e+00])
        self.M = np.reshape(self.M, (3, 3))      

2、图像预处理:转俯视图,等比例缩小,色彩平衡

 #图像预处理
  def prev_cb_image(self, image):
      #转俯视图
      fs_img = cv2.warpPerspective(image,self.M,(640,480))
      #等比例缩小
      scale_img = cv2.resize(fs_img, (self.scale[0],self.scale[1]), interpolation=cv2.INTER_NEAREST)
      #色彩平衡调整
      color_banlance_img = self.apply_color_balance(self.lower_thresholds, self.higher_thresholds, scale_img)
      cv2.imshow("fs_img",fs_img)
      cv2.imshow("scale_img",scale_img)
      cv2.imshow("color_banlance_img",color_banlance_img)
      cv2.waitKey(0)
      return color_banlance_img

  def apply_color_balance(self, lower_threshold, higher_threshold, image, scale=1):
      if lower_threshold is None:
        	return None
      resized_image = cv2.resize(image, (0, 0), fx=scale, fy=scale)
      channels = cv2.split(resized_image)
      out_channels = []
      for idx, channel in enumerate(channels):
          thresholded = self.apply_threshold(channel, lower_threshold[idx], higher_threshold[idx])
          normalized = cv2.normalize(thresholded, thresholded.copy(), 0, 255, cv2.NORM_MINMAX)
          out_channels.append(normalized)
      return cv2.merge(out_channels)

  def apply_threshold(self, matrix, low_value, high_value):
      low_mask = matrix < low_value
      matrix = self.apply_mask(matrix, low_mask, low_value)
      high_mask = matrix > high_value
      matrix = self.apply_mask(matrix, high_mask, high_value)
      return matrix

  def apply_mask(self, matrix, mask, fill_value):
      masked = np.ma.array(matrix, mask=mask, fill_value=fill_value)
      return masked.filled() 
  
if __name__=='__main__':
  node = LineDetect()
  image = cv2.imread("images/line.png")
  color_banlance_img = node.prev_cb_image(image)
  cv2.destroyAllWindows()


3、车道线边缘检测

def getEdges(self, image):    
        #图像转化为HSV格式
        hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) 
        #检测图像中的边缘
        edges = cv2.Canny(image,self.canny_thresholds[0],self.canny_thresholds[1],apertureSize=self.canny_aperture_size)
        cv2.imshow('edges', edges)
        #检测图像中白色、黄色、红色的区域
        white_map = cv2.inRange(hsv, np.array([0,0,150]),np.array([180,30,255]))
        yellow_map = cv2.inRange(hsv, np.array([26,43,100]),np.array([34,255,255]))
        red_map = cv2.inRange(hsv, np.array([0,43,46]),np.array([15,255,255]))
        red_map |= cv2.inRange(hsv, np.array([156,43,100]),np.array([180,255,255]))
        #膨胀腐蚀内核设置,单倍内核
        kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (self.dilation_kernel_size, self.dilation_kernel_size))
        #白色、黄色区域进行膨胀处理
        white_map = cv2.dilate(white_map, kernel)
        yellow_map = cv2.dilate(yellow_map, kernel)
        #对红色区域特殊处理,先单核腐蚀,在双核膨胀,减少干扰
        red_map = cv2.erode(red_map, kernel)  
        kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (self.dilation_kernel_size*2, self.dilation_kernel_size*2))
        red_map = cv2.dilate(red_map, kernel)     
        #边缘与各颜色区域求交集,即为各颜色区域的边缘数据
        edge_white = cv2.bitwise_and(white_map, edges)
        edge_yellow = cv2.bitwise_and(yellow_map, edges)
        edge_red = cv2.bitwise_and(red_map, edges)
        
        cv2.imshow('edge_white', edge_white)
        cv2.imshow('edge_yellow', edge_yellow)
        cv2.imshow('edge_red', edge_red)
        cv2.waitKey(0)        
        return edge_white,edge_yellow,edge_red  

主函数

if __name__=='__main__':
    node = LineDetect()
    image = cv2.imread("images/line.png")
    color_banlance_img = node.prev_cb_image(image)    
    cv2.destroyAllWindows()
    edge_white,edge_yellow,edge_red = node.getEdges(color_banlance_img)
    cv2.destroyAllWindows()

注:由于途中没有红色区域,所以检测不到红色边缘。


4、检测边缘中的的直线

def getLines(self, edges):
        #检测边缘中的线段,线段以起点终点坐标体现
        lines = cv2.HoughLinesP(edges,rho=1,theta=np.pi / 180,threshold=self.hough_threshold,minLineLength=self.hough_min_line_length,maxLineGap=self.hough_max_line_gap)
        if lines is not None:
            #重新整理数据格式
            lines = lines.reshape((-1,4))
            return lines
        else:
            return None
if __name__=='__main__':
    node = LineDetect()
    image = cv2.imread("images/line.png")
    color_banlance_img = node.prev_cb_image(image)    
    cv2.destroyAllWindows()
    edge_white,edge_yellow,edge_red = node.getEdges(color_banlance_img)
    cv2.destroyAllWindows()
    
    white_lines = node.getLines(edge_white)
    yellow_lines = node.getLines(edge_yellow)
    red_lines = node.getLines(edge_red)
    
    fig = plt.figure(figsize=(20, 20))
    ax1 = fig.add_subplot(1, 2, 1)
    ax1.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
    ax1.set_title('Input image'), ax1.set_xticks([]), ax1.set_yticks([])
    
    ax2 = fig.add_subplot(1, 2, 2)
    ax2.set_title('lines')

    if white_lines is not None:       
        for line in white_lines:
            [x1, y1, x2, y2] = line
            X = np.array([x1, x2])
            Y = np.array([y1, y2])  
            c = 'b-'            
            ax2.plot(X, Y, c)
    if yellow_lines is not None:       
        for line in yellow_lines:
            [x1, y1, x2, y2] = line
            X = np.array([x1, x2])
            Y = np.array([y1, y2])  
            c = 'g-'            
            ax2.plot(X, Y, c)
    if red_lines is not None:       
        for line in red_lines:
            [x1, y1, x2, y2] = line
            X = np.array([x1, x2])
            Y = np.array([y1, y2])
            c = 'r-'            
            ax2.plot(X, Y, c)
    plt.show()


5、坐标转换

上文计算出的坐标参数是以图像的坐标做为参考,原点为左上角向右为x轴正,向下为y轴正,而我们计算车辆位置时,是以车辆所在位置为原点计算的,向前为x轴正,向左为y轴正,同时计算单位也要换算成米,在虚拟仿真环境中,每块地图块尺寸为0.585米,转化为俯视图后为640像素,然后图像又缩小4倍,所以像素坐标转化为尺寸坐标需要经过以下转变:

V = v*4/640*0.585

图像坐标转观察坐标(观察坐标原点为图像坐标中的(320,480),等比例缩小后为(80,120)):

X = 120-y

Y = 80-x

所以:

观察坐标(米):

X = (120-y)*4/640*0.585

Y = (80-x)*4/640*0.585

编程实现坐标转化:

def lines_to_coors(self, white_lines,yellow_lines,red_lines):
        white_coors = []
        yellow_coors = []
        red_coors = []
        #计算黄色线段x轴平均值
        yellow_mean_x = np.mean(np.mean(yellow_lines, 0).reshape((2,2)), 0)[0] if yellow_lines is not None else 0
        if yellow_lines is not None:
            for line in yellow_lines:        
                [x1, y1, x2, y2] = line
                yellow_coors.append(self.calcRealCoor(x1,y1,x2,y2))    
        white_lines_use = None
        white_mean_x = 0
        if white_lines is not None:
            white_lines_use = []
            for line in white_lines:
                [x1, y1, x2, y2] = line
                #如果白色线段x坐标小于黄线均值,说明这条白线在黄线左侧,无参考价值,跳过删除
                if (x1+x2)/2<yellow_mean_x:
                    continue
                else:
                    white_coors.append(self.calcRealCoor(x1,y1,x2,y2))             
                    white_lines_use.append([x1, y1, x2, y2])
            white_lines_use = np.array(white_lines_use)
            if len(white_lines_use)>0:
                white_mean_x = np.mean(np.mean(white_lines_use, 0).reshape((2,2)), 0)[0]
        
        if red_lines is not None:
            for line in red_lines:
                [x1, y1, x2, y2] = line
                #如果红色线段不在白线和黄线之间,无参考价值,跳过删除
                if (yellow_mean_x!=0 and (x1+x2)/2<yellow_mean_x) or (white_mean_x!=0 and (x1+x2)/2>white_mean_x):
                    continue
                else:    
                    red_coors.append(self.calcRealCoor(x1,y1,x2,y2))
        return white_coors, yellow_coors, red_coors
    
    def calcRealCoor(self, x1, y1, x2, y2):
        X1 = (120-y1)*4/640*0.585
        Y1 = (80-x1)*4/640*0.585
        X2 = (120-y2)*4/640*0.585
        Y2 = (80-x2)*4/640*0.585   
        return [X1,Y1,X2,Y2] 

显示转化后的坐标:

white_coors, yellow_coors, red_coors = node.lines_to_coors(white_lines,yellow_lines,red_lines)
    if white_coors is not None:       
        for line in white_coors:
            [x1, y1, x2, y2] = line
            X = np.array([x1, x2])
            Y = np.array([y1, y2])  
            c = 'b-'            
            ax2.plot(X, Y, c)
    if yellow_coors is not None:       
        for line in yellow_coors:
            [x1, y1, x2, y2] = line
            X = np.array([x1, x2])
            Y = np.array([y1, y2])  
            c = 'g-'            
            ax2.plot(X, Y, c)
    if red_coors is not None:       
        for line in red_coors:
            [x1, y1, x2, y2] = line
            X = np.array([x1, x2])
            Y = np.array([y1, y2])
            c = 'r-'            
            ax2.plot(X, Y, c)
    plt.show()

由图可以大致观察出黄线和白线之间的距离大约为0.22米左右,与实际情况相符。

到此为止,我们得到了车道线边缘上的线段坐标,在实际应用中,需要转化为ROS系统中的消息格式,这一步需要引入duckietown_msgs中的Segment、以及SegmentList,具体转化我们在下一节中介绍。

若有收获,就点个赞吧

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

溪风沐雪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值