【深度学习】【目标检测】【Ultralytics-YOLO系列】YOLOV3核心文件detect.py解读

news2025/5/11 1:30:16

【深度学习】【目标检测】【Ultralytics-YOLO系列】YOLOV3核心文件detect.py解读

文章目录

  • 【深度学习】【目标检测】【Ultralytics-YOLO系列】YOLOV3核心文件detect.py解读
  • 前言
  • if name == ‘main’
  • parse_opt函数
  • main函数
  • run函数
  • 不同命令参数的推理结果
    • 常规推理命令
    • 推理命令(新增save-txt参数)
    • 推理命令(新增save-conf参数)
    • 推理命令(新增save-crop参数)
    • 推理命令(新增visualize参数)
  • 总结


前言

在详细解析YOLOV3网络之前,首要任务是搭建Ultralytics–YOLOV3【Windows11下YOLOV3人脸检测】所需的运行环境,并完成模型的训练和测试,展开后续工作才有意义。
本博文对detect.py代码进行解析,detect.py文件实现YOLOV3网络模型的推理。其他代码后续的博文将会陆续讲解。这里只做YOLOV3相关模块的代码解析。


if name == ‘main’

Python脚本入口点:启动程序,调用【detect.py】的parse_opt()函数负责解析命令行参数和【detect.py】的main()函数检查依赖并调用推理函数。

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

parse_opt函数

解析命令行参数,用于配置YOLOv3模型的推理过程。

def parse_opt():
    """
    解析命令行参数,用于配置YOLOv3模型的推理过程
    :return:命令行参数的对象
    """
    parser = argparse.ArgumentParser()
    # weights:模型的权重地址,默认yolov3.pt  nargs='+'参数选项用于指定命令行参数可以接受一个或多个值,并以列表的形式存储
    parser.add_argument('--weights', nargs='+', type=str, default=ROOT / 'yolov3.pt', help='model path(s)')
    # source:测试数据文件(图片或视频)的保存路径,默认data/images
    parser.add_argument('--source', type=str, default=ROOT / 'data/images', help='file/dir/URL/glob, 0 for webcam')
    # imgsz:网络输入图片的大小,默认640
    parser.add_argument('--imgsz', '--img', '--img-size', nargs='+', type=int, default=[640], help='inference size h,w')
    # conf-thres:置信度阈值,默认0.25
    parser.add_argument('--conf-thres', type=float, default=0.25, help='confidence threshold')
    # iou-thres:iou阈值(非极大值抑制NMS),默认0.45
    parser.add_argument('--iou-thres', type=float, default=0.45, help='NMS IoU threshold')
    # max-det:每张图片最大的目标个数,默认1000
    parser.add_argument('--max-det', type=int, default=1000, help='maximum detections per image')
    # device:执行的设备cuda(单卡0或者多卡0,1,2,3)或者cpu
    parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
    # view-img:是否展示预测之后的图片或视频,默认False
    parser.add_argument('--view-img', action='store_true', help='show results')
    # save-txt:是否保存预测的边界框坐标到tx文件中,默认False,保存到runs/detect/expn/labels下每张图片的txt文件
    parser.add_argument('--save-txt', action='store_true', help='save results to *.txt')
    # save-conf:是否保存预测目标的置信度到tx文件中,默认False,保存到runs/detect/expn/labels下每张图片的txt文件
    parser.add_argument('--save-conf', action='store_true', help='save confidences in --save-txt labels')
    # save-crop:是否需要从原图中扣出预测到的目标剪切保存,在runs/detect/expn/crops下每个类别都有自己的文件夹保存对应的剪切图片,默认False
    parser.add_argument('--save-crop', action='store_true', help='save cropped prediction boxes')
    # nosave:是否不要保存预测后的图片,默认False,保存预测后的图片
    parser.add_argument('--nosave', action='store_true', help='do not save images/videos')
    # classes:nms是否是只保留特定的类default=[0,6,1,8,9,7],默认是None,保留所有类
    parser.add_argument('--classes', nargs='+', type=int, help='filter by class: --classes 0, or --classes 0 2 3')
    # agnostic-nms:是否进行类别无关的nms,不区分类别直接对所有类别的边界框进行统一处理,默认False
    parser.add_argument('--agnostic-nms', action='store_true', help='class-agnostic NMS')
    # augment:是否使用数据增强进行推理,默认为False
    parser.add_argument('--augment', action='store_true', help='augmented inference')
    # visualize:是否可视化特征图,默认为False,在runs/detect/expn下每张图片都有自己的文件夹保存的不同阶段的特征图
    parser.add_argument('--visualize', action='store_true', help='visualize features')
    # update:对所有模型进行strip_optimizer操作,从保存模型文件中移除优化器状态以及其他不必要的信息从而生成更轻量化的模型文件,默认为False
    parser.add_argument('--update', action='store_true', help='update all models')
    # project:当前测试结果放在哪个主文件夹下,默认runs/detect
    parser.add_argument('--project', default=ROOT / 'runs/detect', help='save results to project/name')
    # name:当前测试结果放在run/detect下的文件名,默认是exp,exp1,exp2.... 以此类推
    parser.add_argument('--name', default='exp', help='save results to project/name')
    # exist-ok:是否覆盖已有结果,默认为False
    parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment')
    # line-thickness:画边界框的线条宽度,默认为3
    parser.add_argument('--line-thickness', default=3, type=int, help='bounding box thickness (pixels)')
    # hide-labels:是否隐藏标签信息,默认为False
    parser.add_argument('--hide-labels', default=False, action='store_true', help='hide labels')
    # hide-conf:是否隐藏置信度信息,默认为False
    parser.add_argument('--hide-conf', default=False, action='store_true', help='hide confidences')
    # half:是否使用半精度Float16推理(缩短推理时间),默认是False
    parser.add_argument('--half', action='store_true', help='use FP16 half-precision inference')
    # dnn:是否使 OpenCV DNN进行ONNX推理,默认为False
    parser.add_argument('--dnn', action='store_true', help='use OpenCV DNN for ONNX inference')
    # 解析命令行参数,并将结果存储在opt对象中
    opt = parser.parse_args()
    # imgsz参数的长度为1则其值乘以2,否则保持不变
    opt.imgsz *= 2 if len(opt.imgsz) == 1 else 1
    # 打印解析后的参数
    print_args(FILE.stem, opt)  # FILE.stem是不含扩展名的文件名称:这里是detect
    # 返回命令行参数的对象
    return opt

main函数

检查环境和打印参数,并根据输入参数启动程序。
调用了【utils/general.py】的check_requirements函数;
调用了【detect.py】的run函数。

def main(opt):
    """
    检查环境和打印参数,并根据输入参数启动程序
    :param opt:命令行参数的对象
    :return:None
    """
    # 检查项目所需的依赖项:requrement.txt的包是否安装
    check_requirements(exclude=('tensorboard', 'thop'))    # 排除'tensorboard'和'thop'这两个库
    # 调用run启动程序:命令行参数使用字典形式
    run(**vars(opt))

run函数

加载预训练的YOLO模型,并对给定的图像或视频流执行目标检测任务。
调用了【utils/general.py】的increment_path函数、check_img_size函数、check_imshow函数、increment_path函数、non_max_suppression函数、scale_coords函数、xyxy2xywh函数、colorstr函数和strip_optimizer函数;
调用了【utils/torch_utils.py】的select_device函数;
调用了【models/common.py】的DetectMultiBackend类;
调用了【utils/datasets.py】的LoadStreams类和LoadImages类;
调用了【utils/plots.py】的Annotator类和save_one_box函数。

  • 数据准备: 主要负责数据输入的准备和验证,确保程序能够正确处理不同来源的数据(如本地文件、网络链接或摄像头流)。
    # ===================================== 1.数据准备 =====================================
    source = str(source)    # 输入图像/视频的路径
    save_img = not nosave and not source.endswith('.txt')  # 保存推理图像标志:输出只要不以.txt结尾且选择保存预测结果,则都要保存预测后的图片
    # .suffix用于获取文件路径中的扩展名部分
    is_file = Path(source).suffix[1:] in (IMG_FORMATS + VID_FORMATS)                        # 文件标志:检查是否为图像文件或视频文件
    is_url = source.lower().startswith(('rtsp://', 'rtmp://', 'http://', 'https://'))       # URL标志:检查是否为URL链接
    # .isnumeric():是否是纯数字字符串,数字通常用来表示摄像头设备的索引
    # .endswith('.txt') 是否以.txt结尾
    # (is_url and not is_file)是否是URL且不是本地文件
    webcam = source.isnumeric() or source.endswith('.txt') or (is_url and not is_file)      # 检查是否为摄像头输入
    if is_url and is_file:              # 网上的图像文件或视频文件
        source = check_file(source)     # 下载文件
    # ======================================================================================
    
  • 预测结果保持路径: 根据是否需要保存标签文件来决定是否创建子目录,它确保每次运行程序时,结果不会覆盖之前的结果,而是保存到一个独立的新目录中。
    # ====================================2.预测结果保持路径========================================
    save_dir = increment_path(Path(project) / name, exist_ok=exist_ok)                   # 递增生成的路径
    (save_dir / 'labels' if save_txt else save_dir).mkdir(parents=True, exist_ok=True)   # 创建路径
    # ===========================================================================================
    
  • 模型加载: 加载目标检测模型并配置推理参数,包括设备选择、模型格式判断、图像尺寸调整和半精度设置等。
    # ===================================== 3.模型加载 =====================================
    device = select_device(device)      # 选择设备CUDA或CPU
    model = DetectMultiBackend(weights, device=device, dnn=dnn)     # 加载模型
    # stride:推理时所用到最大步长,默认为32;names:保存预测类别对象列表;pt:加载的是否是pytorch模型;jit:加载的是否是jit格式;onnx:加载的是否是onnx模型
    stride, names, pt, jit, onnx = model.stride, model.names, model.pt, model.jit, model.onnx   # 获取模型属性
    # 确保输入图片的尺寸imgsz能整除stride=32
    imgsz = check_img_size(imgsz, s=stride)
    
    # 半精度仅支持在CUDA上运行PyTorch模型
    half &= pt and device.type != 'cpu'
    if pt:
        # 将模型参数从FP32转换为FP16
        model.model.half() if half else model.model.float()
    # ====================================================================================
    
  • 数据加载: 确保了程序能够正确处理不同类型的输入,无论是实时的摄像头流还是静态的图像或视频文件。
    # ====================================4.数据加载========================================
    # 不同的输入源设置不同的加载方式
    if webcam:  # 使用摄像头/网络视频流作为输入
        view_img = check_imshow()   # 检测cv2.imshow()方法是否可以执行,不能执行则抛出异常
        cudnn.benchmark = True       # 设置为True可以加速固定尺寸图像的推理
        dataset = LoadStreams(source, img_size=imgsz, stride=stride, auto=pt and not jit)   # 加载输入数据流
        bs = len(dataset)       # 批量大小,输入视频流的数量
    else:       # 获取本地图片/视频作为输入
        dataset = LoadImages(source, img_size=imgsz, stride=stride, auto=pt and not jit)    
        bs = 1  # 批量大小,单张图片或视频
    # 用于保存输出视频路径和文件
    vid_path, vid_writer = [None] * bs, [None] * bs
    # ====================================================================================
    
  • 模型推理: 模型推理与结果处理全流程,包含了从图像预处理、模型前向传播、NMS 后处理到最终的可视化和保存。
    # ====================================5.模型推理========================================
    # 模型预热
    if pt and device.type != 'cpu':
        # 对模型进行预热,以提高后续推理速度(即用一个空张量运行一次前向传播),目的是让GPU提前分配内存优化计算图等,从而提高后续推理速度
        # .to(device)将张量移动到指定设备(GPU或CPU);.type_as(...)匹配模型参数的数据类型
        # next(model.model.parameters())的作用是从模型的参数迭代器中获取第一个参数
        model(torch.zeros(1, 3, *imgsz).to(device).type_as(next(model.model.parameters())))
    # 在后续的推理过程中会被用来记录时间(三个阶段的时间消耗)和处理的图片或视频帧数量
    dt, seen = [0.0, 0.0, 0.0], 0
    # 遍历数据集中的每张图片或视频帧,并进行预处理
    for path, im, im0s, vid_cap, s in dataset:
        # ----------------5.1数据预处理-------------------
        t1 = time_sync()            # 高精度时间函数通常用于性能测试,作为预处理阶段的起始时间
        im = torch.from_numpy(im).to(device)    # 将NumPy数组转换为PyTorch张量并将张量移动到指定设备
        im = im.half() if half else im.float()  # 数据类型转换,张量的数据类型使用FP16半精度或FP32单精度
        im /= 255   # 将像素值从[0,255]的范围归一化到[0.0,1.0]的范围
    
        if len(im.shape) == 3:  # 张量的形状是三维
            im = im[None]       # 增加一个批量维度(批量维度)
        t2 = time_sync()        # 作为预处理阶段的结束时间
        dt[0] += t2 - t1        # 记录预处理阶段总耗时
        # ----------------------------------------------
    
        # ----------------5.2执行推理-------------------
        # 生成一个用于保存可视化结果的路径
        visualize = increment_path(save_dir / Path(path).stem, mkdir=True) if visualize else False
        # 输入数据进行推理,返回预测结
        # augment:是否启用测试时增强;visualize:是否保存网络中间层的特征图
        pred = model(im, augment=augment, visualize=visualize)
        t3 = time_sync()    # 作为推理阶段的结束时间
        dt[1] += t3 - t2    # 记录推理阶段总耗时
        # --------------------------------------------
        
        # ----------------5.3NMS-------------------
        # 对模型的预测结果进行非极大值抑制,去除冗余的检测框,保留最优的结果
        pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det)     # 非极大值抑制NMS
        dt[2] += time_sync() - t3   # 记录后处理阶段的总耗时
        # -----------------------------------------
    
        # Second-stage classifier (optional)
        # pred = utils.general.apply_classifier(pred, classifier_model, im, im0s)
    
        # 遍历每张图片的预测结果
        for i, det in enumerate(pred):  # 每次处理一张图像或视频帧
            seen += 1       # 统计总共处理的图像/帧,自增1
            if webcam:  # 摄像头/网络视频流输入 batch_size >= 1 多个摄像头或网络视频流
                # p:图像路径;im0:未归一化原始图像;frame:当前帧编号
                p, im0, frame = path[i], im0s[i].copy(), dataset.count
                # 通常有多个视频流输入作为批量输入,处理当前批次的那个图片序号就表示处理的是那个视频流的帧
                s += f'{i}: '   # 记录日志信息
            else:       # 本地图像或视频帧输入
                # 从dataset对象中获取名为frame的属性值;没有定义frame属性,返回默认值0
                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:]  # 日志字符串中添加当前图像的尺寸信息
            # 原始图像形状为(H,W,C)或(H,W),[[1,0,1,0]]提取[W,H,W,H]
            gn = torch.tensor(im0.shape)[[1, 0, 1, 0]]  # 将检测框坐标从[0~1]归一化格式转换回原始图像像素坐标的缩放因子
            imc = im0.copy() if save_crop else im0  # 保存裁剪目标,复制原始图像,防止原图被修改
            annotator = Annotator(im0, line_width=line_thickness, example=str(names))
            if len(det):    # 有检测结果则进行坐标映射
                # 将检测框坐标从模型输入尺寸640x640映射回原始图像尺寸
                det[:, :4] = scale_coords(im.shape[2:], det[:, :4], im0.shape).round()
                # 统计检测类别及数量
                # det[:, -1]:取出所有检测目标的类别编号;.unique():返回张量中的唯一值,去重后的元素
                for c in det[:, -1].unique():   # 遍历所有检测到的类别编号
                    n = (det[:, -1] == c).sum()  # 统计每个类别出现次数
                    s += f"{n} {names[int(c)]}{'s' * (n > 1)}, "  # 构造日志字符串
    
                # *xyxy:左上角和右下角坐标[x1,y1,x2,y2];conf:置信度;cls:类别编号
                for *xyxy, conf, cls in reversed(det):   # 用于遍历检测结果,通常优先处理高置信度的检测框
                    if save_txt:  # 将检测框信息写入txt文件
                        #  xyxy2xywh()将[x1,y1,x2,y2]转换为[xc,yc,w,h]格式
                        xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist()  # 将坐标归一化到[0,1
                        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)  # 类别序号
                        # hide_labels:不显示标签;hide_conf:不显示置信度
                        # 不显示标签则直接不显示置信度
                        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)     # 保存裁剪的目标区域到指定路径
    
            # 打印单帧图像的目标检测推理所用时间(不包括前处理和后处理)
            LOGGER.info(f'{s}Done. ({t3 - t2:.3f}s)')
    
            im0 = annotator.result()    # 获取带有边界框标签等信息的图像
            if view_img:    # 显示图像
                cv2.imshow(str(p), im0)  # 将标注了检测框的结果图像显示在窗口中,用于实时查看检测效果
                cv2.waitKey(1)  # 等待1毫秒,保持窗口更新,防止卡死
    
            if save_img:    # 保存检测结果图像或视频
                if dataset.mode == 'image':         # 图像模式
                    cv2.imwrite(save_path, im0)     # cv2.imwrite() 保存图片
                else:  # 视频或流媒体模式
                    if vid_path[i] != save_path:    # 检查当前视频路径是否与新的保存路径不同
                        vid_path[i] = save_path     # 不同则更新路径,开始处理新的视频文件
                        if isinstance(vid_writer[i], cv2.VideoWriter):  # 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:  # 摄像头输入
                            # 设置默认帧率为30,宽度和高度为当前帧尺寸
                            fps, w, h = 30, im0.shape[1], im0.shape[0]
                            save_path += '.mp4'     # .mp4格式保存
                        # save_path:保存路径;v2.VideoWriter_fourcc(*'mp4v'):MP4编码格式
                        # fps:帧率;(w, h):视频分辨率
                        vid_writer[i] = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*'mp4v'), fps, (w, h))    # 创建新的VideoWriter对象,用于写入处理后的帧
                    vid_writer[i].write(im0)    # 将当前帧写入视频文件
    # ====================================================================================
    

完整代码

def run(weights=ROOT / 'yolov3.pt',                 # 训练的权重路径
        source=ROOT / 'data/images',                # 图像/视频文件,图像目录,URL,0(摄像头)
        imgsz=640,                                  # 推理图像分辨率
        conf_thres=0.25,                            # 置信度阈值
        iou_thres=0.45,                             # IOU阈值(非极大值抑制NMS)
        max_det=1000,                               # 每张图像的最大检测数
        device='',                                  # CUDA单卡或多卡(0或0,1,2,3)或CPU
        view_img=False,                             # 显示结果
        save_txt=False,                             # 将结果(类别和边框大小位置)保存到txt
        save_conf=False,                            # 将置信度保存到txt
        save_crop=False,                            # 保存裁剪后的图像
        nosave=False,                               # 不保存图像/视频结果
        classes=None,                               # 按类别过滤筛选保留
        agnostic_nms=False,                         # 类别无关的NMS
        augment=False,                              # 增强推理
        visualize=False,                            # 可视化特征
        update=False,                               # 更新所有模型(简洁化)
        project=ROOT / 'runs/detect',               # 保存结果的路径
        name='exp',                                 # 保存结果的名称
        exist_ok=False,                             # 覆盖现有结果,不名称递增保存
        line_thickness=3,                           # 边界框厚度(像素)
        hide_labels=False,                          # 输出结果隐藏标签
        hide_conf=False,                            # 输出结果隐藏置信度
        half=False,                                 # 使用FP16半精度推理
        dnn=False,                                  # 使用OpenCV DNN进行ONNX推理
        ):
    """
    加载预训练的YOLO模型,并对给定的图像或视频流执行目标检测任务
    :param weights:训练的权重路径,可以使用自己训练的权重,也可以使用官网提供的权重,默认官网的权重yolov3.pt
    :param source:测试数据,可以是图片/视频路径,也可以是'0'(电脑自带摄像头),也可以是rtsp等视频流,默认data/images
    :param imgsz: 网络模型输入图片尺寸,默认的大小是640
    :param conf_thres:置信度阈值,默认为0.25
    :param iou_thres:nms的iou阈值,默认为0.45
    :param max_det:保留的最大检测框数量,每张图片中检测目标的个数最多为1000
    :param device:设置设备CPU/CUDA/多CUDA,默认不设置
    :param view_img:是否界面展示检测结果(图片/视频),默认False
    :param save_txt:是否将预测的框坐标以txt文件形式保存,默认False,使用时在路径runs/detect/exp*/labels/*.txt下生成每张图片预测的txt文件
    :param save_conf:是否将置信度conf也保存到txt中,默认False,使用时在路径runs/detect/exp*/labels/*.txt下生成每张图片预测的txt文件
    :param save_crop:是否保存裁剪预测框图片,默认为False,使用时在runs/detect/exp*/crop/剪切类别文件夹/ 路径下会保存每个接下来的目标
    :param nosave:不保存图片/视频, 默认保存因此不设置,在runs/detect/exp*/保存预测的结果
    :param classes:设置只保留某一部分类别[0,6,1,8,9,7],默认不设置,设置时在路径runs/detect/exp*/下保存[0,6,1,8,9,7]对应的类别的图片
    :param agnostic_nms:进行NMS去除不同类别之间的框,默认False
    :param augment: 测试时增强/多尺度预测
    :param visualize:是否可视化网络层输出特征
    :param update:为True则对所有模型进行strip_optimizer操作,去除pt文件中的优化器等信息,默认为False
    :param project:保存测试日志的文件夹路径,默认runs/detect
    :param name:保存测试日志文件夹的名字,所以最终是保存在project/name中
    :param exist_ok:是否重新创建日志文件,False时重新创建文件
    :param line_thickness:画框的线条粗细
    :param hide_labels:预测结果隐藏标签
    :param hide_conf:预测结果隐藏置信度
    :param half:是否使用F16精度推理,半进度提高检测速度
    :param dnn:用OpenCV DNN预测
    :return:None
    """
    # ===================================== 1.数据准备 =====================================
    source = str(source)    # 输入图像/视频的路径
    save_img = not nosave and not source.endswith('.txt')  # 保存推理图像标志:输出只要不以.txt结尾且选择保存预测结果,则都要保存预测后的图片
    # .suffix用于获取文件路径中的扩展名部分
    is_file = Path(source).suffix[1:] in (IMG_FORMATS + VID_FORMATS)                        # 文件标志:检查是否为图像文件或视频文件
    is_url = source.lower().startswith(('rtsp://', 'rtmp://', 'http://', 'https://'))       # URL标志:检查是否为URL链接
    # .isnumeric():是否是纯数字字符串,数字通常用来表示摄像头设备的索引
    # .endswith('.txt') 是否以.txt结尾
    # (is_url and not is_file)是否是URL且不是本地文件
    webcam = source.isnumeric() or source.endswith('.txt') or (is_url and not is_file)      # 检查是否为摄像头输入
    if is_url and is_file:              # 网上的图像文件或视频文件
        source = check_file(source)     # 下载文件
    # ======================================================================================

    # ====================================2.预测结果保持路径========================================
    save_dir = increment_path(Path(project) / name, exist_ok=exist_ok)                   # 递增生成的路径
    (save_dir / 'labels' if save_txt else save_dir).mkdir(parents=True, exist_ok=True)   # 创建路径
    # ===========================================================================================

    # ===================================== 3.模型加载 =====================================
    device = select_device(device)      # 选择设备CUDA或CPU
    model = DetectMultiBackend(weights, device=device, dnn=dnn)     # 加载模型
    # stride:推理时所用到最大步长,默认为32;names:保存预测类别对象列表;
    # pt:加载的是否是pytorch模型;jit:加载的是否是jit格式;onnx:加载的是否是onnx模型
    stride, names, pt, jit, onnx = model.stride, model.names, model.pt, model.jit, model.onnx   # 获取模型属性
    # 确保输入图片的尺寸imgsz能整除stride=32
    imgsz = check_img_size(imgsz, s=stride)

    # 半精度仅支持在CUDA上运行PyTorch模型
    half &= pt and device.type != 'cpu'
    if pt:
        # 将模型参数从FP32转换为FP16
        model.model.half() if half else model.model.float()
    # ====================================================================================

    # ====================================4.数据加载========================================
    # 不同的输入源设置不同的加载方式
    if webcam:  # 使用摄像头/网络视频流作为输入
        view_img = check_imshow()   # 检测cv2.imshow()方法是否可以执行,不能执行则抛出异常
        cudnn.benchmark = True       # 设置为True可以加速固定尺寸图像的推理
        dataset = LoadStreams(source, img_size=imgsz, stride=stride, auto=pt and not jit)   # 加载输入数据流
        bs = len(dataset)       # 批量大小,输入视频流的数量
    else:       # 获取本地图片/视频作为输入
        dataset = LoadImages(source, img_size=imgsz, stride=stride, auto=pt and not jit)
        bs = 1  # 批量大小,单张图片或视频
    # 用于保存输出视频路径和文件
    vid_path, vid_writer = [None] * bs, [None] * bs
    # ====================================================================================

    # ====================================5.模型推理========================================
    # 模型预热
    if pt and device.type != 'cpu':
        # 对模型进行预热,以提高后续推理速度(即用一个空张量运行一次前向传播),目的是让GPU提前分配内存优化计算图等,从而提高后续推理速度
        # .to(device)将张量移动到指定设备(GPU或CPU);.type_as(...)匹配模型参数的数据类型
        # next(model.model.parameters())的作用是从模型的参数迭代器中获取第一个参数
        model(torch.zeros(1, 3, *imgsz).to(device).type_as(next(model.model.parameters())))
    # 在后续的推理过程中会被用来记录时间(三个阶段的时间消耗)和处理的图片或视频帧数量
    dt, seen = [0.0, 0.0, 0.0], 0
    # 遍历数据集中的每张图片或视频帧,并进行预处理
    for path, im, im0s, vid_cap, s in dataset:
        # ----------------5.1数据预处理-------------------
        t1 = time_sync()            # 高精度时间函数通常用于性能测试,作为预处理阶段的起始时间
        im = torch.from_numpy(im).to(device)    # 将NumPy数组转换为PyTorch张量并将张量移动到指定设备
        im = im.half() if half else im.float()  # 数据类型转换,张量的数据类型使用FP16半精度或FP32单精度
        im /= 255   # 将像素值从[0,255]的范围归一化到[0.0,1.0]的范围

        if len(im.shape) == 3:  # 张量的形状是三维
            im = im[None]       # 增加一个批量维度(批量维度)
        t2 = time_sync()        # 作为预处理阶段的结束时间
        dt[0] += t2 - t1        # 记录预处理阶段总耗时
        # ----------------------------------------------

        # ----------------5.2执行推理-------------------
        # 生成一个用于保存可视化结果的路径
        visualize = increment_path(save_dir / Path(path).stem, mkdir=True) if visualize else False
        # 输入数据进行推理,返回预测结
        # augment:是否启用测试时增强;visualize:是否保存网络中间层的特征图
        pred = model(im, augment=augment, visualize=visualize)
        t3 = time_sync()    # 作为推理阶段的结束时间
        dt[1] += t3 - t2    # 记录推理阶段总耗时
        # --------------------------------------------
        
        # ----------------5.3NMS-------------------
        # 对模型的预测结果进行非极大值抑制,去除冗余的检测框,保留最优的结果
        pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det)     # 非极大值抑制NMS
        dt[2] += time_sync() - t3   # 记录后处理阶段的总耗时
        # -----------------------------------------

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

        # 遍历每张图片的预测结果
        for i, det in enumerate(pred):  # 每次处理一张图像或视频帧
            seen += 1       # 统计总共处理的图像/帧,自增1
            if webcam:  # 摄像头/网络视频流输入 batch_size >= 1 多个摄像头或网络视频流
                # p:图像路径;im0:未归一化原始图像;frame:当前帧编号
                p, im0, frame = path[i], im0s[i].copy(), dataset.count
                # 通常有多个视频流输入作为批量输入,处理当前批次的那个图片序号就表示处理的是那个视频流的帧
                s += f'{i}: '   # 记录日志信息
            else:       # 本地图像或视频帧输入
                # 从dataset对象中获取名为frame的属性值;没有定义frame属性,返回默认值0
                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:]  # 日志字符串中添加当前图像的尺寸信息
            # 原始图像形状为(H,W,C)或(H,W),[[1,0,1,0]]提取[W,H,W,H]
            gn = torch.tensor(im0.shape)[[1, 0, 1, 0]]  # 将检测框坐标从[0~1]归一化格式转换回原始图像像素坐标的缩放因子
            imc = im0.copy() if save_crop else im0  # 保存裁剪目标,复制原始图像,防止原图被修改
            annotator = Annotator(im0, line_width=line_thickness, example=str(names))
            if len(det):    # 有检测结果则进行坐标映射
                # 将检测框坐标从模型输入尺寸640x640映射回原始图像尺寸
                det[:, :4] = scale_coords(im.shape[2:], det[:, :4], im0.shape).round()
                # 统计检测类别及数量
                # det[:, -1]:取出所有检测目标的类别编号;.unique():返回张量中的唯一值,去重后的元素
                for c in det[:, -1].unique():   # 遍历所有检测到的类别编号
                    n = (det[:, -1] == c).sum()  # 统计每个类别出现次数
                    s += f"{n} {names[int(c)]}{'s' * (n > 1)}, "  # 构造日志字符串

                # *xyxy:左上角和右下角坐标[x1,y1,x2,y2];conf:置信度;cls:类别编号
                for *xyxy, conf, cls in reversed(det):   # 用于遍历检测结果,通常优先处理高置信度的检测框
                    if save_txt:  # 将检测框信息写入txt文件
                        #  xyxy2xywh()将[x1,y1,x2,y2]转换为[xc,yc,w,h]格式
                        xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist()  # 将坐标归一化到[0,1
                        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)  # 类别序号
                        # hide_labels:不显示标签;hide_conf:不显示置信度
                        # 不显示标签则直接不显示置信度
                        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)     # 保存裁剪的目标区域到指定路径

            # 打印单帧图像的目标检测推理所用时间(不包括前处理和后处理)
            LOGGER.info(f'{s}Done. ({t3 - t2:.3f}s)')

            im0 = annotator.result()    # 获取带有边界框标签等信息的图像
            if view_img:    # 显示图像
                cv2.imshow(str(p), im0)  # 将标注了检测框的结果图像显示在窗口中,用于实时查看检测效果
                cv2.waitKey(1)  # 等待1毫秒,保持窗口更新,防止卡死

            if save_img:    # 保存检测结果图像或视频
                if dataset.mode == 'image':         # 图像模式
                    cv2.imwrite(save_path, im0)     # cv2.imwrite() 保存图片
                else:  # 视频或流媒体模式
                    if vid_path[i] != save_path:    # 检查当前视频路径是否与新的保存路径不同
                        vid_path[i] = save_path     # 不同则更新路径,开始处理新的视频文件
                        if isinstance(vid_writer[i], cv2.VideoWriter):  # 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:  # 摄像头输入
                            # 设置默认帧率为30,宽度和高度为当前帧尺寸
                            fps, w, h = 30, im0.shape[1], im0.shape[0]
                            save_path += '.mp4'     # .mp4格式保存
                        # save_path:保存路径;v2.VideoWriter_fourcc(*'mp4v'):MP4编码格式
                        # fps:帧率;(w, h):视频分辨率
                        vid_writer[i] = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*'mp4v'), fps, (w, h))    # 创建新的VideoWriter对象,用于写入处理后的帧
                    vid_writer[i].write(im0)    # 将当前帧写入视频文件
    # ====================================================================================

    t = tuple(x / seen * 1E3 for x in dt)  # 计算每个阶段(预处理、推理、NMS)每张图像所花费的平均时间
    # 使用日志记录器输出模型的速度信息
    LOGGER.info(f'Speed: %.1fms pre-process, %.1fms inference, %.1fms NMS per image at shape {(1, 3, *imgsz)}' % t)
    if save_txt or save_img:    # 保存文本标签或者保存图像
        # save_dir.glob('labels/*.txt'):查找指定目录下所有.txt文件;
        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)  # update model (to fix SourceChangeWarning)

不同命令参数的推理结果

一些常用命令参数的使用情况,并展示和说明其输出结果。

常规推理命令

只进行最基础的设置:
–weights指定模型权重文件路径,用于加载训练好的模型进行推理;
–source:指定要检测的数据源,可以是图片、视频、摄像头等;
–device:指定运行设备:0 表示使用 GPU(CUDA),也可以指定多个GPU如 0,1,或者使用 cpu;
–conf-thres:设置置信度阈值(confidence threshold),只有置信度高于该值的目标才会被保留;
–iou-thres:设置 NMS(非极大值抑制)的 IoU 阈值,用于去除重叠过多的预测框。

python detect.py --weights runs/train/exp/weights/best.pt --source data/images --device 0 --conf-thres 0.7 --iou-thres 0.3

控制台输出结果:

文件保存内容:

推理命令(新增save-txt参数)

–save-txt:保存预测边界框信息到文本文件中。

python detect.py --weights runs/train/exp/weights/best.pt --source data/images --device 0 --conf-thres 0.7 --iou-thres 0.3 --save-txt

控制台输出结果:

文件保存内容:
在这里插入图片描述

0表示标签序号;剩下的四个数值分别是框中心坐标以及框的尺寸。

推理命令(新增save-conf参数)

–save-conf:保存置信度信息到文本文件中。

python detect.py --weights runs/train/exp/weights/best.pt --source data/images --device 0 --conf-thres 0.7 --iou-thres 0.3 --save-txt --save-conf

使用–save-conf参数一定要先使用–save-txt参数,单独使用不起作用,并且保存的数据是在同一个txt文件中。

控制台输出结果:

文件保存内容:

0表示标签序号;中间四个数值分别是框中心坐标以及框的尺寸;最后一个数值是置信度。

推理命令(新增save-crop参数)

–save-crop:将检测到的目标从原图中裁剪出来并单独保存。

python detect.py --weights runs/train/exp/weights/best.pt --source data/images --device 0 --conf-thres 0.7 --iou-thres 0.3 --save-txt --save-conf --save-crop

控制台输出结果:

文件保存内容:

推理命令(新增visualize参数)

–visualize:可视化模型中间层特征图。

python detect.py --weights runs/train/exp/weights/best.pt --source data/images --device 0 --conf-thres 0.7 --iou-thres 0.3 --save-txt --save-conf --save-crop --visualize

控制台输出结果:

32/XXX:32是保存特征图的数量,XXX是当前网络层输出特征图的数量。

文件保存内容:

每个被检测的图像都单独生成一个以图像文件名命名的文件夹,保存不同网络层的特征图,这里每层限制保存32张特征图。


总结

尽可能简单、详细的介绍了核心文件detect.py文件的作用:根据命令行参数设置YOLOv3模型的推理流程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2372762.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

耳机插进电脑只有一边有声音怎么办 解决方法分享

当您沉浸在音乐或电影中时,如果突然发现耳机只有一边有声音,这无疑会破坏您的体验。本文将提供一系列检查和修复方法,帮助您找出并解决问题,让您的耳机恢复正常的立体声效果。 一、检查耳机连接是否正常 首先需要确认耳机与播放设…

Dp通用套路(闫式)

闫式dp分析法: 从集合角度来分析DP问题。 核心思想: DP是一种求有限集中的最值或者个数问题 由于集合中元素的数量都是指数级别的,直接用定义去求,把每种方案都用dfs暴力枚举一遍,时间复杂度很高,此时用…

华为设备端口隔离

端口隔离的理论与配置指南 一、端口隔离的理论 基本概念 端口隔离(Port Isolation)是一种在交换机上实现的安全功能,用于限制同一VLAN内指定端口间的二层通信。被隔离的端口之间无法直接通信,但可通过上行端口访问公共资源&#…

RabbitMq(尚硅谷)

RabbitMq 1.RabbitMq异步调用 2.work模型 3.Fanout交换机(广播模式) 4.Diret交换机(直连) 5.Topic交换机(主题交换机,通过路由匹配) 6.Headers交换机(头交换机) 6…

GAF-CNN-SSA-LSSVM故障诊断/分类预测,附带模型研究报告(Matlab)

GAF-CNN-SSA-LSSVM故障诊断/分类预测,附带模型研究报告(Matlab) 目录 GAF-CNN-SSA-LSSVM故障诊断/分类预测,附带模型研究报告(Matlab)效果一览基本描述程序设计参考资料 效果一览 基本描述 本研究提出的GA…

数据分析怎么做?高效的数据分析方法有哪些?

目录 一、数据分析的对象和目的 (一)数据分析的常见对象 (二)数据分析的目的 二、数据分析怎么做? (一)明确问题 (二)收集数据 (三)清洗和…

stm32之TIM定时中断详解

目录 1.引入1.1 简介1.2 类型1.2.1 基本定时器1.2.2 通用定时器1. 触发控制单元 (Trigger Control Unit)2. 输入捕获单元 (Input Capture Unit)3. 输出比较单元 (Output Compare Unit)4. CNT 计数器5. 自动重装载寄存器 (ARR)6. 预分频器 (PSC)7. 中断与 DMA 事件8. 刹车功能 (…

【el-admin】el-admin关联数据字典

数据字典使用 一、新增数据字典1、新增【图书状态】和【图书类型】数据字典2、编辑字典值 二、代码生成配置1、表单设置2、关联字典3、验证关联数据字典 三、查询操作1、模糊查询2、按类别查询(下拉框) 四、数据校验 一、新增数据字典 1、新增【图书状态…

【LUT技术专题】ECLUT代码解读

目录 原文概要 1. 训练 2. 转表 3. 测试 本文是对ECLUT技术的代码解读,原文解读请看ECLUT。 原文概要 ECLUT通过EC模块增大网络感受野,提升超分效果,实现SRLUT的改进,主要是2个创新点: 提出了一个扩展卷积&…

大物重修之浅显知识点

第一章 质点运动学 例1 知识点公式如下: 例2 例3 例4 例5 例6 第四章 刚体的转动 例1 例2 例3 例4 例5 例6 第五章 简谐振动 例1 例2 例3 第六章 机械波 第八章 热力学基础 第九章 静电场 第十一章 恒定磁场…

并发设计模式实战系列(16):屏障(Barrier)

🌟 大家好,我是摘星! 🌟 今天为大家带来的是并发设计模式实战系列,第十六章屏障(Barrier),废话不多说直接开始~ 目录 一、核心原理深度拆解 1. 屏障的同步机制 2. 关键参数 二…

算法探秘:和为K的子数组问题解析

算法探秘:和为K的子数组问题解析 一、引言 在算法的奇妙世界里,数组相关的问题总是层出不穷。“和为K的子数组”问题,看似简单,实则蕴含着丰富的算法思想和技巧。它要求我们在给定的整数数组中,找出和为特定值K的子数组个数。通过深入研究这个问题,我们不仅能提升对数组…

电力MOSFET的专用集成驱动电路IR2233

IR2233是IR2133/IR2233/IR2235 系列驱动芯片中的一种,是专为高电压、高速度的电力MOSFET和IGBT驱动而设计的。该系列驱动芯片内部集成了互相独立的三组板桥驱动电路,可对上下桥臂提供死区时间,特别适合于三相电源变换等方面的应用。其内部集成了独立的运算放大器可通过外部桥…

Qt 的原理及使用(1)——qt的背景及安装

1. Qt 背景介绍 1.1 什么是 Qt Qt 是⼀个 跨平台的 C 图形⽤⼾界⾯应⽤程序框架 。它为应⽤程序开发者提供了建⽴艺术级图形 界⾯所需的所有功能。它是完全⾯向对象的,很容易扩展。Qt 为开发者提供了⼀种基于组件的开发模 式,开发者可以通过简单的拖拽…

范式之殇-关系代数与参照完整性在 Web 后台的落寞

最近参加了一个PostgreSQL相关的茶会,感慨良多。原本话题是PostgreSQL 在 SELECT 场景中凭借其成熟的查询优化器、丰富的功能特性和灵活的执行策略,展现出显著优势。在窗口函数(Window Functions)、JOIN 优化、公共表表达式&#…

广西某建筑用花岗岩矿自动化监测

1. 项目简介 某矿业有限公司成立于2021年,是由某建筑材料有限公司与个人共同出资成立,矿区面积0.4069平方公里,可开采筑用花岗岩、建筑用砂岩。建筑用花岗岩、建筑用砂岩可利用资源量分别为6338.69万吨、303.39万吨,设计生产规模…

想更好应对突发网络与业务问题?需要一款“全流量”工具

目录 什么是“全流量”? 为什么“全流量”在突发问题中如此重要? 1. 抓住问题发生的“第一现场” 2. 绕开日志盲区 3. 精准应对安全威胁 实战场景下的“全流量”价值体现 实施“全流量”需要注意哪些点? 1. 数据量巨大,需…

C#里创建一个MaterialDesign3的导航条

本文里主要创建如下的窗口: 在这里就是实现左边的导航窗口的列表。 第一步先要定义下面的代码: <Window x:Class="MDIXWindow.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microso…

Oracle OCP认证考试考点详解083系列09

题记&#xff1a; 本系列主要讲解Oracle OCP认证考试考点&#xff08;题目&#xff09;&#xff0c;适用于19C/21C,跟着学OCP考试必过。 41. 第41题&#xff1a; 题目 解析及答案&#xff1a; 关于应用程序容器&#xff0c;以下哪三项是正确的&#xff1f; A) 它可以包含单个…

如何进行室内VR全景拍摄?

如何进行室内VR全景拍摄&#xff1f; 室内VR全景拍摄作为先进的视觉技术&#xff0c;能够为用户提供沉浸式的空间体验。本文介绍如何进行室内VR全景拍摄&#xff0c;并阐述众趣科技在这一领域的技术支持和服务优势。 室内VR全景拍摄基础 1. 室内VR全景拍摄概述 室内VR全景拍…