点击上方“小白学视觉”,选择加"星标"或“置顶

重磅干货,第一时间送达

导 读

本文主要介绍如何使用Yolo-V5 + DeepSORT实现多目标检测与跟踪。(公众号:OpenCV与AI深度学习)

fbb10bcc8f3f1e80ea63c1987f829894.gif

背景介绍

    目标跟踪是一种利用检测到对象的空间和时间特征在整个视频帧中跟踪检测到对象的方法。本文中,我们将与YOLOv5一起实现一种最流行的跟踪算法DeepSORT,并使用MOTA和其他指标在MOT17数据集上进行测试。

目标跟踪简介

    深度学习中的跟踪是使用对象的空间和时间特征预测整个视频中对象位置的任务。从技术上讲,跟踪是获取初始检测集,分配唯一的 id,并在整个视频源的帧中跟踪它们,同时保持分配的 id。目标跟踪通常可以分为两步:

    • 目标定位检测模块:

    该模块负责使用一些对象检测器(如 YOLOv4、CenterNet 等)检测和定位画面中的对象。

    • 运动预测器:

    该模块负责使用其过去的信息预测对象的未来运动

【1】目标跟踪的必要性

    您可能会疑惑,为什么我们需要对象跟踪?为什么我们不能只使用物体检测?需要目标跟踪的原因很多,例如:

    • 对象检测失败时的跟踪:

      在许多情况下,对象检测器可能会失败。但是如果我们有一个对象跟踪器,它仍然能够预测帧中的对象。例如,考虑一个视频,其中一辆摩托车穿过树林,我们应用检测器来检测摩托车。这是在这种情况下会发生的情况,每当自行车被树遮挡或重叠时,检测器就会失效。但是,如果我们有一个跟踪器,我们仍然能够预测和跟踪摩托车。

    • ID 分配:

      在使用检测器时,它只显示对象的位置,如果我们只查看输出数组,我们将不知道哪个坐标属于哪个框。另一方面,跟踪器为其跟踪的每个对象分配一个 ID,并保持该 ID 直到该对象在该帧中的生命周期。

    • 实时预测:

      跟踪器非常快,通常比检测器快。由于这个特性,跟踪器可以在实时场景中使用,并且在现实世界中有很多应用。

【2】目标跟踪的应用

    目标跟踪可以有许多实际应用:

    • 交通监控

      跟踪器可用于监控交通和跟踪道路上的车辆。它们可用于判断交通、检测违规行为等等。

    • 体育运动/赛事:

      跟踪器也可用于体育运动,如球跟踪或球员跟踪。这反过来又可以用来检测犯规、比赛中的得分手等等。

    • 多摄像头监控:

    • 在跟踪中,可以应用多摄像头监控。在这方面,核心思想是重新识别。如果一个人在一个带有 id 的摄像机中被跟踪,并且该人走出画面并在另一台摄像机中返回。然后该人将保留他们以前的相同身份。此应用程序可以帮助重新识别重新出现在不同相机中的对象,并可用于入侵检测。

edd7fb62ec1ea66c7f3b5461c0d0f389.png

【3】目标跟踪的类型

    跟踪器可以根据许多类别进行分类,例如跟踪方法或要跟踪的对象数量。下面我们将通过一些示例来了解不同的跟踪器类型。

    • 单目标跟踪

      即使帧中存在许多其他对象,这些类型的跟踪器也仅跟踪单个对象。它们首先在第一帧中初始化对象的位置,然后在整个帧序列中跟踪它。这些类型的跟踪方法非常快。其中一些是使用传统计算机视觉构建的 CSRT、KCF 等等。然而,现在证明基于深度衰减的跟踪器比传统跟踪器准确得多。例如,GOTURN 和 SiamRPN 是基于深度学习的单目标跟踪器的示例。

    • 多目标跟踪:

      这些类型的跟踪器可以跟踪帧中存在的多个对象。与传统跟踪器不同,多个对象跟踪器或 MOT 是在大量数据上训练的。因此,它们被证明更准确,因为它们可以同时跟踪多个对象甚至不同类别的对象,同时保持高速。其中一些算法包括 DeepSORT、JDE 和 CenterTrack,它们是非常强大的算法,可以处理跟踪器面临的大部分挑战。

    • 通过检测跟踪

    • 目标检测器检测帧中的对象,然后跨帧执行数据关联以生成轨迹从而跟踪对象的跟踪算法类型。这些类型的算法有助于跟踪多个对象并跟踪框架中引入的新对象。最重要的是,即使对象检测失败,它们也有助于跟踪对象。

    • 无检测跟踪

    • 跟踪算法的类型,其中手动初始化对象的坐标,然后在进一步的帧中跟踪对象。如前所述,这种类型主要用于传统的计算机视觉算法。

DeepSORT简介

    DeepSORT 是一种计算机视觉跟踪算法,用于在为每个对象分配 ID 的同时跟踪对象。DeepSORT 是 SORT(简单在线实时跟踪)算法的扩展。DeepSORT 将深度学习引入到 SORT 算法中,通过添加外观描述符来减少身份切换,从而提高跟踪效率。要了解 DeepSORT,首先让我们看看 SORT 算法是如何工作的。

0b59fcebf0b448d253d8459dd2449493.png

【1】简单的在线实时跟踪(SORT)

    SORT 是一种对象跟踪方法,其中使用卡尔曼滤波器和匈牙利算法等基本方法来跟踪对象,并声称比许多在线跟踪器更好。SORT 由以下 4 个关键组件组成:

    • 检测

      这是跟踪模块的第一步。在此步骤中,对象检测器检测帧中要跟踪的对象。然后将这些检测传递到下一步。FrRCNN、YOLO 等检测器是最常用的。

    • 估计:

      在这一步中,我们将检测从当前帧传播到下一帧,使用恒速模型估计下一帧中目标的位置。当检测与目标相关联时,检测到的边界框用于更新目标状态,其中速度分量通过卡尔曼滤波器框架得到最优解。

    • 数据关联

      我们现在有了目标边界框和检测到的边界框。因此,成本矩阵被计算为每个检测与来自现有目标的所有预测边界框之间的交并比 (IOU) 距离。分配是使用匈牙利算法优化解决的。如果检测和目标的 IOU 小于称为 IOUmin 的某个阈值,则该分配被拒绝。该技术解决了遮挡问题并有助于维护 ID。

    • 跟踪目标ID的创建与删除:

      该模块负责ID的创建和删除。根据 IOUmin 创建和销毁唯一身份。如果检测和目标的重叠小于IOUmin,则表示未跟踪目标。如果没有为 TLost 帧检测到轨道,则会终止轨道,您可以指定 TLost 的帧数。如果一个对象重新出现,跟踪将隐含地在一个新的身份下恢复。

    SORT 算法做目标跟踪非常成功,可以击败许多最先进算法 。目标检测器为我们提供检测,卡尔曼滤波器为我们提供跟踪,匈牙利算法执行数据关联。那么,为什么我们还需要 DeepSORT? 

【2】深度排序

cff241584243d5cebe022e431884befe.png    SORT 在跟踪精度和准确度方面表现非常出色。但是 SORT 返回具有大量 ID 开关的轨道,并且在遮挡的情况下失败。这是因为使用了关联矩阵。            DeepSORT 使用结合了运动和外观描述符的更好的关联度量。DeepSORT 可以定义为跟踪算法,它不仅基于对象的速度和运动,而且还基于对象的外观来跟踪对象。

    出于上述目的,在实施跟踪之前离线训练一个具有良好区分性的特征嵌入。该网络在大规模人员重新识别数据集上进行训练,使其适用于跟踪上下文。在 DeepSORT余弦度量学习方法中训练深度关联度量模型。根据 DeepSORT 的论文,“余弦距离考虑了外观信息,当运动的判别力较低时,这对于在长期遮挡后恢复身份特别有用。” 这意味着余弦距离是一种度量,可帮助模型在长期遮挡和运动估计失败的情况下恢复身份。使用这些简单的东西可以使跟踪器更加强大和准确。

【3】DeepSORT实现

    DeepSORT 可以用于各种现实生活中的应用程序,其中之一就是体育运动。在本节中,我们将在足球和 100m短跑等运动中实现 DeepSORT。与 DeepSORT一起,YOLOv5 将用作检测器来检测所需的对象。该代码是在 Tesla T4 GPU 上的 Google Colab 上实现的。 

  • YOLOv5实现

    首先,我们将克隆 YOLOv5 官方存储库以访问功能和预训练的权重。

    !mkdir ./yolov5_deepsort
    !git clone https://github.com/ultralytics/yolov5.git

    安装requirements.

    %cd ./yolov5
    !pip install -r requirements.txt

    现在我们已经准备好 YOLOv5,让我们将DeepSORT与它集成。

  • 集成DeepSORT

    同样,我们将克隆 DeepSORT 的官方实现以访问其代码和功能。

    !git clone https://github.com/nwojke/deep_sort.git

    最后,一切就绪!但是 DeepSORT 将如何与检测器集成呢?YOLOv5detect.py file负责推理。我们将使用该detect.py文件并将使用它的 DeepSORT 功能添加到新文件中detect_track.py.

%%writefile detect_track.py
# YOLOv5 🚀 by Ultralytics, GPL-3.0 license.
"""
Run inference on images, videos, directories, streams, etc.


Usage - sources:
    $ python path/to/detect.py --weights yolov5s.pt --source 0             
 # webcam
                                                           
 img.jpg      # image
                                                           
 vid.mp4       # video
                                                            
path/         # directory
                                                            
path/*.jpg    # glob
                                                            
'https://youtu.be/Zgi9g1ksQHc'  # YouTube
                                                            
'rtsp://example.com/media.mp4'  # RTSP, RTMP, HTTP stream
"""


import argparse
import os
import sys
from pathlib import Path


import torch
import torch.backends.cudnn as cudnn


FILE = Path(__file__).resolve()
ROOT = FILE.parents[0]  # YOLOv5 root directory.
if str(ROOT) not in sys.path:
    sys.path.append(str(ROOT))  # Add ROOT to PATH.
ROOT = Path(os.path.relpath(ROOT, Path.cwd()))  # Relative.


# DeepSORT -> Importing DeepSORT.
from deep_sort.application_util import preprocessing
from deep_sort.deep_sort import nn_matching
from deep_sort.deep_sort.detection import Detection
from deep_sort.deep_sort.tracker import Tracker
from deep_sort.tools import generate_detections as gdet




from models.common import DetectMultiBackend
from utils.dataloaders import IMG_FORMATS, VID_FORMATS, LoadImages, LoadStreams
from utils.general import (LOGGER, check_file, check_img_size, check_imshow, check_requirements, colorstr, cv2,
                          increment_path, non_max_suppression, print_args, scale_coords, strip_optimizer, xyxy2xywh)
from utils.plots import Annotator, colors, save_one_box
from utils.torch_utils import select_device, time_sync




@torch.no_grad()
def run(
        weights=ROOT / 'yolov5s.pt',  # model.pt path(s)
        source=ROOT / 'data/images',  # file/dir/URL/glob, 0 for webcam
        data=ROOT / 'data/coco128.yaml',  # dataset.yaml path.
        imgsz=(640, 640),  # Inference size (height, width).
        conf_thres=0.25,  # Confidence threshold.
        iou_thres=0.45,  # NMS IOU threshold.
        max_det=1000,  # Maximum detections per image.
        device='',  # Cuda device, i.e. 0 or 0,1,2,3 or cpu.
        view_img=False,  # Show results.
        save_txt=False,  # Save results to *.txt.
        save_conf=False,  # Save confidences in --save-txt labels.
        save_crop=False,  # Save cropped prediction boxes.
        nosave=False,  # Do not save images/videos.
        classes=None,  # Filter by class: --class 0, or --class 0 2 3.
        agnostic_nms=False,  # Class-agnostic NMS.
        augment=False,  # Augmented inference.
        visualize=False,  # Visualize features.
        update=False,  # Update all models.
        project=ROOT / 'runs/detect',  # Save results to project/name.
        name='exp',  # Save results to project/name.
        exist_ok=False,  # Existing project/name ok, do not increment.
        line_thickness=3,  # Bounding box thickness (pixels).
        hide_labels=False,  # Hide labels.
        hide_conf=False,  # Hide confidences.
        half=False,  # Use FP16 half-precision inference.
        dnn=False,  # Use OpenCV DNN for ONNX inference.
):
    source = str(source)
    save_img = not nosave and not source.endswith('.txt')  # Save inference images.
    is_file = Path(source).suffix[1:] in (IMG_FORMATS + VID_FORMATS)
    is_url = source.lower().startswith(('rtsp://', 'rtmp://', 'http://', 'https://'))
    webcam = source.isnumeric() or source.endswith('.txt') or (is_url and not is_file)
    if is_url and is_file:
        source = check_file(source)  # Download.


    # DeepSORT -> Initializing tracker.
    max_cosine_distance = 0.4
    nn_budget = None
    model_filename = './model_data/mars-small128.pb'
    encoder = gdet.create_box_encoder(model_filename, batch_size=1)
    metric = nn_matching.NearestNeighborDistanceMetric("cosine", max_cosine_distance, nn_budget)
    tracker = Tracker(metric)
   
    # Directories.
    if not os.path.isdir('./runs/'):
      os.mkdir('./runs/')
    save_dir = os.path.join(os.getcwd(), "runs")
    print(save_dir)
    '''save_dir = increment_path(Path(project) / name, exist_ok=exist_ok)  # increment run
    (save_dir / 'labels' if save_txt else save_dir).mkdir(parents=True, exist_ok=True)  # make dir'''


    # Load model.
    device = select_device(device)
    model = DetectMultiBackend(weights, device=device, dnn=dnn, data=data, fp16=half)
    stride, names, pt = model.stride, model.names, model.pt
    imgsz = check_img_size(imgsz, s=stride)  # Check image size.


    # Dataloader.
    if webcam:
        view_img = check_imshow()
        cudnn.benchmark = True  # Set True to speed up constant image size inference.
        dataset = LoadStreams(source, img_size=imgsz, stride=stride, auto=pt)
        bs = len(dataset)  # batch_size.
    else:
        dataset = LoadImages(source, img_size=imgsz, stride=stride, auto=pt)
        bs = 1  # batch_size.
    vid_path, vid_writer = [None] * bs, [None] * bs


    # Run inference.
    model.warmup(imgsz=(1 if pt else bs, 3, *imgsz))  # Warmup.
    dt, seen = [0.0, 0.0, 0.0], 0
    frame_idx=0
    for path, im, im0s, vid_cap, s in dataset:
        t1 =  time_sync()
        im = torch.from_numpy(im).to(device)
        im = im.half() if model.fp16 else im.float()  # uint8 to fp16/32.        im /= 255  # 0 - 255 to 0.0 - 1.0
        if len(im.shape) == 3:
            im = im[None]  # Expand for batch dim.
        t2 = time_sync()
        dt[0] += t2 - t1


        # Inference.
        visualize = increment_path(save_dir / Path(path).stem, mkdir=True) if visualize else False
        pred = model(im, augment=augment, visualize=visualize)
        t3 = time_sync()
        dt[1] += t3 - t2


        # NMS.
        pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det)
        dt[2] += time_sync() - t3


        # Second-stage classifier (optional).
        # pred = utils.general.apply_classifier(pred, classifier_model, im, im0s)


        frame_idx=frame_idx+1
        # Process predictions.
        for i, det in enumerate(pred):  # Per image.
            seen += 1
            if webcam:  # batch_size >= 1
                p, im0, frame = path[i], im0s[i].copy(), dataset.count
                s += f'{i}: '
            else:
                p, im0, frame = path, im0s.copy(), getattr(dataset, 'frame', 0)


            p = Path(p)  # To Path.
            print("stem", p.stem)
            print("dir", save_dir)
            save_path = os.path.join(save_dir, p.name)  # im.jpg
            txt_path = os.path.join(save_dir , p.stem) # im.txt
            s += '%gx%g ' % im.shape[2:]  # Print string.
            gn = torch.tensor(im0.shape)[[1, 0, 1, 0]]  # Normalization gain whwh.
            imc = im0.copy() if save_crop else im0  # For save_crop.
            annotator = Annotator(im0, line_width=line_thickness, example=str(names))


            if len(det):
                # Rescale boxes from img_size to im0 size.
                det[:, :4] = scale_coords(im.shape[2:], det[:, :4], im0.shape).round()


                # Print results.
                for c in det[:, -1].unique():
                    n = (det[:, -1] == c).sum()  # Detections per class.
                    s += f"{n} {names[int(c)]}{'s' * (n > 1)}, "  # Add to string.


                # DeepSORT -> Extracting Bounding boxes and its confidence scores.
                bboxes = []
                scores = []
                for *boxes, conf, cls in det:
                  bbox_left = min([boxes[0].item(), boxes[2].item()])
                  bbox_top = min([boxes[1].item(), boxes[3].item()])
                  bbox_w = abs(boxes[0].item() - boxes[2].item())
                  bbox_h = abs(boxes[1].item() - boxes[3].item())
                  box = [bbox_left, bbox_top, bbox_w, bbox_h]
                  bboxes.append(box)
                  scores.append(conf.item())
               
                # DeepSORT -> Getting appearance features of the object.
                features = encoder(im0, bboxes)
                # DeepSORT -> Storing all the required info in a list.
                detections = [Detection(bbox, score, feature) for bbox, score, feature in zip(bboxes, scores, features)]


                # DeepSORT -> Predicting Tracks.
                tracker.predict()
                tracker.update(detections)
                #track_time = time.time() - prev_time


                # DeepSORT -> Plotting the tracks.
                for track in tracker.tracks:
                  if not track.is_confirmed() or track.time_since_update > 1:
                      continue


                  # DeepSORT -> Changing track bbox to top left, bottom right coordinates.
                  bbox = list(track.to_tlbr())


                  # DeepSORT -> Writing Track bounding box and ID on the frame using OpenCV.
                  txt = 'id:' + str(track.track_id)
                  (label_width,label_height), baseline = cv2.getTextSize(txt , cv2.FONT_HERSHEY_SIMPLEX,1,1)
                  top_left = tuple(map(int,[int(bbox[0]),int(bbox[1])-(label_height+baseline)]))
                  top_right = tuple(map(int,[int(bbox[0])+label_width,int(bbox[1])]))
                  org = tuple(map(int,[int(bbox[0]),int(bbox[1])-baseline]))


                  cv2.rectangle(im0, (int(bbox[0]), int(bbox[1])), (int(bbox[2]), int(bbox[3])), (255,0,0), 1)
                  cv2.rectangle(im0, top_left, top_right, (255,0,0), -1)
                  cv2.putText(im0, txt, org, cv2.FONT_HERSHEY_SIMPLEX, 1, (255,255,255), 1)


                  # DeepSORT -> Saving Track predictions into a text file.
                  save_format = '{frame},{id},{x1},{y1},{w},{h},{x},{y},{z}\n'
                  print("txt: ", txt_path, '.txt')
                  with open(txt_path + '.txt', 'a') as f:
                    line = save_format.format(frame=frame_idx, id=track.track_id, x1=int(bbox[0]), y1=int(bbox[1]), w=int(bbox[2]- bbox[0]), h=int(bbox[3]-bbox[1]), x = -1, y = -1, z = -1)
                    f.write(line)


            # Stream results.
            im0 = annotator.result()


            # Save results (image with detections and tracks).
            if save_img:
                if dataset.mode == 'image':
                    cv2.imwrite(save_path, im0)
                else:  # 'video' or 'stream'
                    if vid_path[i] != save_path:  # New video.
                        vid_path[i] = save_path
                        if isinstance(vid_writer[i], cv2.VideoWriter):
                            vid_writer[i].release()  # Release previous video writer.
                        if vid_cap:  # video
                            fps = vid_cap.get(cv2.CAP_PROP_FPS)
                            w = int(vid_cap.get(cv2.CAP_PROP_FRAME_WIDTH))
                            h = int(vid_cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
                        else:  # Stream.
                            fps, w, h = 30, im0.shape[1], im0.shape[0]
                        save_path = str(Path(save_path).with_suffix('.mp4'))  # Force *.mp4 suffix on results videos.
                        vid_writer[i] = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*'mp4v'), fps, (w, h))
                    vid_writer[i].write(im0)


        # Print time (inference-only).
        LOGGER.info(f'{s}Done. ({t3 - t2:.3f}s)')


    # Print results.
    t = tuple(x / seen * 1E3 for x in dt)  # Speeds per image.
    LOGGER.info(f'Speed: %.1fms pre-process, %.1fms inference, %.1fms NMS per image at shape {(1, 3, *imgsz)}' % t)
    if update:
        strip_optimizer(weights)  # Update model (to fix SourceChangeWarning).




def parse_opt():
    parser = argparse.ArgumentParser()
    parser.add_argument('--weights', nargs='+', type=str, default=ROOT / 'yolov5s.pt', help='model path(s)')
    parser.add_argument('--source', type=str, default=ROOT / 'data/images', help='file/dir/URL/glob, 0 for webcam')
    parser.add_argument('--data', type=str, default=ROOT / 'data/coco128.yaml', help='(optional) dataset.yaml path')
    parser.add_argument('--imgsz', '--img', '--img-size', nargs='+', type=int, default=[640], help='inference size h,w')
    parser.add_argument('--conf-thres', type=float, default=0.25, help='confidence threshold')
    parser.add_argument('--iou-thres', type=float, default=0.45, help='NMS IoU threshold')
    parser.add_argument('--max-det', type=int, default=1000, help='maximum detections per image')
    parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
    parser.add_argument('--view-img', action='store_true', help='show results')
    parser.add_argument('--save-txt', action='store_true', help='save results to *.txt')
    parser.add_argument('--save-conf', action='store_true', help='save confidences in --save-txt labels')
    parser.add_argument('--save-crop', action='store_true', help='save cropped prediction boxes')
    parser.add_argument('--nosave', action='store_true', help='do not save images/videos')
    parser.add_argument('--classes', nargs='+', type=int, help='filter by class: --classes 0, or --classes 0 2 3')
    parser.add_argument('--agnostic-nms', action='store_true', help='class-agnostic NMS')
    parser.add_argument('--augment', action='store_true', help='augmented inference')
    parser.add_argument('--visualize', action='store_true', help='visualize features')
    parser.add_argument('--update', action='store_true', help='update all models')
    parser.add_argument('--project', default=ROOT / 'runs/detect', help='save results to project/name')
    parser.add_argument('--name', default='exp', help='save results to project/name')
    parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment')
    parser.add_argument('--line-thickness', default=3, type=int, help='bounding box thickness (pixels)')
    parser.add_argument('--hide-labels', default=False, action='store_true', help='hide labels')
    parser.add_argument('--hide-conf', default=False, action='store_true', help='hide confidences')
    parser.add_argument('--half', action='store_true', help='use FP16 half-precision inference')
    parser.add_argument('--dnn', action='store_true', help='use OpenCV DNN for ONNX inference')
    opt = parser.parse_args()
    opt.imgsz *= 2 if len(opt.imgsz) == 1 else 1  # Expand.
    print_args(vars(opt))
    return opt


def main(opt):
    check_requirements(exclude=('tensorboard', 'thop'))
    run(**vars(opt))


if __name__ == "__main__":
    opt = parse_opt()
    main(opt)

    只需在检测器代码中添加几行代码,我们就集成了可供使用的 DeepSORT。根据 YOLOv5 的官方实现,将结果保存到一个名为 runs 的新文件夹中,跟踪器结果和输出视频也将保存在同一文件夹中。让我们运行这个脚本,看看它是如何执行的。

【4】推理

   如前所述,此追踪器将在运动场景中进行测试。detect_track 脚本接受许多参数,但其中一些参数需要传递。

  • --weights: 这里传递的权重名称会自动下载。我们将使用 YOLOv5m 媒体网络。

  • --img: 指定图片大小,默认大小为 640

  • --source:指定图像或视频文件、目录、网络摄像头或链接的路径。

  • --classes:指定类的索引。例如 0 代表人,32 代表足球。更多类参考yolov5/data/coco.yaml。

  • --line-thickness:指定边界框宽度。

 首先,让我们在下面的视频中进行测试。 

!python detect_track.py --weights yolov5m.pt  --img 640  --source ./football-video.mp4 --classes 0 32 --line-thickness 1

cf4cab971c45cd1748aea65f9f94964b.gif

    速度很快,每张图像的推理时间为 20.6 毫秒,大致相当于 50 FPS。甚至输出看起来也不错。输出如下所示。

    检测器的工作做得非常好。但是在球的情况下以及球员的情况下,遮挡都没有得到很好的处理。此外,还有许多 ID 开关。视频中的球员被很好地跟踪,但由于运动模糊,球甚至没有被检测到,也没有被正确跟踪。也许 DeepSORT 在密度较低的视频上会表现更好。

    让我们在另一个短跑比赛的体育视频上对其进行测试。

!python detect_track.py --weights yolov5m.pt  --img 640  --source ./sprint.mp4 --save-txt --classes 0 --line-thickness 1

    速度仍然同样快,大约 52 FPS。输出还是很准确的,ID开关非常少,但是在遮挡时无法保持。输出可以在下面查看。

45e93cda885dbc8fd9137931eda41251.gif

    因此,我们可以从这些结果中得出结论,DeepSORT 不能很好地处理遮挡并可能导致 ID 切换。 

评价

    在试验和测试新事物时,评估总是扮演着重要的角色。同样,对于 DeepSORT,我们将根据一些标准指标来判断其性能。众所周知,DeepSORT 是一种多目标跟踪算法,因此要判断其性能,我们需要特殊的指标和基准数据集。我们将使用 CLEARMOT 指标来判断我们的 DeepSORT 在 MOT17 数据集上的性能。让我们在下一节中更深入地研究这些内容。

【1】MOT挑战基准

    MOT Challenge benchmark是一个框架,它提供了大量具有挑战性的真实世界序列、准确注释和许多指标的数据集。MOT Challenge 由各种数据集组成,例如人、对象、2D、3D 等等。更具体地说,每年都会发布数据集的几种变体,例如 MOT15、MOT17 和 MOT20,用于衡量多个对象跟踪器的性能。

    • MOT15,以及过去几年提交的众多最先进的结果

    • MOT16,其中包含新的具有挑战性的视频

    • MOT17,用更精确的标签扩展了 MOT16 序列

    • MOT20,其中包含自上而下视图的视频

    对于我们的评估,我们将使用 MOT17 数据集的一个子集。

数据集格式:

    所有数据集变体都具有相同的格式,为我们提供

    • 视频文件

    • seqinfo.ini – 关于文件的信息

    • gt.txt - 跟踪的地面实况注释

    • det.txt - 检测的地面实况注释

    输出格式:

        为了评估性能,跟踪器的输出应采用特定格式,即     frame,id,x1,y1,x2,y2,1,-1,-1,-1 其中:

      • 帧:帧序号

      • id:被跟踪对象的id

      • x1, y1:左上坐标

      • x2, y2:右下坐标

    Ground Truth格式:

        我们将需要注释 zip 文件中的 gt.txt 文件进行评估。它可以从这里下载。

格式如下:Frame、ID、bbox、是否忽略、classes、occlusion

      • 帧:视频的帧数

      • ID:被跟踪对象的ID

      • bbox:物体的边界框坐标

      • 是否忽略:是否忽略对象,0表示忽略

      • classes:包含行人、汽车、静态人等类别。

      • 遮挡:显示对象是否被其他对象覆盖或切割 

【2】ClearMOT指标

    它是用于评估跟踪器在不同参数上的性能的框架。总共给出了 8 个不同的指标来评估对象检测、定位和跟踪性能。它还为我们提供了两个新颖的指标:

    • 多目标跟踪精度(MOTP)

    • 多目标跟踪精度(MOTA)

    这些指标有助于评估跟踪器的整体优势并判断其总体性能。其他措施如下:

65d1cbfb797447581cc192047dfe2d51.png

    对于人员跟踪,我们将基于 MOTA 评估我们的性能,它告诉我们检测、未命中和 ID 切换的性能。跟踪器的准确度,MOTA(多目标跟踪准确度)计算公式为:

c9691a46bd3fcc7225285116bbcce491.png

    其中 FN 是误报的数量,FP 是误报的数量,IDS 是时间 t 的身份转换数量,GT 是基本事实。MOTA 也可以是负面的。

【3】性能评估

    如前所述,MOT17 将用于测试,您可以从此处下载视频序列。让我们一个一个地运行序列。

86e6e44a56f9d366d89dd0998370fcc3.png

    为了评估性能,我们将通过克隆来引用以下存储库。

!git clone https://github.com/shenh10/mot_evaluation.git
%cd ./mot_evaluation

    视频分辨率为 960×540,注释分辨率为 1920×1080。因此,需要根据视频分辨率修改注释。您可以从此处直接下载调整大小的地面实况注释。 

    我们将使用evaluate_tracking.py下面的文件mot_evaluation来评估结果。该脚本采用三个参数,如下所示:

  • --seqmap: 这里,指定要进行评估的视频文件名。

  • --track:指定跟踪结果文件夹的路径。

  • --gt:指定ground truth文件路径。

!python evaluate_tracking.py --seqmap './MOT17/videos' --track './yolov5/runs/' --gt './label_960x540'

    这给出了所有三个视频的准确性,并给出了所有视频的平均结果。这是结果。

040985df6e4cd5e134deeeb4b8cd70b6.png

结论

    DeepSORT 在视觉上的表现相当不错。然而,这些指标显示的结果并不那么好。DeepSORT 有许多缺点,例如 ID 切换、不良遮挡处理、运动模糊等等。我们得到的平均准确率是 28.6,非常低。但是,这里好的一件事是速度。所有的事情都可以通过使用最新的算法来解决。FairMOT、CentreTrack 等算法非常先进,可以显着减少 ID 切换并很好地处理遮挡。因此,请多尝试 DeepSORT 一段时间。尝试不同的场景,使用 YOLOv5 模型的其他变体或在自定义对象检测器上使用 DeepSORT。

参考链接:

https://learnopencv.com/understanding-multiple-object-tracking-using-deepsort/

好消息!

小白学视觉知识星球

开始面向外开放啦👇👇👇

 
 

7fa2938ab339b31f5c14456d1b50101b.jpeg

下载1:OpenCV-Contrib扩展模块中文版教程

在「小白学视觉」公众号后台回复:扩展模块中文教程,即可下载全网第一份OpenCV扩展模块教程中文版,涵盖扩展模块安装、SFM算法、立体视觉、目标跟踪、生物视觉、超分辨率处理等二十多章内容。


下载2:Python视觉实战项目52讲
在「小白学视觉」公众号后台回复:Python视觉实战项目,即可下载包括图像分割、口罩检测、车道线检测、车辆计数、添加眼线、车牌识别、字符识别、情绪检测、文本内容提取、面部识别等31个视觉实战项目,助力快速学校计算机视觉。


下载3:OpenCV实战项目20讲
在「小白学视觉」公众号后台回复:OpenCV实战项目20讲,即可下载含有20个基于OpenCV实现20个实战项目,实现OpenCV学习进阶。


交流群

欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计算摄影、检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐