【YOLOv3】源码(detect.py)

概述

该文件类似于实时监控与检测团队,负责在建筑项目中实时监控施工进展、检测潜在问题并记录和报告结果

主要模块

  • 参数解析与初始化
    • 功能:解析命令行参数,设置检测配置
    • 实时监控团队制定详细的监控计划和资源配置
  • 模型加载与配置
    • 功能:加载模型和配置文件,配置设备,设置检测工具参数
    • 实时监控团队选择和配置检测工具,确保检测过程高效和准确
  • 数据加载与源解析
    • 功能:根据输入源加载数据,配置数据加载器,进行设备优化和预热
    • 实时监控团队选择不同的监控源,配置相应的检测流程,确保数据的顺利输入和处理
  • 推理循环
    • 功能:对每张图像或视频帧进行推理,应用NMS,处理和记录检测结果,保存和可视化检测结果
    • 实时监控团队对每个监控点或区域进行实际检测和记录,确保施工过程中的每个部分符合设计标准
  • 结果处理与保存​​​​​​​
    • 功能:保存检测结果到文本文件,绘制和保存边界框,裁剪并保存检测到的目标区域
    • 实时监控团队记录检测到的问题,标记问题位置,并根据需要保存详细的检测图片,便于后续分析和修复

主要模块

参数解析与初始化

与train.py文件类似,详细参考代码注释

def parse_opt():
    """
    解析命令行参数,设置检测相关的所有参数。
    """
    parser = argparse.ArgumentParser()

    # ----------------------------- 模型相关参数 -----------------------------------
    parser.add_argument('--weights', nargs='+', type=str, default=ROOT / 'weight/yolov3.pt',
                        help='模型路径,可以是单个路径或多个路径列表')  # 权重文件路径
    parser.add_argument('--source', type=str, default=ROOT / 'data/images',
                        help='输入数据来源:可以是文件、文件夹、URL 或摄像头 (0 表示摄像头)')  # 输入数据路径
    parser.add_argument('--imgsz', '--img', '--img-size', nargs='+', type=int, default=[416],
                        help='推理时的图片尺寸(可以是单个数值,也可以是 [h, w] 格式)')  # 图片输入尺寸

    # ----------------------------- 推理相关参数 -----------------------------------
    parser.add_argument('--conf-thres', type=float, default=0.6, help='置信度阈值')  # 检测框置信度阈值
    parser.add_argument('--iou-thres', type=float, default=0.5, help='非极大值抑制(NMS)的 IoU 阈值')  # NMS 阈值
    parser.add_argument('--max-det', type=int, default=1000, help='每张图像的最大检测数量')  # 最大检测框数量
    parser.add_argument('--device', default='', help='指定设备,例如 "0" 或 "0,1,2,3" 或 "cpu"')  # 设备选择

    # ----------------------------- 结果相关参数 -----------------------------------
    parser.add_argument('--view-img', action='store_true', default=True, help='显示检测结果')  # 是否显示结果
    parser.add_argument('--save-txt', action='store_true', default=True, help='将检测结果保存为 *.txt 文件')  # 保存结果为文本
    parser.add_argument('--save-conf', action='store_true', default=True, help='保存检测结果的置信度值')  # 保存置信度
    parser.add_argument('--save-crop', action='store_true', default=True, help='保存裁剪后的预测框图像')  # 保存裁剪结果
    parser.add_argument('--nosave', action='store_true', help='不保存检测的图片或视频')  # 禁止保存图片/视频
    parser.add_argument('--classes', nargs='+', type=int,
                        help='根据类别进行过滤:--classes 0 或 --classes 0 2 3')  # 只检测指定的类别
    parser.add_argument('--agnostic-nms', action='store_true', help='使用类别无关的 NMS')  # 类别无关的 NMS
    parser.add_argument('--augment', action='store_true', help='使用增强推理')  # 推理时是否使用数据增强
    parser.add_argument('--visualize', action='store_true', help='可视化特征')  # 特征可视化

    # ----------------------------- 其他配置参数 -----------------------------------
    parser.add_argument('--update', action='store_true', help='更新所有模型')  # 更新所有模型(仅适用于特定流程)
    parser.add_argument('--project', default=ROOT / 'runs/detect', help='保存结果的路径')  # 检测结果保存路径
    parser.add_argument('--name', default='exp', help='保存结果的子目录名')  # 检测结果保存子目录
    parser.add_argument('--exist-ok', action='store_true', default=False,
                        help='如果目录已存在,不创建新目录')  # 不创建新目录,直接使用现有目录
    parser.add_argument('--line-thickness', default=3, type=int, help='边界框的线条粗细(像素)')  # 检测框的线条粗细
    parser.add_argument('--hide-labels', default=False, action='store_true', help='隐藏检测结果的类别标签')  # 隐藏类别标签
    parser.add_argument('--hide-conf', default=False, action='store_true', help='隐藏检测结果的置信度值')  # 隐藏置信度值

    # ----------------------------- 高级参数 -----------------------------------
    parser.add_argument('--half', action='store_true', help='使用 FP16 半精度推理')  # 是否使用半精度推理
    parser.add_argument('--dnn', action='store_true', help='使用 OpenCV 的 DNN 模块进行 ONNX 推理')  # 是否使用 OpenCV 推理

    # 解析参数
    opt = parser.parse_args()

    # 如果输入图像尺寸只有一个数值,则自动扩展为 [h, w] 格式
    opt.imgsz *= 2 if len(opt.imgsz) == 1 else 1

    # 打印解析后的参数
    print_args(FILE.stem, opt)

    return opt

模型加载与配置

主要功能则是加载模型权重和配置文件,设置模型参数,配置设备(CPU/GPU),初始化推理数据。类似于实时监控与检测团队选择和配置检测工具、准备检测资料

位于YOLOv3流程中的前置准备阶段

主要流程总结

  •  模型加载:加载 YOLOv3 模型的权重、设置设备和精度
  • 数据加载:根据输入源类型(摄像头或文件)加载数据
  • 模型预热:在开始推理之前运行一次“空推理”以预热模型,主要是为了让后面的推理速度加快
# 载入模型和模型参数并调整模型 

# 加载模型
device = select_device(device)  # 选择设备,支持 GPU 或 CPU
model = DetectMultiBackend(weights, device=device, dnn=dnn)  # 加载模型,支持多种后端(PyTorch、ONNX 等)
stride, names, pt, jit, onnx = model.stride, model.names, model.pt, model.jit, model.onnx  # 获取模型的相关属性
# stride: 模型的步长,用于调整输入图像尺寸;names: 类别名称列表;
# pt: 是否是 PyTorch 模型;jit: 是否是 JIT 模型;onnx: 是否是 ONNX 模型
imgsz = check_img_size(imgsz, s=stride)  # 检查并调整输入图像尺寸,使其符合模型的步长要求

# 设置半精度推理
half &= pt and device.type != 'cpu'  # 半精度仅适用于 CUDA 上的 PyTorch 模型
if pt:  # 如果模型是 PyTorch 模型
    model.model.half() if half else model.model.float()  # 将模型设置为半精度 (float16) 或全精度 (float32)

数据加载与源解析

根据输入源加载相应的数据,配置数据加载器,从而确保检测过程中数据的顺利输入和处理。这类似于实时监控与检测团队选择和配置不同的监控源,如实时摄像头、预先录制的视频或图片资料

# ===================================== 3、加载推理数据 =====================================

# 设置数据加载器
if webcam:  # 如果输入源是摄像头
    view_img = check_imshow()  # 检查当前环境是否支持图像显示(例如 GUI 环境是否可用)
    cudnn.benchmark = True  # 启用 cuDNN 的 benchmark 模式
    # 作用:当输入数据尺寸固定时,启用 benchmark 可以提升推理速度
    dataset = LoadStreams(source, img_size=imgsz, stride=stride, auto=pt and not jit)
    # `LoadStreams` 用于加载实时流数据(例如摄像头输入或视频流)。
    # 参数:
    # - `source`:数据源,可以是摄像头或视频流。
    # - `img_size`:输入图像大小。
    # - `stride`:模型步长,用于调整输入图像尺寸。
    # - `auto`:是否自动调整输入图像尺寸以匹配模型要求。
    bs = len(dataset)  # 设置批量大小为流的数量(多个摄像头或视频流的数量)
else:  # 如果输入源是文件(例如图片或视频文件)
    dataset = LoadImages(source, img_size=imgsz, stride=stride, auto=pt and not jit)
    # `LoadImages` 用于从本地文件加载数据(图片或视频文件)。
    # 参数与 `LoadStreams` 类似。
    bs = 1  # 设置批量大小为 1,因为处理文件时通常是逐帧进行

# 初始化视频路径和写入器
vid_path, vid_writer = [None] * bs, [None] * bs
# `vid_path`:存储视频文件的路径,用于保存处理后的推理结果。
# `vid_writer`:视频写入器,用于将推理结果写入视频文件。

# 如果使用 PyTorch 模型(pt=True)且设备类型不是 CPU:
if pt and device.type != 'cpu':
    # 模型预热
    model(
        torch.zeros(1, 3, *imgsz)  # 创建一个全零张量,形状为 (1, 3, img_height, img_width)
        .to(device)  # 将张量移动到指定设备(GPU 或 CPU)
        .type_as(next(model.model.parameters()))  # 确保张量的数据类型与模型参数一致(float16 或 float32)
    )  # 运行一次模型推理(空推理),预热模型
    # 预热的目的是加载必要的计算资源,并优化后续推理的性能

# 初始化变量
dt, seen = [0.0, 0.0, 0.0], 0
# `dt`:记录时间消耗的列表,其中包含三个阶段的时间:
# - dt[0]:数据加载时间
# - dt[1]:推理时间
# - dt[2]:后处理(如 NMS)时间
# `seen`:已处理的图像数量,用于统计推理进度

推理循环

主要步骤分析

  • 图像预处理:对待检测的部分进行标准化和预处理,从而确保检测工具可以准确的识别和分析
  • 预测:开始实际的检测
  • 非极大值抑制:过滤重复的检测结果,保证每一个区域只检测一次
  • 结果处理与保存
  • 可视化显示
  • 结果输出
# ===================================== 5、正式推理 =====================================

# 遍历数据集,逐帧或逐图片处理
for path, im, im0s, vid_cap, s in dataset:
    """
    path: 当前帧或图片的路径
    im: 调整尺寸并添加填充后的图片(用于推理的输入)
    im0s: 原始图片(未经过调整和填充)
    vid_cap: 如果是视频文件,为视频捕获对象;如果是图片,则为 None
    s: 输出的初始字符串信息
    """

    # 5.1 获取当前时间,用于计算处理时间
    t1 = time_sync()

    # 5.2 将图片转换为 PyTorch 张量并加载到指定设备
    im = torch.from_numpy(im).to(device)
    im = im.half() if half else im.float()  # 如果启用半精度,转换为 fp16,否则为 fp32
    im /= 255  # 归一化像素值到 [0, 1]
    if len(im.shape) == 3:  # 如果图片是 3 维(无 batch 维度)
        im = im[None]  # 添加 batch 维度,变成 [1, channels, height, width]
    t2 = time_sync()  # 获取当前时间
    dt[0] += t2 - t1  # 累计数据预处理时间

    # 如果启用了可视化,将设置保存路径并创建目录
    visualize = increment_path(save_dir / Path(path).stem, mkdir=True) if visualize else False

    # 5.3 使用模型进行前向推理
    pred = model(im, augment=augment, visualize=visualize)  # 推理预测
    t3 = time_sync()
    dt[1] += t3 - t2  # 累计推理时间

    # 5.4 非极大值抑制 (NMS)
    pred = non_max_suppression(
        pred,
        conf_thres,  # 置信度阈值
        iou_thres,  # IoU 阈值
        classes,  # 是否仅保留特定类别
        agnostic_nms,  # 是否进行类别无关的 NMS
        max_det=max_det  # 每张图片的最大检测目标数
    )
    dt[2] += time_sync() - t3  # 累计 NMS 时间

    # 5.5 第二阶段分类器(可选)
    # 如果启用了第二阶段分类器,则将结果传递给分类器进行进一步处理
    # pred = utils.general.apply_classifier(pred, classifier_model, im, im0s)

    # 5.6 后续保存或打印预测信息
    # 遍历每张图片的预测结果
    for i, det in enumerate(pred):  # 对于每张图片的预测结果
        seen += 1  # 已处理的图片数
        if webcam:  # 如果数据来源是摄像头或实时流
            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)  # 将路径字符串转换为 Path 对象
        save_path = str(save_dir / p.name)  # 保存图片的路径
        txt_path = str(save_dir / 'labels' / p.stem) + ('' if dataset.mode == 'image' else f'_{frame}')
        s += '%gx%g ' % im.shape[2:]  # 输出推理的图片尺寸
        gn = torch.tensor(im0.shape)[[1, 0, 1, 0]]  # 归一化比例,用于将检测框还原到原始图片尺寸
        imc = im0.copy() if save_crop else im0  # 用于保存裁剪目标的图片
        annotator = Annotator(im0, line_width=line_thickness, example=str(names))  # 用于绘制检测框的工具

        if len(det):  # 如果有检测结果
            # 将检测框从 img_size 映射回原图尺寸
            det[:, :4] = scale_coords(im.shape[2:], det[:, :4], im0.shape).round()

            # 打印每个类别的检测结果
            for c in det[:, -1].unique():
                n = (det[:, -1] == c).sum()  # 每个类别的检测数量
                s += f"{n} {names[int(c)]}{'s' * (n > 1)}, "  # 添加检测信息到字符串

            # 保存检测结果到文件或图像
            for *xyxy, conf, cls in reversed(det):  # 遍历每个检测框
                if save_txt:  # 保存到文本文件
                    xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist()  # 转换为归一化 xywh
                    line = (cls, *xywh, conf) if save_conf else (cls, *xywh)  # 根据配置添加置信度
                    with open(txt_path + '.txt', 'a') as f:
                        f.write(('%g ' * len(line)).rstrip() % line + '\n')

                # 如果需要保存裁剪目标或绘制检测框
                if save_img or save_crop or view_img:
                    c = int(cls)  # 类别索引
                    label = None if hide_labels else (names[c] if hide_conf else f'{names[c]} {conf:.2f}')
                    annotator.box_label(xyxy, label, color=colors(c, True))  # 绘制检测框
                    if save_crop:  # 保存裁剪后的目标
                        save_one_box(xyxy, imc, file=save_dir / 'crops' / names[c] / f'{p.stem}.jpg', BGR=True)

        # 打印处理时间
        print(f'{s}Done. ({t3 - t2:.3f}s)')

        # 如果启用了查看功能,则显示检测结果
        im0 = annotator.result()
        if view_img:  # 显示检测结果
            cv2.imshow(str(p), im0)
            cv2.waitKey(1)

        # 如果需要保存图片或视频结果
        if save_img:
            if dataset.mode == 'image':  # 如果是图片
                cv2.imwrite(save_path, im0)  # 保存检测后的图片
            else:  # 如果是视频
                if vid_path[i] != save_path:  # 如果路径发生变化,重新创建视频写入器
                    vid_path[i] = save_path
                    if isinstance(vid_writer[i], cv2.VideoWriter):
                        vid_writer[i].release()  # 释放之前的视频写入器
                    if vid_cap:  # 视频文件
                        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:  # 实时流
                        fps, w, h = 30, im0.shape[1], im0.shape[0]
                        save_path += '.mp4'
                    vid_writer[i] = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*'mp4v'), fps, (w, h))
                vid_writer[i].write(im0)  # 写入当前帧

# 打印推理速度
t = tuple(x / seen * 1E3 for x in dt)  # 计算每张图片的平均处理时间(毫秒)
print(f'Speed: %.1fms pre-process, %.1fms inference, %.1fms NMS per image at shape {(1, 3, *imgsz)}' % t)

# 保存结果信息
if save_txt or save_img:
    s = (f"\n{len(list(save_dir.glob('labels/*.txt')))} labels saved to {save_dir / 'labels'}"
         if save_txt else '')
    LOGGER.info(f"Results saved to {colorstr('bold', save_dir)}{s}")

# 如果启用了更新选项,则移除优化器信息
if update:
    strip_optimizer(weights)  # 去除优化器信息,减小模型大小

结果处理与保存

将检测到的目标结果保存为文本文件(可选),在图像上绘制边界框和标签,并根据需要保存裁剪后的目标图像

类似于实时监控与检测团队记录检测到的问题、标记问题位置,并根据需要保存问题区域的详细图片

总结

  • 保存预测信息:将检测到的问题记录在报告中,包含问题类别、位置、置信度等信息
  • 绘制边界框:在原始图像或视频帧上标记检测到的问题区域,类似于在施工图上标注发现的质量问题
  • 裁剪并保存问题区域:将检测到的问题区域剪切出来保存为独立的图片,便于后续的详细分析和修复
for *xyxy, conf, cls in reversed(det):  # 遍历每个检测结果(从后往前)
    """
    xyxy: 检测框的左上角和右下角坐标 (x1, y1, x2, y2)
    conf: 检测框的置信度分数
    cls: 检测框的类别索引
    det: 包含所有检测结果的张量,形状为 [num_detections, 6],每行是 [x1, y1, x2, y2, conf, cls]
    """

    # 保存预测信息到文本文件
    if save_txt:  # 如果启用了保存文本选项
        # 将检测框坐标 (xyxy) 转换为中心点坐标和宽高格式 (xywh),并进行归一化
        xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist()  
        """
        xyxy2xywh:将边界框坐标从 (x1, y1, x2, y2) 格式转换为 (x_center, y_center, width, height)
        gn:归一化因子,是 [width, height, width, height],用于将绝对坐标归一化到 0-1 之间
        .view(1, 4):将 xyxy 转换为 1 行 4 列的张量
        .tolist():将张量转换为 Python 列表
        """

        # 根据是否需要保存置信度,生成保存行的格式
        line = (cls, *xywh, conf) if save_conf else (cls, *xywh)  # 保存类别、归一化后的坐标和置信度

        # 将检测结果写入对应的文本文件
        with open(txt_path + '.txt', 'a') as f:  # 打开或创建 .txt 文件
            f.write(('%g ' * len(line)).rstrip() % line + '\n')  # 按格式写入每一行

    # 在图像上绘制检测框或保存裁剪的目标区域
    if save_img or save_crop or view_img:  # 如果需要保存图片、裁剪目标或显示结果
        c = int(cls)  # 将类别索引转换为整数
        # 根据配置,生成标签内容(类别名称,或类别名称加置信度)
        label = None if hide_labels else (names[c] if hide_conf else f'{names[c]} {conf:.2f}')
        # 在原图上绘制检测框和标签
        annotator.box_label(xyxy, label, color=colors(c, True))  
        """
        annotator.box_label:在图像上绘制边界框并添加类别标签
        xyxy:边界框的左上角和右下角坐标
        label:要显示的标签
        colors(c, True):为当前类别选择颜色
        """

        # 如果启用了裁剪保存选项
        if save_crop:
            # 裁剪检测框对应的目标区域,并保存为图片
            save_one_box(
                xyxy,  # 边界框坐标
                imc,  # 原始图像的副本
                file=save_dir / 'crops' / names[c] / f'{p.stem}.jpg',  # 保存路径
                BGR=True  # 保存为 BGR 格式(适用于 OpenCV)
            )
            """
            save_one_box:
            - 裁剪目标区域并保存为单独的图片
            - file:保存路径,包含类别名称文件夹和图片文件名
            - BGR:裁剪的图片保存为 BGR 格式,适配 OpenCV 的格式需求
            """

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值