【python】OpenCV—Tracking(10.2)—BackgroundSubtractor
借助 BackgroundSubtractor 类,可检测阴影,用阈值排除阴影,从而关注实际特征。做 gif 的时候只设置了播放一次,重复播放需要刷新。Opencv 有三种背景分割器。

文章目录
1、背景分割器介绍
在OpenCV中,背景分割器是处理视频或图像序列以区分前景(如移动物体)和背景的重要工具。以下是对OpenCV中几种常用背景分割器(MOG2、KNN、GMG)的使用场景、优缺点的详细解释:
一、Mixture of Gaussians (MOG2)
使用场景:
- 适用于场景中存在很多变化和动态光照条件的情况。
- 在处理光照变化和动态背景时表现良好。
优点:
- 对光照变化有较好的适应性。
- 在动态背景环境下表现良好。
缺点:
- 对于相对静态的背景可能效果不佳。
二、K-Nearest Neighbors (KNN)
使用场景:
- 在处理运动目标和部分遮挡情况时可能更有效。
优点:
- 根据像素的颜色特征和邻近像素的情况进行分类,因此在处理运动目标和部分遮挡时可能更有效。
缺点:
- 在复杂动态背景下表现可能欠佳。
三、Geometric Multigrid (GMG)
使用场景:
- 在动态背景和光照变化下表现较好。
优点:
- 结合了几何学和统计学的方法,对动态背景和光照变化具有较好的鲁棒性。
缺点:
- 对于较为静态的场景可能不够精确。
四、总结
MOG2 更适合光照变化和动态背景较多的场景。
KNN 在处理运动目标和部分遮挡时可能更有效,但在复杂动态背景下可能表现不佳。
GMG 在动态背景和光照变化下表现良好,但可能在静态场景下不够精确。
在选择背景分割器时,需要根据具体的应用场景和需求进行权衡。例如,如果场景中光照变化较大,那么MOG2可能是更好的选择;而如果主要目标是检测运动目标并处理部分遮挡,那么KNN可能更为合适。
opencv 中背景分割器可借助 BackgroundSubtractor 类,检测阴影,用阈值排除阴影,从而关注实际特征
2、cv2.createBackgroundSubtractorMOG2
createBackgroundSubtractorMOG2 是 OpenCV 库中的一个函数,用于创建基于混合高斯模型(Mixture of Gaussians, MOG2)的背景减除器。以下是对 createBackgroundSubtractorMOG2 的中文文档,清晰地分点表示和归纳了其主要内容:
一、函数概述
createBackgroundSubtractorMOG2 是 OpenCV 中用于背景/前景分割的算法之一,基于混合高斯模型(MOG2)进行背景建模和前景检测。该函数允许用户通过调整参数来适应不同的应用场景。
二、函数原型
retval = cv2.createBackgroundSubtractorMOG2([, history[, varThreshold[, detectShadows]]])
三、参数解释
history(可选,整型,默认值 200):
- 表示用于训练背景模型的时间长度,单位是帧数。它决定了背景模型更新的速度,值越大,背景模型更新的速度就越慢——过往帧数,500帧,选择history = 1就变成两帧差
varThreshold(可选,浮点型,默认值 15):
- 用于判断像素是否为背景的阈值。这个参数对结果有很大影响,值越小,检测到的运动物体就越多,但也可能增加误检——像素与模型之间的马氏距离,值越大,只有那些最新的像素会被归到前景,值越小前景对光照越敏感。
detectShadows(可选,布尔型,默认值 True):
- 一个布尔值,用于指示算法是否检测阴影并对其进行标记。启用阴影检测会稍微降低速度,因此如果不需要此功能,建议将其设置为 False。
四、返回值
该函数返回一个背景减除器对象,该对象可以用于对视频帧或图像序列进行前景检测。
五、工作原理
模型初始化:在开始时,算法会对视频中的每个像素建立一个混合高斯模型。这个模型会学习并适应场景中的背景变化。
背景建模:随着新帧的到来,算法会更新每个像素的高斯分布。对于与现有高斯分布匹配良好的像素,这些分布会被更新以反映最新的像素值。对于不匹配任何现有分布的像素,会创建新的高斯分布或替换最不可能代表背景的高斯分布。
前景检测:如果某个像素的值与所有高斯分布都不匹配,或者只与表示前景的高斯分布匹配,则该像素被视为前景像素。
阴影检测(如果启用):算法还可以检测并标记出由于前景对象遮挡而产生的阴影区域。
六、使用场景
动态背景:在背景经常变化的情况下,如光照变化、树叶摇动等,MOG2 表现出较好的鲁棒性。
视频监控:在视频监控系统中,用于检测运动物体或行人。
实时分析:由于 MOG2 的计算效率较高,因此适用于需要实时处理的应用场景。
七、注意事项
参数调整:根据具体的应用场景和需求,可能需要调整 history、varThreshold 和 detectShadows 等参数以获得最佳效果。
性能考虑:虽然 MOG2 在大多数情况下都能提供较好的性能,但在某些极端情况下(如快速变化的光照条件),可能需要考虑使用其他更复杂的背景减除算法。
import cv2
import os
# bs = cv2.createBackgroundSubtractorKNN(detectShadows=True)
bs = cv2.createBackgroundSubtractorMOG2(detectShadows=True)
os.makedirs("frame1", exist_ok=True)
os.makedirs("frame2", exist_ok=True)
os.makedirs("frame3", exist_ok=True)
camera = cv2.VideoCapture('car.mkv')
index = 0
while True:
ret, frame = camera.read()
index += 1
frame_h, frame_w, _ = frame.shape
fgmask = bs.apply(frame)
th = cv2.threshold(fgmask.copy(), 244, 255, cv2.THRESH_BINARY)[1]
dilated = cv2.dilate(th, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)),
iterations=2)
contours, _ = cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for c in contours:
# if cv2.contourArea(c) > frame_w*0.075 * frame_h*0.075:
if cv2.contourArea(c) > 1000:
(x, y, w, h) = cv2.boundingRect(c)
cv2.rectangle(frame, (x,y), (x+w, y+h), (0, 0, 255), 5)
cv2.imshow("mog", fgmask)
cv2.imwrite("./frame1/{}.jpg".format(index), fgmask)
cv2.imshow("thresh", th)
cv2.imwrite("./frame2/{}.jpg".format(index), th)
cv2.imshow("detection", frame)
cv2.imwrite("./frame3/{}.jpg".format(index), frame)
if cv2.waitKey(30) & 0xff == ord("q"):
break
camera.release()
cv2.destroyAllWindows()



做 gif 的时候只设置了播放一次,重复播放需要刷新该页面
3、cv2.createBackgroundSubtractorKNN
用法基本和 cv2.createBackgroundSubtractorMOG2 一致,工作原理如下
模型初始化:在开始时,KNN 算法会收集一系列初始帧来构建背景模型。
背景建模:对于每个新的帧,KNN 算法会计算当前像素与背景模型中所有样本之间的距离,并基于这些距离来决定该像素是前景还是背景。
前景检测:如果当前像素与所有背景模型中的样本距离都大于 dist2Threshold,则该像素被视为前景。
阴影检测(如果启用):如果 detectShadows 设置为 True,KNN 算法会尝试检测并标记出前景中的阴影区域。
import cv2
import numpy as np
bs = cv2.createBackgroundSubtractorKNN(detectShadows=True)
camera = cv2.VideoCapture('car.mkv')
index = 0
while True:
ret, frame = camera.read()
index += 1
frame_h, frame_w, _ = frame.shape
fgmask = bs.apply(frame)
th = cv2.threshold(fgmask.copy(), 244, 255, cv2.THRESH_BINARY)[1]
dilated = cv2.dilate(th, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)),
iterations=2)
contours, _ = cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for c in contours:
# if cv2.contourArea(c) > frame_w*0.075 * frame_h*0.075:
if cv2.contourArea(c) > 1000:
(x, y, w, h) = cv2.boundingRect(c)
cv2.rectangle(frame, (x,y), (x+w, y+h), (0, 0, 255), 5)
cv2.imshow("mog", fgmask)
cv2.imwrite("./frame1/{}.jpg".format(index), fgmask)
cv2.imshow("thresh", th)
cv2.imwrite("./frame2/{}.jpg".format(index), th)
cv2.imshow("detection", frame)
cv2.imwrite("./frame3/{}.jpg".format(index), frame)
if cv2.waitKey(30) & 0xff == ord("q"):
break
camera.release()
cv2.destroyAllWindows()



4、跟踪目标中引入目标 id
上面相当于检测出来了目标,下面给每个目标一个 id,就更贴近跟踪的应用场景了(虽然还做不到同一个目标来回出现,id 不跳动)
import cv2
from tracker import *
# Create tracker object
tracker = EuclideanDistTracker()
cap = cv2.VideoCapture("highway.mp4")
# Object detection from Stable camera
object_detector = cv2.createBackgroundSubtractorMOG2(history=100, varThreshold=40)
num = 0
while True:
num += 1
ret, frame = cap.read()
height, width, _ = frame.shape
# Extract Region of interest
roi = frame[340: 720, 500: 800]
# 1. Object Detection
mask = object_detector.apply(roi)
_, mask = cv2.threshold(mask, 254, 255, cv2.THRESH_BINARY)
contours, _ = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
detections = []
for cnt in contours:
# Calculate area and remove small elements
area = cv2.contourArea(cnt)
if area > 100:
#cv2.drawContours(roi, [cnt], -1, (0, 255, 0), 2)
x, y, w, h = cv2.boundingRect(cnt)
detections.append([x, y, w, h])
# 2. Object Tracking
boxes_ids = tracker.update(detections)
for box_id in boxes_ids:
x, y, w, h, id = box_id
cv2.putText(roi, str(id), (x, y - 15), cv2.FONT_HERSHEY_PLAIN, 2, (255, 0, 0), 2)
cv2.rectangle(roi, (x, y), (x + w, y + h), (0, 255, 0), 3)
cv2.imshow("roi", roi)
cv2.imshow("Frame", frame)
cv2.imshow("Mask", mask)
if 0: # 保存结果
index = str(num).zfill(3)
cv2.imwrite(f"./roi/{index}.jpg", roi)
cv2.imwrite(f"./Frame/{index}.jpg", frame)
cv2.imwrite(f"./Mask/{index}.jpg", mask)
key = cv2.waitKey(30)
if key == 27:
break
cap.release()
cv2.destroyAllWindows()
上面展示的代码和之前章节的没有多大差异
看看核心赋予 id 的部分, EuclideanDistTracker 的实现如下
import math
class EuclideanDistTracker:
def __init__(self):
# Store the center positions of the objects
self.center_points = {}
# Keep the count of the IDs
# each time a new object id detected, the count will increase by one
self.id_count = 0
def update(self, objects_rect):
# Objects boxes and ids
objects_bbs_ids = [] # 存放历史 bbox
# Get center point of new object
for rect in objects_rect: # 遍历当前帧的所有 bbox
x, y, w, h = rect
cx = (x + x + w) // 2 # 计算中心点坐标
cy = (y + y + h) // 2
# Find out if that object was detected already
same_object_detected = False
for id, pt in self.center_points.items(): # 遍历历史帧所有存储的 bbox
dist = math.hypot(cx - pt[0], cy - pt[1]) # 计算当前帧和历史帧的中心点距离
if dist < 25: # 中心点的距离小于 25,就判定是已存在的目标
self.center_points[id] = (cx, cy)
# print(self.center_points)
objects_bbs_ids.append([x, y, w, h, id])
same_object_detected = True
break
# New object is detected we assign the ID to that object
if same_object_detected is False:
self.center_points[self.id_count] = (cx, cy) # 如果当前bbox不存在于历史的bbox,加入历史列表中
objects_bbs_ids.append([x, y, w, h, self.id_count])
self.id_count += 1
if 0: # 感觉不到下面代码的作用???
# Clean the dictionary by center points to remove IDS not used anymore
new_center_points = {}
for obj_bb_id in objects_bbs_ids:
_, _, _, _, object_id = obj_bb_id
center = self.center_points[object_id]
new_center_points[object_id] = center
# Update dictionary with IDs not used removed
self.center_points = new_center_points.copy()
# print(objects_bbs_ids)
return objects_bbs_ids
核心思想是通过判定历史帧所有 bbox 的中心点与当前帧所有中心点的 bbox 的欧氏距离,小于一定阈值就默认为同一物体,id 不更新,高于阈值则认为新的物体出现,赋予新的 id
缺点也是很明显,移动较快的时候 id 更新不过来,同一物体来回在画面移动,id 也会一直更新
最后看看实际效果
原始视频
tracking-highway
roi 区域,我们仅跟踪 roi 区域
tracking-roi
跟踪时候的 mask,也即目标检测的输入(通过找轮廓检测出来目标)
tracking-mask
5、涉及到的库函数
5.1、cv2.threshold
cv2.threshold 是 OpenCV 库中的一个重要函数,用于对图像进行阈值化处理。阈值化处理是数字图像处理中常用的一种方法,可以将图像转换为二值图像,即图像中每个像素只有黑白两种颜色。以下是 cv2.threshold 函数的详细中文文档:
一、函数原型
retval, dst = cv2.threshold(src, thresh, maxval, type[, dst])
二、参数说明
src:输入图像,必须是单通道灰度图像。
thresh:阈值,用于对像素值进行比较的界限值。可以是一个固定的数值,也可以是一个自适应阈值算法。
maxval:当像素值大于阈值时,赋予的新像素值。在二值化处理中,通常设置为 255(即白色)。
type:阈值类型,决定了对像素值大于或小于阈值的像素进行何种操作。常见的阈值类型包括:
- cv2.THRESH_BINARY:大于阈值的像素设置为 maxval,小于等于阈值的像素设置为 0。
- cv2.THRESH_BINARY_INV:大于阈值的像素设置为 0,小于等于阈值的像素设置为 maxval。
- cv2.THRESH_TRUNC:大于阈值的像素设置为阈值,小于等于阈值的像素保持不变。
- cv2.THRESH_TOZERO:大于阈值的像素保持不变,小于等于阈值的像素设置为 0。
- cv2.THRESH_TOZERO_INV:大于阈值的像素设置为 0,小于等于阈值的像素保持不变。
dst:输出图像,可选参数。如果未提供,将创建一个新的图像来存储结果。
三、返回值
retval:选取的阈值。在某些情况下,如果使用了自适应阈值算法,这个值可能有所不同。
dst:输出图像,与输入图像具有相同的大小和类型。
四、应用场景
阈值处理在图像处理中有广泛的应用,包括但不限于:
- 图像分割:将图像中的目标物体与背景进行分离。
- 目标检测:使用轮廓提取等方法进行目标检测和识别。
- 图像增强:改善图像的对比度和清晰度。
- 文字识别:更容易地提取出文字区域。
- 图像压缩:减小图像文件的大小,实现图像的压缩和存储。
5.2、cv2.dilate
cv2.dilate 是 OpenCV 库中的一个函数,用于对图像进行膨胀操作。膨胀是一种形态学操作,它可以用来增加图像中物体的大小,填充图像中的空洞,连接相邻的物体等。以下是 cv2.dilate 函数的详细中文文档:
一、函数原型
dst = cv2.dilate(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]])
二、参数说明
src:输入图像,可以是灰度图像或彩色图像。
kernel:
- 用于膨胀操作的结构元素,控制膨胀的形状和大小。
- 可以使用 cv2.getStructuringElement() 函数来创建结构元素,该函数接受形状、大小等参数。
- 常见的形状有:MORPH_RECT(矩形)、MORPH_CROSS(交叉形)、MORPH_ELLIPSE(椭圆形)。
dst:
- 输出图像,与输入图像具有相同的尺寸和深度。
- 可选参数,如果未提供,将创建一个新的图像来存储结果。
anchor:
- 结构元素的锚点位置,决定了结构元素在图像上的位置。
- 默认为 (-1, -1),表示锚点位于结构元素的中心。
iterations:
- 膨胀操作的迭代次数。
- 默认为 1,表示只进行一次膨胀操作。
borderType:
- 边界扩充的类型。
- 默认为 BORDER_CONSTANT。
borderValue:
- 边界扩充的数值。
- 默认为 0。
三、返回值
dst:膨胀后的输出图像。
四、用法解释
cv2.dilate 函数的作用是将图像中的物体进行膨胀操作,使其变大。膨胀操作的效果取决于所使用的结构元素的形状和大小。通常情况下,结构元素可以是矩形、圆形或者自定义形状。膨胀操作会将结构元素覆盖区域内的像素值取最大值(对于灰度图像)或进行逻辑运算(对于二值图像),从而使目标物体的边界变得更加平滑,填充空洞,连接相邻的物体。
5.3、cv2.findContours
cv2.findContours 是 OpenCV 库中用于在二值图像中查找轮廓的函数。以下是对 cv2.findContours 函数的详细中文文档,包括函数原型、参数说明、返回值以及示例代码。
一、函数原型
contours, hierarchy = cv2.findContours(image, mode, method[, contours[, hierarchy[, offset]]])
二、参数说明
image:
- 输入的二值化图像,一般为灰度图像或二值图像。
mode:轮廓检索模式,指定轮廓的层级关系。
- cv2.RETR_EXTERNAL:只返回最外层的轮廓。
- cv2.RETR_LIST:返回所有轮廓,不建立轮廓间的层级关系。
- cv2.RETR_TREE:返回所有轮廓,并建立完整的层级关系。
- cv2.RETR_CCOMP:返回所有轮廓,并重建嵌套轮廓的完整层次结构(二级)。
method:轮廓逼近方法,指定轮廓的表示方式。
- cv2.CHAIN_APPROX_NONE:保存所有的轮廓点。
- cv2.CHAIN_APPROX_SIMPLE:仅保存轮廓的端点。
- cv2.CHAIN_APPROX_TC89_L1 和 cv2.CHAIN_APPROX_TC89_KCOS:应用 Teh-Chin 链逼近算法。
contours(可选):
- 用于存储检测到的轮廓的变量,如果传递了该参数,则不会返回此值。
hierarchy(可选):
- 用于存储轮廓的层次关系信息的变量,如果传递了该参数,则不会返回此值。
offset(可选):
- 可选的偏移量,用于在输出轮廓坐标中移动每个点。默认为 Point()。
三、返回值
contours:
轮廓列表,其中每个轮廓都是一个 NumPy 数组,包含该轮廓上所有点的坐标。
hierarchy:
轮廓的层次关系信息的 NumPy 数组,其中每个轮廓的层次关系由四个整数 [next, previous, first child, parent] 组成。
5.4、cv2.boundingRect
cv2.boundingRect 是 OpenCV 库中的一个函数,用于计算点集或灰度图像中非零像素的最小外接矩形(边界框)。以下是关于 cv2.boundingRect 函数的详细中文文档:
一、函数原型
rect = cv2.boundingRect(points)
或者
x, y, w, h = cv2.boundingRect(points)
二、参数说明
points:
- 输入的轮廓点集合,可以是 numpy.ndarray 类型,通常通过 cv2.findContours 函数获取。
三、返回值
rect(或者 x, y, w, h):
- 返回一个矩形边界框的信息。
- 如果返回单个变量 rect,它是一个包含四个整数值的元组,即 (x, y, w, h)。
- 如果返回四个单独的变量,则 x 和 y 是矩形左上角的坐标(通常是整数),w 是矩形的宽度,h 是矩形的高度。
四、细节解析
坐标系统:在 OpenCV 中,坐标系的原点 (0, 0) 位于图像的左上角,x 轴向右为正方向,y 轴向下为正方向。
边界框属性:
- (x, y):矩形左上角的坐标。
- w:矩形的宽度(水平方向上的像素数)
- h:矩形的高度(垂直方向上的像素数)
与 cv2.findContours 的关系:
- 通常,cv2.boundingRect 函数用于处理 cv2.findContours 函数返回的轮廓数据。cv2.findContours 可以从二值图像中检测出轮廓,并返回轮廓的点集信息。
5.5、math.hypot
math.hypot 是 Python 中 math 模块的一个函数,用于计算欧几里得范数,也就是直角三角形的斜边长度,或者说是两个或多个数值作为直角边的直角三角形的斜边长度。这个函数可以接受任意数量的参数,返回这些参数作为直角边所构成的直角三角形的斜边的长度。它实际上是计算了这些参数的平方和的平方根。
这个函数非常有用,尤其是在处理二维或三维空间中的距离计算时。例如,如果你想计算一个点在二维空间中的距离到原点的距离,你可以使用 math.hypot(x, y),其中 x 和 y 分别是该点的横纵坐标。对于三维空间中的点,你可以使用 math.hypot(x, y, z) 来计算该点到原点的距离。
使用 math.hypot 而不是直接计算平方和的平方根(如 s q r t ( x 2 + y 2 ) sqrt(x^2 + y^2) sqrt(x2+y2))的好处是,math.hypot 可以避免在计算过程中出现中间结果的溢出,因为它使用了更精确的算法来处理大数和小数的组合。
示例代码:
import math
# 计算二维空间中点到原点的距离
distance_2d = math.hypot(3, 4)
print(distance_2d) # 输出 5.0
# 计算三维空间中点到原点的距离
distance_3d = math.hypot(3, 4, 5)
print(distance_3d) # 输出 7.0710678118654755
在这个例子中,math.hypot(3, 4) 计算了直角边为3和4的直角三角形的斜边长度,结果是5,因为3-4-5是一个勾股数。同样,math.hypot(3, 4, 5) 计算了直角边分别为3、4和5的三维直角三角形的斜边长度,即点到原点的空间距离。
6、参考
- 目标跟踪(5)使用 Opencv 和 Python 进行对象跟踪
- https://pysource.com/2021/01/28/object-tracking-with-opencv-and-python/
- 完整代码地址:
链接:https://pan.baidu.com/s/1smt4hwD8Zi8dN977gLjYhA
提取码:123a
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐

所有评论(0)