opencv入门实战——背景消除建模

前言

开门说点题外话,感谢bilibili让我白嫖到学习资源,感谢唐宇迪老师浅显易懂的讲解,个人感觉唐老师opencv课偏向于直觉课,如果想要更深入的了解还是要阅读官方文档甚至源码的,当然我这里暂时没有太深入的需求,因此也只是简单的记录一下课程内容,还加入了一些其他边边角角的内容,便于后续复习。

言归正传,背景消除建模按照我的理解就是把正在运动的前景物体和保持静止不变的背景画面分开。举个最简单的例子,在监控视频中检测路上的行人——监控视频的背景是几乎不变的,而行人一般处在不断的运动当中,因此可以通过一些手段把前景和背景区分开,从而观测行人的动作和行动轨迹等等。

在这里先介绍基本原理,然后基于两种方法对demo进行背景消除建模。

原理

1. 帧差法

帧差法顾名思义就是对比帧和帧之间的差值。对于背景,不同帧之间的灰度值几乎不发生变化(仅在镜头轻微的抖动或者光影发生轻微的变化时有明显变化);而对于移动的前景,由于移动的关系,灰度值会发生比较明显的变化,如果这个变化超过了一定阈值,则将其灰度值置为255,否则将其置为0。因此就可以通过这种方式将运动的部分和背景分离出来。

帧差法比较简单,但是缺点就是会引入空洞和噪声。想象这样一个场景,对于一个穿着白色衣服的人,在短时间内的移动较短的距离,则只有衣服边缘的像素点被检测出来,而衣服内部由于灰度值几乎不变(只是从衣服一边移到另一边),因此该方法会产生非常明显的空洞。下图是课程中给的示例。

帧差.png

2. 混合高斯模型

混合高斯模型是指在进行前景检测前,先对背景进行训练,对于图像中每一个背景采用一个混合高斯模型进行模拟,然后在测试阶段将新像素与GMM进行匹配,若能够匹配其中一个高斯分布,则认为是背景,否则认为是前景。

高斯分布.png

它的学习方法是:

  1. 首先初始化每一个高斯模型矩阵参数
  2. 取视频中T帧图像训练高斯混合模型,先用一个像素计算高斯分布,对于后面的像素,如果在已有的模型均值差的三倍方差以内,则加入模型并更新参数
  3. 如果不满足当前的高斯分布,则利用这个新来的像素创建一个新的高斯分布

由于它是动态学习的过程,因此对于噪声有很好的抑制作用,并且对于动态背景有一定的鲁棒性。

3. 基于KNN的模型

KNN算法是一个比较经典的机器学习算法,简单来说就是对于每一个新加入的元素都计算与其他元素的“距离”,然后选择K个最小距离的现有元素,把这个新元素归到现有K个最小元素中占比最多的那一类。

在opencv中,默认的BackgroundSubtractor方法就是KNN方法。具体思想和KNN类似,阅读了Mega_Li的博客,发现其在保存和更新像素信息时采用了较为复杂的规则,想了解的朋友可以去原博客看一看。

blog.csdn.net/lwx30902516…

首先每一个像素值都有一定的历史信息,包括前几帧的像素值和像素点被判断为是前景点还是背景点,对于每一个新像素点都通过一定规则判断其是属于前景点还是背景点,然后更新其历史信息。这部分比较复杂,我自己也没搞明白,这里就不在赘述了,直接进入实战。

实战项目

视频素材采用的是抠像的藤原千花的书记舞,其实已经经过抠像的话用改良帧差法应该是最准确的(只要人物身上没有大面积的背景颜色,那么用视频每一帧的像素值减去背景像素值,就能够达到像素级别的检测),当然我们这里只是试验一下其他两种方法,并不太要求精准度,况且实际情况中并没有这么好的背景完全不改变且没什么噪音的视频素材。

效果展示

在运行中能够实时监测人物的位置并且绘制检测框。只是感觉精确度不是很够,有时候会出现识别上半身和下半身分离的情况。(x

注意虽然这里背景是绿的,但是换成其他不变的花哨背景也是可以的,并不是只有纯色背景才可以。
背景123.png

背景124.png

代码


import cv2

cap = cv2.VideoCapture('test.mp4')


kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(3,3))
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))

#这里是选择使用KNN还是GMM方法,我个人实验下来好像差别不大,不过可能是视频内容比较简单的缘故
#fgbg = cv2.createBackgroundSubtractorMOG2()
fgbg = cv2.createBackgroundSubtractorKNN()

while 1:
    ret, frame = cap.read()
    fgmask = fgbg.apply(frame)
    fgmask = cv2.morphologyEx(fgmask, cv2.MORPH_OPEN, kernel)
    #这里使用了多次膨胀,虽然降低了检测的精度,但是也同时减少了人物不同部分分散的情况
    fgmask = cv2.dilate(fgmask,sqKernel)
    fgmask = cv2.dilate(fgmask,sqKernel)
    fgmask = cv2.dilate(fgmask,sqKernel)
    
    fgmask = cv2.morphologyEx(fgmask, cv2.MORPH_CLOSE, rectKernel)
    fgmask = cv2.morphologyEx(fgmask, cv2.MORPH_CLOSE, sqKernel)
    
    contours, hierarchy = cv2.findContours(fgmask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    
    for c in contours:
        perimeter = cv2.arcLength(c,True)
        if perimeter > 500:
            x, y, w, h = cv2.boundingRect(c)
            
            cv2.rectangle(frame,(x,y),(x+w,y+h),(255,0,0),2)
    cv2.imshow('frame', frame)
    cv2.imshow('fgmask', fgmask)
    k = cv2.waitKey(150) &0xff
    
    if k == 27:
        break
cap.release()
复制代码

小结

现在好像大部分都是用深度学习做目标检测了,准确率又高算的又快,后续可以去研究一下。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享