"目标跟踪 (Object Tracking)"是机器视觉领域中的一个重要研究领域。根据跟踪的目标数量,可以将其分为两大类:单目标跟踪 (Single Object Tracking,简称 SOT) 和多目标跟踪 (Multi Object Tracking,简称 MOT)。
多目标跟踪往往面临一些挑战,例如需要同时跟踪多个目标、目标可能频繁遮挡,这些因素使得目标跟丢成为一个常见问题。为了解决这些问题,可以借助跟踪器 DeepSORT 以及检测器 YOLO v8,从而构建一个高性能的实时多目标跟踪模型。
在深度学习领域中,目标跟踪是一项任务,旨在使用对象在空间和时间上的特征来预测它们在整个视频序列中的位置。从技术上讲,目标跟踪包括获取初始的目标检测集,为每个目标分配唯一的标识(ID),并在整个视频帧序列中跟踪它们,同时保持它们的标识不变。
目标检测:
在计算机视觉任务中,目标检测是在图像和视频(一系列的图像)中扫描和搜寻目标并给出目标所在图像的位置坐标(边界框):
在对视频进行检测时,也是把视频拆分成一帧帧来进行处理,这处理的过程中,检测所获取目标在帧与帧之间是无法进行关联的,假设,要对视频进行行人检测时,第一帧检测到了行人,第二帧也检测到同一个行人,但这两帧的这个行人目标是无法进行关联的,假设当前视频中有五个行人,目标检测只能检测到当前视频每一帧都有这个几个目标,并不能确定这几个目标中哪个是目标A,哪个是目标B,那个目标C等等。
视频示例:
行人检测
目标追踪:
从上面的示例可以看出,当使用对象检测器时,只会得到一个包含对象位置信息的输出数组。这个输出数组可能包含多个坐标,每个坐标代表一个检测到的对象的位置。然而,这个输出数组通常不提供有关对象之间的关联信息,因此在查看输出数组时,你无法知道哪个坐标对应于哪个检测框。
与此不同,目标跟踪器在处理检测结果时为每个对象分配一个唯一的标识符(ID),并且会在整个帧序列中保持该ID不变,直到该对象的生命周期结束。这意味着,无论对象在视频中如何移动或遮挡,跟踪器都能够将相同的ID与该对象关联起来,以维护对象的一致性标识。这种ID分配使得跟踪器能够区分不同的对象,并跟踪它们的轨迹,而不仅仅是提供它们的位置信息。
视频示例:
运动目标追踪
目标检测和目标跟踪的区别:
目标检测:
目标跟踪:
目标跟踪器根据不同的分类标准可以分为多种类型:
单目标跟踪(Single Object Tracking,SOT):
多目标跟踪(Multi Object Tracking,MOT):
通过检测跟踪(Detection-Based Tracking):
无检测跟踪(Detection-Free Tracking):
不同类型的目标跟踪器适用于不同的应用场景,选择适当的跟踪器取决于任务要求、对象数量和性能要求。
交通监控:
体育运动/赛事:
多摄像头监控:
智能监控和安全:
无人机和自动驾驶:
在实际应用中,结合目标检测和目标跟踪可以实现更高效的处理方式。可以使用目标检测来定期锁定目标,然后使用目标跟踪来跟踪目标的位置,这样可以降低计算成本,同时可以定期进行目标分类,从而减轻系统的负担。这种方法可以有效提高处理速度,并在实际场景中得到广泛应用。
目标检测:
特征提取:
目标匹配:
外观特征描述符:
处理复杂情况:
DeepSORT是一种计算机视觉目标跟踪算法,旨在为每个对象分配唯一的ID并跟踪它们。它是SORT(Simple Online and Realtime Tracking,简单在线实时跟踪)算法的扩展和改进版本。SORT是一种轻量级目标跟踪算法,用于处理实时视频流中的目标跟踪问题。DeepSORT引入了深度学习技术,以加强SORT的性能,并特别关注在多个帧之间跟踪目标的一致性。
SORT 是一种对象跟踪方法,其中使用卡尔曼滤波器和匈牙利算法等基本方法来跟踪对象,并声称比许多在线跟踪器更好。SORT 由以下 4 个关键组件组成:
检测:首先,在跟踪流程的第一步,目标检测器被用来检测当前帧中需要跟踪的目标对象。常用的目标检测器包括Faster R-CNN、YOLO等。
估计:在估计阶段,检测结果从当前帧传播到下一帧,使用恒速模型来估计下一帧中目标的位置。当检测结果与已知的目标相关联时,检测到的边界框信息用于更新目标的状态,包括速度分量,这是通过卡尔曼滤波器框架来实现的。
数据关联:在数据关联步骤中,目标的边界框信息与检测结果结合,从而形成一个成本矩阵,该矩阵计算每个检测与已知目标的所有预测边界框之间的交并比(IOU)距离。然后,使用匈牙利算法来优化分配,以确保正确地将检测结果与目标关联起来。这个技术有助于解决遮挡问题并保持目标的唯一身份。
管理目标ID的创建与删除:跟踪模块负责创建和销毁目标的唯一身份(ID)。如果检测结果与目标的IOU小于某个预定义的阈值(通常称为IOUmin),则不会将检测结果与目标相关联,这表示目标未被跟踪。此外,如果在连续TLost帧中没有检测到目标,跟踪将终止该目标的轨迹,其中TLost是一个可配置的参数。如果目标重新出现,跟踪将在新的身份下恢复。
SORT在目标跟踪的精度和准确性方面表现出色,但它在生成大量不断切换的ID轨道和遇到遮挡情况时表现不佳。这是因为SORT使用关联矩阵来进行数据关联,而这种方法在复杂场景下存在一些限制。
DeepSORT是一种进化的跟踪算法,它改进了数据关联的方式,引入了更好的关联度量。DeepSORT可被定义为一种跟踪算法,它不仅仅基于目标的速度和运动来进行跟踪,还结合了目标的外观特征。
为了实现这些改进,DeepSORT首先在实际跟踪之前离线训练了一个具有出色区分性的特征嵌入网络。这个网络在大规模人员重新识别数据集上进行了训练,以确保在目标跟踪的上下文中具有高度区分性的特征。
在DeepSORT中,余弦距离度量方法用于训练深度关联度量模型。根据DeepSORT的论文,余弦距离考虑了目标的外观信息,这在处理遮挡和运动估计失败的情况下尤为有用。这意味着余弦距离是一种度量方法,有助于在目标长时间遮挡或其他复杂情况下准确恢复目标的身份。
YOLOv8是一种基于图像全局信息进行预测并且它是一种端到端的目标检测系统,最初的YOLO模型由Joseph Redmon和Ali Farhadi于2015年提出,并随后进行了多次改进和迭代,产生了一系列不同版本的YOLO模型,如YOLOv2、YOLOv3、YOLOv4,YOLOv5等。这些更新和迭代旨在提高模型的性能、精度和速度,使其在实际应用中更具竞争力。
YOLOv8的核心思想是将图像划分为网格,并在每个网格单元中预测物体的边界框和类别。这种设计使得YOLO非常适合实时目标检测应用,因为它可以在较短的时间内完成目标检测任务。
关于YOLOv8目标检测的相关内容可以看之前的博客:YoloV8目标检测与实例分割——目标检测onnx模型推理。
在每一帧中,目标检测器识别并提取出边界框(bbox),这些边界框表示在当前帧中检测到的目标物体。
def detect(self,cv_src):
boxes, scores, class_ids = self.detector(cv_src)
pred_boxes = []
for i in range(len(boxes)):
x1,y1 = int(boxes[i][0]),int(boxes[i][1])
x2,y2 = int(boxes[i][2]),int(boxes[i][3])
lbl = class_names[class_ids[i]]
# print(class_ids[i])
# if lbl in ['person','sack','elec','bag','box','caron']:
# continue
pred_boxes.append((x1,y1,x2,y2,lbl,class_ids[i]))
return cv_src,pred_boxes
从这些检测到的边界框中,生成称为"detections"的目标检测结果。每个detection通常包含有关目标的信息,如边界框坐标和可信度分数。
# deep_sort.py
def update(self, bbox_xywh, confidences, ori_img):
self.height, self.width = ori_img.shape[:2]
# 提取每个bbox的feature
features = self._get_features(bbox_xywh, ori_img)
# [cx,cy,w,h] -> [x1,y1,w,h]
bbox_tlwh = self._xywh_to_tlwh(bbox_xywh)
# 过滤掉置信度小于self.min_confidence的bbox,生成detections
detections = [Detection(bbox_tlwh[i], conf, features[i]) for i,conf in enumerate(confidences) if conf > self.min_confidence]
# NMS (这里self.nms_max_overlap的值为1,即保留了所有的detections)
boxes = np.array([d.tlwh for d in detections])
scores = np.array([d.confidence for d in detections])
indices = non_max_suppression(boxes, self.nms_max_overlap, scores)
detections = [detections[i] for i in indices]
...
对于已知的跟踪对象(“tracks”),在下一帧中进行卡尔曼滤波预测,以估计其新的位置和速度。
# track.py
def predict(self, kf):
"""Propagate the state distribution to the current time step using a
Kalman filter prediction step.
Parameters
----------
kf: The Kalman filter.
"""
self.mean, self.covariance = kf.predict(self.mean, self.covariance) # 预测
self.age += 1 # 该track自出现以来的总帧数加1
self.time_since_update += 1 # 该track自最近一次更新以来的总帧数加1
这是DeepSORT中的核心步骤。DeepSORT使用匈牙利算法来将预测的tracks和当前帧的detections进行匹配。这个匹配可以采用两种级联方法:首先,通过计算马氏距离来估算预测对象与检测对象之间的关联,如果马氏距离小于指定的阈值,则将它们匹配为同一目标。其次,DeepSORT还使用外观特征余弦距离度量,通过一个重识别模型获得不同物体的特征向量,然后构建余弦距离代价函数,以计算预测对象与检测对象的相似度。这两个代价函数的结果都趋向于小,如果边界框接近且特征相似,则将它们匹配为同一目标。
# tracker.py
def _match(self, detections):
def gated_metric(racks, dets, track_indices, detection_indices):
"""
基于外观信息和马氏距离,计算卡尔曼滤波预测的tracks和当前时刻检测到的detections的代价矩阵
"""
features = np.array([dets[i].feature for i in detection_indices])
targets = np.array([tracks[i].track_id for i in track_indices]
# 基于外观信息,计算tracks和detections的余弦距离代价矩阵
cost_matrix = self.metric.distance(features, targets)
# 基于马氏距离,过滤掉代价矩阵中一些不合适的项 (将其设置为一个较大的值)
cost_matrix = linear_assignment.gate_cost_matrix(self.kf, cost_matrix, tracks,
dets, track_indices, detection_indices)
return cost_matrix
# 区分开confirmed tracks和unconfirmed tracks
confirmed_tracks = [i for i, t in enumerate(self.tracks) if t.is_confirmed()]
unconfirmed_tracks = [i for i, t in enumerate(self.tracks) if not t.is_confirmed()]
# 对confirmd tracks进行级联匹配
matches_a, unmatched_tracks_a, unmatched_detections = \
linear_assignment.matching_cascade(
gated_metric, self.metric.matching_threshold, self.max_age,
self.tracks, detections, confirmed_tracks)
# 对级联匹配中未匹配的tracks和unconfirmed tracks中time_since_update为1的tracks进行IOU匹配
iou_track_candidates = unconfirmed_tracks + [k for k in unmatched_tracks_a if
self.tracks[k].time_since_update == 1]
unmatched_tracks_a = [k for k in unmatched_tracks_a if
self.tracks[k].time_since_update != 1]
matches_b, unmatched_tracks_b, unmatched_detections = \
linear_assignment.min_cost_matching(
iou_matching.iou_cost, self.max_iou_distance, self.tracks,
detections, iou_track_candidates, unmatched_detections)
# 整合所有的匹配对和未匹配的tracks
matches = matches_a + matches_b
unmatched_tracks = list(set(unmatched_tracks_a + unmatched_tracks_b))
return matches, unmatched_tracks, unmatched_detections
# 级联匹配源码 linear_assignment.py
def matching_cascade(distance_metric, max_distance, cascade_depth, tracks, detections,
track_indices=None, detection_indices=None):
...
unmatched_detections = detection_indice
matches = []
# 由小到大依次对每个level的tracks做匹配
for level in range(cascade_depth):
# 如果没有detections,退出循环
if len(unmatched_detections) == 0:
break
# 当前level的所有tracks索引
track_indices_l = [k for k in track_indices if
tracks[k].time_since_update == 1 + level]
# 如果当前level没有track,继续
if len(track_indices_l) == 0:
continue
# 匈牙利匹配
matches_l, _, unmatched_detections = min_cost_matching(distance_metric, max_distance, tracks, detections,
track_indices_l, unmatched_detections)
matches += matches_l
unmatched_tracks = list(set(track_indices) - set(k for k, _ in matches))
return matches, unmatched_tracks, unmatched_detections
匹配后,DeepSORT使用检测到的detections来更新每个已知的跟踪对象的状态,例如位置和速度。这有助于保持跟踪对象的准确性和连续性。
def update(self, detections):
"""Perform measurement update and track management.
Parameters
----------
detections: List[deep_sort.detection.Detection]
A list of detections at the current time step.
"""
# 得到匹配对、未匹配的tracks、未匹配的dectections
matches, unmatched_tracks, unmatched_detections = self._match(detections)
# 对于每个匹配成功的track,用其对应的detection进行更新
for track_idx, detection_idx in matches:
self.tracks[track_idx].update(self.kf, detections[detection_idx])
# 对于未匹配的成功的track,将其标记为丢失
for track_idx in unmatched_tracks:
self.tracks[track_idx].mark_missed()
# 对于未匹配成功的detection,初始化为新的track
for detection_idx in unmatched_detections:
self._initiate_track(detections[detection_idx])
...
目标追踪