0x00 往期博文

0x01 项目介绍

之前使用yolo3实现了红绿灯识别,但是部署起来略显复杂;
本期提供Opencv识别红绿灯颜色的思路;评论区留下邮箱提供Jupyter调试文档; 

0x02 实验结果

交通灯会周期性地切换颜色显示状态。
机器人识别信号灯状态,做到"红灯停,绿灯行,黄灯等一等"。
Tips: 想象一下该实验结合循迹实验,我们可以做出一些有趣的场景;

0x03 导入依赖库

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

0x04 获取测试图片

分别准备红灯、绿灯、黄灯的照片;

打开一张亮灯和未亮灯的图像;用作对比

img = cv2.imread('./img/yellow_light2.jpg')      # 亮灯时的照片
img_n_l = cv2.imread('./img/none_light.jpg') # 未亮灯时的图像

0x05 将图像转为LAB色彩空间;

使用LAB色彩空间处理的优势:

  • LAB的三个分量描述和红绿灯的亮度、颜色相吻合,更容易调试出结果

为什么不采用HSV色彩空间?

  • HSV的色相H有红、橙、黄、绿、青、蓝、紫多种颜色,相较于LAB的红、绿、蓝、黄,HSV复杂很多。除非有青色或紫色的交通灯, 皮皮虾可能会看到(皮皮虾有很多种色感细胞)。
  • HSV的饱和度S、亮度V不好控制,不如LAB的L,一个分量识别出灯的亮灭。
img_lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
img_n_l_lab = cv2.cvtColor(img_n_l, cv2.COLOR_BGR2LAB)

0x06 调整LAB色彩空间阈值

关于 LAB 色彩空间的知识请参考往期博文:旭日X3pi :: 三原色识别

根据色彩空间轴进行调整,最终获得各颜色阈值为:

lab_data = {
        'red': {'lower': (73, 159, 154), 'upper': (142, 199, 192)},
        'yellow': {'lower': (168, 108, 168), 'upper': (256, 128, 256)},
        'green': {'lower': (128, 0, 128), 'upper': (256, 102, 256)}
    }

0x07 识别逻辑颜色标注

# 交通灯识别函数
def traffic_light_identify(_img):
    global traffic_light

    lab_data = {
        'red': {'lower': (73, 159, 154), 'upper': (142, 199, 192)},
        'yellow': {'lower': (168, 108, 168), 'upper': (256, 128, 256)},
        'green': {'lower': (128, 0, 128), 'upper': (256, 102, 256)}
    }

    # color img dict
    c_c_d = {
        'red': None,
        'yellow': None,
        'green': None
    }

    img_debug = _img.copy()

    # 中值滤波降噪
    imt_blur = cv2.blur(_img, (9, 9))

    # 转换到LAB色彩空间
    img_lab = cv2.cvtColor(imt_blur, cv2.COLOR_BGR2LAB)

    # 遍历lab颜色特征
    for _c in lab_data:
        # 图像掩膜
        _img_mask = cv2.inRange(img_lab, lab_data[_c]['lower'], lab_data[_c]['upper'])
        cv2.imshow(_c + 'inRange', _img_mask)

        # 找出所有外轮廓
        contours, _ = cv2.findContours(_img_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)

        if len(contours) == 0:
            continue

        # 对轮廓对象按面积进行排序, 降序, 面积最大的为序号为0
        c_sorted = sorted(contours,
                          key=lambda x:
                          math.fabs(
                              cv2.contourArea(x)
                          ), reverse=True)

        c_0 = c_sorted[0]
        if math.fabs(cv2.contourArea(c_0)) > 100:  # 轮廓面积大于100时允许赋值, 否则颜色对应轮廓为None
            c_c_d[_c] = c_sorted[0]

    """
    c_c_d = {'red': 轮廓, 'yellow': 轮廓, 'green': 轮廓}  # 轮廓 可能是 None
    """
    # 处理轮廓数据
    try:
        set(list(c_c_d.values()))  # 取唯一值
        # 如果Set列表后,全是None,没有灯亮起,直接返回
        traffic_light = None  # 没有灯亮起
        return
    except TypeError:
        # 如果有轮廓数据, 继续运行;
        # 这里利用了, Set方法无法对None类型做出哈希运算,会抛出一个TypeError异常;
        # 捕获这个异常, 直接运行即可;
        pass

    _l = None  # 根据面积比较,找出交通灯颜色
    area_max = 100
    for _c in lab_data:
        if c_c_d[_c] is not None:
            area_tmp = math.fabs(cv2.contourArea(c_c_d[_c]))
            if area_tmp > area_max:
                area_max = area_tmp
                _l = _c

    traffic_light = _l  # 取颜色值

代码写的很优美,像小恐龙;

评论区留下邮箱,提供完整的Jupyter文档;