OpenCV计算机视觉实战 - 背景建模 & 光流估计

本文介绍了两种视频处理技术:背景建模和光流估计。背景建模通过帧差法和混合高斯模型实现运动目标检测,其中混合高斯模型能更好地适应动态背景。光流估计利用亮度恒定、小运动和空间一致的原理进行目标跟踪,通过cv2.calcOpticalFlowPyrLK()和cv2.goodFeaturesToTrack()函数实现。这两种方法在实际应用中各有优缺点,适用于不同的视频分析场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

###############################################################
纸上得来终觉浅,绝知此事要躬行
B站视频
新课件:https://pan.baidu.com/s/1frWHqCVGR2VTn5QBtW4lPA 提取码:xh02
老课件:https://pan.baidu.com/s/1Wi31FxSPBqWiuJX9quX-jA 提取码:bbfg
##############################################################

一、背景建模

法1:帧差法

由于场景中的目标在运动,目标的影像在不同图像帧中的位置不同。该类算法对时间上连续的两帧图像进行差分运算,不同帧对应的像素点相减,判断灰度差的绝对值,当绝对值超过一定阈值时,即可判断为运动目标,从而实现目标的检测功能。
在这里插入图片描述
背景为0,运动目标为1,过滤掉背景

帧差法非常简单,但是会引入噪音 和 空洞问题

法2:混合高斯模型

在进行前景检测前,先对背景进行训练,对图像中每个背景采用一个混合高斯模型进行模拟,每个背景的混合高斯的个数可以自适应。
然后在测试阶段,对新来的像素进行GMM(高斯混合模型)匹配,如果该像素值能够匹配其中一个高斯,则认为是背景,否则认为是前景
由于整个过程GMM模型在不断更新学习中,所以对动态背景有一定的鲁棒性。最后通过对一个有树枝摇摆的动态背景进行前景检测,取得了较好的效果。

在视频中对于像素点的变化情况应当是符合高斯分布
在这里插入图片描述
背景的实际分布应当是多个高斯分布混合在一起每个高斯模型也可以带有权重
在这里插入图片描述

混合高斯模型学习方法

1.首先初始化每个高斯模型矩阵参数。
2.取视频中T帧数据图像用来训练高斯混合模型。来了第一个像素之后用它来当做第一个高斯分布。
3.当后面来的像素值时,与前面已有的高斯的均值比较,
如果该像素点的值与其模型均值差在3倍的方差内,则属于该分布,并对其进行参数更新。
4.如果下一次来的像素不满足当前高斯分布,用它来创建一个新的高斯分布。

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

混合高斯模型测试方法

在测试阶段,对新来像素点的值与混合高斯模型中的每一个均值进行比较,如果其差值在2倍的方差之间的话,则认为是背景,否则认为是前景。将前景赋值为255,背景赋值为0。这样就形成了一副前景二值图。

在这里插入图片描述

import numpy as np
import cv2

# 经典的测试视频
cap = cv2.VideoCapture(‘test.avi’)
# 形态学操作需要使用
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(3,3))
# 创建混合高斯模型用于背景建模
fgbg = cv2.createBackgroundSubtractorMOG2()

while(True):
ret, frame = cap.read()
fgmask = fgbg.apply(frame) # 应用到每一帧中提取背景
# 形态学开运算去噪点
fgmask = cv2.morphologyEx(fgmask, cv2.MORPH_OPEN, kernel)
# 寻找视频中的轮廓
contours, hierarchy = cv2.findContours(fgmask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

<span class="token keyword">for</span> c <span class="token keyword">in</span> contours<span class="token punctuation">:</span>
    <span class="token comment"># 计算各轮廓的周长</span>
    perimeter <span class="token operator">=</span> cv2<span class="token punctuation">.</span>arcLength<span class="token punctuation">(</span>c<span class="token punctuation">,</span><span class="token boolean">True</span><span class="token punctuation">)</span>
    <span class="token keyword">if</span> perimeter <span class="token operator">&gt;</span> <span class="token number">188</span><span class="token punctuation">:</span>
        <span class="token comment"># 找到一个直矩形(不会旋转)</span>
        x<span class="token punctuation">,</span>y<span class="token punctuation">,</span>w<span class="token punctuation">,</span>h <span class="token operator">=</span> cv2<span class="token punctuation">.</span>boundingRect<span class="token punctuation">(</span>c<span class="token punctuation">)</span>
        <span class="token comment"># 画出这个矩形</span>
        cv2<span class="token punctuation">.</span>rectangle<span class="token punctuation">(</span>frame<span class="token punctuation">,</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span>y<span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token punctuation">(</span>x<span class="token operator">+</span>w<span class="token punctuation">,</span>y<span class="token operator">+</span>h<span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span><span class="token number">255</span><span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token number">2</span><span class="token punctuation">)</span>    

cv2<span class="token punctuation">.</span>imshow<span class="token punctuation">(</span><span class="token string">'frame'</span><span class="token punctuation">,</span>frame<span class="token punctuation">)</span>
cv2<span class="token punctuation">.</span>imshow<span class="token punctuation">(</span><span class="token string">'fgmask'</span><span class="token punctuation">,</span> fgmask<span class="token punctuation">)</span>
k <span class="token operator">=</span> cv2<span class="token punctuation">.</span>waitKey<span class="token punctuation">(</span><span class="token number">150</span><span class="token punctuation">)</span> <span class="token operator">&amp;</span> <span class="token number">0xff</span>
<span class="token keyword">if</span> k <span class="token operator">==</span> <span class="token number">27</span><span class="token punctuation">:</span>
    <span class="token keyword">break</span>

cap.release()
cv2.destroyAllWindows()

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

缺点:不适用于复杂背景

二、光流估计

光流是空间运动物体在观测成像平面上的像素运动的 “瞬时速度”,根据各个像素点的速度矢量特征,可以对图像进行动态分析,例如目标跟踪。

  1. 亮度恒定:同一点随着时间的变化,其亮度不会发生改变。

  2. 小运动:随着时间的变化不会引起位置的剧烈变化,只有小运动情况下才能用前后帧之间单位位置变化引起的灰度变化去近似灰度对位置的偏导数。

  3. 空间一致:一个场景上邻近的点投影到图像上也是邻近点,且邻近点速度一致。因为光流法基本方程约束只有一个,而要求x,y方向的速度,有两个未知变量。所以需要连立n多个方程求解。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    如何求解方程组呢?看起来一个像素点根本不够,在物体移动过程中还有哪些特性呢?
    在这里插入图片描述

  • cv2.calcOpticalFlowPyrLK():
    参数:
    prevImage 前一帧图像
    nextImage 当前帧图像
    prevPts 待跟踪的特征点向量
    winSize 搜索窗口的大小
    maxLevel 最大的金字塔层数

    返回:
    nextPts 输出跟踪特征点向量
    status 特征点是否找到,找到的状态为1,未找到的状态为0

  • cv2.goodFeaturesToTrack:去跟踪好的特征
    feature_params
    qualityLevel 品质因子越大,得到的角点越少
    maxLevel 在这个距离范围内,哪个品质因子最强

import numpy as np
import cv2

cap = cv2.VideoCapture(‘test.avi’)

# 角点检测所需参数
feature_params = dict( maxCorners = 100,
qualityLevel = 0.3,
minDistance = 7)

# lucas kanade参数
lk_params = dict( winSize = (15,15),
maxLevel = 2)

# 随机颜色条
color = np.random.randint(0,255,(100,3))

# 拿到第一帧图像
ret, old_frame = cap.read()
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)
# 返回所有检测特征点,需要输入图像,角点最大数量(效率),品质因子(特征值越大的越好,来筛选)
# 距离相当于这区间有比这个角点强的,就不要这个弱的了
p0 = cv2.goodFeaturesToTrack(old_gray, mask = None, **feature_params) # 第一帧的角点

# 创建一个mask
mask = np.zeros_like(old_frame)

while(True):
ret,frame = cap.read()
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

<span class="token comment"># 需要传入前一帧和当前图像以及前一帧检测到的角点</span>
p1<span class="token punctuation">,</span> st<span class="token punctuation">,</span> err <span class="token operator">=</span> cv2<span class="token punctuation">.</span>calcOpticalFlowPyrLK<span class="token punctuation">(</span>old_gray<span class="token punctuation">,</span> frame_gray<span class="token punctuation">,</span> p0<span class="token punctuation">,</span> <span class="token boolean">None</span><span class="token punctuation">,</span> <span class="token operator">**</span>lk_params<span class="token punctuation">)</span>

<span class="token comment"># st=1表示</span>
good_new <span class="token operator">=</span> p1<span class="token punctuation">[</span>st<span class="token operator">==</span><span class="token number">1</span><span class="token punctuation">]</span>
good_old <span class="token operator">=</span> p0<span class="token punctuation">[</span>st<span class="token operator">==</span><span class="token number">1</span><span class="token punctuation">]</span>

<span class="token comment"># 绘制轨迹</span>
<span class="token keyword">for</span> i<span class="token punctuation">,</span><span class="token punctuation">(</span>new<span class="token punctuation">,</span>old<span class="token punctuation">)</span> <span class="token keyword">in</span> <span class="token builtin">enumerate</span><span class="token punctuation">(</span><span class="token builtin">zip</span><span class="token punctuation">(</span>good_new<span class="token punctuation">,</span>good_old<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">:</span>
    a<span class="token punctuation">,</span>b <span class="token operator">=</span> new<span class="token punctuation">.</span>ravel<span class="token punctuation">(</span><span class="token punctuation">)</span>
    c<span class="token punctuation">,</span>d <span class="token operator">=</span> old<span class="token punctuation">.</span>ravel<span class="token punctuation">(</span><span class="token punctuation">)</span>
    mask <span class="token operator">=</span> cv2<span class="token punctuation">.</span>line<span class="token punctuation">(</span>mask<span class="token punctuation">,</span> <span class="token punctuation">(</span>a<span class="token punctuation">,</span>b<span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token punctuation">(</span>c<span class="token punctuation">,</span>d<span class="token punctuation">)</span><span class="token punctuation">,</span> color<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>tolist<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span>
    frame <span class="token operator">=</span> cv2<span class="token punctuation">.</span>circle<span class="token punctuation">(</span>frame<span class="token punctuation">,</span><span class="token punctuation">(</span>a<span class="token punctuation">,</span>b<span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token number">5</span><span class="token punctuation">,</span>color<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>tolist<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span>
img <span class="token operator">=</span> cv2<span class="token punctuation">.</span>add<span class="token punctuation">(</span>frame<span class="token punctuation">,</span>mask<span class="token punctuation">)</span>

cv2<span class="token punctuation">.</span>imshow<span class="token punctuation">(</span><span class="token string">'frame'</span><span class="token punctuation">,</span>img<span class="token punctuation">)</span>
k <span class="token operator">=</span> cv2<span class="token punctuation">.</span>waitKey<span class="token punctuation">(</span><span class="token number">150</span><span class="token punctuation">)</span> <span class="token operator">&amp;</span> <span class="token number">0xff</span>
<span class="token keyword">if</span> k <span class="token operator">==</span> <span class="token number">27</span><span class="token punctuation">:</span>
    <span class="token keyword">break</span>

<span class="token comment"># 更新</span>
old_gray <span class="token operator">=</span> frame_gray<span class="token punctuation">.</span>copy<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment"># 当前帧作为old帧</span>
p0 <span class="token operator">=</span> good_new<span class="token punctuation">.</span>reshape<span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">2</span><span class="token punctuation">)</span>

cv2.destroyAllWindows()
cap.release()

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

缺点:

  1. 前一帧检测不到的角点(st=0),下一帧(及后续帧)也都检测不到。
  2. 以视频的第一帧为背景,后面出现的新目标也检测不到
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值