在这里我们主要实现了一个多目标跟踪器,管理多个卡尔曼滤波器对象,主要包括以下内容:
- 初始化:最大检测数,目标未被检测的最大帧数
- 目标跟踪结果的更新,即跟踪成功和失败的目标的更新
- 初始化
def __init__(self, max_age=1, min_hits=3): """ 初始化:设置SORT算法的关键参数 """ # 最大检测数:目标未被检测到的帧数,超过之后会被删 self.max_age = max_age # 目标命中的最小次数,小于该次数不返回 self.min_hits = min_hits # 卡尔曼跟踪器 self.trackers = [] # 帧计数 self.frame_count = 0
1、目标跟踪结果的更新:
该方法实现了SORT算法,输入是当前帧中所有物体的检测框的集合,包括目标的score,输出是当前帧标的跟踪框集合,包括目标的跟踪的id要求是即使检测框为空,也必须对每一帧调用此方法,返回一个类似的输出数组,最后一列是目标对像的id。需要注意的是,返回的目标对象数量可能与检测框的数量不同.
def update(self, dets): self.frame_count += 1 # 在当前帧逐个预测轨迹位置,记录状态异常的跟踪器索引 # 根据当前所有的卡尔曼跟踪器个数(即上一帧中跟踪的目标个数)创建二维数组:行号为卡尔曼滤波器的标识索引,列向量为跟踪框的位置和ID trks = np.zeros((len(self.trackers), 5)) # 存储跟踪器的预测 to_del = [] # 存储要删除的目标框 ret = [] # 存储要返回的追踪目标框 # 循环遍历卡尔曼跟踪器列表 for t, trk in enumerate(trks): # 使用卡尔曼跟踪器t产生对应目标的跟踪框 pos = self.trackers[t].predict()[0] # 遍历完成后,trk中存储了上一帧中跟踪的目标的预测跟踪框 trk[:] = [pos[0], pos[1], pos[2], pos[3], 0] # 如果跟踪框中包含空值则将该跟踪框添加到要删除的列表中 if np.any(np.isnan(pos)): to_del.append(t) # numpy.ma.masked_invalid 屏蔽出现无效值的数组(NaN 或 inf) # numpy.ma.compress_rows 压缩包含掩码值的2-D 数组的整行,将包含掩码值的整行去除 # trks中存储了上一帧中跟踪的目标并且在当前帧中的预测跟踪框 trks = np.ma.compress_rows(np.ma.masked_invalid(trks)) # 逆向删除异常的跟踪器,防止破坏索引 for t in reversed(to_del): self.trackers.pop(t) # 将目标检测框与卡尔曼滤波器预测的跟踪框关联获取跟踪成功的目标,新增的目标,离开画面的目标 matched, unmatched_dets, unmatched_trks = associate_detections_to_trackers(dets, trks) # 将跟踪成功的目标框更新到对应的卡尔曼滤波器 for t, trk in enumerate(self.trackers): if t not in unmatched_trks: d = matched[np.where(matched[:, 1] == t)[0], 0] # 使用观测的边界框更新状态向量 trk.update(dets[d, :][0]) # 为新增的目标创建新的卡尔曼滤波器对象进行跟踪 for i in unmatched_dets: trk = KalmanBoxTracker(dets[i, :]) self.trackers.append(trk) # 自后向前遍历,仅返回在当前帧出现且命中周期大于self.min_hits(除非跟踪刚开始)的跟踪结果;如果未命中时间大于self.max_age则删除跟踪器。 # hit_streak忽略目标初始的若干帧 i = len(self.trackers) for trk in reversed(self.trackers): # 返回当前边界框的估计值 d = trk.get_state()[0] # 跟踪成功目标的box与id放入ret列表中 if (trk.time_since_update < 1) and (trk.hit_streak >= self.min_hits or self.frame_count <= self.min_hits): ret.append(np.concatenate((d, [trk.id + 1])).reshape(1, -1)) # +1 as MOT benchmark requires positive i -= 1 # 跟踪失败或离开画面的目标从卡尔曼跟踪器中删除 if trk.time_since_update > self.max_age: self.trackers.pop(i) # 返回当前画面中所有目标的box与id,以二维矩阵形式返回 if len(ret) > 0: return np.concatenate(ret) return np.empty((0, 5))
我们将上述两个方法封装在一个类中。
总结
了解sort进行多目标跟踪的实现
功能实现
# 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:当前视频经过了多少帧的计数 """ # 最大检测数:目标未被检测到的帧数,超过之后会被删 set.max_age = max_age # 目标连续命中的最小次数,小于该次数update函数不返回该目标的KalmanBoxTracker卡尔曼滤波对象 self.min_hits=min_hits # 卡尔曼滤波跟踪器链,存储多个 KalmanBoxTracker 卡尔曼滤波对象 self.trackers = [] #帧计数 self.frane_count = 0 """ update(dets): 输入dets: 当前帧中yolo所检测出的所有目标的检测框的集合,包含每个目标的score 以[[x1,y1,x2,y2,score],[x1,y1,x2,y2,score],...]形式输入的numpy.array x1、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.frane_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)#跟踪器对当前帧的图像预测结果 """ 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,unmatche_trkes = associate_detection_to_tracker(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 unmatche_trkes: 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]) 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_streak 1.连续更新的次数,每执行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_age 1.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))