• 部署并应用ByteTrack实现目标跟踪


    尽管YOLOv8已经集成了ByteTrack算法,但在这里我还是想利用ByteTrack官网的代码,自己实现目标跟踪。

    要想应用ByteTrack算法,首先就要从ByteTrack官网上下载并安装。虽然官网上介绍得很简单,只需要区区6行代码,但对于国内用户来说,要想安装ByteTrack,只要这些代码是万万不会成功的。我按照复现经典目标跟踪算法ByteTrack之路:调通第一个demo这个网站介绍的安装过程成功地实现了ByteTrack的部署。该博文介绍得很详细,我在这里就不再赘述了。下面我详细介绍如何应用ByteTrack。

    我们首先给出ByteTrack的核心关键代码。

    导入ByteTrack:

    1. import sys
    2. sys.path.append(f"D:/ByteTrack")
    3. from yolox.tracker.byte_tracker import BYTETracker

    D:\ByteTrack为下载ByteTrack时,其所在的目录。

    下面设置ByteTrack的参数:

    1. class BYTETrackerArgs:
    2. track_thresh: float = 0.25
    3. track_buffer: int = 30
    4. match_thresh: float = 0.8
    5. aspect_ratio_thresh: float = 3.0
    6. min_box_area: float = 1.0
    7. mot20: bool = False

    track_thresh表示跟踪置信阈值。简单地说,该值越大,被赋予目标跟踪ID的数量越少,也就意味着系统会把不太确定的轨迹抛弃掉。默认值为0.5。

    track_buffer用于保留丢失轨迹的帧数。对于没有出现的ID,最多保留该值的帧数。默认值为30。

    match_thresh表示跟踪匹配阈值。该值越大,目标与轨迹越容易匹配上。默认值为0.8。

    aspect_ratio_thresh表示目标边框长宽之比的阈值。目标长宽之比大于该值时会把该目标滤除掉,这是因为长宽比过大时,显然它不会是任何物体。默认值为1.6。

    min_box_area表示目标边框的面积阈值。目标面积小于该值时会把该目标滤除掉。默认值为10。

    mot20表示是否使用mot20数据集进行测试。默认值为False。

    实例化ByteTrack,并带入参数:

    byte_tracker = BYTETracker(BYTETrackerArgs(), frame_rate=fps)

    frame_rate表示视频每秒传输的帧数。默认值为30。

    得到目标ID:

    tracks = byte_tracker.update(outputs, img_info=frame.shape, img_size=frame.shape)

    outputs表示目标检测器的输出,ByteTrack需要先进行目标检测,然后才能利用ByteTrack算法实现跟踪,outputs应为二维数组,第一维表示目标,第二维表示该目标的信息,其前四个元素为目标边框的左上角和右下角的坐标,第5个元素为该目标的得分值,一般我们可以为该值赋予目标的置信值。

    img_info表示输入视频图像的尺寸。

    img_size表示输出图像的尺寸,如果不对视频图像的尺寸进行改变的话,就让该值等于img_info。

    输出tracks即为目标跟踪的结果,我们先用print(tracks)看看它的输出:

    [OT_2_(1-28), OT_3_(1-28), OT_4_(1-28), OT_7_(26-28)]

    从中可以看出,我们共得到了4个目标跟踪结果,它们的ID分别为2、3、4和7,其中ID为2的目标在第1帧开始出现,28为当前帧数,即在第28帧时,我们使用了print(tracks)这个代码。

    我们再看看tracks的几个重要属性:

    1. print(tracks[0].tlbr)
    2. print(tracks[0].tlwh)
    3. print(tracks[0].track_id)
    4. print(tracks[0].score)

    输出为:

    1. [ 820.39 184.35 852.77 204.6]
    2. [ 820.39 184.35 32.382 20.246]
    3. 2
    4. 0.7806676

    tlbr表示该目标边框的左上角和右下角坐标;tlwh表示该目标边框的左上角坐标和它的长宽;track_id表示该目标的ID;score表示该目标的得分值。

    有了目标ID,我们就可以为视频添加各类信息,如为目标添加ID和类别,以及绘制目标边框。我们可以直接利用tracks完成上述操作,但这里会有几个问题:第一由tracks得到的目标边框没有由outputs得到的目标边框准确;第二tracks没有目标类别信息。因此在这里我们还是利用outputs为目标添加各类信息,它要解决的问题是目标的ID是什么。

    我们只需比较outputs和tracks的目标尺寸,完成匹配成对,就可以为outputs中的目标赋予ID。我们利用IOU算法来实现尺寸比较,为此我们编写下面函数:

    1. def iou(box: np.ndarray, boxes: np.ndarray):
    2. # 计算交集
    3. xy_max = np.minimum(boxes[:, 2:], box[2:])
    4. xy_min = np.maximum(boxes[:, :2], box[:2])
    5. inter = np.clip(xy_max-xy_min, a_min=0, a_max=np.inf)
    6. inter = inter[:, 0]*inter[:, 1]
    7. # 计算面积
    8. area_boxes = (boxes[:, 2]-boxes[:, 0])*(boxes[:, 3]-boxes[:, 1])
    9. area_box = (box[2]-box[0])*(box[3]-box[1])
    10. return inter/(area_box+area_boxes-inter)

    对于这个函数我们不做过多解释,它实现一对多的计算。下面给出它的应用:

    1. for track in tracks:
    2. box_iou = iou(track.tlbr, outputs[:,:4])
    3. maxindex = np.argmax(box_iou)
    4. newoutput = np.append(outputs[maxindex], track.track_id)
    5. print(newoutput)

    输出为:

    1. [820.86 184.48 852.67 204.75 0.78067 2 2]
    2. [766.21 212.08 808.44 246.9 0.73741 2 3]
    3. [479.06 178.3 517.84 217.07 0.68729 2 4]
    4. [508.42 147.98 529.87 165.88 0.62819 2 7]

    每行的最后一个元素就是它的ID。

    ByteTrack严重依赖于目标检测器的准确性。ByteTrack利用每个目标的得分值来计算目标跟踪,并赋予ID。我们一般都是把目标检测得到的置信值作为这个得分值传递给ByteTrack,作为其计算的依据。因此当置信值偏低,并且track_thresh偏高时,会出现tracks得到的目标少于outputs的目标,也就出现了有一些目标没有被赋予ID。

    为了减少这类问题出现,我们可以人为的为目标置信值赋予更高的值(充分信任目标检测器),然后再传给ByteTrack,即

    1. for output in outputs:
    2. output[4] = 0.95

    应用ByteTrack进行目标跟踪的关键部分我们都解释清楚了,下面就给出完整的代码,在这里,我们仍然选择YOLOv8作为目标检测器,除了YOLO易于实现外,另一个原因是它的输出与ByteTrack所要求的数据输入的格式完全相同:

    1. import numpy as np
    2. import cv2
    3. from ultralytics import YOLO
    4. import sys
    5. sys.path.append(f"D:/ByteTrack")
    6. from yolox.tracker.byte_tracker import BYTETracker
    7. class BYTETrackerArgs:
    8. track_thresh: float = 0.25
    9. track_buffer: int = 30
    10. match_thresh: float = 0.8
    11. aspect_ratio_thresh: float = 3.0
    12. min_box_area: float = 1.0
    13. mot20: bool = False
    14. model = YOLO('yolov8l.pt')
    15. cap = cv2.VideoCapture("D:/track/Highway Traffic.mp4")
    16. fps = cap.get(cv2.CAP_PROP_FPS)
    17. size = (int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)),int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))
    18. fNUMS = cap.get(cv2.CAP_PROP_FRAME_COUNT)
    19. fourcc = cv2.VideoWriter_fourcc(*'mp4v')
    20. videoWriter = cv2.VideoWriter("D:/track/mytrack.mp4", fourcc, fps, size)
    21. byte_tracker = BYTETracker(BYTETrackerArgs(),frame_rate= fps)
    22. def box_label(image, box, label='', color=(128, 128, 128), txt_color=(255, 255, 255)):
    23. p1, p2 = (int(box[0]), int(box[1])), (int(box[2]), int(box[3]))
    24. cv2.rectangle(image, p1, p2, color, thickness=1, lineType=cv2.LINE_AA)
    25. if label:
    26. w, h = cv2.getTextSize(label, 0, fontScale=2 / 3, thickness=1)[0]
    27. outside = p1[1] - h >= 3
    28. p2 = p1[0] + w, p1[1] - h - 3 if outside else p1[1] + h + 3
    29. cv2.rectangle(image, p1, p2, color, -1, cv2.LINE_AA)
    30. cv2.putText(image,
    31. label, (p1[0], p1[1] - 2 if outside else p1[1] + h + 2),
    32. 0, 2/3, txt_color, thickness=1, lineType=cv2.LINE_AA)
    33. def iou(box: np.ndarray, boxes: np.ndarray):
    34. xy_max = np.minimum(boxes[:, 2:], box[2:])
    35. xy_min = np.maximum(boxes[:, :2], box[:2])
    36. inter = np.clip(xy_max-xy_min, a_min=0, a_max=np.inf)
    37. inter = inter[:, 0]*inter[:, 1]
    38. area_boxes = (boxes[:, 2]-boxes[:, 0])*(boxes[:, 3]-boxes[:, 1])
    39. area_box = (box[2]-box[0])*(box[3]-box[1])
    40. return inter/(area_box+area_boxes-inter)
    41. while cap.isOpened():
    42. success, frame = cap.read()
    43. if success:
    44. results = model(frame,conf=0.5)
    45. outputs = results[0].boxes.data.cpu().numpy()
    46. if outputs is not None:
    47. for output in outputs:
    48. output[4] = 0.95
    49. tracks = byte_tracker.update(outputs[:,:5], img_info=frame.shape, img_size=frame.shape)
    50. for track in tracks:
    51. box_iou = iou(track.tlbr, outputs[:,:4])
    52. maxindex = np.argmax(box_iou)
    53. if outputs[maxindex, 5] == 2:
    54. box_label(frame, outputs[maxindex], '#'+str(track.track_id)+' car' , (167, 146, 11))
    55. elif outputs[maxindex, 5] == 5:
    56. box_label(frame, outputs[maxindex], '#'+str(track.track_id)+' bus', (186, 55, 2))
    57. elif outputs[maxindex, 5] == 7:
    58. box_label(frame, outputs[maxindex], '#'+str(track.track_id)+' truck', (19, 222, 24))
    59. cv2.putText(frame, "https://blog.csdn.net/zhaocj", (25, 50),
    60. cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
    61. cv2.imshow("ByteTrack", frame)
    62. videoWriter.write(frame)
    63. if cv2.waitKey(1) & 0xFF == ord("q"):
    64. break
    65. else:
    66. break
    67. cap.release()
    68. videoWriter.release()
    69. cv2.destroyAllWindows()

    结果为:

    ByteTrack

    我们也可以再看一个示例:

    people

  • 相关阅读:
    300元开放式耳机推荐哪款好用一点、最便宜的开放式耳机
    Spring配置文件
    vue中的传值
    virtio_net 与 virtio-pci 驱动关联浅析
    九大核心专题,630页内容,熬夜23天吃透,我收割了3个大厂offer,相信你们也可以的
    (黑马出品_高级篇_01)SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式
    如何在Linux上使用git远程上传至gitee托管(add-commit-push指令详解)
    STM32单片机控制直流电机实现PID闭环控制源码集锦
    【linux网络编程】阻塞、非阻塞、同步、异步
    Leetcode刷题详解——括号生成
  • 原文地址:https://blog.csdn.net/zhaocj/article/details/133497071