智慧交通day02-车流量检测实现14:代码汇总+问题修正


代码+权重文件+资源https://download.csdn.net/download/qq_39237205/43072746https://download.csdn.net/download/qq_39237205/43072746

环境要求:python2.7 

环境配置:见文末requirements.txt

1.YOLO.py

# encoding:utf-8
import imutils
import time
import cv2
import numpy as np
from kalman import *
import matplotlib.pyplot as plt#根据摄像头的图像尺寸进行设置
line = [(0,150),(2560,150)]
#车辆总数
counter = 0
#正向车道的车辆数
counter_up = 0
#逆向车道的车辆数
counter_down = 0#创建跟踪器的对象
tracker = Sort()
memory = {}#线与线的碰撞检测--二维叉乘的方法检测两个直线之间是否相交
# 计算叉乘符号
def ccw(A, B, C):return (C[1] - A[1]) * (B[0] - A[0]) > (B[1] - A[1]) * (C[0] - A[0])# 检测AB和CD两条直线是否相交
def intersect(A, B, C, D):return ccw(A, C, D) != ccw(B, C, D) and ccw(A, B, C) != ccw(A, B, D)#利用yolov3模型进行目标检测
#加载模型相关信息
#加载可以检测的目标的类型#labelPath:类别标签文件的路径
labelPath = "./yolo-coco/coco.names"# 加载类别标签文件
LABELS = open(labelPath).read().strip().split("\n")#生成多种不同的颜色的检测框 用来标注物体
np.random.seed(42)
COLORS = np.random.randint(0,255,size=(200,3),dtype='uint8')#加载预训练的模型:权重 配置信息、进行恢复模型
#weights_path:模型权重文件的路径
weightsPath = "./yolo-coco/yolov3.weights"
#configPath:模型配置文件的路径
configPath = "./yolo-coco/yolov3.cfg"net = cv2.dnn.readNetFromDarknet(configPath,weightsPath)
#获取YOLO每一层的名称
#getLayerNames():获取网络所有层的名称。
ln = net.getLayerNames()
# 获取输出层的名称: [yolo-82,yolo-94,yolo-106]
# getUnconnectedOutLayers():获取输出层的索引
ln = [ln[i[0] - 1] for i in net.getUnconnectedOutLayers()]#读取视频
vs = cv2.VideoCapture('input/test_1.mp4')
#获取宽和高
(W,H)=(None,None)
writer = Nonetry:prop = cv2.cv.CV_CAP_PROP_Frame_COUNT if imutils.is_cv2() else cv2.CAP_PROP_FRAME_COUNT#获取视频的总帧数total = int(vs.get(prop))print("INFO:{} total Frame in video".format(total))
except:print("[INFO] could not determine in video")#遍历每一帧的图像
while True:#获取帧的结果(grabed,frame)=vs.read()#如果没有 则跳出循环if not grabed:breakif W is None or H is None:(H, W) = frame.shape[:2]# 将图片构建成一个blob,设置图片尺寸,然后执行一次前向传播# YOLO前馈网络计算,最终获取边界框和相应概率blob = cv2.dnn.blobFromImage(frame,1/255.0,(416,416),swapRB=True,crop=False)#将blob送入网络net.setInput(blob)start = time.time()#前向传播,进行预测,返回目标框的边界和响应的概率layerOutouts = net.forward(ln)end = time.time()#存放目标的检测框boxes = []#置信度confidences = []#目标类别classIDs = []# 迭代每个输出层,总共三个for output in layerOutouts:#遍历每个检测结果for detection in output:# 提取类别ID和置信度#detction:1*85 [5:]表示类别,[0:4]bbox的位置信息 [5]置信度、可信度scores = detection[5:]classID = np.argmax(scores)confidence= scores[classID]# 只保留置信度大于某值的边界框if confidence >0.3:# 将边界框的坐标还原至与原图片相匹配,记住YOLO返回的是边界框的中心坐标以及边界框的宽度和高度box = detection[0:4] * np.array([W, H, W, H])(centerX,centerY,width,height) = box.astype("int")# 计算边界框的左上角位置x = int(centerX-width/2)y = int(centerY-height/2)# 更新目标框,置信度(概率)以及类别boxes.append([x,y,int(width),int(height)])confidences.append(float(confidence))classIDs.append(classID)# 使用非极大值抑制方法抑制弱、重叠的目标框idxs = cv2.dnn.NMSBoxes(boxes,confidences,0.5,0.3)#检测框的结果:左上角坐标、右下角坐标dets = []# 确保至少有一个边界框if len(idxs)>0:# 迭代每个边界框for i in idxs.flatten():# 提取边界框的坐标if LABELS[classIDs[i]] == "car":(x,y)=(boxes[i][0],boxes[i][1])(w,h)=(boxes[i][2],boxes[i][3])cv2.rectangle(frame,(x,y),(x+w,y+h),(0,255,0),2)dets.append([x,y,x+w,y+h,confidences[i]])# 类型设置np.set_printoptions(formatter={'float': lambda x: "{0:0.3f}".format(x)})dets = np.asarray(dets)#SORT目标跟踪if np.size(dets) == 0:continueelse:tracks = tracker.update(dets)# 存放跟踪框boxes = []#存储置信度/可靠性indexIDs = []#上一帧的跟踪结果previous = memory.copy()memory = {}for track in tracks:boxes.append([track[0],track[1],track[2],track[3]])indexIDs.append(int(track[4]))memory[indexIDs[-1]] = boxes[-1]#从SORT跟踪框的结果中进行碰撞检测if len(boxes)>0:i = int(0)#遍历跟踪框for box in boxes:(x, y) = (int(box[0]), int(box[1])) #左上角坐标(w, h) = (int(box[2]), int(box[3])) #宽高color = [int(c) for c in COLORS[indexIDs[i]%len(COLORS)]]cv2.rectangle(frame, (x, y), (w, h), color, 2)#根据在上一帧的检测结果与当前帧的检测结果,利用虚拟线圈完成车辆的计数if indexIDs[i] in previous:previous_box = previous[indexIDs[i]]#上一帧图像的左上角坐标(x2,y2) = (int(previous_box[0]),int(previous_box[1]))# 上一帧图像的宽高(w2,h2) = (int(previous_box[2]),int(previous_box[3]))#上一帧的中心点的坐标p1 = (int(x2 + (w2 - x2) / 2), int(y2 + (h2 - y2) / 2))# 当前帧的中心点的坐标p0 = (int(x + (w - x) / 2), int(y + (h - y) / 2))# 利用p0,p1与line进行碰撞检测if intersect(p0, p1, line[0], line[1]):counter += 1# 判断行进方向if y2 > y:counter_down += 1else:counter_up += 1i += 1# 将车辆计数的相关结果放在视频上print("将车辆计数的相关结果放在视频上")cv2.line(frame, line[0], line[1], (0, 255, 0), 3)cv2.putText(frame, str(counter), (30, 80), cv2.FONT_HERSHEY_DUPLEX, 3.0, (255, 0, 0), 3)cv2.putText(frame, str(counter_up), (130, 80), cv2.FONT_HERSHEY_DUPLEX, 3.0, (0, 255, 0), 3)cv2.putText(frame, str(counter_down), (230, 80), cv2.FONT_HERSHEY_DUPLEX, 3.0, (0, 0, 255), 3)# 将检测结果保存在视频
# 未设置视频的编解码信息时,执行以下代码if writer is None:# 设置编码格式fourcc = cv2.VideoWriter_fourcc(*"mp4v")# 视频信息设置writer = cv2.VideoWriter("./output/output.mp4",fourcc,30,(frame.shape[1], frame.shape[0]),True)# 将处理后的帧写入到视频中print ("将处理后的帧写入到视频中")writer.write(frame)# 显示当前帧的结果cv2.imshow("", frame)# 按下q键退出if cv2.waitKey(1) & 0xFF == ord('q'):break# "释放资源"
writer.release()
vs.release()
cv2.destroyAllWindows()

2.kalman.py

# encoding:utf-8
from __future__ import print_function
# from numba import jit
import numpy as np
from scipy.optimize import linear_sum_assignment
from filterpy.kalman import KalmanFilter#计算IOU(交并比)
# @jit
def iou(bb_test,bb_gt):"""在两个box间计算IOU:param bb_test: box1 = [x1,y1,x2,y2] 左上角坐标:param bb_gt: box2 = [x1,y1,x2,y2] 右下角坐标:return: 交并比IOU"""#在两个box间的左上角坐标的最大值xx1 = np.maximum(bb_test[0],bb_gt[0])#左上角坐标x的最大值yy1 = np.maximum(bb_test[1],bb_gt[1])#左上角坐标y的最大值#在两个box间的右下角坐标的最小值xx2 = np.minimum(bb_test[2],bb_gt[2])#右下角坐标x的最小值yy2 = np.minimum(bb_test[3],bb_test[3])#右下角坐标y的最小值#交的宽高w = np.maximum(0,xx2-xx1)h = np.maximum(0,yy2-yy1)#交的面积wh = w*h#并的面积s = ((bb_test[2] - bb_test[0]) * (bb_test[3] - bb_test[1])+ (bb_gt[2] - bb_gt[0]) * (bb_gt[3] - bb_gt[1]) - wh)#计算IOU并且返回IOUo_rate = wh/sreturn o_rate#左上角坐标[x1,y1]和右下角坐标[x2,y2],
#将候选框从坐标形式[x1,y1,x2,y2]转换为中心点坐标和面积的形式[x,y,s,r]
#其中x,y是框的中心坐标,s是面积,尺度,r是宽高比
def convert_bbox_to_z(bbox):"""将[x1,y1,x2,y2]形式的检测框转为滤波器的状态表示形式[x,y,s,r]。其中x,y是框的中心坐标,s是面积,尺度,r是宽高比:param bbox: [x1,y1,x2,y2] 分别是左上角坐标和右下角坐标:return: [ x, y, s, r ] 4行1列,其中x,y是box中心位置的坐标,s是面积,r是纵横比w/h"""w = bbox[2] - bbox[0]#宽 x2-x1:#右下角的x坐标 - 左上角的x坐标 = 检测框的宽h = bbox[3] - bbox[1]#高 y2-y1:#右下角的y坐标 - 左上角的y坐标 = 检测框的高x = bbox[0] + w/2.0#检测框的中心坐标x:  x1+(x2-x1)/2.0 #左上角的x坐标 + 宽/2 = 检测框中心位置的x坐标y = bbox[1] + h/2.0#检测框的中心坐标y: y1+(y2-y1)/2.0 #左上角的y坐标 + 高/2 = 检测框中心位置的y坐标s = w*h #检测框的面积 #检测框的宽 * 高 = 检测框面积r = w/float(h) #检测框的宽高比# 因为卡尔曼滤波器的输入格式要求为4行1列,因此该[x, y, s, r]的形状要转换为4行1列再输入到卡尔曼滤波器return np.array([x,y,s,r]).reshape([4,1]) #kalman需要四行一列的形式#将候选框从中心面积[x,y,s,r]的形式转换成左上角坐标和右下角坐标[x1,y1,x2,y2]的形式
#即:将[cx,cy,s,r]的目标框表示转为[x_min,y_min,x_max,y_max]的形式
def convert_x_to_bbox(x,score=None):"""将[cx,cy,s,r]的目标框表示转为[x_min,y_min,x_max,y_max]的形式:param x:[ x, y, s, r ],其中x,y是box中心位置的坐标,s是面积,r是纵横比w/h:param score: 置信度:return:[x1,y1,x2,y2],左上角坐标和右下角坐标""""""x[2]:s是面积,原公式s的来源为s = w * h,即检测框的宽 * 高 = 检测框面积。x[3]:r是纵横比w/h,原公式r的来源为r = w / float(h),即检测框的宽w / 高h = 宽高比。x[2] * x[3]:s*r 即(w * h) * (w / float(h)) = w^2sqrt(x[2] * x[3]):sqrt(w^2) = w"""w = np.sqrt(x[2] * x[3]) #w =sqrt(s*r)=sqrt(s*w/h)=sqrt(w*h * w/h)=sqrt(w*w)h = x[2]/w #h =s/w =w*h/w =hx1 = x[0]-w/2.0 #左上角x坐标:x1 = x-w/2.0 #检测框中心位置的x坐标 - 宽 / 2y1 = x[1]-h/2.0 #左上角y坐标:y1 = y-h/2.0 #检测框中心位置的y坐标 - 高 / 2x2 = x[0]+w/2.0 #右下角x坐标:x2 = x+w/2.0 #检测框中心位置的x坐标 + 宽 / 2y2 = x[1]+h/2.0 #右下角y坐标:y2 = y+h/2.0 #检测框中心位置的y坐标 + 高 / 2if score is None:return np.array([x1,y1,x2,y2]).reshape((1,4))else:return np.array([x1,x1,x2,y2,score]).reshape((1,5))"""
卡尔曼滤波器进行跟踪的相关内容的实现目标估计模型:1.根据上一帧的目标框结果来预测当前帧的目标框状态,预测边界框(目标框)的模型定义为一个等速运动/匀速运动模型。2.每个目标框都有对应的一个卡尔曼滤波器(KalmanBoxTracker实例对象),KalmanBoxTracker类中的实例属性专门负责记录其对应的一个目标框中各种统计参数,并且使用类属性负责记录卡尔曼滤波器的创建个数,增加一个目标框就增加一个卡尔曼滤波器(KalmanBoxTracker实例对象)。  3.yoloV3、卡尔曼滤波器预测/更新流程步骤1.第一步:yoloV3目标检测阶段:--> 1.检测到目标则创建检测目标链/跟踪目标链,反之检测不到目标则重新循环目标检测。--> 2.检测目标链/跟踪目标链不为空则进入卡尔曼滤波器predict预测阶段,反之为空则重新循环目标检测。2.第二步:卡尔曼滤波器predict预测阶段:连续多次预测而不进行一次更新操作,那么代表了每次预测之后所进行的“预测目标和检测目标之间的”相似度匹配都不成功,所以才会出现连续多次的“预测然后相似度匹配失败的”情况,导致不会进入一次更新阶段。如果一次预测然后相似度匹配成功的话,那么然后就会进入更新阶段。--> 1.目标位置预测1.kf.predict():目标位置预测2.目标框预测总次数:age+=1。3.if time_since_update > 0:hit_streak = 0time_since_update += 11.连续预测的次数,每执行predict一次即进行time_since_update+=1。2.在连续预测(连续执行predict)的过程中,一旦执行update的话,time_since_update就会被重置为0。3.在连续预测(连续执行predict)的过程中,只要连续预测的次数time_since_update大于0的话,就会把hit_streak(连续更新的次数)重置为0,表示连续预测的过程中没有出现过一次更新状态更新向量x(状态变量x)的操作,即连续预测的过程中没有执行过一次update。4.在连续更新(连续执行update)的过程中,一旦开始连续执行predict两次或以上的情况下,当连续第一次执行predict时,因为time_since_update仍然为0,并不会把hit_streak重置为0,然后才会进行time_since_update+=1;当连续第二次执行predict时,因为time_since_update已经为1,那么便会把hit_streak重置为0,然后继续进行time_since_update+=1。--> 2.预测的目标和检测的目标之间的相似度匹配成功则进入update更新阶段,反之匹配失败则删除跟踪目标。3.第三步:卡尔曼滤波器update更新阶段:如果一次预测然后“预测目标和检测目标之间的”相似度匹配成功的话,那么然后就会进入更新阶段。kf.update([x,y,s,r]):使用的是通过yoloV3得到的“并且和预测框相匹配的”检测框来更新预测框。--> 1.目标位置信息更新到检测目标链/跟踪目标链 1.目标框更新总次数:hits+=1。2.history = []time_since_update = 0hit_streak += 11.history列表用于在预测阶段保存单个目标框连续预测的多个结果,一旦执行update就会清空history列表。2.连续更新的次数,每执行update一次即进行hit_streak+=1。3.在连续预测(连续执行predict)的过程中,一旦执行update的话,time_since_update就会被重置为0。4.在连续预测(连续执行predict)的过程中,只要连续预测的次数time_since_update大于0的话,就会把hit_streak(连续更新的次数)重置为0,表示连续预测的过程中没有出现过一次更新状态更新向量x(状态变量x)的操作,即连续预测的过程中没有执行过一次update。5.在连续更新(连续执行update)的过程中,一旦开始连续执行predict两次或以上的情况下,当连续第一次执行predict时,因为time_since_update仍然为0,并不会把hit_streak重置为0,然后才会进行time_since_update+=1;当连续第二次执行predict时,因为time_since_update已经为1,那么便会把hit_streak重置为0,然后继续进行time_since_update+=1。--> 2.目标位置修正。1.kf.update([x,y,s,r]):使用观测到的目标框bbox更新状态变量x(状态更新向量x)。使用的是通过yoloV3得到的“并且和预测框相匹配的”检测框来更新卡尔曼滤波器得到的预测框。1.初始化、预测、更新1.__init__(bbox):初始化卡尔曼滤波器的状态更新向量x(状态变量x)、观测输入[x,y,s,r](通过[x1,y1,x2,y2]转化而来)、状态转移矩阵F、量测矩阵H(观测矩阵H)、测量噪声的协方差矩阵R、先验估计的协方差矩阵P、过程激励噪声的协方差矩阵Q。2.update(bbox):根据观测输入来对状态更新向量x(状态变量x)进行更新3.predict():根据状态更新向量x(状态变量x)更新的结果来预测目标的边界框2.状态变量、状态转移矩阵F、量测矩阵H(观测矩阵H)、测量噪声的协方差矩阵R、先验估计的协方差矩阵P、过程激励噪声的协方差矩阵Q1.状态更新向量x(状态变量x)状态更新向量x(状态变量x)的设定是一个7维向量:x=[u,v,s,r,u^,v^,s^]T。u、v分别表示目标框的中心点位置的x、y坐标,s表示目标框的面积,r表示目标框的纵横比/宽高比。u^、v^、s^分别表示横向u(x方向)、纵向v(y方向)、面积s的运动变化速率。u、v、s、r初始化:根据第一帧的观测结果进行初始化。u^、v^、s^初始化:当第一帧开始的时候初始化为0,到后面帧时会根据预测的结果来进行变化。2.状态转移矩阵F定义的是一个7*7的方阵(其对角线上的值都是1)。。运动形式和转换矩阵的确定都是基于匀速运动模型,状态转移矩阵F根据运动学公式确定,跟踪的目标假设为一个匀速运动的目标。通过7*7的状态转移矩阵F 乘以 7*1的状态更新向量x(状态变量x)即可得到一个更新后的7*1的状态更新向量x,其中更新后的u、v、s即为当前帧结果。3.量测矩阵H(观测矩阵H)量测矩阵H(观测矩阵H),定义的是一个4*7的矩阵。通过4*7的量测矩阵H(观测矩阵H) 乘以 7*1的状态更新向量x(状态变量x) 即可得到一个 4*1的[u,v,s,r]的估计值。4.测量噪声的协方差矩阵R、先验估计的协方差矩阵P、过程激励噪声的协方差矩阵Q1.测量噪声的协方差矩阵R:diag([1,1,10,10]T)2.先验估计的协方差矩阵P:diag([10,10,10,10,1e4,1e4,1e4]T)。1e4:1x10的4次方。3.过程激励噪声的协方差矩阵Q:diag([1,1,1,1,0.01,0.01,1e-4]T)。1e-4:1x10的-4次方。4.1e数字的含义1e4:1x10的4次方1e-4:1x10的-4次方5.diag表示对角矩阵,写作为diag(a1,a2,...,an)的对角矩阵实际表示为主对角线上的值依次为a1,a2,...,an,而主对角线之外的元素皆为0的矩阵。对角矩阵(diagonal matrix)是一个主对角线之外的元素皆为0的矩阵,常写为diag(a1,a2,...,an) 。对角矩阵可以认为是矩阵中最简单的一种,值得一提的是:对角线上的元素可以为 0 或其他值,对角线上元素相等的对角矩阵称为数量矩阵;对角线上元素全为1的对角矩阵称为单位矩阵。对角矩阵的运算包括和、差运算、数乘运算、同阶对角阵的乘积运算,且结果仍为对角阵。
"""
"""
1.跟踪器链(列表):实际就是多个的卡尔曼滤波KalmanBoxTracker自定义类的实例对象组成的列表。每个目标框都有对应的一个卡尔曼滤波器(KalmanBoxTracker实例对象),KalmanBoxTracker类中的实例属性专门负责记录其对应的一个目标框中各种统计参数,并且使用类属性负责记录卡尔曼滤波器的创建个数,增加一个目标框就增加一个卡尔曼滤波器(KalmanBoxTracker实例对象)。把每个卡尔曼滤波器(KalmanBoxTracker实例对象)都存储到跟踪器链(列表)中。
2.unmatched_detections(列表):1.检测框中出现新目标,但此时预测框(跟踪框)中仍不不存在该目标,那么就需要在创建新目标对应的预测框/跟踪框(KalmanBoxTracker类的实例对象),然后把新目标对应的KalmanBoxTracker类的实例对象放到跟踪器链(列表)中。2.同时如果因为“跟踪框和检测框之间的”两两组合的匹配度IOU值小于iou阈值,则也要把目标检测框放到unmatched_detections中。
3.unmatched_trackers(列表):1.当跟踪目标失败或目标离开了画面时,也即目标从检测框中消失了,就应把目标对应的跟踪框(预测框)从跟踪器链中删除。unmatched_trackers列表中保存的正是跟踪失败即离开画面的目标,但该目标对应的预测框/跟踪框(KalmanBoxTracker类的实例对象)此时仍然存在于跟踪器链(列表)中,因此就需要把该目标对应的预测框/跟踪框(KalmanBoxTracker类的实例对象)从跟踪器链(列表)中删除出去。2.同时如果因为“跟踪框和检测框之间的”两两组合的匹配度IOU值小于iou阈值,则也要把跟踪目标框放到unmatched_trackers中。
"""#卡尔曼滤波:对于目标框的状态进行预测
class KalmanBoxTracker(object):"""每个目标框都有对应的一个卡尔曼滤波器(KalmanBoxTracker实例对象),KalmanBoxTracker类中的实例属性专门负责记录其对应的一个目标框中各种统计参数,并且使用类属性负责记录卡尔曼滤波器的创建个数,增加一个目标框就增加一个卡尔曼滤波器(KalmanBoxTracker实例对象)。"""#记录跟踪框的个数count = 0 #类属性负责记录卡尔曼滤波器的创建个数,增加一个目标框就增加一个卡尔曼滤波器(KalmanBoxTracker实例对象"""__init__(bbox)使用目标框bbox为卡尔曼滤波的状态进行初始化。初始化时传入bbox,即根据观测到的检测框的结果来进行初始化。每个目标框都有对应的一个卡尔曼滤波器(KalmanBoxTracker实例对象),KalmanBoxTracker类中的实例属性专门负责记录其对应的一个目标框中各种统计参数,并且使用类属性负责记录卡尔曼滤波器的创建个数,增加一个目标框就增加一个卡尔曼滤波器(KalmanBoxTracker实例对象)。1.kf = KalmanFilter(dim_x=7, dim_z=4)定义一个卡尔曼滤波器,利用这个卡尔曼滤波器对目标的状态进行估计。dim_x=7定义是一个7维的状态更新向量x(状态变量x):x=[u,v,s,r,u^,v^,s^]T。dim_z=4定义是一个4维的观测输入,即中心面积的形式[x,y,s,r],即[检测框中心位置的x坐标,y坐标,面积,宽高比]。2.kf.F = np.array(7*7的方阵)状态转移矩阵F,定义的是一个7*7的方阵其(对角线上的值都是1)。通过7*7的状态转移矩阵F 乘以 7*1的状态更新向量x(状态变量x)即可得到一个更新后的7*1的状态更新向量x,其中更新后的u、v、s即为当前帧结果。通过状态转移矩阵对当前的观测结果进行估计获得预测的结果,然后用当前的预测的结果来作为下一次估计预测的基础。3.kf.H = np.array(4*7的矩阵)量测矩阵H(观测矩阵H),定义的是一个4*7的矩阵。通过4*7的量测矩阵H(观测矩阵H) 乘以 7*1的状态更新向量x(状态变量x) 即可得到一个 4*1的[u,v,s,r]的估计值。4.相应的协方差参数的设定,根据经验值进行设定。1.R是测量噪声的协方差矩阵,即真实值与测量值差的协方差。R=diag([1,1,10,10]T)kf.R[2:, 2:] *= 10.2.P是先验估计的协方差矩阵diag([10,10,10,10,1e4,1e4,1e4]T)。1e4:1x10的4次方。kf.P[4:, 4:] *= 1000.  # 设置了一个较大的值,给无法观测的初始速度带来很大的不确定性kf.P *= 10.3.Q是过程激励噪声的协方差矩阵diag([1,1,1,1,0.01,0.01,1e-4]T)。1e-4:1x10的-4次方。kf.Q[-1, -1] *= 0.01kf.Q[4:, 4:] *= 0.015.kf.x[:4] = convert_bbox_to_z(bbox)convert_bbox_to_z负责将[x1,y1,x2,y2]形式的检测框bbox转为中心面积的形式[x,y,s,r]。状态更新向量x(状态变量x)设定是一个七维向量:x=[u,v,s,r,u^,v^,s^]T。x[:4]即表示 u、v、s、r初始化为第一帧bbox观测到的结果[x,y,s,r]。6.单个目标框对应的单个卡尔曼滤波器中的统计参数的更新每个目标框都有对应的一个卡尔曼滤波器(KalmanBoxTracker实例对象),KalmanBoxTracker类中的实例属性专门负责记录其对应的一个目标框中各种统计参数,并且使用类属性负责记录卡尔曼滤波器的创建个数,增加一个目标框就增加一个卡尔曼滤波器(KalmanBoxTracker实例对象)。1.卡尔曼滤波器的个数有多少个目标框就有多少个卡尔曼滤波器,每个目标框都会有一个卡尔曼滤波器,即每个目标框都会有一个KalmanBoxTracker实例对象。count = 0:类属性负责记录卡尔曼滤波器的创建个数,增加一个目标框就增加一个卡尔曼滤波器(KalmanBoxTracker实例对象。id = KalmanBoxTracker.count:卡尔曼滤波器的个数,即目标框的个数。KalmanBoxTracker.count += 1:每增加一个目标框,即增加一个KalmanBoxTracker实例对象(卡尔曼滤波器),那么类属性count+=1。2.统计一个目标框对应的卡尔曼滤波器中各参数统计的次数1.age = 0:该目标框进行预测的总次数。每执行predict一次,便age+=1。2.hits = 0:该目标框进行更新的总次数。每执行update一次,便hits+=1。3.time_since_update = 01.连续预测的次数,每执行predict一次即进行time_since_update+=1。2.在连续预测(连续执行predict)的过程中,一旦执行update的话,time_since_update就会被重置为0。3.在连续预测(连续执行predict)的过程中,只要连续预测的次数time_since_update大于0的话,就会把hit_streak(连续更新的次数)重置为0,表示连续预测的过程中没有出现过一次更新状态更新向量x(状态变量x)的操作,即连续预测的过程中没有执行过一次update。4.hit_streak = 01.连续更新的次数,每执行update一次即进行hit_streak+=1。2.在连续更新(连续执行update)的过程中,一旦开始连续执行predict两次或以上的情况下,当连续第一次执行predict时,因为time_since_update仍然为0,并不会把hit_streak重置为0,然后才会进行time_since_update+=1;当连续第二次执行predict时,因为time_since_update已经为1,那么便会把hit_streak重置为0,然后继续进行time_since_update+=1。7.history = []:保存单个目标框连续预测的多个结果到history列表中,一旦执行update就会清空history列表。将预测的候选框从中心面积的形式[x,y,s,r]转换为坐标的形式[x1,y1,x2,y2] 的bbox 再保存到 history列表中。"""# 内部使用KalmanFilter,7个状态变量和4个观测输入def __init__(self,bbox):"""初始化边界框和跟踪器:param bbox:"""#等速模型#卡尔曼滤波:状态转移矩阵:7,观测输入矩阵:4self.kf = KalmanFilter(dim_x=7,dim_z=4) #初始化卡尔曼滤波器# F:状态转移/状态变化矩阵 7*7 用当前的矩阵预测下一次的估计self.kf.F = np.array([[1, 0, 0, 0, 1, 0, 0],[0, 1, 0, 0, 0, 1, 0],[0, 0, 1, 0, 0, 0, 1],[0, 0, 0, 1, 0, 0, 0],[0, 0, 0, 0, 1, 0, 0],[0, 0, 0, 0, 0, 1, 0],[0, 0, 0, 0, 0, 0, 1]])#H:量测矩阵/观测矩阵:4*7self.kf.H = np.array([[1, 0, 0, 0, 0, 0, 0],[0, 1, 0, 0, 0, 0, 0],[0, 0, 1, 0, 0, 0, 0],[0, 0, 0, 1, 0, 0, 0]])#R:测量噪声的协方差,即真实值与测量值差的协方差self.kf.R[2:,2:] *= 10#P:先验估计的协方差self.kf.P[4:,4:] *= 1000 #give high uncertainty to the unobservable initial velocities 对不可观测的初始速度给予高度不确定性self.kf.P *= 10#Q:过程激励噪声的的协方差self.kf.Q[-1,-1] *= 0.01self.kf.Q[4:, 4:] *= 0.01#X:观测结果、状态估计self.kf.x[:4] = convert_bbox_to_z(bbox)#参数的更新self.time_since_update = 0self.id = KalmanBoxTracker.countKalmanBoxTracker.count += 1self.history=[]self.hits = 0self.hit_streak = 0self.age = 0"""update(bbox):使用观测到的目标框bbox更新状态更新向量x(状态变量x)1.time_since_update = 01.连续预测的次数,每执行predict一次即进行time_since_update+=1。2.在连续预测(连续执行predict)的过程中,一旦执行update的话,time_since_update就会被重置为0。2.在连续预测(连续执行predict)的过程中,只要连续预测的次数time_since_update大于0的话,就会把hit_streak(连续更新的次数)重置为0,表示连续预测的过程中没有出现过一次更新状态更新向量x(状态变量x)的操作,即连续预测的过程中没有执行过一次update。2.history = []      清空history列表。history列表保存的是单个目标框连续预测的多个结果([x,y,s,r]转换后的[x1,y1,x2,y2]),一旦执行update就会清空history列表。3.hits += 1:该目标框进行更新的总次数。每执行update一次,便hits+=1。4.hit_streak += 11.连续更新的次数,每执行update一次即进行hit_streak+=1。2.在连续更新(连续执行update)的过程中,一旦开始连续执行predict两次或以上的情况下,当连续第一次执行predict时,因为time_since_update仍然为0,并不会把hit_streak重置为0,然后才会进行time_since_update+=1;当连续第二次执行predict时,因为time_since_update已经为1,那么便会把hit_streak重置为0,然后继续进行time_since_update+=1。5.kf.update(convert_bbox_to_z(bbox))convert_bbox_to_z负责将[x1,y1,x2,y2]形式的检测框转为滤波器的状态表示形式[x,y,s,r],那么传入的为kf.update([x,y,s,r])。然后根据观测结果修改内部状态x(状态更新向量x)。使用的是通过yoloV3得到的“并且和预测框相匹配的”检测框来更新卡尔曼滤波器得到的预测框。"""#使用观测到的目标框更新状态变量def update(self,bbox):"""使用观察到的目标框更新状态向量。filterpy.kalman.KalmanFilter.update 会根据观测修改内部状态估计self.kf.x。重置self.time_since_update,清空self.history。:param bbox:目标框:return:"""#重置部分参数self.time_since_update = 0#清空self.history = []#hitsself.hits += 1self.hit_streak += 1#根据观测结果修改内部状态xself.kf.update(convert_bbox_to_z(bbox))"""predict:进行目标框的预测并返回预测的边界框结果1.if(kf.x[6] + kf.x[2]) <= 0:self.kf.x[6] *= 0.0状态更新向量x(状态变量x)为[u,v,s,r,u^,v^,s^]T,那么x[6]为s^,x[2]为s。如果x[6]+x[2]<= 0,那么x[6] *= 0.0,即把s^置为0.0。2.kf.predict()进行目标框的预测。3.age += 1该目标框进行预测的总次数。每执行predict一次,便age+=1。4.if time_since_update > 0:hit_streak = 0time_since_update += 11.连续预测的次数,每执行predict一次即进行time_since_update+=1。2.在连续预测(连续执行predict)的过程中,一旦执行update的话,time_since_update就会被重置为0。3.在连续预测(连续执行predict)的过程中,只要连续预测的次数time_since_update大于0的话,就会把hit_streak(连续更新的次数)重置为0,表示连续预测的过程中没有出现过一次更新状态更新向量x(状态变量x)的操作,即连续预测的过程中没有执行过一次update。4.在连续更新(连续执行update)的过程中,一旦开始连续执行predict两次或以上的情况下,当连续第一次执行predict时,因为time_since_update仍然为0,并不会把hit_streak重置为0,然后才会进行time_since_update+=1;当连续第二次执行predict时,因为time_since_update已经为1,那么便会把hit_streak重置为0,然后继续进行time_since_update+=1。5.history.append(convert_x_to_bbox(kf.x))convert_x_to_bbox(kf.x):将目标框所预测的结果从中心面积的形式[x,y,s,r] 转换为 坐标的形式[x1,y1,x2,y2] 的bbox。history列表保存的是单个目标框连续预测的多个结果([x,y,s,r]转换后的[x1,y1,x2,y2]),一旦执行update就会清空history列表。6.predict 返回值:history[-1]把目标框当前该次的预测的结果([x,y,s,r]转换后的[x1,y1,x2,y2])进行返回输出。"""#进行目标框的预测:推进状态变量并返回预测的边界框结果def predict(self):"""推进状态向量并返回预测的边界框估计。将预测结果追加到self.history。由于 get_state 直接访问 self.kf.x,所以self.history没有用到:return:"""#状态变量if(self.kf.x[6] + self.kf.x[2]) <= 0:self.kf.x[6] *= 0# 进行预测self.kf.predict()#卡尔曼滤波的预测次数self.age += 1#若过程中未进行更新,则将hit_streak置为0if self.time_since_update > 0:self.hit_streak=0self.time_since_update += 1#将预测结果追加到hietory中self.history.append(convert_x_to_bbox(self.kf.x))return self.history[-1]"""get_state():获取当前目标框预测的结果([x,y,s,r]转换后的[x1,y1,x2,y2])。return convert_x_to_bbox(kf.x):将候选框从中心面积的形式[x,y,s,r] 转换为 坐标的形式[x1,y1,x2,y2] 的bbox并进行返回输出。直接访问 kf.x并进行返回,所以history没有用到。"""#获取到当前的边界框的预测结果def get_state(self):"""返回当前边界框估计值:return:"""return convert_x_to_bbox(self.kf.x)# 将YOLO模型的检测框和卡尔曼滤波的跟踪框进行匹配def associate_detections_to_trackers(detections,trackers,iou_threshold=0.3):"""将检测框bbox与卡尔曼滤波器的跟踪框进行关联匹配:param detections:检测框:param trackers:跟踪框,即跟踪目标:param iou_threshold:IOU阈值:return:跟踪成功目标的矩阵:matchs新增目标的矩阵:unmatched_detections跟踪失败即离开画面的目标矩阵:unmatched_trackers"""#跟踪/检测为0时:直接构造返回结果if (len(trackers) == 0) or (len(detections) == 0):return np.empty((0, 2), dtype=int), np.arange(len(detections)), np.empty((0, 5), dtype=int)# 跟踪/检测不为0时:# iou 不支持数组计算,故IOU 逐个进行交并比计算,构造矩阵scipy.linear_assignment进行匹配iou_matrix = np.zeros((len(detections), len(trackers)), dtype=np.float32)# 遍历目标检测的bbox集合,每个检测框的标识为dfor d,det in enumerate(detections):# 遍历跟踪框(卡尔曼滤波器预测)bbox集合,每个跟踪框标识为tfor t,trk in enumerate(trackers):iou_matrix[d,t] = iou(det,trk)#通过匈牙利算法(linear_assignment)将跟踪框和检测框以[[d,t]...]的二维矩阵的形式存储在match_indices中result = linear_sum_assignment(-iou_matrix)#将匹配结果以 [[d,t]]的形式存储匹配结果matched_indices = np.array(list(zip(*result)))#记录未匹配的检测框及跟踪框#未匹配的检测框放入unmatched_detections中,表示有新的目标进入画面,要新增所要跟踪的目标序列unmatched_detections = []for d,det in enumerate(detections):if d not in matched_indices[:,0]:unmatched_detections.append(d)#未匹配的跟踪框放入unmatched_trackers中,表示目标离开之前的画面,应删除对应的跟踪器unmatched_trackers = []for t,trk in enumerate(trackers):if t not in matched_indices[:,1]:unmatched_trackers.append(t)#将匹配成功的跟踪框放入matches中进行存储matches = []for m in matched_indices:# 过滤掉IOU低的匹配,将其放入到unmatched_detections和unmatched_trackersif iou_matrix[m[0], m[1]] < iou_threshold:unmatched_detections.append(m[0])unmatched_trackers.append(m[1])else:matches.append(m.reshape(1, 2))#格式转换:初始化matchs,以np.array的形式返回if len(matches) == 0:matches = np.empty((0, 2), dtype=int)else:matches = np.concatenate(matches, axis=0)return matches, np.array(unmatched_detections),np.array(unmatched_trackers)"""
利用sort算法完成多目标追踪在这里我们主要实现了一个多目标跟踪器,管理多个卡尔曼滤波器对象,主要包括以下内容:1.初始化:最大检测数,目标未被检测的最大帧数2.目标跟踪结果的更新,即跟踪成功和失败的目标的更新该方法实现了SORT算法,输入是当前帧中所有物体的检测框的集合,包括目标的score,输出是当前帧的跟踪框集合,包括目标的跟踪的id要求是即使检测框为空,也必须对每一帧调用此方法,返回一个类似的输出数组,最后一列是目标对像的id。需要注意的是,返回的目标对象数量可能与检测框的数量不同。
"""# 1.SORT目标跟踪:
#       1.第一帧刚开始时:对第一帧所有的检测框生成对应的新跟踪框。
#       2.第二帧开始到以后所有帧:
#           上一帧成功跟踪并且保留下来的的跟踪框 在当前帧中 进行新一轮的预测新的跟踪框,
#           并且针对所预测的新跟踪框和当前帧中的检测框进行iou计算和使用匈牙利算法对该两者进行关联匹配,
#           通过上述操作后成功返回跟踪目标成功的跟踪框(即和当前帧中的目标检测框相匹配的跟踪框),
#           并且另外发现了新出现目标的检测框、跟踪目标失败的跟踪框(即目标离开了画面/两者匹配度IOU值小于iou阈值),
#           那么首先使用当前帧中的检测框对“成功关联匹配的跟踪框中的”状态向量进行更新,
#           然后对新增目标的检测框生成对应新的跟踪框,最后把跟踪目标失败的跟踪框从跟踪器链列表中移除出去。
# 2.传入的检测框dets:[检测框的左上角的x/y坐标, 检测框的右下角的x/y坐标, 检测框的预测类别的概率值]
# 3.返回值tracks:
#   当前帧中跟踪目标成功的跟踪框/预测框的集合,包含目标的跟踪的id(也即该跟踪框(卡尔曼滤波实例对象)是创建出来的第几个)
#   第一种返回值方案:[[左上角的x坐标, 左上角的x坐标y坐标, 右下角的x坐标, 右下角的y坐标, yolo识别目标是某种物体的可信度, trk.id] ...]
#   第二种返回值方案(当前使用的为该种):[[左上角的x坐标, 左上角的x坐标y坐标, 右下角的x坐标, 右下角的y坐标, trk.id] ...]
#   d:[左上角的x坐标, 左上角的x坐标y坐标, 右下角的x坐标, 右下角的y坐标]
#   trk.id:卡尔曼滤波器的个数/目标框的个数,也即该跟踪框(卡尔曼滤波实例对象)是创建出来的第几个。
#Sort多目标跟踪 管理多个卡尔曼滤波器
class Sort(object):"""Sort 是一个多目标跟踪器的管理类,管理多个 跟踪器链中的多个 KalmanBoxTracker 卡尔曼滤波对象"""#设置Sort算法的参数def __init__(self,max_age = 1,min_hits = 3):"""初始化:设置SORT算法的关键参数:param max_age: 最大检测数:目标未被检测到的帧数,超过之后会被删除:param min_hits: 目标命中的最小次数,小于该次数update函数不返回该目标的KalmanBoxTracker卡尔曼滤波对象""""""max_age:跟踪框的最大连续跟丢帧数。如果当前跟踪框连续N帧大于最大连续跟丢帧数的话,则从跟踪器链中删除该卡尔曼滤波对象的预测框(跟踪框)。min_hits:跟踪框连续成功跟踪到目标的最小次数(目标连续命中的最小次数),也即跟踪框至少需要连续min_hits次成功跟踪到目标。trackers:卡尔曼滤波跟踪器链,存储多个 KalmanBoxTracker 卡尔曼滤波对象frame_count:当前视频经过了多少帧的计数"""# 最大检测数:目标未被检测到的帧数,超过之后会被删self.max_age = max_age# 目标连续命中的最小次数,小于该次数update函数不返回该目标的KalmanBoxTracker卡尔曼滤波对象self.min_hits=min_hits# 卡尔曼滤波跟踪器链,存储多个 KalmanBoxTracker 卡尔曼滤波对象self.trackers = []#帧计数self.frame_count = 0"""update(dets):输入dets:当前帧中yolo所检测出的所有目标的检测框的集合,包含每个目标的score以[[x1,y1,x2,y2,score],[x1,y1,x2,y2,score],...]形式输入的numpy.arrayx1、y1 代表检测框的左上角坐标;x2、y2代表检测框的右上角坐标;score代表检测框对应预测类别的概率值。输出ret:当前帧中跟踪目标成功的跟踪框/预测框的集合,包含目标的跟踪的id(也即该跟踪框(卡尔曼滤波实例对象)是创建出来的第几个)第一种返回值方案:[[左上角的x坐标, 左上角的x坐标y坐标, 右下角的x坐标, 右下角的y坐标, yolo识别目标是某种物体的可信度, trk.id] ...]第二种返回值方案(当前使用的为该种):[[左上角的x坐标, 左上角的x坐标y坐标, 右下角的x坐标, 右下角的y坐标, trk.id] ...]d:[左上角的x坐标, 左上角的x坐标y坐标, 右下角的x坐标, 右下角的y坐标]trk.id:卡尔曼滤波器的个数/目标框的个数,也即该跟踪框(卡尔曼滤波实例对象)是创建出来的第几个。注意:即使检测框为空,也必须对每一帧调用此方法,返回一个类似的输出数组,最后一列是目标对像的id。返回的目标对象数量可能与检测框的数量不同。"""#更新数值def update(self,dets):"""该方法实现了SORT算法,输入是当前帧中所有物体的检测框的集合,包括目标的score,输出是当前帧目标的跟踪框集合,包括目标的跟踪的id要求是即使检测框为空,也必须对每一帧调用此方法,返回一个类似的输出数组,最后一列是目标对像的id注意:返回的目标对象数量可能与检测框的数量不同:param dets:以[[x1,y1,x2,y2,score],[x1,y1,x2,y2,score],...]形式输入的numpy.array:return:"""""" 每经过一帧,frame_count+=1"""self.frame_count +=1"""1.trackers:上一帧中的跟踪器链(列表),保存的是上一帧中成功跟踪目标的跟踪框,也即上一帧中成功跟踪目标的KalmanBoxTracker卡尔曼滤波对象。2.trks = np.zeros((len(trackers), 5))上一帧中的跟踪器链(列表)中的所有跟踪框(卡尔曼滤波对象)在当前帧中成功进行predict预测新跟踪框后返回的值。所有新跟踪框的左上角的x坐标和y坐标、右下角的x坐标和y坐标、置信度 的一共5个值。1.因为一开始第一帧时,trackers跟踪器链(列表)仍然为空,所以此时的trks初始化如下:np.zeros((0, 5))     输出值:array([], shape=(0, 5), dtype=float64)输出值类型:<class 'numpy.ndarray'>2.np.zeros((len(trackers), 5)) 创建目的:1.用于存储上一帧中的跟踪器链中所有跟踪框(KalmanBoxTracker卡尔曼滤波对象)在当前帧中进行predict预测新跟踪框后返回的值,之所以创建的numpy数组的列数为5,是因为一个跟踪框在当前帧中进行predict预测新跟踪框后返回的值为1行5列的矩阵,返回值分别为新跟踪框的左上角的x坐标和y坐标、右下角的x坐标和y坐标、置信度 的一共5个值。2.如果是在视频的第一帧中,那么因为跟踪器链不存在任何跟踪框(KalmanBoxTracker卡尔曼滤波对象),因此np.zeros((len(trackers), 5))创建的是空列表:array([], shape=(0, 5), dtype=float64)。3.trackers:跟踪器链(列表)1.跟踪器链中存储了上一帧中成功跟踪目标并且在当前帧中的预测框(跟踪框),同时也存储了“为了当前帧中的检测框中的新增目标所创建的”新预测框(新跟踪框),但是同时不存储当前帧中预测跟踪失败的预测框(跟踪框),同时也不存储2.跟踪器链实际就是多个的卡尔曼滤波KalmanBoxTracker自定义类的实例对象组成的列表。每个目标框都有对应的一个卡尔曼滤波器(KalmanBoxTracker实例对象),KalmanBoxTracker类中的实例属性专门负责记录其对应的一个目标框中各种统计参数,并且使用类属性负责记录卡尔曼滤波器的创建个数,增加一个目标框就增加一个卡尔曼滤波器(KalmanBoxTracker实例对象)。把每个卡尔曼滤波器(KalmanBoxTracker实例对象)都存储到跟踪器链(列表)中。"""# 存储跟踪器在当前帧逐个预测轨迹位置,记录状态异常的跟踪器索引# 根据当前所有的卡尔曼跟踪器个数(即上一帧中跟踪的目标个数)创建二维数组:行号为卡尔曼滤波器的标识索引,列向量为跟踪框的位置和ID# trks = np.zeros(len(self.trackers),5)#跟踪器对当前帧的图像预测结果trks = np.zeros((len(self.trackers), 5))#跟踪器对当前帧的图像预测结果""" to_del:存储“跟踪器链中某个要删除的”KalmanBoxTracker卡尔曼滤波对象的索引 """to_del = []#存储要删除的目标框ret = []#返回的跟踪目标#遍历卡尔曼滤波器中的跟踪框"""for t, trk in enumerate(ndarray类型的trks)t:为从0到列表长度-1的索引值trk:ndarray类型的trks中每个(1, 5)形状的一维数组"""""" 遍历trks 用于存储上一帧中的跟踪器链中所有跟踪框(KalmanBoxTracker卡尔曼滤波对象)在当前帧中进行predict预测新跟踪框后返回的值 """for t,trk in enumerate(trks):""" 上一帧中的跟踪器链中所有跟踪框(KalmanBoxTracker卡尔曼滤波对象)在当前帧中进行predict预测新跟踪框 """#使用卡尔曼跟踪器t产生对应目标的跟踪框,即对目标进行预测pos = self.trackers[t].predict()[0]""" 新跟踪框的左上角的x坐标和y坐标、右下角的x坐标和y坐标、置信度 的一共5个值。trk中存储了上一帧中目标的跟踪框在当前帧中新的跟踪框的信息值。"""# 遍历完成后,trk中存储了上一帧中跟踪的目标的预测结果的跟踪框trk[:] = [pos[0],pos[1],pos[2],pos[3],0]""" 如果预测的新的跟踪框的信息(1行5列一共5个值)中包含空值的话,则将该跟踪框在跟踪器链(列表)中的索引值t放到to_del列表中。使用np.any(np.isnan(pos))即能判断这1行5列一共5个值是否包含空值。后面下一步将会根据to_del列表中保存的跟踪框的索引值到跟踪器链(列表)中将该跟踪框从其中移除出去。"""#若预测结果pos中包含空值,添加到del中if np.any(np.isnan(pos)):to_del.append(t)""" np.ma.masked_invalid(跟踪器链trks矩阵):将会对跟踪器链trks矩阵中出现了NaN或inf的某行进行生成掩码,用于屏蔽出现无效值该整行的跟踪器框。np.ma.compress_rows(包含掩码值的跟踪器链trks矩阵):将包含掩码值的整行从中进行移除出去。最终跟踪器链trks矩阵:只包含“上一帧中的跟踪器链中所有跟踪框在当前帧中成功进行predict预测”的新跟踪框。"""#trks中去除无效值的行,保存根据上一帧结果预测当前帧的内容# numpy.ma.masked_invalid 屏蔽出现无效值的数组(NaN 或 inf)# numpy.ma.compress_rows 压缩包含掩码值的2-D 数组的整行,将包含掩码值的整行去除# trks中存储了上一帧中跟踪的目标并且在当前帧中的预测跟踪框trks = np.ma.compress_rows(np.ma.masked_invalid(trks))"""1.for t in reversed(列表):1.t:列表中的元素值2.要想从List列表中删除任意索引位置的元素的话,必须不能从列表头开始遍历删除元素,必须从列表尾向列表头的方向进行遍历删除元素,因为如果从列表头开始遍历删除元素的话,便会导致后面的元素会移动补充到被删除元素的索引位置上,那么再向后进行遍历时便会出现漏遍历的元素,也即防止破坏索引,因此删除列表中元素时需要从列表尾向列表头的方向进行遍历。2.for t in reversed(to_del)1.t:列表中的元素值2.此处to_del列表中的元素值保存的是trackers跟踪器链(列表)中要删除元素的索引值,因此从to_del列表的列表尾向列表头的方向进行遍历出“trackers跟踪器链(列表)中要删除元素的”索引值。然后使用trackers.pop(t)根据trackers跟踪器链(列表)中元素的索引值t自动从列表中移除该元素。3.List pop()方法1.pop()方法语法:list.pop([index=-1])2.pop()函数用于移除列表中的一个元素(默认最后一个元素),并且返回该元素的值。3.pop(可选参数)中参数:可选参数,要移除列表元素的索引值,不能超过列表总长度,默认为 index=-1,删除最后一个列表值。4.pop()返回值:该方法返回从列表中被移除的元素对象。5.pop(要移除的列表中元素的索引值):根据列表中元素的索引值自动从列表中移除"""#删除nan的结果,逆向删除异常的跟踪器,防止破坏索引for t in reversed(to_del):"""根据to_del列表中保存的跟踪框的索引值到跟踪器链(列表)中将该跟踪框从其中移除出去。trackers:上一帧中的跟踪器链(列表),保存的是上一帧中成功跟踪目标的跟踪框,也即成功跟踪目标的KalmanBoxTracker卡尔曼滤波对象。trackers.pop(要移除的某个跟踪框的索引值):即能根据该索引值从跟踪器链(列表)中把该跟踪框移除出去"""# pop(要移除的列表中元素的索引值):根据列表中元素的索引值自动从列表中移除self.trackers.pop(t)"""matches:[[检测框的索引值, 跟踪框的索引值] [检测框的索引值, 跟踪框的索引值] 。。。]跟踪成功并且两两匹配组合的IOU值大于iou阈值的检测框和跟踪框组成的矩阵unmatched_detections:[检测框的索引值,。。。]1.新增目标的检测框在detections检测框列表中的索引位置2.两两匹配组合的IOU值小于iou阈值的检测框在detections检测框列表中的索引位置unmatched_trackers:[跟踪框的索引值,。。。]1.跟踪失败的跟踪框/预测框在trackers跟踪框列表中的索引位置2.两两匹配组合的IOU值小于iou阈值的跟踪框/预测框在trackers跟踪框列表中的索引位置1.matched:跟踪成功目标的矩阵。即前后帧都存在的目标,并且匹配成功同时大于iou阈值。2.unmatched_detections(列表):1.检测框中出现新目标,但此时预测框(跟踪框)中仍不不存在该目标,那么就需要在创建新目标对应的预测框/跟踪框(KalmanBoxTracker类的实例对象),然后把新目标对应的KalmanBoxTracker类的实例对象放到跟踪器链(列表)中。2.同时如果因为“跟踪框和检测框之间的”两两组合的匹配度IOU值小于iou阈值,则也要把目标检测框放到unmatched_detections中。3.unmatched_trackers(列表):1.当跟踪目标失败或目标离开了画面时,也即目标从检测框中消失了,就应把目标对应的跟踪框(预测框)从跟踪器链中删除。unmatched_trackers列表中保存的正是跟踪失败即离开画面的目标,但该目标对应的预测框/跟踪框(KalmanBoxTracker类的实例对象)此时仍然存在于跟踪器链(列表)中,因此就需要把该目标对应的预测框/跟踪框(KalmanBoxTracker类的实例对象)从跟踪器链(列表)中删除出去。2.同时如果因为“跟踪框和检测框之间的”两两组合的匹配度IOU值小于iou阈值,则也要把跟踪目标框放到unmatched_trackers中。"""#使用匈牙利算法:将目标检测框和卡尔曼滤波器预测的跟踪框进行匹配,分别获取跟踪成功的目标,新增的目标,离开画面的目标matched, unmatched_dets, unmatched_trks = associate_detections_to_trackers(dets, trks)"""for t, trk in enumerate(trackers列表)t:为从0到列表长度-1的索引值trk:trackers列表中每个KalmanBoxTracker卡尔曼滤波对象"""#将跟踪成功的目标更新到对应的卡尔曼滤波器for t,trk in enumerate(self.trackers):""" 1.trackers:上一帧中的跟踪器链(列表),保存的是上一帧中成功跟踪目标的跟踪框,也即成功跟踪目标的KalmanBoxTracker卡尔曼滤波对象。2.for t, trk in enumerate(trackers):遍历上一帧中的跟踪器链(列表)中从0到列表长度-1的索引值t 和 每个KalmanBoxTracker卡尔曼滤波对象trk。3.if t not in unmatched_trks:如果上一帧中的跟踪框(KalmanBoxTracker卡尔曼滤波对)的索引值不在当前帧中的unmatched_trackers(列表)中的话,即代表上一帧中的跟踪框在当前帧中成功跟踪到目标,并且代表了“上一帧中的跟踪框在当前帧中的”预测框和当前帧中的检测框的匹配度IOU值大于iou阈值。4.matched[:, 1]:获取的是跟踪框的索引值,即[[检测框的索引值, 跟踪框的索引值] 。。。]中的跟踪框的索引值。5.np.where(matched[:, 1] == t)[0]:where返回的为符合条件的“[检测框的索引值, 跟踪框的索引值]”数组在matched矩阵中的索引值,即行值。因此最后使用[0]就是从array([索引值/行值])中把索引值/行值取出来。6.matched[索引值/行值, 0]:根据索引值/行值获取出matched矩阵中的[检测框的索引值, 跟踪框的索引值],然后获取出第一列的“检测框的索引值”。7.dets[d, :]:根据检测框的索引值/行值从当前帧中的dets检测框列表获取出该检测框的所有列值,最终返回的是一个二维矩阵如下所示:第一种方案:[[左上角的x坐标, 左上角的x坐标y坐标, 右下角的x坐标, 右下角的y坐标, yolo识别目标是某种物体的可信度]]第二种方案(当前使用的为该种):[[左上角的x坐标, 左上角的x坐标y坐标, 右下角的x坐标, 右下角的y坐标]]8.dets[d, :][0]:获取出[左上角的x坐标, 左上角的x坐标y坐标, 右下角的x坐标, 右下角的y坐标]9.trk.update(检测框的5个值的列表):使用检测框进行更新状态更新向量x(状态变量x),也即使用检测框更新跟踪框。"""if t not in unmatched_trks:d = matched[np.where(matched[:, 1] == t)[0], 0]# 使用观测的边界框更新状态向量trk.update(dets[d, :][0])"""unmatched_detections(列表)保存了出现新目标的检测框的索引值,还保存了“因为跟踪框和检测框之间的两两组合的匹配度IOU值小于iou阈值的”目标检测框的索引值。dets[i, :]:根据索引值从当前帧中的检测框列表dets中获取对应的检测框,即该行的所有列值。该检测框的值为:第一种方案:[[左上角的x坐标, 左上角的x坐标y坐标, 右下角的x坐标, 右下角的y坐标, yolo识别目标是某种物体的可信度]]第二种方案(当前使用的为该种):[[左上角的x坐标, 左上角的x坐标y坐标, 右下角的x坐标, 右下角的y坐标]]KalmanBoxTracker(dets[i, :]):传入检测框进行创建该新目标对应的跟踪框KalmanBoxTracker卡尔曼滤波对象trk。每个目标框都有对应的一个卡尔曼滤波器(KalmanBoxTracker实例对象),增加一个目标框就增加一个卡尔曼滤波器(KalmanBoxTracker实例对象)。trackers.append(trk):把新增的卡尔曼滤波器(KalmanBoxTracker实例对象trk)存储到跟踪器链(列表)trackers中"""#为新增目标创建新的卡尔曼滤波器的跟踪器for i in unmatched_dets:# trk = KalmanBoxTracker(dets[i,0])trk = KalmanBoxTracker(dets[i, :])self.trackers.append(trk)# 自后向前遍历,仅返回在当前帧出现且命中周期大于self.min_hits(除非跟踪刚开始)的跟踪结果;如果未命中时间大于self.max_age则删除跟踪器。# hit_streak忽略目标初始的若干帧""" i为trackers跟踪器链(列表)长度,从列表尾向列表头的方向 每遍历trackers跟踪器链(列表)一次 即进行 i-=1 """i = len(self.trackers)""" reversed逆向遍历trackers跟踪器链(列表),目的为删除列表中的元素的同时不会造成漏遍历元素的问题 """# 逆向遍历for trk in reversed(self.trackers):""" (跟踪框)KalmanBoxTracker卡尔曼滤波对象trk.get_state():获取跟踪框所预测的在当前帧中的预测结果(已经从[x,y,s,r]转换为[x1,y1,x2,y2]) [x1,y1,x2,y2]即为[左上角的x坐标, 左上角的x坐标y坐标, 右下角的x坐标, 右下角的y坐标]。get_state()[0] 中使用[0] 是因为返回的为二维矩阵如下:   第一种方案:[[左上角的x坐标, 左上角的x坐标y坐标, 右下角的x坐标, 右下角的y坐标, yolo识别目标是某种物体的可信度]]第二种方案(当前使用的为该种):[[左上角的x坐标, 左上角的x坐标y坐标, 右下角的x坐标, 右下角的y坐标]]"""#返回当前边界框的估计值d = trk.get_state()[0]"""1.trk.time_since_update < 1:1.time_since_update:记录了该目标对应的卡尔曼滤波器中的预测框(跟踪框)进行连续预测的次数,每执行predict一次即进行time_since_update+=1。在连续预测(连续执行predict)的过程中,一旦执行update的话,time_since_update就会被重置为0。2. time_since_update < 1:该目标对应的卡尔曼滤波器一旦update更新的话该变量值便重置为0,因此要求该目标对应的卡尔曼滤波器必须执行update更新步骤。update更新代表了使用检测框来更新状态更新向量x(状态变量x)的操作,实际即代表了使用“通过yoloV3得到的并且和预测框(跟踪框)相匹配的”检测框来更新该目标对应的卡尔曼滤波器中的预测框(跟踪框)。2.trk.hit_streak >= min_hits:1.hit_streak1.连续更新的次数,每执行update一次即进行hit_streak+=1。2.在连续更新(连续执行update)的过程中,一旦开始连续执行predict两次或以上的情况下,当连续第一次执行predict时,因为time_since_update仍然为0,并不会把hit_streak重置为0,然后才会进行time_since_update+=1;当连续第二次执行predict时,因为time_since_update已经为1,那么便会把hit_streak重置为0,然后继续进行time_since_update+=1。     2.min_hits跟踪框连续成功跟踪到目标的最小次数,也即跟踪框至少需要连续min_hits次成功跟踪到目标。3.hit_streak >= min_hits跟踪框连续更新的次数hit_streak必须大于等于min_hits。而小于该min_hits次数的话update函数不返回该目标的KalmanBoxTracker卡尔曼滤波对象。3.frame_count <= min_hits:因为视频的一开始frame_count为0,而需要每经过一帧frame_count才会+=1。因此在视频的一开始前N帧中,即使frame_count 小于等于min_hits 也可以。"""# 跟踪成功目标的box与id放入ret列表中if (trk.time_since_update < 1) and (trk.hit_streak >= self.min_hits or self.frame_count <= self.min_hits):""" 1.ret:当前帧中跟踪目标成功的跟踪框/预测框的集合,包含目标的跟踪的id(也即该跟踪框(卡尔曼滤波实例对象)是创建出来的第几个)第一种返回值方案:[[左上角的x坐标, 左上角的x坐标y坐标, 右下角的x坐标, 右下角的y坐标, yolo识别目标是某种物体的可信度, trk.id] ...]第二种返回值方案(当前使用的为该种):[[左上角的x坐标, 左上角的x坐标y坐标, 右下角的x坐标, 右下角的y坐标, trk.id] ...]d:[左上角的x坐标, 左上角的x坐标y坐标, 右下角的x坐标, 右下角的y坐标]trk.id:卡尔曼滤波器的个数/目标框的个数,也即该跟踪框(卡尔曼滤波实例对象)是创建出来的第几个。2.np.concatenate((d, [trk.id + 1])).reshape(1, -1)[[左上角的x坐标, 左上角的x坐标y坐标, 右下角的x坐标, 右下角的y坐标, 该跟踪框是创建出来的第几个]]"""ret.append(np.concatenate((d, [trk.id + 1])).reshape(1, -1))  # +1 as MOT benchmark requires positive""" i为trackers跟踪器链(列表)长度,从列表尾向列表头的方向 每遍历trackers跟踪器链(列表)一次 即进行 i-=1 """i -= 1"""trk.time_since_update > max_age1.time_since_update:记录了该目标对应的卡尔曼滤波器中的预测框(跟踪框)进行连续预测的次数,每执行predict一次即进行time_since_update+=1。在连续预测(连续执行predict)的过程中,一旦执行update的话,time_since_update就会被重置为0。2.max_age:最大跟丢帧数。如果当前连续N帧大于最大跟丢帧数的话,则从跟踪器链中删除该卡尔曼滤波对象的预测框(跟踪框)。3.time_since_update > max_age:每预测一帧time_since_update就会+=1,只有预测的跟踪框跟踪到目标(即预测的跟踪框和检测框相似度匹配)才会执行update更新,那么time_since_update才会被重置为0。那么当连续time_since_update帧都没有跟踪到目标的话,即当连续time_since_update帧大于最大跟丢帧数时,那么就需要根据该跟踪失败的跟踪器框的索引把该跟踪器框从跟踪器链(列表)trackers中进行移除出去。"""# 跟踪失败或离开画面的目标从卡尔曼跟踪器中删除if trk.time_since_update > self.max_age:"""trackers:上一帧中的跟踪器链(列表),保存的是上一帧中成功跟踪目标的跟踪框,也即成功跟踪目标的KalmanBoxTracker卡尔曼滤波对象。trackers.pop(要移除的某个跟踪框的索引值):即能根据该索引值从跟踪器链(列表)中把该跟踪框移除出去"""# pop(要移除的列表中元素的索引值):根据列表中元素的索引值自动从列表中移除self.trackers.pop(i)# 返回当前画面中所有目标的box与id,以二维矩阵形式返回if len(ret) > 0:""" ret:当前帧中跟踪目标成功的跟踪框/预测框的集合,包含目标的跟踪的id(也即该跟踪框(卡尔曼滤波实例对象)是创建出来的第几个)第一种返回值方案:[[左上角的x坐标, 左上角的x坐标y坐标, 右下角的x坐标, 右下角的y坐标, yolo识别目标是某种物体的可信度, trk.id] ...]第二种返回值方案(当前使用的为该种):[[左上角的x坐标, 左上角的x坐标y坐标, 右下角的x坐标, 右下角的y坐标, trk.id] ...]d:[左上角的x坐标, 左上角的x坐标y坐标, 右下角的x坐标, 右下角的y坐标]trk.id:卡尔曼滤波器的个数/目标框的个数,也即该跟踪框(卡尔曼滤波实例对象)是创建出来的第几个。[[左上角的x坐标, 左上角的x坐标y坐标, 右下角的x坐标, 右下角的y坐标, yolo识别目标是某种物体的可信度, 该跟踪框是创建出来的第几个] [...][...]]"""return np.concatenate(ret)return np.empty((0, 5))

3.环境配置 requirements.txt

amqp==2.5.2
billiard==3.6.3.0
celery==4.4.2
certifi==2019.11.28
chardet==3.0.4
click==7.1.1
colorama==0.3.9
cycler==0.10.0
Cython==0.29.19
decorator==4.4.2
Django==2.2.10
django-redis==4.11.0
filterpy==1.4.5
fire==0.1.3
Flask==1.1.1
h5py==2.8.0
idna==2.8
imageio==2.8.0
imageio-ffmpeg==0.4.1
importlib-metadata==1.6.0
imutils==0.5.3
itsdangerous==1.1.0
Jinja2==2.11.1
Kalman==0.1.3
kiwisolver==1.1.0
kombu==4.6.8
llvmlite==0.31.0
MarkupSafe==1.1.1
matplotlib==2.2.3
moviepy==1.0.1
numba==0.39.0
numpy==1.15.4
opencv-python==3.4.3.18
pandas==0.23.4
Pillow==7.0.0
proglog==0.1.9
protobuf==3.11.3
pyparsing==2.4.6
python-dateutil==2.8.1
pytz==2019.3
redis==3.4.1
requests==2.21.0
scipy==1.1.0
six==1.14.0
sqlparse==0.3.1
tensorboardX==1.6
torch==0.4.1
torchvision==0.2.1
tqdm==4.29.1
urllib3==1.24.3
vine==1.3.0
Werkzeug==1.0.0
zipp==3.1.0

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

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

相关文章

从资源池和管理的角度理解物理内存

早就想搞一下内存问题了&#xff01;这次正趁着搞bigmemory内核&#xff0c;可以写一篇文章了。本文旨在记录&#xff0c;不包含细节&#xff0c;细节的话&#xff0c;google&#xff0c;百度均可&#xff0c;很多人已经写了不少了。我只是按照自己的理解记录一下内存的点点滴滴…

从头开始学一个android activity

一、类层次结构&#xff1a; 二、什么是Activity&#xff0c;如何理解Activity 1、 用户与应用程序的交互的接口 2、 控件的容器&#xff0c;我们要把控件摆放在这个容器中 三、如何创建一个Activity 新建一个类&#xff1a; 1、 继承Activity类 [java] view plaincopyprint…

python3 numpy中矩阵np.dot(a,b)乘法运算

python np.dot(a,b)乘法运算 首先我们知道矩阵运算是不满足交换律的&#xff0c;np.dot(a, b)与np.dot(b, a)是不一样的 另外np.dot(a,b)和a.dot(b)果是一样的 1.numpy中数组&#xff08;矩阵&#xff09;相乘np.dot(a,b)运算&#xff1a; 对于两数组a和b &#xff1a; 示例…

ML Backpropagation算法的计算过程举例

Backpropagation计算过程举例 初始权重(initialize weights)是随机产生的(如-1~1之间) 初始化可以选择均值为0&#xff0c;方差为1/n_in的正态分布&#xff0c;n_in为输入的实例个数&#xff0c;Python中可使用np.random.normal函数来初始化权重&#xff1a; np.random.normal…

Python基础知识__字符串

字符串介绍 一. 认识字符串 字符串是 Python 中最常用的数据类型。我们一般使用引号来创建字符串。创建字符串很简单&#xff0c;只要为变量分配一个值即可。 a hello world b "abcdefg" print(type(a)) print(type(b)) 注意&#xff1a;控制台显示结果为<cl…

智慧交通day03-车道线检测实现01:车道线检测概述

项目简介 汽车的日益普及在给人们带来极大便利的同时&#xff0c;也导致了拥堵的交通路况&#xff0c;以及更为频发的交通事故。而自动驾驶技术的出现可以有效的缓解了此类问题&#xff0c;减少交通事故&#xff0c;提升出行效率。 国内外检测车道线的方法主要有两类&#xf…

vsphere平台windows虚拟机克隆的小插曲(无法登陆系统)

问题&#xff1a; 1、克隆完windows虚拟化后输入法乱码。 2、开启远程的情况下远程登录输入正确的密码也无法登录。 解决&#xff1a; 1、更改管理员用户密码&#xff08;不输入原win7密码更改win7密码&#xff09;。 2、重新启用管理员。 3、重启系统 不输入当前密码改win密码…

智慧交通day03-车道线检测实现02-1:相机校正

1. 相机标定的意义 我们所处的世界是三维的&#xff0c;而照片是二维的&#xff0c;我们可以把相机认为是一个函数&#xff0c;输入量是一个场景&#xff0c;输出量是一幅灰度图。这个从三维到二维的过程的函数是不可逆的。 相机标定的一个目的是要找一个合适的数学模型&#…

把输入字符的小写转换成大写并输出

#include <stdio.h> #include <stdlib.h> #include <string.h> /******************************* *把小写字母转成大写字母 ********************************/ int main(void) { char *srcNULL;unsigned char i0;char *new_1NULL;char *new_2NULL;char *dN…

nginx服务无法启动,启动时报错1067以外终止

解决方法&#xff1a; 首先cmd命令打开&#xff0c;输入nginx -t测试配置文件是否能运行OK&#xff0c;如果报错bind() to 0.0.0.0:80 failed (10013: An attempt was made to access a …) 则说明配置文件中的端口0.0.0.0:80被占用 可使用下面两种方法解决&#xff1a; cmd输…

智慧交通day03-车道线检测实现02-2:张氏标定法+双目标定

6.张氏标定法 张氏标定法是张正友博士在1999年发表在国际顶级会议ICCV上的论文《Flexible Camera Calibration By Viewing a Plane From Unknown Orientations》中&#xff0c;提出的一种利用平面棋盘格进行相机标定的实用方法。 该方法介于传统标定法和自标定法之间&#xff…

指针的基本操作(10.1 Basic Pointer Operations)

[This section corresponds to K&R Sec. 5.1] The first things to do with pointers are to declare a pointer variable, set it to point somewhere, and finally manipulate the value that it points to. A simple pointer declaration looks like this: 对指针的第…

HTML animate()动画更改多个元素背景颜色但不同时生效问题

问题&#xff1a; 我想点击按钮时把页面中的body颜色和button中颜色【同时】改变 这个时候出现了body颜色先变化&#xff0c;而button颜色后变化&#xff0c;似乎是因为我的代码是这样有先后顺序的&#xff1a; $("html,body").animate({backgroundColor:BGC[rando…

plt.axis()用法详解

plt.axis(‘square’) 作图为正方形&#xff0c;并且x,y轴范围相同&#xff0c;即y m a x − y m i n x m a x − x m i n y_{max}-y_{min} x_{max}-x_{min}y plt.axis(‘equal’) x,y轴刻度等长 plt.axis(‘off’) 关闭坐标轴 官网上也贴出了其他的一些选项 plt.axis([a,…

struts2学习笔记之十一:struts2的类型转换器

Struts2的类型转换器如何实现Struts2的类型转换器&#xff1f;* 继承StrutsTypeConverter* 覆盖convertFromString和convertToString注册类型转换器* 局部类型转换器&#xff0c;局部类型转换器只对当前Action起作用&#xff0c;需要提供如下配置文件&#xff1a;MyActionName-…

【Pytorch神经网络基础理论篇】 01 从零开始介绍深度学习算法和代码实现

同学你好&#xff01;本文章于2021年末编写&#xff0c;已与实际存在较大的偏差&#xff01; 故在2022年末对本系列进行填充与更新&#xff0c;欢迎大家订阅最新的专栏&#xff0c;获取基于Pytorch1.10版本的理论代码(2023版)实现&#xff0c; Pytorch深度学习理论篇(2023版)…

【Pytorch神经网络基础理论篇】 02 pytorch环境的安装

同学你好&#xff01;本文章于2021年末编写&#xff0c;已与实际存在较大的偏差&#xff01; 故在2022年末对本系列进行填充与更新&#xff0c;欢迎大家订阅最新的专栏&#xff0c;获取基于Pytorch1.10版本的理论代码(2023版)实现&#xff0c; Pytorch深度学习理论篇(2023版)…

JavaScript (If...Else和Switch和循环遍历) 语句以及常用消息框

If...Else 语句 JavaScript中if...else语句和Java中的语法和使用方法是一样的。 只是在JavaScript中要使用小写字母。使用大写的 IF 会出错&#xff01; 至于if...else...else if...和Java中的都是一样的。 例&#xff1a; var d new Date() var time d.getHours()if (time&l…

numpy中两个array数值比较,在IDE中显示完全相同,但是bool判断两个array是否相等却返回False

numpy踩坑&#xff1a;两个array中数值分别比较&#xff0c;在IDE显示相同&#xff0c;但是bool判断两个array是否相等值却是False 在numpy数据比较时&#xff0c;在IDE显示完全相同但是比较是否相等却有时候返回False&#xff1f; 原因出在了精度显示上面&#xff0c;在代码开…

C两个小例子(数组与指针)

大端模式 所谓的大端模式&#xff0c;是指数据的高位&#xff0c;保存在内存的低地址中&#xff0c;而数据的低位&#xff0c;保存在内存的高地址中&#xff0c;这样的存储模式有点儿类似于把数据当作字符串顺序处理&#xff1a;地址由小向大增加&#xff0c;而数据从高位往低位…