运动图像分割
在一个视频文件中,分割出前景目标,之前网上看了一些方法,包括帧差法、GMM等。
帧差法太简单,但是只能提取轮廓,而且会出现前景目标部分区域消失的问题。GMM对于小白菜来说有点高深,看不太懂。
接下来给大家介绍一下帧差和一种能够提取前景目标区域的一种方法。
帧差法
二帧差法:
利用后一帧图像与前一帧图像的差值,取差值的绝对值作为运动目标的检测帧,将检测帧进行使用合适阈值进行二值化,再通过一些常用的图像处理算法进行图像增强。其实二帧差法利用了一阶梯度的思想,对于背景点,变化很小,所以两帧的差很小;对于前景点,像素变化比较大,帧差比较大。不足之处是对噪声比较敏感,如果帧率比较高,可能会出现前景目标整体消失的情况,如果前景目标部分区域未动的话,也会检测不到。
来看一下二帧差的效果,分别用大目标分割和小目标分割测试。大目标可以通过形态学处理增强,小目标很难增强,因为如果膨胀腐蚀的话会改变目标的轮廓和大小,很难去控制。左图红色圈出的就是部分消失的情况。
三帧差法:
直接上流程图和结果,三侦差可以抑制一些噪声,但是还会出现前景目标部分消失的问题,因为用的是与操作,所以线条会细一点。
背景建模法(名字很酷吧)
对于一段视频来说,每个像素点在每一帧的像素值既有可能是背景,也有可能是前景。对于视频文件来说,所有像素点就构成了一个随机过程。
由我们的任务目标,根据统计概率知识做出如下假设:
(1)对于同一个像素点,作为背景时的统计概率较高;
(2)背景的像素值是稳定的。
因此我们利用视频文件中每个像素点出现频率最高的像素值构建出一个背景模型,也就是最大概率准则。
然后再使用每一帧图片,与我们的背景模型在一个可接受域内做前景目标构造。
看一下建立的背景:
均值背景可以看出前景目标的运动轨迹,大家可以试试。最大概率背景也就是我们上面提到的方法建立的背景,有点毛糙,可能是视频录制的不是很好,中值背景稍微光滑点。其实感觉在大多数情况下,中值背景应该和最大概率背景是一样的。
看一下,分割结果。背景建模法对于小目标检测还是很不错的感觉。
频域上使用背景建模法
接下来我们尝试把这种方法应用在频域上,将每一帧图片的傅里叶变换减去背景图的傅里叶变换,然后进行反变换得到运动目标的检测帧,然后再利用形态学方法将得到的二值图进行图像增强。效果如下图所示,其实在频域上减去背景的话并不会直接将背景减去,反而是一个类似图像融合的过程,把前景目标和建模的背景融合到了一起。
最后给大家分享一下背景建模法的代码把,因为个人编程能力有限,编程习惯不太好,不喜勿喷。
import cv2
import numpy as np
from collections import Counter
cap = cv2.VideoCapture("luren.mp4")
cap2 = cv2.VideoCapture("luren.mp4")
frameNum = 0
NpKernel1 = np.uint8(np.ones((3,3)))
NpKernel2 = np.uint8(np.ones((5,5)))
ret, frame = cap.read()
tempframe = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
h,w = tempframe.shape
tempframe=tempframe.reshape(-1)
pix_list = [[] for i in range(len(tempframe))]
pixbg_list = []
for i in range(len(tempframe)):
pix_list[i].append(tempframe[i])
frameNum += 1
while cap.isOpened():
ret, frame = cap.read()
if ret == True:
tempframe = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
tempframe = tempframe.reshape(-1)
for i in range(len(tempframe)):
pix_list[i].append(tempframe[i])
frameNum += 1
print(frameNum)
else:
break
cap.release()
for i in range(len(tempframe)):
#pixbg_list.append(Counter(pix_list[i]).most_common(1)[0][0]) # 最大概率值
# pixbg_list.append((np.sum(np.array(pix_list[i])) / len(pix_list[i])).astype(dtype='uint8')) #均值
pixbg_list.append(np.median(np.array(pix_list[i])).astype(dtype='uint8')) # 中值
pixbg = np.array(pixbg_list).reshape(h,w)
print('ok')
# pixbg = cv2.medianBlur(pixbg, 3)
cv2.imshow('pixbg', pixbg)
cv2.waitKey(0)
while cap2.isOpened():
cv2.waitKey(100)
ret, frame = cap2.read()
if ret == True:
tempframe = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
threshold_frame = np.ones_like(tempframe)
a = tempframe>=pixbg-10
b = tempframe<=pixbg+10
threshold_frame[a & b] = 0
threshold_frame = threshold_frame * 255
# erode_img = cv2.erode(threshold_frame, NpKernel1)
# erode_img = cv2.erode(erode_img, NpKernel1)
# #
# # erode_img = cv2.erode(threshold_frame, NpKernel1)
# # dilate_img = cv2.dilate(erode_img, NpKernel2)
# #
# dilate_img = cv2.dilate(erode_img, NpKernel2)
# erode_img = cv2.erode(dilate_img, NpKernel2)
# dilate_img = cv2.dilate(erode_img, NpKernel2)
median = cv2.medianBlur(threshold_frame, 3)
cv2.imshow('Frame', frame)
cv2.imshow('threshold_frame', median)
if cv2.waitKey(33) & 0xFF == ord('q'):
break
else:
break
cap2.release()
cv2.destroyAllWindows()