当前位置: 首页 > news >正文

【python】OpenCV—Tracking(10.4)—Centroid

在这里插入图片描述

文章目录

  • 1、任务描述
  • 2、人脸检测模型
  • 3、完整代码
  • 4、结果展示
  • 5、涉及到的库函数
  • 6、参考

1、任务描述

基于质心实现多目标(以人脸为例)跟踪

人脸检测采用深度学习的方法

核心步骤:

步骤#1:接受边界框坐标并计算质心
步骤#2:计算新边界框和现有对象之间的欧几里得距离
步骤 #3:更新现有对象的 (x, y) 坐标
步骤#4:注册新对象
步骤#5:注销旧对象
当旧对象无法与任何现有对象匹配总共 N 个后续帧时,我们将取消注册。

2、人脸检测模型

在这里插入图片描述

输入 1x3x300x300

在这里插入图片描述
resnet + ssd,分支还是挺多的

3、完整代码

质心跟踪代码

# import the necessary packages
from scipy.spatial import distance as dist
from collections import OrderedDict
import numpy as npclass CentroidTracker():def __init__(self, maxDisappeared=65):# initialize the next unique object ID along with two ordered# dictionaries used to keep track of mapping a given object# ID to its centroid and number of consecutive frames it has# been marked as "disappeared", respectivelyself.nextObjectID = 0# 用于为每个对象分配唯一 ID 的计数器。如果对象离开帧并且在 maxDisappeared 帧中没有返回,则将分配一个新的(下一个)对象 ID。self.objects = OrderedDict()# 对象 ID 作为键,质心 (x, y) 坐标作为值的字典self.disappeared = OrderedDict()# 保存特定对象 ID(键)已被标记为“丢失”的连续帧数(值)# store the number of maximum consecutive frames a given# object is allowed to be marked as "disappeared" until we# need to deregister the object from trackingself.maxDisappeared = maxDisappeared# 在我们取消注册该对象之前,允许将对象标记为“丢失/消失”的连续帧数。def register(self, centroid):# when registering an object we use the next available object# ID to store the centroid# 注册新的 idself.objects[self.nextObjectID] = centroidself.disappeared[self.nextObjectID] = 0self.nextObjectID += 1def deregister(self, objectID):# to deregister an object ID we delete the object ID from# both of our respective dictionaries# 注销掉 iddel self.objects[objectID]del self.disappeared[objectID]def update(self, rects):# check to see if the list of input bounding box rectangles# is empty# rects = (startX, startY, endX, endY)if len(rects) == 0: # 没有检测到目标# loop over any existing tracked objects and mark them# as disappeared# 我们将遍历所有对象 ID 并增加它们的disappeared计数for objectID in list(self.disappeared.keys()):self.disappeared[objectID] += 1# if we have reached a maximum number of consecutive# frames where a given object has been marked as# missing, deregister itif self.disappeared[objectID] > self.maxDisappeared:self.deregister(objectID)# return early as there are no centroids or tracking info# to updatereturn self.objects# initialize an array of input centroids for the current frameinputCentroids = np.zeros((len(rects), 2), dtype="int")# loop over the bounding box rectanglesfor (i, (startX, startY, endX, endY)) in enumerate(rects):# use the bounding box coordinates to derive the centroidcX = int((startX + endX) / 2.0)  # 检测框中心横坐标cY = int((startY + endY) / 2.0)  # 检测框中心纵坐标inputCentroids[i] = (cX, cY)# if we are currently not tracking any objects take the input# centroids and register each of them# 如果当前没有我们正在跟踪的对象,我们将注册每个新对象if len(self.objects) == 0:for i in range(0, len(inputCentroids)):self.register(inputCentroids[i])# otherwise, are are currently tracking objects so we need to# try to match the input centroids to existing object# centroidselse:# grab the set of object IDs and corresponding centroidsobjectIDs = list(self.objects.keys())objectCentroids = list(self.objects.values())# compute the distance between each pair of object# centroids and input centroids, respectively -- our# goal will be to match an input centroid to an existing# object centroidD = dist.cdist(np.array(objectCentroids), inputCentroids)# in order to perform this matching we must (1) find the# smallest value in each row and then (2) sort the row# indexes based on their minimum values so that the row# with the smallest value as at the *front* of the index# listrows = D.min(axis=1).argsort()# next, we perform a similar process on the columns by# finding the smallest value in each column and then# sorting using the previously computed row index listcols = D.argmin(axis=1)[rows]# in order to determine if we need to update, register,# or deregister an object we need to keep track of which# of the rows and column indexes we have already examinedusedRows = set()usedCols = set()# loop over the combination of the (row, column) index# tuplesfor (row, col) in zip(rows, cols):# if we have already examined either the row or# column value before, ignore it# val# 老目标被选中过或者新目标被选中过if row in usedRows or col in usedCols:continue# otherwise, grab the object ID for the current row,# set its new centroid, and reset the disappeared# counterobjectID = objectIDs[row]  # 老目标 idself.objects[objectID] = inputCentroids[col]  # 更新老目标 id 的质心为新目标的质心,因为两者距离最近self.disappeared[objectID] = 0  # 丢失索引重置为 0# indicate that we have examined each of the row and# column indexes, respectivelyusedRows.add(row)usedCols.add(col)# compute both the row and column index we have NOT yet# examinedunusedRows = set(range(0, D.shape[0])).difference(usedRows)unusedCols = set(range(0, D.shape[1])).difference(usedCols)# in the event that the number of object centroids is# equal or greater than the number of input centroids# we need to check and see if some of these objects have# potentially disappeared# 如果老目标不少于新目标if D.shape[0] >= D.shape[1]:# loop over the unused row indexesfor row in unusedRows:# grab the object ID for the corresponding row# index and increment the disappeared counterobjectID = objectIDs[row]  # 老目标 idself.disappeared[objectID] += 1  # 跟踪帧数 +1# check to see if the number of consecutive# frames the object has been marked "disappeared"# for warrants deregistering the object# 检查disappeared计数是否超过 maxDisappeared 阈值,如果是,我们将注销该对象if self.disappeared[objectID] > self.maxDisappeared:self.deregister(objectID)# otherwise, if the number of input centroids is greater# than the number of existing object centroids we need to# register each new input centroid as a trackable objectelse:  # 新目标多于老目标for col in unusedCols:self.register(inputCentroids[col])  # 注册新目标# return the set of trackable objectsreturn self.objects

self.nextObjectID = 0用于为每个对象分配唯一 ID 的计数器。如果对象离开帧并且在 maxDisappeared 帧中没有返回,则将分配一个新的(下一个)对象 ID。

self.objects = OrderedDict() 对象 ID 作为键,质心 (x, y) 坐标作为值的字典

self.disappeared = OrderedDict() 保存特定对象 ID(键)已被标记为“丢失”的连续帧数(值)

self.maxDisappeared = maxDisappeared,在我们取消注册该对象之前,允许将对象标记为“丢失/消失”的连续帧数。

有初始化,注册,注销,更新过程

核心代码在 def update

如果没有检测到目标,当前所有 id 的 self.disappeared 会加 1

如果注册的 id 为空,这当前帧所有质心会被注册上新的 id,否则会计算历史质心和当前帧质心的距离,距离较近的匹配上,更新 id 的质心,没有被分配上的历史质心对应 id 的 self.disappeared 会加 1,没有被分配的当前帧质心会被注册新的 id

超过 self.maxDisappeared 的 id 会被注销掉

根据 D 计算 rowscols 的时候有点绕,看看下面这段注释就会好理解一些

"""
Darray([[  2.        , 207.48252939],[206.65188119,   1.41421356]])D.min(axis=1)  每行最小值array([2.        , 1.41421356])rows 每行最小值再排序,表示按从小到大行数排序,比如最小的数在rows[0]所在的行,第二小的数在rows[1]所在的行array([1, 0])D.argmin(axis=1) 返回指定轴最小值的索引,也即每行最小值的索引array([0, 1])cols  每行每列最小值,按行从小到大排序array([1, 0])
"""

再看看一个 shape 不一样的例子

import numpy as npD = np.array([[2., 1, 3],[1.1, 1.41421356, 0.7]])print(D.min(axis=1))
print(D.argmin(axis=1))rows = D.min(axis=1).argsort()cols = D.argmin(axis=1)[rows]print(rows)
print(cols)"""
[1.  0.7]
[1 2][1 0]
[2 1]
"""

实际运用的时候,rows 是历史 ids,cols 是当前帧的 ids


人脸检测+跟踪+绘制结果 pipeline

# USAGE
# python object_tracker.py --prototxt deploy.prototxt --model res10_300x300_ssd_iter_140000.caffemodel# import the necessary packages
from CentroidTracking.centroidtracker import CentroidTracker
import numpy as np
import argparse
import imutils
import cv2# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-p", "--prototxt", default="deploy.prototxt.txt",help="path to Caffe 'deploy' prototxt file")
ap.add_argument("-m", "--model", default="res10_300x300_ssd_iter_140000.caffemodel",help="path to Caffe pre-trained model")
ap.add_argument("-c", "--confidence", type=float, default=0.5,help="minimum probability to filter weak detections")
args = vars(ap.parse_args())# initialize our centroid tracker and frame dimensions
ct = CentroidTracker()
(H, W) = (None, None)# load our serialized model from disk
print("[INFO] loading model...")
net = cv2.dnn.readNetFromCaffe(args["prototxt"], args["model"])# initialize the video stream
print("[INFO] starting video stream...")
vs = cv2.VideoCapture("4.mp4")index = 0# loop over the frames from the video stream
while True:index += 1# read the next frame from the video stream and resize itrect, frame = vs.read()if not rect:breakframe = imutils.resize(frame, width=600)(H, W) = frame.shape[:2]# construct a blob from the frame, pass it through the network,# obtain our output predictions, and initialize the list of# bounding box rectanglesblob = cv2.dnn.blobFromImage(frame, 1.0, (W, H),(104.0, 177.0, 123.0))net.setInput(blob)detections = net.forward()  # (1, 1, 200, 7)"""detections[0][0][0]array([0.        , 1.        , 0.9983138 , 0.418003  , 0.22666326,0.5242793 , 0.50829136], dtype=float32)第三个维度是 score最后四个维度是左上右下坐标"""rects = []  # 记录所有检测框的左上右下坐标# loop over the detectionsfor i in range(0, detections.shape[2]):# filter out weak detections by ensuring the predicted# probability is greater than a minimum thresholdif detections[0, 0, i, 2] > args["confidence"]:# compute the (x, y)-coordinates of the bounding box for# the object, then update the bounding box rectangles listbox = detections[0, 0, i, 3:7] * np.array([W, H, W, H])rects.append(box.astype("int"))# draw a bounding box surrounding the object so we can# visualize it(startX, startY, endX, endY) = box.astype("int")cv2.rectangle(frame, (startX, startY), (endX, endY),(0, 0, 255), 2)# update our centroid tracker using the computed set of bounding# box rectanglesobjects = ct.update(rects)# loop over the tracked objectsfor (objectID, centroid) in objects.items():# draw both the ID of the object and the centroid of the# object on the output frametext = "ID {}".format(objectID)cv2.putText(frame, text, (centroid[0] - 10, centroid[1] - 10),cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)cv2.circle(frame, (centroid[0], centroid[1]), 4, (0, 0, 255), -1)# show the output framecv2.imshow("Frame", frame)# cv2.imwrite(f"./frame4/{str(index).zfill(3)}.jpg", frame)key = cv2.waitKey(1) & 0xFF# if the `q` key was pressed, break from the loopif key == ord("q"):break# do a bit of cleanup
cv2.destroyAllWindows()
vs.release()

网络前向后的结果,第三个维度是 score,最后四个维度是左上右下坐标

核心跟新坐标的过程在 objects = ct.update(rects)

4、结果展示

单个人脸

tracking-face1

多个人脸

tracking-face2

多个人脸,存在漏检情况,id 还是持续了很长一段时间(算法中配置的是 50 帧)

tracking-face3

多个人脸

tracking-face4

5、涉及到的库函数

scipy.spatial.distance.cdist 是 SciPy 库中 spatial.distance 模块的一个函数,用于计算两个输入数组之间所有点对之间的距离。这个函数非常有用,特别是在处理大规模数据集时,需要计算多个点之间的距离矩阵。

函数的基本用法如下:

from scipy.spatial.distance import cdist  # 假设 XA 和 XB 是两个二维数组,其中每一行代表一个点的坐标  
XA = ...  # 形状为 (m, n) 的数组,m 是点的数量,n 是坐标的维度  
XB = ...  # 形状为 (p, n) 的数组,p 是另一个集合中点的数量  # 计算 XA 和 XB 中所有点对之间的距离  
# metric 参数指定使用的距离度量,默认为 'euclidean'(欧氏距离)  
D = cdist(XA, XB, metric='euclidean')

在这个例子中,D 将是一个形状为 (m, p) 的数组,其中 D[i, j] 表示 XA 中第 i 个点和 XB 中第 j 个点之间的距离。

cdist 函数支持多种距离度量,通过 metric 参数指定。除了默认的欧氏距离外,还可以选择如曼哈顿距离(‘cityblock’)、切比雪夫距离(‘chebyshev’)、闵可夫斯基距离(‘minkowski’,需要额外指定 p 值)、余弦距离(注意:虽然余弦通常用作相似度度量,但可以通过 1 - cosine(u, v) 转换为距离度量,不过 cdist 不直接支持负的余弦距离,需要手动计算)、汉明距离(‘hamming’,仅适用于布尔数组或整数数组)等。

6、参考

  • https://github.com/gopinath-balu/computer_vision
  • https://pyimagesearch.com/2018/07/23/simple-object-tracking-with-opencv/
  • 链接:https://pan.baidu.com/s/1UX_HmwwJLtHJ9e5tx6hwOg?pwd=123a
    提取码:123a
  • 目标跟踪(7)使用 OpenCV 进行简单的对象跟踪
  • https://pixabay.com/zh/videos/woman-phone-business-casual-154833/

相关文章:

【python】OpenCV—Tracking(10.4)—Centroid

文章目录 1、任务描述2、人脸检测模型3、完整代码4、结果展示5、涉及到的库函数6、参考 1、任务描述 基于质心实现多目标(以人脸为例)跟踪 人脸检测采用深度学习的方法 核心步骤: 步骤#1:接受边界框坐标并计算质心 步骤#2&…...

为什么TCP(TIME_WAIT)2倍MSL

为什么TCP(TIME_WAIT)2倍MSL 一、TCP关闭连接的四次挥手流程进入TIME_WAIT 二、TIME_WAIT状态的意义1. 确保ACK报文到达对方2. 防止旧报文干扰新连接 三、为什么是2倍MSL四、TIME_WAIT的图解五、TIME_WAIT在实际应用中的影响总结 在TCP连接的关闭过程中&…...

jieba-fenci 05 结巴分词之简单聊一聊

拓展阅读 DFA 算法详解 为了便于大家学习,项目开源地址如下,欢迎 forkstar 鼓励一下老马~ 敏感词 sensitive-word 分词 segment 分词系列专题 jieba-fenci 01 结巴分词原理讲解 segment jieba-fenci 02 结巴分词原理讲解之数据归一化 segment jieba…...

Hadoop期末复习(完整版)

前言(全部为语雀导出,个人所写,仅用于学习!!!!) 复习之前我们要有目的性,明确考什么,不考什么。 对于hadoop来说,首先理论方面是跑不掉的&#x…...

Python篮球王子

系列文章 序号直达链接爱心系列1Python制作一个无法拒绝的表白界面2Python满屏飘字表白代码3Python无限弹窗满屏表白代码4Python李峋同款可写字版跳动的爱心5Python流星雨代码6Python漂浮爱心代码7Python爱心光波代码8Python普通的玫瑰花代码9Python炫酷的玫瑰花代码10Python多…...

分享一些在部署k8s集群时遇到的问题

目录 一、k8s拉取镜像失败,多半是docker镜像源失效了,需要经常更新 1.编辑该配置文件: 2.重启服务器: 二、kubectl get nodes时出现:The connection to the server localhost:8080 was refused - did you specify t…...

【Canal 中间件】Canal使用原理与基本组件概述

文章目录 一、canal 概述1.2 什么是 canal2.3 canal 的所有组件 二、canal 工作原理2.1 MySQL 主备复制原理2.2 canal 工作原理 三、canal.server 组件3.1 canal.server 的架构3.2 instance 模块组成部分 四、canal.client 组件4.1 类设计4.2 server/clinet 交互协议4.3 使用案…...

《Baichuan-Omni》论文精读:第1个7B全模态模型 | 能够同时处理文本、图像、视频和音频输入

技术报告Baichuan-Omni Technical ReportGitHub仓库地址 文章目录 论文摘要1. 引言简介2. 训练2.1. 高质量的多模态数据2.2. 多模态对齐预训练2.2.1. 图像-语言分支2.2.2. 视频语音分支2.2.3. 音频语言分支2.2.4. 图像-视频-音频全方位对齐 2.3. 多模态微调监督 3. 实验3.1. 语…...

YOLOv6-4.0部分代码阅读笔记-common.py

common.py yolov6\layers\common.py 目录 common.py 1.所需的库和模块 2.class SiLU(nn.Module): 3.class ConvModule(nn.Module): 4.class ConvBNReLU(nn.Module): 5.class ConvBNSiLU(nn.Module): 6.class ConvBN(nn.Module): 7.class ConvBNHS(nn.Module): …...

移植 AWTK 到 纯血鸿蒙 (HarmonyOS NEXT) 系统 (4) - 平台适配

在移植 AWTK 到 HarmonyOS NEXT 系统之前,我们需要先完成平台适配,比如文件、多线程(线程和同步)、时间、动态库和资源管理。 1. 文件 HarmonyOS NEXT 支持标准的 POSIX 文件操作接口,我们可以直接使用下面的代码&am…...

Java 多线程(八)—— 锁策略,synchronized 的优化,JVM 与编译器的锁优化,ReentrantLock,CAS

前言 本文为 Java 面试小八股,一句话,理解性记忆,不能理解就死背吧。 锁策略 悲观锁与乐观锁 悲观锁和乐观锁是锁的特性,并不是特指某个具体的锁。 我们知道在多线程中,锁是会被竞争的,悲观锁就是指锁…...

【项目分享】法拉利中控台模拟 html+css+js

引入: 制作一个模拟法拉利中控台的网页是一个有趣且富有挑战性的项目。为了简化这个任务,我们可以使用一些HTML、CSS和JavaScript来实现一个基本的界面。以下是一个简单的示例,展示了如何创建一个基本的法拉利中控台模拟网页。 效果展示&…...

Rust 力扣 - 2461. 长度为 K 子数组中的最大和

文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 我们遍历长度为k的窗口,用一个哈希表记录窗口内的所有元素(用来对窗口内元素去重),我们取哈希表中元素数量等于k的窗口总和的最大值 题解代码 use std::collecti…...

stm32103c8t6 pwm驱动舵机(SG90)

本方法采用通用定时器(TIM2、TIM3、TIM4、TIM5)实现 代码: PWM.h #ifndef __PWM_H // 防止头文件重复包含 #define __PWM_H#include "stm32f10x.h" // 包含STM32F10x系列的设备头文件// 函数声明 void TIM2_PWM_In…...

Python For循环

Python 的 for 循环是自动化重复任务的强大工具,可以使代码更高效、更易于管理。本教程将解释 for 循环的工作原理,探讨不同的应用场景,并提供大量实用示例。无论你是初学者还是希望提升技能的开发者,这些示例都将帮助你更好地在 …...

C++入门——“C++11-右值引用和移动语义”

C11相比于C98增加以许多新特性,让C语言更加灵活好用,但是貌似也增加了许多学习的难度,现在先看第一部分。 一、右值引用和移动语义 1.右值引用和左值引用 在C中,值可以大致分为右值和左值,左值大概是哪些已经被定义的变…...

timm使用笔记

timm(Timm is a model repository for PyTorch)是一个 PyTorch 原生实现的计算机视觉模型库。它提供了预训练模型和各种网络组件,可以用于各种计算机视觉任务,例如图像分类、物体检测、语义分割等等。timm(库提供了预训…...

android浏览器源码 可输入地址或关键词搜索 android studio 2024 可开发可改地址

Android 浏览器是一种运行在Android操作系统上的应用程序,主要用于访问和查看互联网内容。以下是关于Android浏览器的详细介绍: 1. 基本功能 Android浏览器提供了用户浏览网页的基本功能,如: 网页加载:支持加载静态…...

贪心算法入门(一)

1.什么是贪心算法? 贪心算法是一种解决问题的策略,它将复杂的问题分解为若干个步骤,并在每一步都选择当前最优的解决方案,最终希望能得到全局最优解。这种策略的核心在于“最优”二字,意味着我们追求的是以最少的时间和…...

C# ref和out 有什么区别,分别用在那种场景

在C#中,ref和out都是用于按引用传递参数的关键字,但它们有一些细微的差别和使用场景。 ref 关键字 ref 关键字用于按引用传递参数。这意味着当你将一个变量作为参数传递给一个方法时,你不是传递变量的值,而是传递变量的引用。因…...

RestClient

什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端,它允许HTTP与Elasticsearch 集群通信,而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级&#xff…...

相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解

【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下: 一、场景操作步骤 操作步…...

Qt Widget类解析与代码注释

#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...

(二)TensorRT-LLM | 模型导出(v0.20.0rc3)

0. 概述 上一节 对安装和使用有个基本介绍。根据这个 issue 的描述,后续 TensorRT-LLM 团队可能更专注于更新和维护 pytorch backend。但 tensorrt backend 作为先前一直开发的工作,其中包含了大量可以学习的地方。本文主要看看它导出模型的部分&#x…...

解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错

出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上,所以报错,到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本,cu、torch、cp 的版本一定要对…...

BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践

6月5日,2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席,并作《智能体在安全领域的应用实践》主题演讲,分享了在智能体在安全领域的突破性实践。他指出,百度通过将安全能力…...

2023赣州旅游投资集团

单选题 1.“不登高山,不知天之高也;不临深溪,不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...

技术栈RabbitMq的介绍和使用

目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...

GruntJS-前端自动化任务运行器从入门到实战

Grunt 完全指南:从入门到实战 一、Grunt 是什么? Grunt是一个基于 Node.js 的前端自动化任务运行器,主要用于自动化执行项目开发中重复性高的任务,例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...

关于easyexcel动态下拉选问题处理

前些日子突然碰到一个问题,说是客户的导入文件模版想支持部分导入内容的下拉选,于是我就找了easyexcel官网寻找解决方案,并没有找到合适的方案,没办法只能自己动手并分享出来,针对Java生成Excel下拉菜单时因选项过多导…...