无人驾驶虚拟仿真(十二)--图像处理之红绿灯识别

简介:在交通地图中有红绿灯组件,一般放置在T形或者十字路口处,车辆行驶过程中,需要检测红绿灯信号来控制车辆启停,以符合基本的交通规则。红绿灯识别基本分为图像截取、斑点检测、颜色识别三步来实现。

1、新建地图

为了完成红绿灯检测,我们需要创建一个有红绿灯的仿真环境,地图文件如下:

注:地图文件保存路径请参考第一章说明,同时修改duckiebot_node配置文件内的map-name配置

tiles:
- [floor  , floor       , floor     , floor      , floor         , floor         , floor         , floor  ]
- [floor  , curve_left/W, straight/W, 3way_left/W, straight/W    , straight/W    , curve_left/N  , floor  ]
- [floor  , straight/S  , floor     , straight/N , floor         , floor         , straight/N    , floor  ]
- [floor  , 3way_left/S , straight/W, 4way       , straight/E    , straight/E    , 3way_left/N   , floor  ]
- [floor  , straight/S  , floor     , straight/S , floor         , floor         , straight/N    , floor  ]
- [floor  , straight/S  , floor     , straight/S , floor         , curve_right/N , curve_left/E  , floor  ]
- [floor  , curve_left/S, straight/E, 3way_left/E, straight/E    , curve_left/E  , floor         , floor  ]
- [floor  , floor       , floor     , floor      , floor         , floor         , floor         , floor  ]

objects:
  trafficlight1:
    kind: trafficlight
    place:
      tile: [3,4]
      relative:
        ~SE2Transform:
          p: [-0.18,-0.18]
          theta_deg: 135
    height: 0.3
    optional: true
  trafficlight2:
    kind: trafficlight
    place:
      tile: [6,4]
      relative:
        ~SE2Transform:
          p: [-0.18,-0.18]
          theta_deg: 135
    height: 0.3
    optional: true
  trafficlight3:
    kind: trafficlight
    place:
      tile: [1,4]
      relative:
        ~SE2Transform:
          p: [-0.18,-0.18]
          theta_deg: 135
    height: 0.3
    optional: true
  trafficlight4:
    kind: trafficlight
    place:
      tile: [3,6]
      relative:
        ~SE2Transform:
          p: [-0.18,-0.18]
          theta_deg: 135
    height: 0.3
    optional: true
  trafficlight5:
    kind: trafficlight
    place:
      tile: [3,1]
      relative:
        ~SE2Transform:
          p: [-0.18,-0.18]
          theta_deg: 135
    height: 0.3
    optional: true

tile_size: 0.585

地图是一个8*8的地图,周围一圈空地,实际面积是6*6的,有4个丁字路口,一个十字路口,每个路口预设一个红绿灯,并有一个连续弯道:

参考前面章节,截两张红绿灯的图片:


2、图片截取

当车辆运行到红绿灯附近时,红绿灯在车辆视野中的位置基本都是偏上偏左,所以截取左上区域的图片进行处理,以减少计算量:

h, w, n = image.shape #获取原始图片尺寸
crop_norm = [[0.0, 0.2], [0.0, 0.6]] #设定截取比例,上下:0~20%,左右0~60%
h_start = int(np.floor(h * crop_norm[0][0]))
h_end = int(np.ceil(h * crop_norm[0][1]))
w_start = int(np.floor(w * crop_norm[1][0]))
w_end = int(np.ceil(w * crop_norm[1][1]))
img_tl = image[h_start:h_end, w_start:w_end, :] #获得交通灯所在区域图像

3、斑点检测

斑点检测采用霍夫圆检测的方法

HoughCircles(gray,cv2.HOUGH_GRADIENT,5,15,param1,param2,minRadius,maxRadius)

gray:对一幅彩色图片进行处理之后的单通道灰度图片

HOUGH_GRADIENT:是使用霍夫梯度法检测圆

5:这个参数是double类型的dp,用来检测圆心的累加器图像的分辨率于输入图像之比的倒数,且此参数允许创建一个比输入图像分辨率低的累加器。上述文字不好理解的话,来看例子吧。例如,如果dp= 1时,累加器和输入图像具有相同的分辨率。如果dp=2,累加器便有输入图像一半那么大的宽度和高度

15:被检测到的圆心的最小距离. 如果该参数很小, 除了一个正确的圆之外, 该圆的邻居也可能被错误地检测出来. 如果该参数很大,一些圆将可能被错过

param1:此参数是对应Canny边缘检测的最大阈值,最小阈值是此参数的一半 也就是说像素的值大于param1是会检测为边缘

param2:它表示在检测阶段圆心的累加器阈值。它越小的话,就可以检测到更多根本不存在的圆,而它越大的话,能通过检测的圆就更加接近完美的圆形了

minRadius:表示能够检测的最小圆的半径

maxRadius:表示能够检测的最大圆的半径

#霍夫圆检测需要用灰度图
gray = cv2.cvtColor(img_tl, cv2.COLOR_BGR2GRAY)
#霍夫圆检测:
circles = cv2.HoughCircles(gray,cv2.HOUGH_GRADIENT,5,15,param1=120,param2=40,minRadius=3,maxRadius=15)   

3、感兴趣区域截取:根据检测到的圆的圆心坐标和半径,截取红绿灯图片

if circles1 is not None:
    circles1 = np.uint16(np.around(circles1)) #数据转化为整数
    for circle in circles1[0,:]: #圆心靠近边界防止坐标成为负数,适当扩大范围
        if circle[0] < circle[2]:
            circle[0] = circle[2]
        if circle[1] < circle[2]:
            circle[1] = circle[2]
        #截取红绿灯图片
        roi = image1[(circle[1]-circle[2]):(circle[1]+circle[2]),(circle[0]-circle[2]):(circle[0]+circle[2])]
        #检测颜色
        r = detectColor(roi)

4、检测颜色

在截取到的红绿灯图片中通过处理计算红色点与绿色点的数量,数量大的颜色且数量超过60,认定为该颜色。

def detectColor(image):
    #转化为HSV颜色格式
    hsv_img = cv2.cvtColor(image,cv2.COLOR_BGR2HSV)
    #设定颜色检测范围
    red_min = np.array([0,43,46])
    red_max = np.array([10,255,255])
    red_min2 = np.array([156,43,46])
    red_max2 = np.array([180,255,255])
    green_min = np.array([35,43,46])
    green_max = np.array([77,255,255])
    #利用cv2.inRange函数设阈值,去除背景部分
    red_thresh = cv2.inRange(hsv_img,red_min,red_max)+cv2.inRange(hsv_img,red_min2,red_max2)
    green_thresh = cv2.inRange(hsv_img,green_min,green_max)
    # 进行中值滤波
    red_blur = cv2.medianBlur(red_thresh,5)
    green_blur = cv2.medianBlur(green_thresh,5)
    #计算红色和绿色的点的数量
    red = cv2.countNonZero(red_blur)
    green = cv2.countNonZero(green_blur)
    #取较大值
    lightColor = max(red,green)
    if lightColor > 60: #有效点数量大于60认为检测到红绿灯
        if lightColor == red: #红色返回1
            return 1
        elif lightColor == green: #绿色返回-1
            return -1
    else: #否则认定为误检测,返回0
        return 0

5、统计结果

同一张图片中可能存在误检测出来的圆,统计所有圆颜色检测结果,结果求和,和大于0认定检测到红灯,小于0检测到绿灯:

image1 = cv2.imread("images/red.png")
image2 = cv2.imread("images/green.png")
circles1 = detectState(image1)
circles2 = detectState(image2)
result1 = 0
result2 = 0
if circles1 is not None:
    circles1 = np.uint16(np.around(circles1))
    for circle in circles1[0,:]:
        if circle[0] < circle[2]:
            circle[0] = circle[2]
        if circle[1] < circle[2]:
            circle[1] = circle[2]
        roi = image1[(circle[1]-circle[2]):(circle[1]+circle[2]),(circle[0]-circle[2]):(circle[0]+circle[2])]
        r = detectColor(roi)
        result1 += r
if result1>0:
    cv2.imshow("image1--red", image1)
elif result1<0:
    cv2.imshow("image1--green", image1) 

if circles2 is not None:
    circles2 = np.uint16(np.around(circles2))
    for circle in circles2[0,:]:
        if circle[0] < circle[2]:
            circle[0] = circle[2]
        if circle[1] < circle[2]:
            circle[1] = circle[2]
        roi = image2[(circle[1]-circle[2]):(circle[1]+circle[2]),(circle[0]-circle[2]):(circle[0]+circle[2])]
        r = detectColor(roi)
        result2 += r
if result2>0:
    cv2.imshow("image2--red", image2)


6、功能移植到ROS平台

进入工作空间目录:

$ cd ~/myros/catkin_ws/src

创建新功能包:

$ catkin_create_pkg traffic_light rospy duckietown_msgs std_msgs

创建配置文件:

$ mkdir -p traffic_light/config/traffic_light_node

$ touch traffic_light/config/traffic_light_node/default.yaml

新建启动脚本:

$ mkdir -p traffic_light/launch

$ touch traffic_light/launch/start.launch

新建源码文件

$ touch traffic_light/src/traffic_light_node.py

修改编译配置文件

$ gedit traffic_light/CMakeLists.txt

修改为:

编辑配置文件:

$ gedit traffic_light/config/traffic_light_node/default.yaml

min_point_count: 60
crop_norm: [[0.0, 0.2], [0.0, 0.6]]
dp: 5
minDist: 15
param1: 120
param2: 40
minRadius: 3
maxRadius: 15

编辑源码文件:

$ gedit traffic_light/src/traffic_light_node.py

#!/usr/lib/env python3

import rospy
import cv2
import numpy as np
import math
from cv_bridge import CvBridge

from std_msgs.msg import Bool
from sensor_msgs.msg import Image,CompressedImage

class TrafficLightNode():
    def __init__(self):
        rospy.init_node("traffic_light_node", anonymous=False)
        self.bridge = CvBridge()
        #红线检测标志
        self.stop_line_flag = False
        #有效点数量
        self.min_point_count = rospy.get_param('~min_point_count', default=60)
        #红绿灯截取参数
        self.crop_norm = rospy.get_param('~crop_norm', default=[[0.0, 0.2], [0.0, 0.6]])
        #霍夫圆检测参数设置
        self.dp = rospy.get_param('~dp', default=5)
        self.minDist = rospy.get_param('~minDist', default=15)
        self.param1 = rospy.get_param('~param1', default=120)
        self.param2 = rospy.get_param('~param2', default=40)
        self.minRadius = rospy.get_param('~minRadius', default=3)
        self.maxRadius = rospy.get_param('~maxRadius', default=15)
        #订阅图像话题
        rospy.Subscriber("~image/compressed", CompressedImage, self.cb_image)
        #订阅红线检测话题,检测到红线时才进行红绿灯识别,减少计算量
        rospy.Subscriber("~detected", Bool, self.cb_stop_line)
        self.pub_tl_info = rospy.Publisher("~tlState", Bool, queue_size=10)
    
    def cb_image(self, msg):
        if self.stop_line_flag: #红线检测结果
            image = self.bridge.compressed_imgmsg_to_cv2(msg)
            result = self.detect(image)
            if result>0:
                self.pub_tl_info.publish(Bool(data=True))
            elif result<0:
                self.pub_tl_info.publish(Bool(data=False))
    
    def cb_stop_line(self, msg):
        self.stop_line_flag = msg.data
    
    def detect(self, image):
        h, w, n = image.shape
        h_start = int(np.floor(h * self.crop_norm[0][0]))
        h_end = int(np.ceil(h * self.crop_norm[0][1]))
        w_start = int(np.floor(w * self.crop_norm[1][0]))
        w_end = int(np.ceil(w * self.crop_norm[1][1]))
        img_tl = image[h_start:h_end, w_start:w_end, :]
        gray = cv2.cvtColor(img_tl, cv2.COLOR_BGR2GRAY)
        circles = cv2.HoughCircles(gray,cv2.HOUGH_GRADIENT,self.dp,self.minDist,param1=self.param1,param2=self.param2,minRadius=self.minRadius,maxRadius=self.maxRadius)   
        result = []     
        if circles is not None:
            circles = np.uint16(np.around(circles))
            for circle in circles[0,:]:
                if circle[0] < circle[2]:
                    circle[0] = circle[2]
                if circle[1] < circle[2]:
                    circle[1] = circle[2]
                roi = image[(circle[1]-circle[2]):(circle[1]+circle[2]),(circle[0]-circle[2]):(circle[0]+circle[2])]
                color = self.detectColor(roi)
                result.append(color)
            return np.sum(result)
        else:
            return 0        
                
    def detectColor(self, image):
        hsv_img = cv2.cvtColor(image,cv2.COLOR_BGR2HSV)
        red_min = np.array([0,43,46])
        red_max = np.array([10,255,255])
        red_min2 = np.array([156,43,46])
        red_max2 = np.array([180,255,255])
        green_min = np.array([35,43,46])
        green_max = np.array([77,255,255])
        red_thresh = cv2.inRange(hsv_img,red_min,red_max)+cv2.inRange(hsv_img,red_min2,red_max2)
        green_thresh = cv2.inRange(hsv_img,green_min,green_max)
        # 进行中值滤波
        red_blur = cv2.medianBlur(red_thresh,5)
        green_blur = cv2.medianBlur(green_thresh,5)
        red = cv2.countNonZero(red_blur)
        green = cv2.countNonZero(green_blur)
        lightColor = max(red,green)
        if lightColor > self.min_point_count:
            if lightColor == red:
                return 1
            elif lightColor == green:
                return -1
        else:
            return 0

if __name__=='__main__':
    node = TrafficLightNode()
    rospy.spin()

编辑启动脚本:

$ gedit traffic_light/launch/start.launch

<launch>
  <arg name="veh"/>
  <arg name="pkg_name" value="traffic_light"/>
  <arg name="node_name" value="traffic_light_node"/>
  <arg name="param_file_name" default="default" doc="Specify a param file. ex:megaman"/>
  <arg name="required" default="false" />

  <group ns="$(arg veh)">
    <remap from="traffic_light_node/image/compressed" to="duckiebot_node/image/compressed"/>
    <remap from="traffic_light_node/detected" to="stop_line_node/detected"/>
    <node name="$(arg node_name)" pkg="$(arg pkg_name)" type="$(arg node_name).py" respawn="true" respawn_delay="10" output="screen" required="$(arg required)">
      <rosparam command="load" file="$(find traffic_light)/config/$(arg node_name)/$(arg param_file_name).yaml"/>
    </node>
  </group>
</launch>

编译:

$ cd ~/myros/catkin_ws

$ catkin_make

编辑多节点启动文件

$ gedit start.launch

<launch>
  <arg name="veh" default="duckiebot"/>
  <group>
    <include file="$(find duckiebot)/launch/duckiebot.launch">
      <arg name="veh" value="$(arg veh)"/>
    </include>
    <include file="$(find anti_instagram)/launch/start.launch">
      <arg name="veh" value="$(arg veh)"/>
    </include>
    <include file="$(find line_detect)/launch/start.launch">
      <arg name="veh" value="$(arg veh)"/>
    </include>
    <include file="$(find lane_filter)/launch/start.launch">
      <arg name="veh" value="$(arg veh)"/>
    </include>
    <include file="$(find car_control)/launch/start.launch">
      <arg name="veh" value="$(arg veh)"/>
    </include>
    <include file="$(find stop_line)/launch/start.launch">
      <arg name="veh" value="$(arg veh)"/>
    </include>
    <include file="$(find traffic_light)/launch/start.launch">
      <arg name="veh" value="$(arg veh)"/>
    </include>
  </group>
</launch>

启动程序:

$ source devel/setup.bash

$ roslaunch start.launch

启动程序,经过测试,发现红线检测范围过小,会因为延迟导致不停车,修改红线检测配置文件,

alarm_dist: 0.12

修改后重新运行,可正常停车。

  • 3
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

溪风沐雪

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

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

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

打赏作者

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

抵扣说明:

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

余额充值