本文提出一种简单有效的基于opencv的车辆检测与计数方法。
首先通过高速公路上的摄像头获取到一段车流量视频,先预处理:利用灰度线性变换,为了只关注视频中车辆移动的特征,不关注不同车辆的不同颜色的干扰特征;然后对灰度视频去除背景,进一步抑制车辆外的其他特征;接着进行高斯滤波降噪得到噪声很少的二值化黑白视频。然后进行腐蚀,膨胀,闭运算等形态学的相关操作,进一步完善我们近景移动车辆的特征,抑制其他无关特征。接着通过外接矩形框的方式对近景的每辆车查找轮廓,确定检测线的高度,将外接矩形框抽象为一点,设置检测线的偏移量,来进一步提高车辆统计的精度。结果 我们获取到的视频中所有车辆都能通过经过检测线的形式来准确记录。结论 基于opencv的车辆检测与计数,能够将摄像头记录的视频中车辆信息给精确记录,但是对部分车辆的精确度还不够高,通过之后的改进,可以运用到我们的实际生活中,完善我们的智能交通管理体系。
cap = cv2.VideoCapture()
ret, frame = cap.read()
第一个参数ret 为True 或者False,代表有没有读取到视频帧
第二个参数frame表示截取到一帧的图片
import cv2
# 加载视频
cap = cv2.VideoCapture('.\\data\\cars1.mp4')
# 循环读取视频帧
while True:
ret, frame = cap.read()
if ret is True:
cv2.imshow('video', frame)
key = cv2.waitKey(20)
# 用户按ESC退出
if key == 27:
break
# 释放资源
cap.release()
cv2.destroyAllWindows()
车流量视频
图像的灰度变换:将一幅彩色图像转换为灰度化图像的过程。
特点:
1. 改善图像的质量,使图像能够显示更多的细节,提高图像的对比度。
2. 有选择的突出图像感兴趣的特征或者抑制图像中不需要的特征。
3. 可以有效的改变图像的直方图分布,使像素的分布更为均匀。
4. 在数字图像处理中一般将各种格式的图像转换为灰度图像以使后续的图像计算量少一些。
背景减除(Background Subtraction)是视频图像中运动目标检测任务的主要预处理步骤,如果我们有完整的静止的背景帧,那么我们可以通过帧差法来计算像素差从而获得到前景对象。
工作原理:使用 K(K = 3 或 5)个高斯分布混合对背景像素进行建模。使用这些颜色存在时间的长短作为混合的权重。背景的颜色一般持续的时间最长,而且更加静止。
移动的物体会被标记为白色,背景会被标记为黑色的
高斯滤波(Gauss)是一种线性平滑滤波,适用于消除高斯噪声,广泛用于图像处理的减噪过程。高斯滤波是对整幅图像进行加权平均的过程,每一个像素点的值都由他本身和邻域内的其他像素值经过加权平均后得到。
卷积核的选取:在高斯滤波中,卷积核中的值按照距离中心点的远近赋予不同的权重,分别用33,77,15*15的卷积核,并对水平方向和垂直方向的权重进行不同的赋值,然后对视频效果分析。
实验结论:
腐蚀(erossion)是指卷积核沿着图像滑动,把物体的边界腐蚀掉。使图像沿着边界向内收缩,可以将小于指定结构体元素的部分去除。实现去除噪声、元素分割等功能。 (扩大黑色部分,减小白色部分)
工作原理:如果卷积核对应的原图的所有像素值为1,那么中心元素就保持原来的值,否则变为零。
操作原因:由于在使用高斯降噪后依然有许多细小边缘和噪声点存在所以使用腐蚀操作可以很好地去除。
膨胀(dilation)是用结构元素的原点遍历所有原图像的背景点,若结构元素与原图像中的前景点有重叠的部分,则标记原图像中的该背景点,使之成新的前景点,从而实现将图像的边界点向外扩张。
工作原理:卷积核对应的原图像像素值中只要有一个是1,中心像素值就是1。
操作原因:由于在腐蚀操作后车辆会丢失部分信息,所以使用膨胀运算可以使车的部分信息进行还原。
闭运算(closing)是先膨胀在腐蚀,主要用于填充白色物体内细小黑色空洞的区域。
闭运算的特点:是能够排除小型空洞,平滑物体轮廓,连接窄的间断点以及沟壑,同时也能够填补断裂的轮廓线。
操作原因:先腐蚀在膨胀的操作相当于进行了一个开运算(消除亮度较高的细小区域),但是得到的视频中汽车内部有细小黑色空洞的区域,所以要使用闭运算来填充汽车内部的小孔。
外接矩形:在闭运算结束之后,已经基本可以识别近景处的汽车轮廓,可以通过外接矩形框出每一辆车,如下左图所示。
获取外接矩形的数据点: (x, y, w, h) = cv2.boundingRect(contour)
画出外接矩形框: cv2.rectangle(frame, (x, y), (x+w, y + h), (0, 255, 0), 2)
存在的问题:虽然经过了前面的闭运算操作,消除了视频中车辆以外的外部干扰因素,但是因为有的汽车内部可能会有车窗、车灯等内部干扰因素,导致部分汽车内部会充满很多细小矩形(如右图所示)。
改进:为了去掉在大矩形内部的小矩形,通过实验,我们确定了最小长宽即min_w=50和min_h=50,当矩形的长宽小于50时,这个矩形就被认为是无效的,这样就可以过滤小矩形(如下图)。
检测线的确定:检测线的作用是当汽车通过线则进行记数,所以在检测线的位置line_high要尽量靠近近景区域,因为近景区域汽车细节更加完善,也更容易进行识别。但不应该过于靠前,因为过于靠前可能反向车道的汽车还没有形成外接矩形,就已经通过线,所以最终确定下图所示的检测线位置:
line_high = 550。
画出检测线:
cv2.line(frame, (0, line_high), (1280, line_high), (255, 255, 0), 2)
存在的问题:当汽车通过检测线时会增加统计数据,但由于很难对判断整个外接矩形是否过线,并且当车辆较多时,可以发现按照矩形框经过检测线的次数来计数,会使统计数目不准。
改进:所以我们将汽车抽象为外接矩形的中心点,这时只需要判断中心点是否通过线就可以清楚地统计经过汽车的数量(如下图)。
获取中心点的坐标:
cx = int(x + 0.5 * w);cy = int(y + 0.5 * h)
画出中心点:
cv2.circle(frame, (cx, cy), 5, (0, 0, 255), -1)
存在的问题:由于视频帧数限制,有的车辆可能没有办法正好落在检测线上。
改进:当存在一个偏移量时,就可以避免了中心点没有落在检测线上而不被统计的情况。
我们定义一个偏移量:offset,用来表示汽车中心点位于检测线最远的偏移距离
这样我们可以得到 有效区间 = [检测线高度 – offset , 检测线高度 + offset]
当汽车的中心点位于有效区间时我们会对车辆进行统计,并且经过多次实验,确定了offset=5时,其车辆计数基本上没有遗漏,满足我们此次项目的需求。
作品效果演示
import cv2
# 加载视频
cap = cv2.VideoCapture(".\\data\\cars1.mp4")
# 创建mog对象:去除图像的背景
mog = cv2.bgsegm.createBackgroundSubtractorMOG()
# 构造5×5的结构元素,结构为十字形
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
# 设定车辆外接矩形框的最小宽度和最小高度
min_w = 50
min_h = 50
# 设定计数线的高度
line_high = 550
# 偏移量
offset = 5
# 对车计数
carnum = 0
# 保存视频
# # 参数:(保存路径,编码器,帧率,画面尺寸,是否彩色)
# width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) # 获取视频的宽度
# height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) # 获取视频的高度
# fps = cap.get(cv2.CAP_PROP_FPS) # 获取视频的帧率
# fourcc = int(cap.get(cv2.CAP_PROP_FOURCC)) # 视频的编码
#
# out = cv2.VideoWriter(".\\data\\cars1_final1.mp4", fourcc, fps, (width, height), True)
# 循环读取视频帧
while True:
# 第一个参数ret 为True 或者False,代表有没有读取到图片
# 第二个参数frame表示截取到一帧的图片
ret, frame = cap.read()
# 通过设置缩放比例对图像进行放大或缩小, (这里若对窗口的粗存更改,则会使矩形框的位置失真)
# dst = cv2.resize(frame, None, fx=0.8, fy=0.8, interpolation=cv2.INTER_CUBIC)
if ret is True:
# 将原始帧灰度化
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 高斯滤波去噪:卷积核为3*3,
blur = cv2.GaussianBlur(gray, (3, 3), 5)
# 获取去背景后的数据
mask = mog.apply(blur)
# 腐蚀:卷积核对应的原图的所有像素值为1吗,那么中心元素像素值不变,否则变为0. ”扩大黑色部分,减小白色部分。“
erode = cv2.erode(mask, kernel)
# 膨胀:卷积核对应的与图像像素值中只要有一个是1,中心像素值就为1.
dilate = cv2.dilate(erode, kernel, iterations=1)
# 闭运算:先膨胀后腐蚀:用于填充白色物体内细小黑色空洞的区域( 消除内部小块)
close = cv2.morphologyEx(dilate, cv2.MORPH_CLOSE, kernel)
# 查找轮廓:
# cv2.findContours(img, mode, method):cv2.RETR_EXTERNAL只检测外轮廓;cv2.RETR_LIST检测的轮廓不建立等级关系。
contours, h = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 画出检测线
cv2.line(frame, (0, line_high), (1280, line_high), (255, 255, 0), 2)
# 画出所有检测出的轮廓
for contour in contours:
# 获得外接矩形的四个数据点:x轴坐标,y轴坐标,矩形的宽,矩形的高
(x, y, w, h) = cv2.boundingRect(contour)
# 通过外接矩形的宽高大小来过滤小矩形
if not ((w >= min_w) and (h >= min_h)):
continue
if (w > min_w) and (h > min_h): # 这里只计算正常的汽车
# 画外接矩形的轮廓 :(0, 255, 0):颜色 2:线条的粗细
cv2.rectangle(frame, (x, y), (x+w, y + h), (0, 0, 255), 2)
# 把车抽象为一点,要通过外接矩形计算矩形的中心点
cx = int(x + 0.5 * w)
cy = int(y + 0.5 * h)
# 画中心圆点
cv2.circle(frame, (cx, cy), 5, (0, 0, 255), -1)
# 判断汽车是否过检测线
if(line_high - offset) < cy < (line_high + offset):
carnum += 1 # 落入有效区间,计数+1
print(carnum)
# cv2.imshow('video1', frame)
# cv2.imshow('video2', dst)
# cv2.imshow('video3', gray)
# cv2.imshow('video4', blur)
# cv2.imshow('video5', mask)
# cv2.imshow('video6', erode)
# cv2.imshow('video7', dilate)
# cv2.imshow('video8', close)
cv2.putText(frame, 'Vehicle Count:' + str(carnum), (420, 60), cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 0, 255), 5)
cv2.imshow('frame', frame)
# 保存视频帧到视频容器
# out.write(frame)
key = cv2.waitKey(50)
# 按ESC退出
if key == 27:
break
# 释放资源
cap.release()
cv2.destroyALLWindows()
通过这几周的不懈努力,项目已基本满足作品的预期和要求。但是从项目的整体性和完美性来说,还存在以下几点需要改进: