基于Yolov5+Deepsort+Pyqt的单目标跟踪器设计detect.py:

opt = parser.parse_args()

---解析命令行参数的代码

---运行结果:

---解析:

  • parser = argparse.ArgumentParser():parser是通过argparse.ArgumentParser()创建的一个命令行参数解析器对象。
  • .parse_args():ArgumentParser()解析命令行传入的参数,将命令行输入的参数转换为一个namespace对象。
  • opt = parser.parse_args():将解析后的参数存储在变量opt中,可以通过print(opt.weights)等访问。如果没有传递函数,parser.add_argument('--demo', action='store_true', help=''),运行print(opt.demo)结果为False。
  • parser.add_argument('--img-size', type=int, default=640, help='inference size (pixels)')命令行参数为img-size,但是运行会被转化为img_size,访问时要用print(opt.img_size)

torch.no_grad()

  • with torch.no_grad():使用pytorch的no_grad上下文管理器,禁用梯度计算,减少内存占用。

webcam

  • webcam = source.isnumeric() or source.endswith('.txt') or source.lower().startswith(('rtsp://', 'rtmp://', 'http://'))
    ------通过逻辑判断确定输入源 source 是否为以下类型之一:
    ------摄像头设备:source.isnumeric() 为 True 表示摄像头索引(如 0 表示默认摄像头)。
    ------视频文件列表:source.endswith('.txt') 为 True 表示输入是一个文本文件,内含多个视频/图像路径(每行一个路径)。
    ------网络视频流:source.lower().startswith(...) 检查是否以流媒体协议(如 rtsp://、rtmp://、http://)开头。
  • .isnumeric判断source是否由数字构成,返回布尔值True或者False。

save_dir

  • save_dir = Path(increment_path(Path(opt.project) / opt.name, exist_ok=opt.exist_ok)):生成一个自动递增的、唯一的保存目录路径。
    ------Path(opt.project) / opt.name:将命令行参数 opt.project(主项目目录,如 runs/detect)和 opt.name(自定义实验名称,如 exp)拼接成路径(如 runs/detect/exp)。
    ------increment_path,处理路径冲突。如果 runs/detect/exp 已存在,则自动重命名为 runs/detect/exp2、runs/detect/exp3 等(避免覆盖)。exist_ok=opt.exist_ok:若 opt.exist_ok 为 True,则允许目录已存在(不递增)。
  • (save_dir / 'labels' if save_txt else save_dir).mkdir(parents=True, exist_ok=True)
    ------如果 save_txt 为 True:创建 save_dir/labels 子目录(用于保存检测结果的文本文件,如 YOLO 格式的 .txt 标注文件)。如果 save_txt 为 False:直接创建 save_dir 目录(仅保存图像/视频结果)。parents=True:自动创建父目录(如 runs/detect/exp 不存在时会一并创建)。
    exist_ok=True:目录已存在时不报错。

device

  • device = select_device(opt.device):根据用户输入的 opt.device 参数选择运行设备(CPU 或 GPU)。‘cpu':强制使用 CPU。 '0' 或 'cuda:0':使用第一块 GPU。 ''(空字符串):自动选择可用设备(优先 GPU)。
  • half = device.type != 'cpu':half=True:仅在 GPU(CUDA)上启用 FP16,可加速推理并减少显存占用。half=False:在 CPU 上强制使用 FP32(单精度),因为 CPU 对 FP16 支持不佳。

model

  • model = attempt_load(weights, map_location=device) # load FP16 model 加载模型权重文件
  • imgsz = check_img_size(imgsz, s=model.stride.max()) #检查用户指定img_size是否满足模型要求,默认stride=32,imgsz为32的倍数,640或者672
  • if half: model.half() # to FP16 #根据device选择GPU可知half=True,加载FP16.

classify

  • classify = False if classify: modelc = load_classifier(name='resnet101', n=2) # initialize modelc.load_state_dict(torch.load('weights/resnet101.pt', map_location=device)['model']).to(device).eval()
    ---设置一个布尔标志 classify,默认为 False,表示不启用分类功能。
    ------若需启用分类(如同时执行检测和分类任务),需手动改为 True 或通过命令行参数控制。
    ---检查是否启用分类功能。若为 True,则执行以下操作:
    ------加载预训练的分类模型(如 ResNet101)。
    ------将分类模型与检测模型结合使用(YOLOv5 默认仅做检测,分类是可选扩展功能)。
    ---这段代码为 YOLOv5 提供了扩展能力,可在目标检测后对检测到的物体进行二次分类(例如检测车辆后进一步分类为“卡车”或“轿车”)。默认不启用,因为 YOLOv5 主要聚焦于检测任务,分类需额外数据和模型支持。
    ---分类模型与检测模型的区别:
    ------检测模型(YOLOv5):输出边界框和类别(如“狗”)。
    ------分类模型(ResNet):对裁剪后的区域进行细粒度分类(如“哈士奇” vs “金毛”)。

是否为实时流


---vid_path:用于存储当前处理的 视频文件路径(如 data/video.mp4),初始化为 None 表示尚未加载任何视频。
---vid_writer:用于存储 视频写入器对象(如 OpenCV 的 VideoWriter),初始化为 None 表示尚未创建写入器。
---当输入源是视频文件或摄像头时,代码会动态更新这两个变量:
------vid_path 记录当前视频路径(用于逻辑判断或调试信息)。
------vid_writer 初始化视频写入器,用于将检测结果保存为新的视频文件。
---if webcam: 判断输入源是否为实时流
---view_img = True 若输入是实时流,强制开启实时显示(view_img 设为 True),便于用户观察摄像头或视频流的检测结果。
---cudnn.benchmark = True 启用 CuDNN 的自动优化模式,加速固定尺寸图像的推理。
适用于实时流场景(图像尺寸通常不变),通过自动选择最优卷积算法提升 GPU 计算效率。
---dataset = LoadStreams(source, img_size=imgsz) 使用 LoadStreams 类加载实时流数据:
------source:摄像头索引(如 0)或流地址(如 rtsp://example.com)。
------img_size:图像缩放尺寸(如 640)。
------LoadStreams 会以多线程方式持续读取帧,并返回迭代器(每项包含帧数组、原始帧、路径等)
---dataset = LoadImages(source, img_size=imgsz)使用 LoadImages 类加载静态文件数据:
------source:文件路径(如 data/image.jpg)或包含多个路径的文本文件。
------img_size:图像缩放尺寸。
------LoadImages 返回迭代器,逐个处理文件。

  • 数据加载器差异:
  • LoadStreams:专为实时流设计,支持多线程读取,适合高帧率处理。
  • LoadImages:针对文件处理,单线程顺序读取,适合批量处理图像/视频。

name、colors

  • names = model.module.names if hasattr(model, 'module') else model.names
    names根据weights权重文件变。
  • colors = [[random.randint(0, 255) for _ in range(3)] for _ in names]

    colors随机分配,从0到255

warmup

  • t0 = time.time()
    记录当前时间戳 t0,通常用于后续计算总耗时(如预处理+推理+后处理的总时间)。
  • img = torch.zeros((1, 3, imgsz, imgsz), device=device) # init img
    创建一个全零的张量作为虚拟输入图像。
    形状 (1, 3, imgsz, imgsz):
    1:批大小(batch size为1)。
    3:通道数(RGB图像)。
    imgsz:图像的高度和宽度(必须是模型支持的输入尺寸,如640x640)。
    device=device:将张量放置在指定设备(如 cuda:0 或 cpu)。
  • _ = model(img.half() if half else img) if device.type != 'cpu' else None # run once
    执行一次虚拟推理(预热模型)。
    img.half() if half else img:
    如果 half=True(启用半精度FP16推理),将输入转换为 torch.float16;否则保持 torch.float32。
    if device.type != 'cpu':
    仅在GPU设备上执行预热(CPU无需预热,因为无CUDA初始化开销)。
    _ = ...:
    忽略输出结果(因为仅是预热,无需处理输出)。
  • 作用:
    1、避免首次推理延迟:GPU首次执行某些操作时(如CUDA内核加载)会有额外开销。预热后,后续推理速度更稳定。
    2、半精度(FP16)适配:
    如果启用 half 模式,确保模型和输入数据精度一致。
    3、设备兼容性:
    跳过CPU的预热(因其无显著初始化延迟)。

dataset

  • for path, img, im0s, vid_cap in dataset:
    从 dataset 中逐项读取数据,每次迭代返回:
    path:图像/视频文件的路径。
    img:预处理后的图像(NumPy数组,通常为缩放后的尺寸,如640x640)。
    im0s:原始图像(未缩放,用于后续可视化)。
    vid_cap:视频捕获对象(若输入是视频)。
  • img = torch.from_numpy(img).to(device)
    将NumPy数组 img 转为PyTorch张量Tensor,并移动到指定设备(如GPU或CPU)。


  • img = img.half() if half else img.float() # uint8 to fp16/32
    根据 half 标志选择精度:
    half=True:转为半精度(torch.float16,适合GPU加速)。
    half=False:转为单精度(torch.float32,默认)
  • img /= 255.0 # 0 - 255 to 0.0 - 1.0
    将像素值从 [0, 255] 范围归一化到 [0.0, 1.0],符合模型输入要求。
  • if img.ndimension() == 3: img = img.unsqueeze(0)
    若输入张量是3维([C, H, W]),则添加批次维度变为4维([1, C, H, W]),因为模型预期输入为批处理形式。

时间戳

  • t0 = time.time()
    Python 标准库 time 中的一个函数,返回当前时间的时间戳(以秒为单位,从 Unix 纪元(1970年1月1日)开始计算的浮点数)。t0 记录了代码执行到这一行时的时刻,后续可以通过 t1 = time.time() 计算时间差(t1 - t0),从而统计某段代码的运行时间。
  • t1 = time_synchronized()
  • t2 = time_synchronized()
    time.time() 是纯 CPU 时间,不处理 GPU 同步,因此在涉及 GPU 计算(如深度学习模型推理)时可能不够准确。time_synchronized()(自定义函数)通常会先同步 GPU 操作,适合需要精确测量 GPU 加速任务的场景。

pred

  • pred = model(img, augment=opt.augment)[0]
    model:训练好的目标检测模型(如 YOLOv5)。
    img:输入的图像数据(通常是经过预处理的张量)。
    augment=opt.augment:是否启用测试时数据增强(Test-Time Augmentation, TTA),由配置参数 opt.augment 控制(例如翻转、缩放等增强操作)。模型可能返回多个输出(如检测结果、特征图等),这里通过 [0] 取第一个输出,即检测结果的预测张量(包含边界框、置信度、类别等信息)。

非极大值抑制NMS

  • pred = non_max_suppression(pred, opt.conf_thres, opt.iou_thres, classes=opt.classes, agnostic=opt.agnostic_nms)
    non_max_suppression:非极大值抑制(NMS),用于去除冗余的检测框。
    pred:模型输出的预测结果。
    opt.conf_thres:置信度阈值,低于此值的预测会被过滤。
    opt.iou_thres:交并比(IoU)阈值,用于判断框之间的重叠程度。
    classes=opt.classes:指定要保留的目标类别。
    agnostic=opt.agnostic_nms:是否进行类别无关的NMS(即跨类别抑制)。

应用分类器Apply Classifier

  • pred = apply_classifier(pred, modelc, img, im0s)
    apply_classifier:应用分类器,对检测结果进行进一步的类别细化或调整。
    pred:经过非极大值抑制(NMS)处理后的预测结果。
    modelc:分类器模型,用于对检测到的目标进行二次分类。
    img:输入图像的预处理形式(通常为归一化后的张量)。
    im0s:原始图像(未预处理的格式),可能用于可视化或辅助分类。

过程分类

  • for i, det in enumerate(pred): 遍历 pred(预测结果),i 是索引,det 是当前检测到的目标信息。
  • if webcam:判断输入是否为摄像头(webcam 模式),如果是,则处理多帧图像(batch_size >= 1)。
  • p, s, im0, frame = ...
  • 摄像头模式:
    path[i]:当前帧的路径。
    '%g: ' % i:格式化字符串,标记当前帧序号。
    im0s[i].copy():复制当前帧图像(避免修改原始数据)。
    dataset.count:帧计数器。
  • 非摄像头模式:
    path:单张图像或视频文件的路径。
    '':空字符串(无帧标记)。
    im0s:原始图像数据。
    getattr(dataset, 'frame', 0):获取 dataset 的 frame 属性(默认为 0)。

p = Path(p)

使用 Python 的 pathlib.Path 将字符串路径 p 转换为 Path 对象,以便更方便地处理文件路径操作(如拼接、解析等)。

  • save_path = str(save_dir / p.name) # img.jpg
  • txt_path = str(save_dir / 'labels' / p.stem) + ('' if dataset.mode == 'image' else f'_{frame}') # img.txt
    save_path 是保存检测结果图片的路径(如 img.jpg)。
    txt_path 是保存检测标签文本的路径(如 img.txt),如果是图像模式(dataset.mode == 'image'),文件名直接使用 p.stem;否则(如视频模式),会在文件名后追加帧号 _{frame}。
  • s += '%gx%g ' % img.shape[2:] # print string
    将图像的宽度和高度(img.shape[2:])格式化为字符串(如 "640x480"),并追加到字符串 s 中。
    %g 是 Python 的格式化占位符,自动选择较短的浮点数或整数表示。
  • gn = torch.tensor(im0.shape)[[1, 0, 1, 0]] # normalization gain whwh
    将原始图像 im0 的形状((H, W, C) 或 (H, W))转换为 PyTorch 张量,并重新排列为 [W, H, W, H]。

缩放尺寸

  • if len(det): # Rescale boxes from img_size to im0 size det[:, :4] = scale_coords(img.shape[2:], det[:, :4], im0.shape).round()
    如果检测结果 det 非空(len(det) > 0),则执行以下操作:调用 scale_coords 函数,将边界框坐标从模型输入尺寸(img.shape[2:],即 [height, width])缩放到原始图像尺寸(im0.shape)
    .round() 对缩放后的坐标进行四舍五入取整,确保边界框坐标为整数像素值。

print


遍历检测结果 det 中的每个唯一类别索引 c。
统计属于当前类别 c 的检测框数量 n。
将类别名称(从 names 列表中获取)和对应的数量格式化为字符串(如 "3 cars, "),并追加到变量 s 中。
最终 s 会包含类似 "3 cars, 2 persons, " 的统计信息。

write results


遍历检测结果:逆序处理检测结果 det,解包每个检测框的坐标 xyxy、置信度 conf 和类别 cls。
坐标转换:将 xyxy(左上右下坐标)转换为 xywh(中心点坐标+宽高)并归一化(除以 gn)。
格式组装:根据配置 opt.save_conf 决定是否在输出行中包含置信度 conf。
写入文件:以追加模式打开文件,将数据格式化为空格分隔的字符串(如 "0 0.5 0.5 0.2 0.2 0.9")并写入。
条件判断:检查是否需要保存图像(save_img)或实时显示图像(view_img)。
生成标签:组合类别名称(从 names 列表获取)和置信度(格式化为两位小数),例如 "car 0.95"。
绘制检测框:调用 plot_one_box 函数,在原始图像 im0 上绘制矩形框:
使用 xyxy 坐标指定框的位置。
使用 colors[int(cls)] 根据类别索引选择颜色。
设置框的线条粗细为 3 像素。

赞赏