首先
首先我们先来介绍一下我们的时间中值滤波。 许多计算机视觉应用的硬件配置往往不会很高,举个例子:交通路口的摄像头。在这种硬件条件的约束下,我们只能使用简单但必须有效的一些技术来实现例如:“监控”的功能。本文中将介绍的中值背景估计就是一种这样的技术。 中值背景估计常用在摄像头这种静态但是场景中会出现一些移动物体的估计场景中。举个例子,现代许多交通和监控摄像机都是固定的,不会轻易改变,也就是它视野内的背景几乎是固定不变的,改变的可能只是闯入视野中的车辆或行人,但那些并不是这里所说的背景。但是如果某辆车一直停在摄像头视野内,那么也可以认为它是背景中的一部分了。这里首先介绍一种叫作时间中值滤波的处理方式,这种技术时常用在对于视频而言的背景提取算法中。
时间中值滤波
我们可以举个经典的例子,例如在Arduino(Arduino为一种单片机开发软件)中使用的温度传感器,其可以监控室内温度或者水体温度,这里准备两个温度传感器,一个能时刻正常工作,另一个会时不时地跳动数值(异常),将它们的数值统计后制成如图所示:纵轴为我们的温度,单位为摄氏度;横轴为时间,单位为秒。
使用中值滤波进行背景估计
在温度传感器的例子中,背景可以理解为真实的温度,这是一个一维的背景(因为只是一个值)。现在回到交通路口的摄像头上,摄像头固定在路口,其所拍摄的视野范围是恒定不变的,与之对应的背景自然就是恒定不变的。 但是有的时候车辆会闯入摄像头的视野内,这个时候对于背景而言,类似于出现了异常的温度传感器中温度突然升高的情况,因为车辆并不是属于背景的一部分。 可以假设大部分时间内,摄像头中的每个像素点都是背景,因为摄像头没有移动。有的时候,镜头内会出现障碍物,例如车辆或其他移动物体出现在摄像头内并遮挡背景。对于这种情况,可以采用随机采样的方式来处理,这里以25帧(即25张图片)图像为例。 换句话而言,现在每个像素点有25个背景估计值(也可以更多)。做如下规定:“如果视野中的某一个像素点被车辆或其他移动物体覆盖的时间不超过总时间的50%,那么通过像素点的中值就能给出该像素点背景的一个良好估计。”在为图像中的每个像素点重复这个操作后,可恢复整个背景。 实现背景估计,需要一个skimage库,首先通过如下命令进行库的安装。
pip install scikit-image
安装完成以后,以提前录好的一段视频1.mp4为例,尝试从视频中获得背景,示例代码如下。
import numpy as np
import cv2
from skimage import data, filters
# 打开视频文件
cap = cv2.VideoCapture('1.mp4')
# 随机选取25帧
frameIds = cap.get(cv2.CAP_PROP_FRAME_COUNT) * np.random.uniform(size=25)
# 将25帧存入一个列表中
frames = []
for fid in frameIds:
cap.set(cv2.CAP_PROP_POS_FRAMES, fid)
ret, frame = cap.read()
frames.append(frame)
# 计算中值
medianFrame = np.median(frames, axis=0).astype(dtype=np.uint8)
# 将背景显示出来
(x,y,z)=medianFrame.shape
cv2.namedWindow('frame',cv2.WINDOW_NORMAL)
cv2.resizeWindow('frame',int(y/2),int(x/2))
cv2.imshow('frame', medianFrame)
cv2.waitKey(0)
cv2.destroyAllWindows()
运行代码,等待几秒后,就能显示出这段视频中的背景,运行结果如下图所示。从视频中随机的选择25帧后,计算这25帧内每个像素点的中位数,并保证只要每个像素点至少有50%的时间处于“背景状态”(即像素值不变),这个中间帧就能成为我们对背景的一种良好估计。
帧差分
问题来了,假定我们通过上述的代码运行获得了视频中的背景,那么能否通过得到的中间帧来为视频中的每一帧创建一个掩码,用来显示图像中的运动部分,也就是将背景帧与视频中的中间帧进行差分(帧与帧中对应像素点间做差后取绝对值的操作)后得到的差值。差值可以理解为在背景中运动的物体,这也是一种特殊的物体追踪手段,该算法可以通过如下步骤实现。 ·将得到的中间帧转换为灰度图。 ·循环播放视频中的所有帧,提取当前帧后将其转换为灰度图。 ·对当前帧和中间帧进行差分的运算(即做差后取绝对值)。 ·对上面的图像进行阈值化来消除噪声并将输出二值化。 示例代码如下所示。
import numpy as np
import cv2
# 打开视频
cap = cv2.VideoCapture('3.mp4')
# 随机选取25帧
frameIds = cap.get(cv2.CAP_PROP_FRAME_COUNT) * np.random.uniform(size=25)
# 将25帧存入列表中
frames = []
for fid in frameIds:
cap.set(cv2.CAP_PROP_POS_FRAMES, fid)
ret, frame = cap.read()
frames.append(frame)
# 计算中间帧
medianFrame = np.median(frames, axis=0).astype(dtype=np.uint8)
# 显示中间帧
cv2.imshow('frame', medianFrame)
cv2.waitKey(0)
# 因为上面进行了随机选取帧后,当前帧的位置此时不确定
# 所以此处我们将当前帧的位置重置为0号位置,也就是视频的最开始处
cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
# 转换为灰度图
grayMedianFrame = cv2.cvtColor(medianFrame, cv2.COLOR_BGR2GRAY)
# 循环处理所有视频帧
ret = True
while(ret):
# 从当前帧位置开始读取帧(因为上面进行了重置,所以此时为从视频开始处进行数据读取)
ret, frame = cap.read()
if frame is None:
break
# 将当前帧转换为灰度图
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
(x,y)=frame.shape
# 将中间帧与当前帧进行差分的计算
dframe = cv2.absdiff(frame, grayMedianFrame)
# 二值化
th, dframe = cv2.threshold(dframe, 30, 255, cv2.THRESH_BINARY)
# 将图像显示出来
cv2.namedWindow('fra',cv2.WINDOW_NORMAL)
cv2.resizeWindow('fra',int(y/2),int(x/2))
cv2.imshow('fra', dframe)
cv2.waitKey(20)
# 施放cap
cap.release()
# 关闭所有的画布
cv2.destroyAllWindows()
运行上述代码后,首先会自动给出这段视频的中间帧(背景图),如图1所示。得到中间帧后,打开视频3.mp4,视频中有一把尺子出现在了镜头内,因为尺子处于运动状态,不属于背景,所以尺子在与中间帧进行差分之后就会被二值化显示成白色,如图2所示。
图1
图2
总结
在本节中,只介绍了中值背景估计这种简单的背景估计方法,对于一些复杂的情况它的处理能力可能不是很理想。使用这种方法来做运动物体的检测具有一定的风险性,原因主要有如下几点。 (1)在程序中所获得的中间帧不一定能够完整代表事物所处的实际环境,也就是如果用来判断背景上是否存在运动物体的背景本身有问题,那么采用视频帧与中间帧进行绝对差分的这种算法根本就是不成立的。 (2)有的时候中间帧选取计算量过大,速度比较慢。上面的例子使用的视频都比较短,视频时长只有几秒,但程序还是需要一定时间才能得到中间帧,如果视频时长增加后,计算量还会进一步增加。 (3)如果是短时间追踪,切物体移动速度比较慢的话,可能会将其误认为背景的一部分,所以这种算法在进行物体追踪的时候,对速度还有一定的要求,速度过慢可能会导致错误的出现。 本文到此结束,谢谢大家。