今天学习运动物体检测
一:帧差法
捕获摄像头的运动的手
import cv2
import numpy as np
# 如果我们想捕获一些运动的物体,每一帧图像中,不动的部分称之为背景,运动的物体称之为前景
# 假如我们的视频捕捉窗口是不动的,比如摄像头放着不动,保证了背景是基本不发生变化的,但是我们怎么捕获前景和背景啊?
# 第一部分: 帧差法
# 通过前后两帧的差值来捕捉运动的物体(一般用时间t的帧减去时间t-1的帧),超过某个阈值,则判断是前景,否则是背景
# 这个方法很简单,但是会带来巨大的噪声(微微震动,零星点)和空洞(运动物体非边缘部分也被判断成了背景)
cap = cv2.VideoCapture(0) # 其参数0表示第一个摄像头,一般就是笔记本的内建摄像头。
# cap = cv2.VideoCapture('images/kk 2022-01-23 18-21-21.mp4') # 来自vedio视频的
# 获取第一帧
ret, frame = cap.read()
frame_prev = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 将彩色图像转成灰度图
kernel1 = np.ones((5, 5), np.uint8) # 为了开运算使用
while (1):
# 获取每一帧
ret, frame = cap.read()
if frame is None:
print("camera is over...")
break
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 将彩色图像转成灰度图
diff = frame - frame_prev # 肯定有一些是负数,全是灰度值的相减
diff_abs = cv2.convertScaleAbs(diff) # 取绝对值,保留我们的差异值
_, thresh1 = cv2.threshold(diff_abs, 100, 255, cv2.THRESH_BINARY) # 二值化处理
MORPH_OPEN_1 = cv2.morphologyEx(thresh1, cv2.MORPH_OPEN, kernel1) # 开运算,去除噪声和毛刺
# erosion_it2r_1 = cv2.dilate(MORPH_OPEN_1, kernel1, iterations=2) # 膨胀操作
# cv2.imshow("capture", thresh1) # 展示该图像
cv2.imshow("capture", MORPH_OPEN_1) # 展示该图像
frame_prev = frame # 更新前一帧
# 进行等待或者退出判断
if cv2.waitKey(1) & 0xFF == 27:
break
cap.release()
cv2.destroyAllWindows()
捕捉视频的帧,捕获坤坤的打篮球和跳舞
import cv2
import numpy as np
# 如果我们想捕获一些运动的物体,每一帧图像中,不动的部分称之为背景,运动的物体称之为前景
# 假如我们的视频捕捉窗口是不动的,比如摄像头放着不动,保证了背景是基本不发生变化的,但是我们怎么捕获前景和背景啊?
# 第一部分: 帧差法
# 通过前后两帧的差值来捕捉运动的物体(一般用时间t的帧减去时间t-1的帧),超过某个阈值,则判断是前景,否则是背景
# 这个方法很简单,但是会带来巨大的噪声(微微震动,零星点)和空洞(运动物体非边缘部分也被判断成了背景)
cap = cv2.VideoCapture('images/kk 2022-01-23 18-21-21.mp4') # 来自vedio视频的
# 获取第一帧
ret, frame = cap.read()
frame_prev = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 将彩色图像转成灰度图
# kernel1 = np.ones((5, 5), np.uint8) # 为了开运算使用
while (1):
# 获取每一帧
ret, frame = cap.read()
if frame is None:
print("camera is over...")
break
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 将彩色图像转成灰度图
diff = frame - frame_prev # 肯定有一些是负数,全是灰度值的相减
diff_abs = cv2.convertScaleAbs(diff) # 取绝对值,保留我们的差异值
_, thresh1 = cv2.threshold(diff_abs, 100, 255, cv2.THRESH_BINARY) # 二值化处理
# MORPH_OPEN_1 = cv2.morphologyEx(thresh1, cv2.MORPH_OPEN, kernel1) # 开运算,去除噪声和毛刺
# erosion_it2r_1 = cv2.dilate(MORPH_OPEN_1, kernel1, iterations=2) # 膨胀操作
cv2.imshow("capture", diff_abs) # 展示该图像
# cv2.imshow("capture", thresh1) # 展示该图像
# cv2.imshow("capture", MORPH_OPEN_1) # 展示该图像
frame_prev = frame # 更新前一帧
# 进行等待或者退出判断
if cv2.waitKey(24) & 0xFF == 27:
break
cap.release()
cv2.destroyAllWindows()
效果如下:
总体而言,帧差法的效果真的是不忍直视。
二:高斯混合模型(GMM)
在进行前景检测前,先进行背景的学习和训练,对图像的每一个背景采用一个GMM进行模拟,每个背景的混合高斯模型是可以训练的,是自适应的。在测试阶段,对新来的像素进行GMM检测,如果像素值匹配某个高斯模型(进行概率值计算,看看和哪个高斯模型接近,或者看看和均值的偏离程度),则认为是背景,否则认为是前景(剧烈变化的像素,属于异常而原理GMM模型)。由于整个过程中GMM模型在不断的更新学习,多以对动态背景有一定的鲁棒性。
我们的视频中的每一帧的像素点不可能是一成不变的(理想很丰满),总会有空气密度导致的变化,吹一吹风,或者摄像头的微微抖动。但是正常情况下,我们都认为背景中的像素点都是满足一个高斯分布,在一定的概率下抖动是正常的。
我们的背景实际上也应当是多个高斯分布的混合分布,且每个高斯模型也是可以带权重的。因为背景有蓝天,建筑,树木花草,马路等。这些都是一些分布。
至于GMM和EM算法的内容和关系,我们在之前学习过,这里就不再讲述了。EM算法是求解GMM的一个方法。
运用在图像中的话,是怎么做的呢?步骤如下:
1:首先初始化每个高斯模型矩阵参数,初始的方差都是假设的(比如设置为5)。
2:先给T帧图像进行训练GMM,来了第一个像素后把他当成第一个高斯分布,比如作为均值。
3:后面再来一个像素,和当前的高斯分布作对比,如果该像素值和与当前的高斯模型的均值的差值在3倍的方差以内,就认为是属于该高斯分布,并对高斯分布参数进行更新。
4:如果该像素不满足该高斯分布。则用这个新的像素建立一个新的高斯分布(用它做均值,假设一个方差(比如设置为5))。但是一个像素位置不要太多个高斯分布,太多了会造成计算量巨大。3~5个基本够了。
GMM测试步骤:
对于新来的像素值,和GMM的的多个高斯分布的均值分别作比较,其差值在2倍的方差之内,则认为是背景,否则是前景。前景像素点设置为255,背景像素点设置为0,行成了一个二值图像。
import cv2
import numpy as np
cap = cv2.VideoCapture('images/kk 2022-01-23 18-21-21.mp4') # 来自vedio视频的
kernel1 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
mog = cv2.createBackgroundSubtractorMOG2() # 创建混合高斯模型来用于北京建模
while (1):
# 获取每一帧
ret, frame = cap.read()
if frame is None:
print("camera is over...")
break
fmask = mog.apply(frame) # 判断哪些是前景和背景
MORPH_OPEN_1 = cv2.morphologyEx(fmask, cv2.MORPH_OPEN, kernel1) # 开运算,去除噪声和毛刺
contours, _ = cv2.findContours(fmask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 只检测外边框
for cont in contours:
# 计算各个轮廓的面积
len = cv2.arcLength(cont, True)
if len > 300: # 去除一些小的噪声点
# 找到一个轮廓
x,y,w,h = cv2.boundingRect(cont)
# 画出这个矩形
cv2.rectangle(frame, (x,y), (x+w, y+h), color=(0,255,0), thickness=3)
# 画出所有的轮廓
cv2.imshow('frame', frame)
cv2.imshow('fmask', fmask)
# 进行等待或者退出判断
if cv2.waitKey(24) & 0xFF == 27:
break
cap.release()
cv2.destroyAllWindows()
效果如下:
三:KNN
也是根据每个像素点的历史信息进行统计额比较,来判断新来的像素值是不是背景。具体的原理后面补充。
import cv2
import numpy as np
cap = cv2.VideoCapture('images/kk 2022-01-23 18-21-21.mp4') # 来自vedio视频的
kernel1 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
knn = cv2.createBackgroundSubtractorKNN() # 创建KNN模型
while (1):
# 获取每一帧
ret, frame = cap.read()
if frame is None:
print("camera is over...")
break
fmask = knn.apply(frame) # 判断哪些是前景和背景
MORPH_OPEN_1 = cv2.morphologyEx(fmask, cv2.MORPH_OPEN, kernel1) # 开运算,去除噪声和毛刺
contours, _ = cv2.findContours(fmask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 只检测外边框
for cont in contours:
# 计算各个轮廓的面积
len = cv2.arcLength(cont, True)
if len > 200: # 去除一些小的噪声点
# 找到一个轮廓
x,y,w,h = cv2.boundingRect(cont)
# 画出这个矩形
cv2.rectangle(frame, (x,y), (x+w, y+h), color=(0,255,0), thickness=3)
# 画出所有的轮廓
cv2.imshow('frame', frame)
cv2.imshow('fmask', fmask)
# 进行等待或者退出判断
if cv2.waitKey(24) & 0xFF == 27:
break
cap.release()
cv2.destroyAllWindows()
效果如下: