Opencv包含了超过2500个函数,几乎任何一个能想到的成熟算法都可以通过调用Opencv的函数来实现,超级方便。这篇博文结合了Python与Opencv来实现在监控视频中识别汽车并检测其是否违规(压双黄线)。

系统环境:Ubuntu16.04LTS+OpenCV3.3.0+Python2.7.12

背景提取

对于监控来说,其位置相对固定,所以背景在长时间内不会随时间的改变而产生相当大的改变。所以可以考虑使用背景差分法来粗略地提取出背景。

在这里我使用的是Opencv中自带的KNN算法,该算法利用监控视频中前面一小部分作为训练帧来训练模型得到大概的背景图像。

当然OpenCV已经实现了几种非常容易使用的算法。具体差异可以参考:背景减除

# coding = utf-8

import numpy as np
import cv2

cap = cv2.VideoCapture('filename')
#指定训练帧数为30
trainframe = 30
#KNN算法背景差分
bgsubstractor = cv2.creatBackgroundSubtractorKNN(detectShadow = True)

预处理

对视频帧依据视频分辨率进行中值滤波处理。对不同的视频分辨率,使用不同的核参数。例如:视频分辨率为240X320,选用的核参数为5X5。

中值滤波后将图像转为灰度图像进行高斯模糊,通过试验可以得到以适合的核参数。 之后是边缘检测,也是通过多次试验得到最低和最高阈值。 最后是进行形态学处理,腐蚀、膨胀等操作,在这里使用默认核,迭代次数为2。

#对原始帧进行中值滤波处理
gray = fg_mask.copy()
gray = cv2.medianBlur(gray, 11, 0)

#图像预处理
thresh = cv2.threshold(gray, 244, 255, cv2.THRESH_BINARY)[1]
thresh = cv2.erode(thresh, None, iterations = 2)
thresh = cv2.dilate(thresh, None, iterations = 2)

运动目标检测

使用Opencv中的findContours()函数获取视频对应帧中的检测框。

注意:应该使用视频帧的副本

选用的轮廓检索方式为cv2.RETR_EXTERNAL,只返回最外的轮廓。轮廓近似方法为 cv2.CHAIN_APPROX_SIMPLE,返回检索框最少的关键点。

#获取检测框
_, conts, _ = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

运动目标筛选

这部分可以使用Opencv中的级联分类器来识别,但迫于没有足够的样本集和时间,在这里选用基于轮廓面积大小进行目标筛选的方法来区别目标与非目标。

具体可参考:Opencv级联分类器目标检测

#目标提取
#得到大概车辆的列表集合carlist
carslist = []
cars = []
cars = sorted(conts, contoursSort)
    for car in cars:
        area = cv2.contourArea(car)
        if area > cv2.contourArea(cars[0])/2 and area > 400:
            carslist.append(car)

上面代码段中有一函数contoursSort(),这个使用来排序的。

#轮廓面积排序规则函数
def contoursSort(c1, c2):
    return cv2.contourArea(c1) > cv2.contourArea(c2)

双黄线标记

可以使用霍夫变换得到较为接近的双黄线的位置。

#霍夫变换
rho = 2
theta = np.pi/360
threshold = 15
min_line_length = 40
max_line_gap = 20
line_frame = np.copy(frame) * 0
lines = cv2.HoughLinesP(masked_edges, rho, theta, threshold, np.array([]), min_line_length, max_line_gap)

#绘制图线
for x1,y1,x2,y2 in lines[0]:    
    cv2.line(frame, (x1,y1), (x2,y2), (0,255,0), 2)  

碰撞处理

先获得当前图框的坐标点,使用boundingRect函数得到图框左上角坐标值$(x,y)$及图框宽、高$(w,h)$。

双黄线位置记为:左边黄线$x$坐标$x_b$,右边黄线$x$坐标$x_c$

碰撞情况: 前进压双黄线: $x_b < x < x_c$ 或 $x_b < x+w < x_c$;

静止或跨越压双黄线: $x < x_b$ 并且 $x+w > x_c$

检测到碰撞情况后绘制违规车辆图框。分别计算轮廓的$x$方向上和$y$方向上的质心坐标,以此为基础选择适当距离画出一矩形框。

#标记违规车辆(红色方框)
#双黄线位置
x_b = 155
x_c = 160
for car in carslist:
    x, y, w, h = cv2.boundingRect(car)
    #前进压双黄线检测值
    k1 = (x_b < x < x_c) or (x_b < (x + x+w)/2 < x_c)
    #静止或跨越压双黄线检测值
    k2 = x < x_b and (x + x+w)/2 > x_c
    if k1 or k2:
        M = cv2.moments(car)  #计算轮廓各属性
        cx = int(M['m10']/M['m00'])  #x方向上的质心
        cy = int(M['m01']/M['m00'])  #y方向上的质心 
        cv2.rectangle(frame, (cx-10, cy-10), (cx+20, cy+20), (0, 0, 255), 2)

保存视频

通常我们都会保存处理后的视频,可以通过预先构建VideoWriter类来实现。

prename = raw_input('文件名字(.avi): ')
name = prename + '.avi'
#获取原视频的帧率
fps = int(cap.get(cv2.CAP_PROP_FPS))
#获取原视频的尺寸
size = (int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)), int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))
#设置视频格式
fourcc = cv2.VideoWriter_fourcc(*'MJPG')
out = cv2.VideoWriter(name, fourcc, fps, size)

总结

以上就是我对监控视频识别汽车违规的实现过程,这个过程十分难熬,但因为我的坚持,整个思路渐渐变得清晰,内心的烦躁也平息了不少。从开始对Python-Opencv的使用一脸懵逼到现在写了这篇入门级别的博文,我学到了不少,先不说那一堆什么形态学转换、边缘检测等图像和视频处理算法,百度/谷歌的技能也是突飞猛进的。从检测结果来看,这个程序并不是那么令人满意,还有很大的改进空间,例如增加阴影消除算法。

参考

感谢万能的百度/Google。

之后是一些参考文献:

OpenCV官方教程(中文版)

秦秀丽.基于 YUV 颜色空间和图论切割的阴影去除算法[D].武汉理工大学.2010

王志勇.基于视频序列的交通违章监测系统设计[D].郑州大学.2015

骆玉荣.安全带识别系统的研究与实现[D].北京工业大学.2008

吴培敏.基于视频的人脸检测与识别研究[D].南昌大学.2011

最后是几个博客/网站:

谷歌首席程序员

伯乐在线

thefutureisour

OpenCV Tutorials