使用粒子滤波(particle filter)进行视频目标跟踪

news2024/12/22 7:36:34

虽然有许多用于目标跟踪的算法,包括较新的基于深度学习的算法,但对于这项任务,粒子滤波仍然是一个有趣的算法。所以在这篇文章中,我们将介绍视频中的目标跟踪:预测下一帧中物体的位置。在粒子滤波以及许多其他经典跟踪算法的情况下,我们根据估计的动态进行预测,然后使用一些测量值更新预测。

我们从数学理论开始。粒子滤波是一种贝叶斯滤波方法,主要用于非线性、非高斯动态系统中的状态估计。它通过使用一组随机样本(称为粒子)来表示状态的后验概率分布,并通过这些粒子的加权平均来估计状态。

在每个时间步(或视频中的一帧),对物体的位置有一些信念(也称为先验知识)。这种信念是基于我们从前面的步骤中得到的信息。为了提高对目标位置的估计,可以通过测量当前时间步长的状态,在初始信念(或预测)中添加额外的信息。通过测量我们可以更新或修正目标的状态(即它的位置)。这种新的修正估计也称为后验估计。

也就是说我们的目标有一些隐藏状态(在这个例子中是位置),它将被标记为x。因为不确定这个状态(这就是它被隐藏的原因),所以可以对它进行估计,同时可以对状态Y进行一些测量,但测量总是有噪声的,这意味着它不是完美的,所以得到的时目标在哪里的一些分布,而不是精确的位置(如果测量是完美的,我们不需要任何估计,我们确切地知道物体在哪里)。

在每个时间步长,目标的状态都会发生变化,我们希望利用过去和当前测量的信息,通过找到最可能的状态来估计当前状态。在数学上首先要根据过去做出预测,以使我们相信当前状态(这是一个概率分布)

然后,使用当前时间步长的测量值对预测进行校正。

一般情况下是通过假设这个过程是马尔可夫的来简化它,这意味着它只依赖于前一个时间步长。所以,关于当前状态的信念只取决于前一步的状态。

修正增加了当前测量的知识。

然后我们可以用贝叶斯规则将最后一个方程表示如下。

这个方程中,当前状态取决于给定我们处于状态X,得到当前测量值Y的概率(如果我们确实处于状态X,看到测量值的可能性)乘以给定我们处于状态X,得到状态X的概率(这是我们的信念或先验)。这样就得到了假设我们在状态X´´´时得到Y´´的概率。

线性动态模型

为了做出我们最初的预测(或信念),我们使用动态模型。这个模型假设在步骤t-1和步骤t的状态之间存在某种关系,我们将使用线性动态模型,这意味着步骤之间的关系是线性的。当我们想要预测一个运动物体的位置时可以将它的状态构造为位置pₓ和p_y,速度vₓ和v_y的向量,因此xₜ=[pₓ,ₜ , p_yₜ , vₓ,ₜ , v_yₜ].一个模型可以将状态x x_i和x x_i关联起来

我们这个例子中Δt=1。也可以把它写成矩阵形式:

这里的M为:

前两行表示新位置的方程,后两行表示x和y方向上的速度都是守恒的。由于存在不确定性,我们还添加了一些噪声。

过程可视化

我们描述的从t-1状态到t时刻的过程可以在下面的图中可视化,它描述了一个一维情况。

在左上的图中,我们看到了步骤t-1中位置x的分布(因为我们是一维的,所以状态只包含沿x轴的位置)。在应用我们的动态后,分布移动到一个新的位置,不改变形状。这一步是确定的,因为我们知道应用的动态。但是我们不确定动态的具体数值,所以需要在分布中加入噪音或不确定性,使其变得更宽,如图右下所示。最后通过测量来增加额外的信息,这对位置进行了修正,降低了不确定性。

粒子滤波

我们可以把分布看作是不同大小的粒子的表示。大粒子位于概率高的地方,小粒子位于概率低的地方。

这个过程与前面描述的过程类似。我们从N个不同权重的粒子开始描述步骤t-1的状态分布。从这些粒子中,对N个粒子的新集合进行采样,其中较大的粒子被选中的概率更高,并且可以对相同的粒子进行多次采样。对于新的粒子集合,我们应用动态学并进行确定性漂移。然后在粒子中加入一些噪声来扩展分布。最后,通过使用测量根据获得这种测量的可能性来设置粒子的权重,如果状态确实与粒子中的状态相同。

数学原理一般都很枯燥,并且难以理解,下面我们来用代码来实现这个过程,这样可以更深入的了解这个方法的数学原理。

Python代码实现

首先导入库:

 import matplotlib.pyplot as plt
 import numpy as np
 import cv2  # openCV

接下来初始化一个视频捕获对象,从视频中读取帧并读取第一帧。

 # create video reader object and read te first frame
 cap = cv2.VideoCapture('simpson.avi')
 ret, image = cap.read()

我们通过选择我们将使用的粒子数量来启动粒子滤波器(粒子越多,分布越准确,但会增加更多的计算成本),并设置初始状态。我们选择状态为[边界框中心的x坐标,它的y坐标,边界框x方向的速度,y方向的速度]。我们还假设边界框的大小是恒定的(但一般来说,它可以是状态的一部分)。

 num_of_particles = 250
 s_init = np.array([81, 169, 0, 0])  # initial state [x_center, y_center, x_velocity, y_velocity]
 
 # bounding box's (half) height and width.
 # We assume those are constant
 half_height = 118
 half_width = 33

初始状态和边界框的大小是手动选择的。在实际计算之前,需要创建了一个视频写入器对象来保存跟踪结果视频。

 # get parameters of the video
 frame_width = int(cap.get(3))
 frame_height = int(cap.get(4))
 fps = 20 #cap.get(5)  # frames per second
 
 # create a video writer object
 size = (frame_width, frame_height)
 result = cv2.VideoWriter('simpson_tracked.avi',
                          cv2.VideoWriter_fourcc(*'MJPG'),
                          fps, size)

对于测量,可以比较来自初始状态的原始边界框与后续帧中新的候选边界框之间ROI的颜色(归一化)直方图。所以这里定义了一个函数来计算规范化直方图。

 def compute_norm_hist(image, state):
     """
     Compute histogram based on the RGB values of the image in the ROI defined by the state
     Args:
         image (np.ndarray): matrix represent image with the object to track in it
         state (np.ndarray): vector of the state of the tracked object
 
     Returns:
         vector of the normalized histogram of the colores in the ROI of the image based on the state
     """
     # get the top-left and bottom-right corner of the object bounding box
     # if the bounding box is outside the frame, we clap it
     x_min = max(np.round(state[0] - half_width).astype(int), 0)
     x_max = min(np.round(state[0] + half_width).astype(int), image.shape[1])
     y_min = max(np.round(state[1] - half_height).astype(int), 0)
     y_max = min(np.round(state[1] + half_height).astype(int), image.shape[0])
 
     roi = image[y_min:y_max+1, x_min:x_max+1]
     roi_reduced = roi // 16  # change range of pixel values from 0-255 to 0-15
     # build vector to represent the combinations of RGB as single value
     roi_indexing = (roi_reduced[..., 0] + roi_reduced[..., 1] * 16 + roi_reduced[..., 2] * 16 ** 2).flatten()
     hist, _ = np.histogram(roi_indexing, bins=4096, range=(0,4096))
     norm_hist = hist / np.sum(hist)  # normalize the histogram
     return norm_hist

根据状态得到边界框左上角和右下角的坐标。只从图像中提取ROI。为了使计算更容易,将颜色的值范围从256个值减少到16个值。’ // '运算符将两个数相除,并将结果舍入到最接近的整数。对于RGB的每个组合(在0-15范围内),所以我们构建一个16进制的数字。第一个颜色通道代表16⁰,第二个是16¹,最后一个是16²。所以得到的新数字的取值范围为0 ~ 4095,即唯一的4096个数字。我们从ROI中的所有值构建一个直方图,并通过直方图的总和将其归一化,这个样所有的比较将是一致的。

我们可以创建初始状态的规范化直方图。

 q = compute_norm_hist(image, s_init)

由于我们目前只有一种状态,可以将其复制为粒子的数量,并为所有粒子设置相同的权重。

 s_new = np.tile(s_init, (num_of_particles, 1)).T
 weights = np.ones(num_of_particles)

然后就是while循环,执行算法的所有步骤:采样、预测(并添加噪声)、测量和重加权。

 # go over the frames in the video
 while True:
     # sample new particles according to the weights
     s_sampled = sample_particle(s_new, weights)
     # predict new particles (states) according to the previous states
     s_new = predict_particles(s_sampled)
     # go over the new predicted states, and compute the histogram for the state and
     # the weight with the original histogram
     for jj, s in enumerate(s_new.T):
         p = compute_norm_hist(image, s)
         weights[jj] = compute_weight(p, q)

让我们把这些步骤逐个实现。首先,我们根据粒子及其分布进行采样。

 def sample_particle(particles_states, weights):
     """
     Sample the particles (states) according to their weights.
     First a random numbers from uniform distribution U~[0,1] are generated.
     The random numbers are subtracted from the cumulative sum of the weights,
     and the sampling is made by selecting the state which its cumulative weights
     sum is the minimal one which is greater than the random number. If c[i] is the
     cumulative sum of the weights for the i-th state and r is a random number, the
     state which will be sampled is the j-th state so c[j] >= r and also c[j] is
     the smaller one that hold this inequality.
     Args:
         particles_states (np.ndarray): matrix which its columns are the states
         weights (np.ndarray): weights of each state
 
     Returns:
         (np.ndarray): matrix of sampled states according to their weights
     """
     normalized_weights = weights / np.sum(weights)
     sampling_weights = np.cumsum(normalized_weights)
     rand_numbers = np.random.random(sampling_weights.size)  # get random numbers from U~[0,1]
     cross_diff = sampling_weights[None, :] - rand_numbers[:, None]  # subtract from each weight any of the random numbers
     cross_diff[cross_diff < 0] = np.inf  # remove negative results
     sampling = np.argmin(cross_diff, axis=1)
     sampled_particles = particles_states[:, sampling]  # sample the particles
     return sampled_particles

我们对粒子的权重进行归一化,并计算它们的累积和。我们在0到1之间随机选择一个数字,然后从之前计算的累积权重向量中减去这个数字。然后选择第一个累积权值大于随机数的元素。权重高的状态更有可能被选中(我们可以多次选择一个状态)。

然后就是抽样进行预测。

 def predict_particles(particles_states):
     """
     Predict new particles (states) based on the previous states
     Args:
         particles_states (np.ndarray): matrix of states. The rows are the properties of the state and the columns are the particles
 
     Returns:
         (np.ndarray): new predicted particles
     """
     # the dynamic we use assume that:
     # x_new = x + v_x (x - center position)
     # y_new = y + v_y (y - center position)
     # v_x_new = v_x (velocity at x)
     # v_y_new = v_y (velocity at y)
     dynamics = np.array(
         [[1 ,0, 1, 0],
          [0, 1, 0, 1],
          [0, 0, 1, 0],
          [0, 0, 0, 1]]
     )
     # multiplying the state by the dynamics and add some noise
     predicted_states = dynamics @ particles_states + np.random.normal(loc=0, scale=1, size=particles_states.shape)
     return predicted_states

使用之前描述过的动态模型来预测新的状态。对于每个新状态,还添加高斯噪声来创建预测状态。

对于新粒子,想要测量它们与原始ROI的相似度。对于测量,我们将使用Bhattacharyya系数,它测量两个标准化直方图之间的相似性。

 def compute_weight(p, q):
     """
     Compute the weight based on Bhattacharyya coefficient between 2 normalized histograms
     Args:
         p (np.ndarray): normalized histogram
         q (np.ndarray): normalized histogram
 
     Returns:
         (float): weight based on Bhattacharyya coefficient between p and q
     """
     # make sure the histogram is normalized
     if not np.isclose(np.sum(p), 1):
         raise ValueError('p histogram is not normalized')
     if not np.isclose(np.sum(q), 1):
         raise ValueError('q histogram is not normalized')
     bc = np.sum(np.sqrt(p * q))  # Bhattacharyya coefficient
     return np.exp(20 * bc)  

对于每个粒子,我们测量其直方图与原始粒子之间的相似性作为权重。最后在帧上绘制边界框并将其保存到视频中。

     image_with_boxes = draw_bounding_box(image, s_new, weights, with_max=True)
     result.write(image_with_boxes)

对于边界框的绘制,计算新状态的加权平均值来得到平均状态并绘制它。我们还添加了最可能状态(权重最高的状态)的边界框。

 def draw_bounding_box(image, states, weights, with_max=True):
     """
     Draw rectangle according to the weighted average state and (optionally) a rectangle of the state with
     the maximum score.
     Args:
         image (np.ndarray): the image to draw recangle on
         states (np.ndarray): the sampling states
         weights (np.ndarray): the weight of each state
         with_max (bool): True to draw a rectangle according to the state with the highest weight
 
     Returns:
         (np.ndarray): image with rectangle drawn on for the tracked object
     """
     mean_box = np.average(states, axis=1, weights=weights)
     x_c_mean, y_c_mean = np.round(mean_box[:2]).astype(int)
     image_with_boxes = image.copy()
     cv2.rectangle(image_with_boxes, (x_c_mean - half_width, y_c_mean - half_height),
                                      (x_c_mean + half_width, y_c_mean + half_height), (0, 255, 0), 1)
     if with_max:
         max_box = states[:, np.argmax(weights)]
         x_c_max, y_c_max = np.round(max_box[:2]).astype(int)
         cv2.rectangle(image_with_boxes, (x_c_max - half_width, y_c_max - half_height),
                                          (x_c_max + half_width, y_c_max + half_height), (0, 0, 255), 1)
     return image_with_boxes

在循环结束时,检查视频的下一帧。如果没有了就结束循环,这时opencv的标准操作

    # read next frame
     ret, image = cap.read()
     if not ret:  # if there is no next frame to read, stop
         break

在最后关闭对象

 cap.release()
 result.release()

总结

在这篇文章中,我们讨论了在视频中跟踪目标的粒子滤波算法。我们只简单的对其进行了实现,其实现实使用时有更多的技术可以对它进行改进(例如其他度量方法)。

这个算法适用于非线性、非高斯系统。实现简单,灵活性高,并且能处理高维状态空间。

但是他的缺点也很明显:计算量较大,特别是在高维状态空间中。并且需要大量粒子才能得到较好的估计精度,容易出现粒子退化问题。

但是该算法可以应用于更多的领域,而不仅仅是跟踪目标,例如机器人定位、金融时间序列分析等。

https://avoid.overfit.cn/post/1dd5dbffcf4c4308b11ef08dd2f38483

作者:DZ

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1850583.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Antd Table 表格 拖拽列宽

antd 的表格组件的列宽&#xff0c;是通过width属性去初始化的&#xff0c;有时候渲染的内容不固定&#xff0c;这个宽做不到通用所以研究怎么实现表格列宽拖动&#xff0c;主要的实现步骤如下&#xff1a; 使用table的components API修改表格头部为 react-resizable提供的组件…

专业技能篇---计算机网络

文章目录 前言计算机网络基础一、网络分层模型 HTTP一、从输入URL到页面显示发生了什么&#xff1f;二、Http的状态码有哪些&#xff1f;三、 HTTP与HTTPS有什么区别&#xff1f;四、URI 和 URL 的区别是什么?五、Cookie和Session有什么区别&#xff1f;六、GET与POST WebSock…

基于机器学习和深度学习的C-MAPSS涡扇发动机剩余寿命RUL预测(Python,Jupyter Notebook环境)

涡扇发动机全称为涡轮风扇发动机&#xff0c;是一种先进的空中引擎&#xff0c;由涡轮喷气发动机发展而来。涡扇发动机主要特点是首级压缩机的面积比涡轮喷气发动机大。同时&#xff0c;空气螺旋桨&#xff08;扇&#xff09;将部分吸入的空气从喷射引擎喷射出来&#xff0c;并…

尚品汇-(四)

&#xff08;1&#xff09;商品的基本知识 1.1基本信息—分类 一般情况可以分为两级或者三级。咱们的项目一共分为三级&#xff0c;即一级分类、二级分类、三级分类。 比如&#xff1a;家用电器是一级分类&#xff0c;电视是二级分类&#xff0c;那么超薄电视就是三级分类。…

一单1800,这个项目凭什么这么火?

AI变现营八期学员一单1800成功拿下&#xff0c;这还是开营不到一周的结果&#xff01; AI代写这个项目为什么现在越来越火&#xff1f; 第一点原因就是因为AI的火爆&#xff0c;让传统代写行业变现效率增加了N倍&#xff0c;普通人可以入局&#xff0c;只要会调教AI就可以了&am…

Win11下安装VS2022失败的解决办法

前几天我把我的HP Z840的操作系统换成了Win11&#xff0c;在重装VS2022时遇到了麻烦&#xff0c;提示无法安装 Microsoft.VisualStudio.Devenv.Msi。 查看安装日志提示&#xff1a;Could not write value devenv.exe to key \SOFTWARE\Microsoft\Internet Explorer\Main\Featur…

基于JSP的交通事故档案管理系统

开头语&#xff1a;你好&#xff0c;我是计算机学长猫哥&#xff0c;如果你对系统有更多的期待或建议&#xff0c;欢迎随时联系我。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;JSPJava 工具&#xff1a;ECLIPSE、Tomcat 系统展示 首页 管理员界…

基于YOLOv5的火灾检测系统的设计与实现

基于YOLOv5的火灾检测系统的设计与实现 概述系统架构主要组件代码结构功能描述YOLOv5检测器视频处理器主窗口 详细代码说明YOLOv5检测器类视频处理类主窗口类 使用说明环境配置运行程序操作步骤 检测示例图像检测视频检测实时检测 数据集介绍数据集获取数据集规模 YOLOv5模型介…

vscode中同一页面使用批量替换

在vscode中像word中那样批量替换 首先搜索要替换的内容快捷键是ctrlf 然后输入你要搜索的内容 第二个框中输入你要替换成的内容 点击全部替换&#xff0c;就可以了

Web应用和Tomcat的集成鉴权1-BasicAuthentication

作者:私语茶馆 1.Web应用与Tomcat的集成式鉴权 Web应用部署在Tomcat时,一般有三层鉴权: (1)操作系统鉴权 (2)Tomcat容器层鉴权 (3)应用层鉴权 操作系统层鉴权包括但不限于:Tomcat可以和Windows的域鉴权集成,这个适合企业级的统一管理。也可以在Tomcat和应用层独立…

高级算法入门必看—21个NPC问题及其证明

文章目录 前言一、布尔可满足性问题二、每子句至多3个变量的布尔可满足性问题&#xff08;3-SAT&#xff09;三、0-1整数规划&#xff08;0-1 integer programming&#xff09;四、Set packing&#xff08;Set packing&#xff09;五、最小顶点覆盖问题&#xff08;Vertex cove…

计算机视觉 | 基于图像处理和边缘检测算法的黄豆计数实验

目录 一、实验原理二、实验步骤1. 图像读取与预处理2. 边缘检测3. 轮廓检测4. 标记轮廓序号 三、实验结果 Hi&#xff0c;大家好&#xff0c;我是半亩花海。 本实验旨在利用 Python 和 OpenCV 库&#xff0c;通过图像处理和边缘检测算法实现黄豆图像的自动识别和计数&#xff0…

港湾周评|高盛眼中的618增长

《港湾商业观察》李镭 年中最重要的购物节618终于尘埃落定了。2024年的618各大电商平台竞技情况如何&#xff1f;又有哪些新的亮点&#xff1f;都成为外界观察消费行为的参考指标。 根据京东618数据显示&#xff1a;累计成交额过10亿的品牌83个&#xff0c;超15万个中小商家销…

python watchdog 配置文件热更新

目录 一、Watchdog示例 二、aiohttp服务配置热更新 在同事的golang代码中学习到了config.json热更新的功能&#xff0c;这里自己也学习了一下python写web服务的时候怎么来实现配置的热更新。主要是利用Watchdog这个第三方python库&#xff0c;来监控文件系统的改变&#xff0…

谷歌主页归属地确认使用的什么接口?

&#x1f3c6;本文收录于「Bug调优」专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&&…

CPU飙升100%怎么办?字节跳动面试官告诉你答案!

小北说在前面 CPU占用率突然飙升是技术人员常遇到的一个棘手问题&#xff0c;它是一个与具体技术无关的普遍挑战。 这个问题可以很简单&#xff0c;也可以相当复杂。 有时候&#xff0c;只是一个死循环在作祟。 有时候&#xff0c;是死锁导致的。 有时候&#xff0c;代码中有…

【项目管理】项目管理表单(excel)

PM项目管理模板 甘特图 OKR周报 团队任务 工作总结

Aquila-Med LLM:开创性的全流程开源医疗语言模型

​论文链接&#xff1a;https://arxiv.org/pdf/2406.12182 开源链接&#xff1a;https://huggingface.co/BAAI/AquilaMed-RL http://open.flopsera.com/flopsera-open/details/AquilaMed_SFT http://open.flopsera.com/flopsera-open/details/AquilaMed_DPO 近年来&#xf…

Android设置页面Activity全屏(隐藏导航栏、状态栏)

3、代码中设置&#xff1a;在setContentView 之前调用 requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); 注意&#xff1a; 当有全面屏手机可以显示虚拟…

认识LogBack.xml

一、logback的三个主要模块 1.logback-core&#xff1a;提供基本的日志功能&#xff1b; 2.logback-classic&#xff1a;建立在logback-core之上&#xff0c;兼容SLF4和log4jAPI&#xff0c;提供一套强大的日志框架&#xff1b; 3.logback-access&#xff1a;允许通过servlet容…