基于卡尔曼滤波实现线性目标跟踪

news2024/11/26 16:46:02

文章目录

  • 前言
  • 卡尔曼滤波
    • 基本推导
    • 运算
  • 实现
    • 目标检测
    • 卡尔曼预测器
    • ID分配器(跟踪器)
  • 完整代码
    • 代码
    • 总结

前言

一个需求,在一个稳定的场景当中,实现目标检测计数算法。

任务点:

  1. 实现目标检测
  2. 完成对不同类别的物品进行计数
  3. 在边缘设备完成部署

难点:

  1. 边缘设备算力不足
  2. 目标识别精度问题,识别类别在28类
  3. 实时检测,存在相同物品重复计数的问题,需要进行区分识别,避免重复计数

易点:

  1. 算力不足可以使用tiny系列的yolo算法,并通过tensorRT进行加速处理
  2. 摄像头运动轨迹固定,为线性移动过程

方案:

  1. 采用yolov3-tiny 算法
  2. 通过卡尔曼滤波实现简单的目标跟踪,记录物品ID和类别即可完成计数

注: 本博文只提供简单思路,以及大致的实现,具体实现细节,甚至是实现语言请自行斟酌。

卡尔曼滤波

这个说来惭愧,去年好像是要写关于卡尔曼滤波的博文来着,好像还写了上篇,下篇没写。没想到,都是有铺垫的,回头还是得补上。

它的原理其实不难,先前我记得还举了什么小车运动的例子。说实话,都是徒劳的,数学语言永远都是最优雅的,当然还有个原因,举例子太麻烦了。反正最后都是对公式进行解释,还不如直面它,战胜恐惧的最好方法就算直面它!!!哪里不会,你就怼哪里,无非就是你愿不愿意花费精力的问题。扯多了哈。

基本推导

卡尔曼滤波是一种最优线性无偏估计算法,它可以对动态系统进行状态估计、控制和预测。其基本原理是通过对状态及其协方差的更新,来不断优化对系统状态的估计。

假设某时刻系统的状态为 x ( k ) x(k) x(k),观测值为 z ( k ) z(k) z(k),可以建立如下的状态方程和观测方程(其中 n ( k ) n(k) n(k) v ( k ) v(k) v(k)分别是系统噪声和观测噪声):

x ( k ) = A x ( k − 1 ) + B u ( k ) + n ( k ) x(k)=Ax(k-1)+Bu(k)+n(k) x(k)=Ax(k1)+Bu(k)+n(k)

z ( k ) = H x ( k ) + v ( k ) z(k)=Hx(k)+v(k) z(k)=Hx(k)+v(k)

其中, A A A是系统状态转移矩阵, B B B是矩阵 u ( k ) u(k) u(k)的系数, H H H是观测矩阵。

首先对于一个已知的状态 x x x,我们需要根据测量值 z z z,来求出新的状态 x ′ x' x。如果不考虑噪声,可以很容易地得到:

x ′ = H − 1 z x' = H^{-1}z x=H1z

但由于存在噪声,上述公式不再适用,因此需要对状态协方差进行估计,从而得到更准确的状态估计。卡尔曼滤波使用贝叶斯概率论框架来处理噪声,因此需要对先验和后验概率进行建模。假设先验概率为 P ( x ) P(x) P(x),后验概率为 P ( x ∣ z ) P(x|z) P(xz),则卡尔曼滤波的基本思想是通过不断更新先验概率,从而获得更准确的后验概率。

一般地,先验概率可以表示为:

P ( x ) = N ( x ( k ) ; x ^ ( k ∣ k − 1 ) , P ( k ∣ k − 1 ) ) P(x)=\mathcal{N}(x(k); \hat{x}(k|k-1), P(k|k-1)) P(x)=N(x(k);x^(kk1),P(kk1))

其中, x ^ ( k ∣ k − 1 ) \hat{x}(k|k-1) x^(kk1)是根据先前的状态和动态方程预测出的下一个状态的估计值, P ( k ∣ k − 1 ) P(k|k-1) P(kk1)是根据协方差矩阵计算出的状态估计误差的度量。

卡尔曼滤波的核心算法是通过观测更新先验概率。观测数据可以用于修正状态估计,进而提高系统估计的精度。在更新协方差矩阵时,卡尔曼滤波使用了最小均方误差(MMSE)方法,并采用贝叶斯公式进行求解。

观测更新可以被描述为:

P ( x ∣ z ( k ) ) = P ( z ( k ) ∣ x ) P ( x ) P ( z ( k ) ) P(x|z(k)) = \frac{P(z(k)|x)P(x)}{P(z(k))} P(xz(k))=P(z(k))P(z(k)x)P(x)

其中, P ( z ( k ) ∣ x ) P(z(k)|x) P(z(k)x)为给定状态 x x x时观测值 z ( k ) z(k) z(k)的概率密度函数, P ( z ( k ) ) P(z(k)) P(z(k))为归一化因子。

通过简单推导,可以得到状态协方差矩阵的更新方式为:

P ( k ∣ k ) = P ( k ∣ k − 1 ) ( I − K ( k ) H ) P(k|k)=P(k|k-1)(I-K(k)H) P(kk)=P(kk1)(IK(k)H)

其中, K ( k ) K(k) K(k)是卡尔曼增益矩阵,表示当前时刻观测信息和先验信息的权重。

K ( k ) = P ( k ∣ k − 1 ) H T H P ( k ∣ k − 1 ) H T + R K(k)=\frac{P(k|k-1)H^T}{HP(k|k-1)H^T+R} K(k)=HP(kk1)HT+RP(kk1)HT

其中, R R R为观测噪声的协方差矩阵,用于描述观测误差。

最后,利用以上推导的公式,通过递归计算,可以得到每个时刻的状态估计和协方差矩阵,从而实现对动态系统进行状态估计。

运算

那么接下来的话,我们来看到它是如何完成整个过程的运算的。
我们遵从如下过程:

  1. 预测:利用前一时刻的状态估计和状态转移矩阵,以及控制输入的信息,预测当前时刻的状态。即:

    x ^ ( k ∣ k − 1 ) = F x ^ ( k − 1 ∣ k − 1 ) + B u ( k ) \hat{x}(k|k-1) = F\hat{x}(k-1|k-1) + Bu(k) x^(kk1)=Fx^(k1∣k1)+Bu(k)

  2. 预测协方差更新:利用前一时刻的协方差矩阵,以及系统的状态转移矩阵和噪声协方差矩阵,来更新先验协方差矩阵。即:

    P ( k ∣ k − 1 ) = F P ( k − 1 ∣ k − 1 ) F T + Q P(k|k-1)=FP(k-1|k-1)F^T + Q P(kk1)=FP(k1∣k1)FT+Q

  3. 观测更新:利用观测信息来修正先验状态估计和协方差矩阵,得到后验状态估计和协方差矩阵。具体来说,我们可以通过以下步骤来进行观测更新:

    1. 计算卡尔曼增益 K ( k ) K(k) K(k)

      K ( k ) = P ( k ∣ k − 1 ) H T ( H P ( k ∣ k − 1 ) H T + R ) − 1 K(k) = P(k|k-1)H^T(H P(k|k-1) H^T + R)^{-1} K(k)=P(kk1)HT(HP(kk1)HT+R)1

    2. 更新后验状态估计:

      x ^ ( k ∣ k ) = x ^ ( k ∣ k − 1 ) + K ( k ) ( z ( k ) − H x ^ ( k ∣ k − 1 ) ) \hat{x}(k|k) = \hat{x}(k|k-1) + K(k)(z(k)-H\hat{x}(k|k-1)) x^(kk)=x^(kk1)+K(k)(z(k)Hx^(kk1))

    3. 更新后验协方差矩阵:

      P ( k ∣ k ) = ( I − K ( k ) H ) P ( k ∣ k − 1 ) P(k|k) = (I-K(k)H)P(k|k-1) P(kk)=(IK(k)H)P(kk1)

其中, Q Q Q为系统噪声协方差矩阵, R R R为观测噪声协方差矩阵。在实际应用中,这些协方差矩阵通常需要通过经验或者试验来确定。

当然在这里,一开始的时候,这些矩阵都是需要初始化的,那么这个如何初始化就要看具体任务了,当然还有前人的经验。

怎么用呢,其实很简单,假设我们有一个物体A,我们知道了上一个时刻A的坐标,那么输入到这个函数当中。你将得到一个kf对象。这个kf对象呢可以得到下一个时刻A的预测坐标。state = kf.predict() x, y = state[0], state[1] 然后呢,你想要知道C是不是上一个时刻的A物体,那么你只要对比A的预测坐标和当前C的坐标进行对比,如果误差在合理的范围内,那么就判定为同一个。

那么在这里的话,显然你已经知道了一点,那就是,如果你需要跟踪多个目标的话,那么你需要多个kf对象,去实现跟踪。至于为什么,看上面的原理你应该明白的。

实现

很好,接下来到了万众期待的实现时刻。

目标检测

首先,我们先来实现实现目标检测部分,这一部分我们当然是通过yolov3-tiny来做。

import cv2
import numpy as np


# 读取yolo模型参数和类别列表
net = cv2.dnn.readNet("yolov3-tiny.weights", "yolov3-tiny.cfg")
classes_file = open("coco.names", "r")
classes = [line.strip() for line in classes_file.readlines()]

while True:
    # 读取当前帧
    ret, frame = cap.read()
    # 对当前帧进行物品检测
    blob = cv2.dnn.blobFromImage(frame, 1 / 255.0, (416, 416), swapRB=True, crop=False)
    net.setInput(blob)
    outs = net.forward(net.getUnconnectedOutLayersNames())
    confidences = []
    boxes = []
    class_ids = []
    for out in outs:
        for detection in out:
            scores = detection[5:]
            class_id = np.argmax(scores)
            confidence = scores[class_id]
            if confidence > 0.45:
                center_x, center_y, w, h = list(map(int, detection[:4] * np.array(
                    [frame.shape[1], frame.shape[0], frame.shape[1], frame.shape[0]])))
                x = int(center_x - w / 2)
                y = int(center_y - h / 2)
                class_name = classes[class_id]
                text = "{} ({})".format(class_name, len(positions))
                cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
                cv2.putText(frame, text, (int(x), int(y) - 10),
                            cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
    # 显示当前帧
    cv2.imshow("frame", frame)
    # 等待退出
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# 释放资源
cap.release()
cv2.destroyAllWindows()

当然这只是一个示例,运行上面那段代码确保你的相互环境正常罢了。

我们实际上的代码是这样的:

class YoloDetect():

    def __init__(self,model_source="yolov3-tiny.weights",mode_cfg="yolov3-tiny.cfg",
                    class_file="coco.names"
                 ):
        self.net = cv2.dnn.readNet(model_source,mode_cfg)
        self.classes_file = open(class_file, "r")
        self.classes = [line.strip() for line in self.classes_file.readlines()]


    def show(self,frame,item,ID=0):

        class_name = self.classes[item.get("class_id")]
        text = "{}:ID({})".format(class_name,ID)
        cv2.rectangle(frame, (item.get("x"), item.get("y")), (item.get("x") + item.get("w"), item.get("y") + item.get("h")), (0, 255, 0), 2)
        cv2.putText(frame, text, (int(item.get("x")), int(item.get("y")) - 10),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
        return frame

    def detect(self, frame, conf_thresh=0.2, nms_thresh=0.45):
        blob = cv2.dnn.blobFromImage(frame, 1 / 255.0, (416, 416), swapRB=True, crop=False)
        self.net.setInput(blob)
        outs = self.net.forward(self.net.getUnconnectedOutLayersNames())

        # 对检测结果进行非极大值抑制
        boxes, scores, class_ids = [], [], []
        for out in outs:
            for detection in out:
                confidence = detection[4]
                if confidence > conf_thresh:
                    c_x, c_y, w, h = list(map(int, detection[:4] * np.array([frame.shape[1], frame.shape[0], frame.shape[1], frame.shape[0]])))
                    x = int(c_x - w / 2)
                    y = int(c_y - h / 2)
                    boxes.append([x, y, w, h])
                    scores.append(float(confidence))
                    class_ids.append(np.argmax(detection[5:]))

        indices = cv2.dnn.NMSBoxes(boxes, scores, conf_thresh, nms_thresh)

        items = []
        # 只保留NMS过后的检测结果
        for index in indices:
            item = {"class_id": class_ids[index], "x": boxes[index][0], "y": boxes[index][1], "w": boxes[index][2], "h": boxes[index][3],
                    "score": scores[index]}
            items.append(item)

        return items

在这里的话,咱们再加入一个NMS

卡尔曼预测器

那么接下来就是我们的卡尔曼预测器的实现了,接下来我们要实现追踪功能。那么我们的核心刚刚也说了,其实非常简单:

class ObjectTrackerKF:
    def __init__(self, x_init, y_init, dt=1, sigma=10):
        self.dt = dt
        # 状态向量,包含位置和速度信息
        self.state = np.array([[x_init], [y_init], [0], [0]], np.float32)

        # 系统矩阵,将状态向量映射为下一时刻的状态向量
        self.A = np.array([
            [1, 0, self.dt, 0],
            [0, 1, 0, self.dt],
            [0, 0, 1, 0],
            [0, 0, 0, 1]], np.float32)

        # 测量矩阵,将状态向量映射为测量向量
        self.H = np.array([
            [1, 0, 0, 0],
            [0, 1, 0, 0]], np.float32)

        # 过程噪声,表示模型中未考虑的外部因素产生的偏差
        self.Q = np.array([
            [self.dt ** 4 / 4, 0, self.dt ** 3 / 2, 0],
            [0, self.dt ** 4 / 4, 0, self.dt ** 3 / 2],
            [self.dt ** 3 / 2, 0, self.dt ** 2, 0],
            [0, 0, 0, self.dt ** 2]], np.float32) * sigma ** 2

        # 测量噪声,表示测量器的误差
        self.R = np.array([
            [1, 0],
            [0, 1]], np.float32) * sigma ** 2

        # 卡尔曼滤波器初始化
        self.kf = cv2.KalmanFilter(4, 2, 0)
        self.kf.statePost = self.state
        self.kf.transitionMatrix = self.A
        self.kf.measurementMatrix = self.H
        self.kf.processNoiseCov = self.Q
        self.kf.measurementNoiseCov = self.R

    def predict(self):
        self.state = self.kf.predict()
        return self.state[:2].reshape(-1)

    def update(self, x, y):
        self.kf.correct(np.array([[x], [y]], np.float32))
        return self.state[:2].reshape(-1)


现在我们可以简单测试一下:

    tr = ObjectTrackerKF(x_init=100, y_init=200)
    print(tr.predict())
    for i in range(10):
        x = i * 10 + 100
        y = i * 20 + 200
        predicted_pos = tr.predict()
        tr.update(x, y)
        print("Predicted position:", predicted_pos)
        print("Updated position:", tr.state[:2].reshape(-1))

ID分配器(跟踪器)

那么接下来就是我们的这个ID的分配器了。也就是跟踪器的实现了,其实跟踪器的实现的话,比较简单,但是在这里的话,我们还要在实现一个回收器,主要是要手动回收一下对象,避免后面炸了。

class MulObjectTracker():

    def __init__(self,w=640,h=480,thorslde=0.4):

        #统计当前出现的不同的类别的数量
        self.counts = {}
        #当前正在更正的跟踪器
        self.objectPres = {}
        #当前正在跟踪的item
        self.track_items = {}
        self.thorslde = thorslde
        self.w = w
        self.h = h

    def __add_tracker(self,item):

        class_name = item.get("class_name")
        #添加一个目标跟踪,同时,对应的计数器累加
        class_trackers = self.objectPres.get(class_name,[])
        ID = len(class_trackers)
        x,y = item.get("x"),item.get("y")
        tracker_pre = ObjectTrackerKF(x,y)
        tracker_pre.ID = ID
        tracker_pre.hit = True
        class_trackers.append(tracker_pre)
        self.objectPres[class_name] = class_trackers
        #同时完成计数统计
        class_count = self.counts.get(class_name,0)
        self.counts[class_name] = class_count+1
        #对当前的item记录
        class_items = self.track_items.get(class_name,[])
        class_items.append(item)
        self.track_items[class_name] = class_items

    def lookdown(self,x,y,x_p,y_p):
        # 判断是不是同一个
        x,y,x_p,y_p = x/self.w,y/self.h,x_p/self.w,y_p/self.h
        dist = np.linalg.norm([x-x_p,y-y_p])
        if(dist<=self.thorslde):
            return True

    def track(self,items):
        #此时我们会进行一次操作
        for item in items:
            class_name = item.get("class_name")
            class_tracks = self.objectPres.get(class_name,None)
            class_items = self.track_items.get(class_name)
            if(not class_tracks):
                self.__add_tracker(item)
            else:
                for i in range(len(class_tracks)):
                    track = class_tracks[i]
                    x_p,y_p = track.predict()
                    x,y = item.get("x"),item.get("y")
                    if(self.lookdown(x,y,x_p,y_p)):
                        #如果满足这个定义,那么说明是同一个目标
                        track.update(x,y)
                        c_it = class_items[i]
                        c_it["x"] = x
                        c_it["y"] = y
                        track.hit = True
                    else:
                        #没有满足那就创建去
                        self.__add_tracker(item)
        self.__gc()

    def __gc(self):

        for class_name in self.objectPres.keys():
            class_tracks = self.objectPres.get(class_name,[])
            class_items = self.track_items.get(class_name,[])
            dels_tr = []
            dels_cl = []
            indexs = []
            for i in range(len(class_tracks)):
                if(class_tracks[i].hit==False):
                    indexs.append(i)
                    dels_tr.append(class_tracks[i])
                    dels_cl.append(class_items[i])
                else:
                    class_tracks[i].hit = False
            #确保一定被回收了
            class_items=self.__romve(class_items,indexs)
            class_tracks=self.__romve(class_tracks,indexs)
            self.objectPres[class_name] = class_tracks
            self.track_items[class_name] = class_items

            for dels_t in dels_tr:
                del dels_t
                gc.collect()
            for dels_c in dels_cl:
                del dels_c
                gc.collect()
    def __romve(self,x,idxs):
        if(len(idxs)==0):
            return x
        j = 0
        res = []
        for i in range(len(x)):
            if(j<len(idxs) and i!=idxs[j]):
                res.append(x[i])
            else:
                j+=1
        return res

    def show(self,frame):
        for class_name in self.track_items.keys():
            class_items = self.track_items.get(class_name, [])
            for i in range(len(class_items)):
                item = class_items[i]
                text = "{}:ID({})".format(class_name,i)
                cv2.rectangle(frame, (item.get("x"), item.get("y")), (item.get("x") + item.get("w"), item.get("y") + item.get("h")), (0, 255, 0), 2)
                cv2.putText(frame, text, (int(item.get("x")), int(item.get("y")) - 10),
                            cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
        return frame

这段代码的实现其实非常简单,当然这里适当进行了一点优化,我们按照类别构建了一颗数,然后再相同类别里面去做比对,计算。

那么在这里我要强调的点就是,我们对于,那个阈值的调整。因为流程是这样的:
在这里插入图片描述

所以这个阈值的选定很重要,同时还有其他的参数。此外对于复杂目标来说,识别算法识别出来的目标框可能也存在波动。所以,这个就要求识别算法需要很精准。博主也做了简单测试,识别人的话效果比较差,识别其他的效果还是可以的,至少统计是ok的。

完整代码

代码

那么接下来就是万众期待的完整代码了。
首先项目结构的话比较简单:
在这里插入图片描述
所以可以自己去下载对应的文件。

核心的是这个运行文件:

import cv2
import numpy as np
import gc
import copy
#基于卡尔曼滤波器的目标轨迹预测器
class ObjectTrackerKF:
    def __init__(self, x_init, y_init, dt=1, sigma=10):
        self.dt = dt
        # 状态向量,包含位置和速度信息
        self.state = np.array([[x_init], [y_init], [0], [0]], np.float32)

        # 系统矩阵,将状态向量映射为下一时刻的状态向量
        self.A = np.array([
            [1, 0, self.dt, 0],
            [0, 1, 0, self.dt],
            [0, 0, 1, 0],
            [0, 0, 0, 1]], np.float32)

        # 测量矩阵,将状态向量映射为测量向量
        self.H = np.array([
            [1, 0, 0, 0],
            [0, 1, 0, 0]], np.float32)

        # 过程噪声,表示模型中未考虑的外部因素产生的偏差
        self.Q = np.array([
            [self.dt ** 4 / 4, 0, self.dt ** 3 / 2, 0],
            [0, self.dt ** 4 / 4, 0, self.dt ** 3 / 2],
            [self.dt ** 3 / 2, 0, self.dt ** 2, 0],
            [0, 0, 0, self.dt ** 2]], np.float32) * sigma ** 2

        # 测量噪声,表示测量器的误差
        self.R = np.array([
            [1, 0],
            [0, 1]], np.float32) * sigma ** 2

        # 卡尔曼滤波器初始化
        self.kf = cv2.KalmanFilter(4, 2, 0)
        self.kf.statePost = self.state
        self.kf.transitionMatrix = self.A
        self.kf.measurementMatrix = self.H
        self.kf.processNoiseCov = self.Q
        self.kf.measurementNoiseCov = self.R

    def predict(self):
        self.state = self.kf.predict()
        return self.state[:2].reshape(-1)

    def update(self, x, y):
        self.kf.correct(np.array([[x], [y]], np.float32))
        return self.state[:2].reshape(-1)


#基于yolo的目标检测器
class YoloDetect():

    def __init__(self,model_source="yolov3-tiny.weights",mode_cfg="yolov3-tiny.cfg",
                    class_file="coco.names"
                 ):
        self.net = cv2.dnn.readNet(model_source,mode_cfg)
        self.classes_file = open(class_file, "r")
        self.classes = [line.strip() for line in self.classes_file.readlines()]


    def show(self,frame,item):

        class_name = self.classes[item.get("class_id")]
        text = "{}".format(class_name)
        cv2.rectangle(frame, (item.get("x"), item.get("y")), (item.get("x") + item.get("w"), item.get("y") + item.get("h")), (0, 255, 0), 2)
        cv2.putText(frame, text, (int(item.get("x")), int(item.get("y")) - 10),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
        return frame

    def detect(self, frame, conf_thresh=0.2, nms_thresh=0.45):
        blob = cv2.dnn.blobFromImage(frame, 1 / 255.0, (416, 416), swapRB=True, crop=False)
        self.net.setInput(blob)
        outs = self.net.forward(self.net.getUnconnectedOutLayersNames())

        # 对检测结果进行非极大值抑制
        boxes, scores, class_ids = [], [], []
        for out in outs:
            for detection in out:
                confidence = detection[4]
                if confidence > conf_thresh:
                    c_x, c_y, w, h = list(map(int, detection[:4] * np.array([frame.shape[1], frame.shape[0], frame.shape[1], frame.shape[0]])))
                    x = int(c_x - w / 2)
                    y = int(c_y - h / 2)
                    boxes.append([x, y, w, h])
                    scores.append(float(confidence))
                    class_ids.append(np.argmax(detection[5:]))

        indices = cv2.dnn.NMSBoxes(boxes, scores, conf_thresh, nms_thresh)

        items = []
        # 只保留NMS过后的检测结果
        for index in indices:
            item = {"class_id": class_ids[index], "x": boxes[index][0], "y": boxes[index][1], "w": boxes[index][2], "h": boxes[index][3],
                    "class_name": self.classes[int(class_ids[index])]}
            items.append(item)

        return items

class MulObjectTracker():

    def __init__(self,w=640,h=480,thorslde=0.4):

        #统计当前出现的不同的类别的数量
        self.counts = {}
        #当前正在更正的跟踪器
        self.objectPres = {}
        #当前正在跟踪的item
        self.track_items = {}
        self.thorslde = thorslde
        self.w = w
        self.h = h

    def __add_tracker(self,item):

        class_name = item.get("class_name")
        #添加一个目标跟踪,同时,对应的计数器累加
        class_trackers = self.objectPres.get(class_name,[])
        ID = len(class_trackers)
        x,y = item.get("x"),item.get("y")
        tracker_pre = ObjectTrackerKF(x,y)
        tracker_pre.ID = ID
        tracker_pre.hit = True
        class_trackers.append(tracker_pre)
        self.objectPres[class_name] = class_trackers
        #同时完成计数统计
        class_count = self.counts.get(class_name,0)
        self.counts[class_name] = class_count+1
        #对当前的item记录
        class_items = self.track_items.get(class_name,[])
        class_items.append(item)
        self.track_items[class_name] = class_items

    def lookdown(self,x,y,x_p,y_p):
        # 判断是不是同一个
        x,y,x_p,y_p = x/self.w,y/self.h,x_p/self.w,y_p/self.h
        dist = np.linalg.norm([x-x_p,y-y_p])
        if(dist<=self.thorslde):
            return True

    def track(self,items):
        #此时我们会进行一次操作
        for item in items:
            class_name = item.get("class_name")
            class_tracks = self.objectPres.get(class_name,None)
            class_items = self.track_items.get(class_name)
            if(not class_tracks):
                self.__add_tracker(item)
            else:
                for i in range(len(class_tracks)):
                    track = class_tracks[i]
                    x_p,y_p = track.predict()
                    x,y = item.get("x"),item.get("y")
                    if(self.lookdown(x,y,x_p,y_p)):
                        #如果满足这个定义,那么说明是同一个目标
                        track.update(x,y)
                        c_it = class_items[i]
                        c_it["x"] = x
                        c_it["y"] = y
                        track.hit = True
                    else:
                        #没有满足那就创建去
                        self.__add_tracker(item)
        self.__gc()

    def __gc(self):

        for class_name in self.objectPres.keys():
            class_tracks = self.objectPres.get(class_name,[])
            class_items = self.track_items.get(class_name,[])
            dels_tr = []
            dels_cl = []
            indexs = []
            for i in range(len(class_tracks)):
                if(class_tracks[i].hit==False):
                    indexs.append(i)
                    dels_tr.append(class_tracks[i])
                    dels_cl.append(class_items[i])
                else:
                    class_tracks[i].hit = False
            #确保一定被回收了
            class_items=self.__romve(class_items,indexs)
            class_tracks=self.__romve(class_tracks,indexs)
            self.objectPres[class_name] = class_tracks
            self.track_items[class_name] = class_items

            for dels_t in dels_tr:
                del dels_t
                gc.collect()
            for dels_c in dels_cl:
                del dels_c
                gc.collect()
    def __romve(self,x,idxs):
        if(len(idxs)==0):
            return x
        j = 0
        res = []
        for i in range(len(x)):
            if(j<len(idxs) and i!=idxs[j]):
                res.append(x[i])
            else:
                j+=1
        return res

    def show(self,frame):
        for class_name in self.track_items.keys():
            class_items = self.track_items.get(class_name, [])
            for i in range(len(class_items)):
                item = class_items[i]
                text = "{}:ID({})".format(class_name,i)
                cv2.rectangle(frame, (item.get("x"), item.get("y")), (item.get("x") + item.get("w"), item.get("y") + item.get("h")), (0, 255, 0), 2)
                cv2.putText(frame, text, (int(item.get("x")), int(item.get("y")) - 10),
                            cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
        return frame


if __name__ == '__main__':


    cap = cv2.VideoCapture(0)
    yolo_d = YoloDetect()
    mtracker = MulObjectTracker()
    while True:
        ret, frame = cap.read()
        results = yolo_d.detect(frame,conf_thresh=0.4)
        mtracker.track(results)
        frame = mtracker.show(frame)
        cv2.imshow("frame", frame)
        print(mtracker.counts)
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    cap.release()
    cv2.destroyAllWindows()

那么之后的话,你可以进行其他的操作了。

运行结果如下:(视频就不展示了,可以自己去测试)
在这里插入图片描述

总结

ok,以上就是全部内容。
此外关于操作系统的那部分更新的话,本来是打算按照书上的来,一天一天更新的。但是发现,太麻烦了,而且实话实话,每一天的内容不多。而且最近还在复习数据结构和期末作业,所以嘛,就不一天一天来,而是一个阶段一个阶段来。虽然初期阅读汇编很枯燥,不过还好,那么本书《30天自制操作系统》作为引导确实不错。加上书上的代码和GPT,学习起来还不错感觉。

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

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

相关文章

Three.js--》实现3d字体模型展示

目录 项目搭建 初始化three.js基础代码 设置环境纹理 加载字体模型 今天简单实现一个three.js的小Demo&#xff0c;加强自己对three知识的掌握与学习&#xff0c;只有在项目中才能灵活将所学知识运用起来&#xff0c;话不多说直接开始。 项目搭建 本案例还是借助框架书写…

前后端交互模型http协议Ajax简介

0、前言&#xff1a;本文只是对“前后端交互模型&http协议&Ajax简介”当中的理论&#xff0c;作用&#xff0c;方法进行总结说明&#xff0c;用于回顾知识&#xff0c;做概括总结&#xff0c;没有具体实现代码。 1、前后端交互模型&#xff1a; 前端发送请求&#xff…

信号机制上(信号概念、发送、定时器、信号捕捉、SIGCHLD)

一、信号机制 概念&#xff1a;信号是在软件层次上对中断机制的一种模拟&#xff0c;是一种异步通信方式 所有信号的产生及处理全部都是由内核完成的 信号的产生&#xff1a; 1 按键产生 2 系统调用函数产生&#xff08;比如raise&#xff0c; kill&#xff09; 3 硬件异…

连接MQTT服务端

MQTT客户端之间要想实现通讯&#xff0c;必须要通过MQTT服务端。因此MQTT客户端无论是发布消息还是订阅消息&#xff0c;首先都要连接MQTT服务端。 MQTT客户端连接服务端一共有两步。 第一步&#xff08;CONNECT请求&#xff09; 首先MQTT客户端将会向服务端发送连接请求。该…

HBase 的关键流程解析

前言 本文隶属于专栏《大数据技术体系》&#xff0c;该专栏为笔者原创&#xff0c;引用请注明来源&#xff0c;不足和错误之处请在评论区帮忙指出&#xff0c;谢谢&#xff01; 本专栏目录结构和参考文献请见大数据技术体系 正文 HBase 客户端会将查询过的 HRegion 的位置信息…

【Python爬虫】采集电商商品评价信息

目录 一、数据采集逻辑二、数据Schema三、数据爬取1.导入库2.对爬虫程序进行伪装3.抓取商品评论信息4.防止反爬&#xff0c;每爬取一页数据后&#xff0c;设置程序休眠环节 四、数据存储1. 存储到csv 2.存储到数据库 一、数据采集逻辑 在进行数据采集之前&#xff0c;明确哪些…

Linux下C语言文件描述符操作(dup / dup2 / sendfile / splice / tee)

Linux的哲学是一切皆文件&#xff0c;而操作文件是通过文件描述符来进行。本文梳理一下dup / dup2 / sendfile / splice/ tee函数对文件描述符的操作。 目录 1.dup 2.dup2 3.sendfile 4.splice 5.tee 1.dup #include <unistd.h> int dup(int fd); 复制一个现有的…

Java基础(maven)——maven新建项目 常用IO工具 Durid数据库工具 案例

目录 引出用Maven建项目0.Maven配置方式1.io流的工具IOUtils/FileUtils1&#xff09;可以读文件、按照行读、读网页等&#xff1b;2&#xff09;配合hasmap进行简体繁体转换 2.durid数据库连接工具1&#xff09;创建连接&#xff0c;durid进行连接管理2&#xff09;查询的方式q…

chatgpt赋能python:Python中的转置函数-一种简单而高效的矩阵操作

Python中的转置函数 - 一种简单而高效的矩阵操作 作为一名10年的Python编程经验工程师&#xff0c;掌握利用Python进行矩阵操作是必不可少的。Python中提供了各种高效的矩阵操作功能&#xff0c;其中之一就是转置函数。 什么是转置&#xff1f; 在数学中&#xff0c;矩阵转置…

追寻幸福:探索幸福的关键特征和行为

目录 1. 积极的心态 2. 良好的人际关系 3. 自我接纳和自尊 4. 追求意义和目标 5. 健康的身心状态 6. 感知和实现个人价值 幸福是一个主观的感受&#xff0c;因此不同的人对于幸福的定义和追求方式可能会有所不同。然而&#xff0c;有一些共同的特点和行为模式&#xff0c…

【数据结构】难度上一个台阶的二叉树实现

【数据结构】难度上一个台阶的二叉树实现 一、什么是树和二叉树&#xff1f;二、目标三、实现3.1、初始化工作3.2、二叉树的前序遍历3.2.1、原理图解3.2.2、代码实现 3.3、二叉树的创建3.3.1、原理解析3.3.2、代码实现 3.4、二叉树的中序遍历3.5、二叉树的后序遍历3.6、二叉树的…

K8S系列文章之快速入门K8S

搭建K8S环境 前置 目前生产部署Kubernetes 集群主要有两种方式&#xff1a; kubeadm Kubeadm 是一个K8s 部署工具&#xff0c;提供kubeadm init 和kubeadm join&#xff0c;用于快速部署Kubernetes 集群。 官方地址&#xff1a;https://kubernetes.io/docs/reference/setu…

ChatGPT出来后,为什么老有人想让我们程序员失业?

&#x1f431; 个人主页&#xff1a;不叫猫先生&#xff0c;公众号&#xff1a;前端舵手 &#x1f64b;‍♂️ 作者简介&#xff1a;2022年度博客之星前端领域TOP 2&#xff0c;前端领域优质作者、阿里云专家博主&#xff0c;专注于前端各领域技术&#xff0c;共同学习共同进步…

【LeetCode每日一题】——1248.统计「优美子数组」

文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【题目提示】七【解题思路】八【时间频度】九【代码实现】十【提交结果】 一【题目类别】 滑动窗口 二【题目难度】 中等 三【题目编号】 1248.统计「优美子数组」 四【题目描述】 给…

NVIDIA NCCL 源码学习(十)- 多机间ncclSend和ncclRecv的过程

先回忆一下单机的执行流程&#xff0c;用户执行ncclSend之后通过ncclEnqueueCheck将sendbuff&#xff0c;sendbytes&#xff0c;peer等信息保存到了comm->p2plist中&#xff1b;然后执行ncclGroupEnd&#xff0c;如果发现channel没有建立到peer的链接则先建链&#xff0c;然…

深入理解设计原则之接口隔离原则(ISP)

系列文章目录 C高性能优化编程系列 深入理解设计原则系列 深入理解设计模式系列 高级C并发线程编程 LSP&#xff1a;接口隔离原则 系列文章目录1、接口隔离原则的定义和解读2、案例解读3、如何判断一个接口是否符合接口隔离原则&#xff1f;小结 1、接口隔离原则的定义和解读…

网络工程师一定要会关键技能:如何进行IP子网划分?

对于所有从事IP网络方面工作的工程师来说&#xff0c;进行IP子网划分操作属于一个必备的关键技能&#xff0c;也属于必须掌握的专业内容&#xff1b;但对于初学者来说&#xff0c;真正理解IP子网划分的概念也是一件相当困难的事情。 怎样才能正确地进行子网划分操作 IP子网划…

影响现代办公室隔断设计的因素有哪些,办公室隔断设计方案要求

影响现代办公室隔断设计的因素有哪些 1. 办公空间的用途和功能要求 2. 员工数量及工作场所的布局 3. 设计风格和企业文化要求 4. 预算和材料选择 5. 环保节能和安全性要求 办公室隔断设计方案要求&#xff1a; 1. 合理利用空间&#xff0c;满足办公室的功能需求 2. 设计…

Creepypastsa VoxEdit 竞赛

召唤所有恐怖都市传闻爱好者。 通过 Creepypasta VoxEdit 竞赛&#xff0c;潜入黑暗领域&#xff0c;并释放你们的创造力&#xff01;踏入阴森恐怖的神秘世界&#xff0c;把你最可怕的噩梦变成现实&#xff01; 设计终极的 Creepypasta 体素资产 你是恐怖大师吗&#xff1f;是一…

RHCE 作业三

1.基于域名访问网站 [rootserver ~]# setenforce 0 [rootserver ~]# systemctl stop firewalld [rootserver ~]# systemctl disable firewalld [rootserver ~]# yum install httpd -y [rootserver ~]# systemctl start httpd [rootserver ~]# syst…