一.总体思路:
(一).图像的预处理
1.图像变成灰度图之后利用高斯滤波去噪(图像通道的转变以及高斯滤波)
2.去噪后消除背景,在计算机视觉中,不动的叫背景,移动的叫前景(这里使用到了一个新的API,是关于去除视频背景的)
3.去掉背景后的视频,不动的地方是黑色的,移动的地方会显现出白色,会发现有黑色的背景有时会出现白色的小方块的噪声,因此采用腐蚀操作去除噪声(腐蚀)
4.因为腐蚀操作会把白色的移动过程中的汽车轮廓给缩小,因此还需要用膨胀操作让汽车变大,更加便于之后的车辆检测,由于车越大更加好判断,因此可以把膨胀操作的iterations设为2,让它膨胀两次,这也是不把3和4合并成一次开运算操作的原因(膨胀)
5.经过观察,白色的移动过程中的车辆中会有黑色的小方块的噪声,因此进行一次闭运算来去除,但是效果不太明显(闭运算)
(二).车辆的查找以及绘制车辆轮廓
6.经过上述的处理后,噪声去除的差不多了,可以直接查找轮廓了(轮廓的查找)
7.把所有的轮廓的最大外接矩形都绘制出来(最大外接矩形的绘制)
8.经过观察,会发现,由于车内有车灯,车牌等物件,可能会在大车的轮廓框内包含一些小的车灯的,车牌的轮廓框,因此我们设置当宽和高大于某一个值的时候,才绘制出显示框,这样就可以把车灯和车牌等干扰项给排除掉
(三).车辆的计数
车辆的计数采取的方法是:找每个车辆的轮廓框的中心点,这个中心点经过一条检测线,就表示有一辆车从这条道路上驶过,车辆数目加一
用人眼做到这一点很容易,但是只用openCV,是无法将视频的这一帧和下一帧联系到一起的,只能单独对一张张的图片处理,因此我们设置一个阈值h,当这一帧检测线上下h的范围内存在上述的中心点,就代表这辆车经过了这条线
因此,设置这个阈值还是很重要的,如果设置小了,可能无法捕捉到中心点出现在那个范围内的一帧,如果设置大了,可能这一帧一辆车的中心点在这个范围内,下一帧同一辆车还没走出这个范围,造成重复计数
二.背景消除
#1.创建对象
bgs = cv2.createBackgroundSubtractorMOG2()
#2.调用函数即可,其中fream是要处理的图像,是视频加载器read()函数返回的第二个值,返回值fgmask是背景去除后的图像
fgmask = bgs.apply(blur)
三.完整代码
import cv2
import numpy as np
"""创建两个变量,分别为检测为车的最小宽度和高度,为最后的车辆检测而使用的"""
min_w = 100#宽度最小为100像素才能认为是车
min_h = 90#高度最小为90像素才能认为是车
"""检测线的位置"""
line_high = 700#检测线画在y坐标为700的地方
"""偏移量"""
offset = 20#一帧视频,在检测线上线20的范围内出现了车辆,认为该车辆经过了检测线
"""用来存放车辆坐标信息的列表"""
cars = []
"""记录车辆的数量"""
num = 0
"""计算外接矩形的中心点,为了美观封装成了一个函数"""
def center(x,y,w,h):
x1 = int(w/2)
y1 = int(h/2)
cx = int(x) + x1
cy = int(y) + y1
return cx,cy
"""获取腐蚀的卷积核,方便以后去噪(膨胀,腐蚀,闭运算等形态学操作)使用"""
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))
"""创建去除背景的对象"""
bgs = cv2.createBackgroundSubtractorMOG2()
"""加载视频"""
cap = cv2.VideoCapture("image/video2.mp4")
while True:
ret,fream = cap.read()
if ret ==True:
"""图像的预处理,可以先去噪再消除背景也可以反过来"""
#变成灰度图,然后去噪
gray = cv2.cvtColor(fream,cv2.COLOR_BGR2GRAY)
#高斯滤波去噪
blur = cv2.GaussianBlur(gray,(3,3),5)
#应用bgs来消除背景,经过处理后,不动的背景变成黑色,动的前景变成白色
fgmask = bgs.apply(blur)
#经过高斯滤波消除噪声后,还有一些比较大块的噪声,用腐蚀操作消除
eroad = cv2.erode(fgmask,kernel)
#腐蚀之后对车的部分造成了影响,再膨胀回来,并且希望车能够膨胀的更大一点,因此膨胀迭代两次,这也是不直接用开运算的原因
dilate = cv2.dilate(eroad,kernel,iterations=2)
#消除车内黑色的小噪声,闭运算
close = cv2.morphologyEx(dilate,cv2.MORPH_CLOSE,kernel)
"""画出检测线,绘制都是在原图上绘制了"""
cv2.line(fream,(10,line_high),(1880,line_high),(255,255,0),3)
"查找并绘制轮廓"
contours,h = cv2.findContours(close,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
for contour in contours:#因为同一帧有多辆车,有多个轮廓,因此需要用for循环这个列表
#绘制轮廓的最大外界矩形
(x,y,w,h) = cv2.boundingRect(contour)
if (w < min_w)|(h < min_h):#只要有一个小于阈值,就认为不是车,continue掉,去判断下一个轮廓
continue
#绘制轮廓,其中点的坐标都是整数
cv2.rectangle(fream,(int(x),int(y)),(int(x+w),int(y+h)),(0,0,255),2)
"""车辆计数"""
#找到每个矩形的中心点
cpoint = center(x,y,w,h)
#cars是一个列表,用于存储当前每个可以判定为车辆 的矩形的中心点
cars.append(cpoint)
cv2.circle(fream,(cpoint),5,(0,0,255),-1)
#如果该中心点在当前这一帧处于判断线附近,可以认为经过了判断线,这个范围offset需要调试
for (x,y) in cars:
if y > (line_high-offset) and y < (line_high + offset):
num+=1
#print(num)
cars.remove((x,y))
"""显示文字"""
cv2.putText(fream,"Vehicle Count:"+str(num),(300,60),cv2.FONT_HERSHEY_SIMPLEX,2,(0,0,255),5)
"""最终显示的还是原图,而不是经过背景去除和去噪之后的图,背景去除和去噪只是为了查找车辆的轮廓位置"""
cv2.imshow("video",fream)
key = cv2.waitKey(30)
if key == 27:#esc键
break
#最后释放资源
cap.release()
cv2.destroyAllWindows()
四.缺点
需要比较清晰并且比较干净的视频才行,因为毕竟是只用opencv实现,当地上车辆的影子经过斑马线的时候,可能斑马线会被误判成移动的物体,造成影响
并且需要车辆不是那么密集的视频,因为离得近会因为车辆影子的覆盖造成两辆车判断成一辆车
其次公路上车辆的速度有快有慢,因此偏移量offset无论怎么设都肯定会有重复计数和漏计的情况,只不过一个好的参数能适当减少这种情况
总之只用opencv来做这个东西效果并不好,真要达到很好的效果还是需要yolo加上一些其他的算法才能实现只能起到复习练手的目的,做这个的目的主要是为了复习之前的学习的知识点和练手