1、帧差法
2、混合高斯模型
3、光流估计
9.11 ~9.17
背景建模是针对摄像头固定场景,对场景中的所有运动的动目标进行分割的一种方法。因此场景必须是固定的,目标必须是运动的。
由于场景中的目标在运动,目标的影像在不同图像帧中的位置不同。该类算法对时间上连续的两帧图像进行差分运算,不同帧对应的像素点相减,判断灰度差的绝对值。当超过一定阈值时,即可判断为运动的目标,从而实现目标检测功能。帧差法一般分为两帧差分和三帧差分。
两帧差分:
取连续的两帧序列,用后一帧减去前一帧,将其结果与阈值比较。
三帧差分:
取连续的三帧序列 k、k+1、k+2,先让前两帧差分,再让后两帧差分,最后对这两个差分图做与运算。
高斯混合模型(Gaussian Mixture Model)通常简称GMM,是一种业界广泛使用的聚类算法,该方法使用了高斯分布作为参数模型,并使用了期望最大(Expectation Maximization,简称EM)算法进行训练。
在进行前景检测前,先对背景进行训练,对图像中每个背景采用一个混合高斯模型进行模拟,每个背景的混合高斯的个数可以自适应。然后在测试中对新来的像素进行GMM匹配,如果该像素值能够匹配其中一个高斯,则认为是背景,否则认为是前景。
混合高斯模型学习方法:
混合高斯模型测试方法
在测试阶段,对新来像素点的值与混合高斯模型中的每一个均值比较,如果其差值在两倍方差之间则认为是背景,否则认为是前景(人)。将前景赋值为255,背景赋值为0。这样就形成了一幅前景二值图

测试代码
cap = cv.VideoCapture('./video/02.mp4')
kernel = cv.getStructuringElement(cv.MORPH_ELLIPSE, (3, 3))
# 混合高斯模型
fgbg = cv.createBackgroundSubtractorMOG2()
while (True):
ret, frame = cap.read()
fgmask = fgbg.apply(frame)
# 形态学开运算去噪声
fgmask = cv.morphologyEx(fgmask, cv.MORPH_OPEN, kernel)
contours, hierarchy = cv.findContours(fgmask, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
# 寻找轮廓
for c in contours:
# 计算周长
peri = cv.arcLength(c, True)
if peri > 188:
(x, y, w, h) = cv.boundingRect(c)
cv.rectangle(frame, (x, y), (x + w, y + h), (0, 0, 255), 2)
cv.imshow('frame', frame)
cv.imshow('fgmask', fgmask)
k = cv.waitKey(150) & 0xff
if k == 27:
break
cap.release()
光流是指在连续的两帧图像当中,由于图像中的物体移动或者摄像头的移动而使得图像中的目标形成的矢量运动轨迹叫做光流。本质上光流是个向量场,表示了一个像素点从第一帧过渡到第二帧的运动过程,体现该像素点在成像平面上的瞬时速度。
光流估计基于以下三个假设:
光流估计效果图:

测试代码:
cap = cv.VideoCapture('./video/02.mp4')
# 角点检测所需参数
feature_params = dict(maxCorners=100, qualityLevel=0.3, minDistance=7)
# lucas kanade参数,maxLevel为金字塔层级,winSize表示选择多少个点进行u和v的求解。
lk_params = dict(winSize=(15, 15), maxLevel=2)
# 随机颜色条
color = np.random.randint(0, 255, (100, 3))
# 拿到第一帧图像
ret, old_frame = cap.read()
old_gray = cv.cvtColor(old_frame, cv.COLOR_BGR2GRAY)
# 返回所有检测特征点,需要传入图像,角点最大数量(效率),品质因子(特征值越大越好)来筛选
# 距离相当于在该区间内只取最大的
p0 = cv.goodFeaturesToTrack(old_gray, mask=None, **feature_params)
print('p0',p0)
# 创建一个mask
mask = np.zeros_like(old_frame)
while (True):
# 读取当前帧
ret, frame = cap.read()
# 转为灰度
frame_gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
# p1表示光流检测后的角点位置,同p0一样,也是一个数组,对应是第一个点的位置,st表示该角点是否是运动的角点,err表示是否出错。
p1, st, err = cv.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)
print('p1',p1)
# st == 1 表示有用的特征点
good_new = p1[st == 1]
good_old = p0[st == 1]
# 绘制轨迹
for i, (new, old) in enumerate(zip(good_new, good_old)):
# ravel()函数用于将二维数组或多维数组更改为连续的扁平数组
a, b = new.ravel()
c, d = old.ravel()
mask = cv.line(mask, (int(a), int(b)), (int(c), int(d)), color[i].tolist(), 2)
frame = cv.circle(frame, (int(a), int(b)), 5, color[i].tolist(), -1)
img = cv.add(frame, mask)
cv.imshow('img', img)
k = cv.waitKey(150) & 0xff
if k == 27:
break
# 更新
old_gray = frame_gray.copy()
p0 = good_new.reshape(-1, 1, 2)
cv.destroyAllWindows()
cap.release()