详解 ultralytics / yolov5 中的 detect.py 文件

 本文适合有一定 python 基础且对 yolov5 有一定了解的朋友阅读

本文将对当前 https://github.com/ultralytics/yolov5.git 中的 detect.py 文件进行详细的流程描述并且附上全程的代码注释, 从而将 detect.py 脚本运行的逻辑清晰地展示出来

注: 只会提供流程描述和每段代码对应的功能, 不会给予语法的讲解

文末有完整的 detect.py 文件 ( 包含笔者的注释 )

第一部分: 如何使用命令行模板调用 detect.py ( 官方提供 )

# YOLOv5 🚀 by Ultralytics, AGPL-3.0 license


################################### 如何使用 detect.py 脚本 ( 命令行模板 ) ###################################

"""
Run YOLOv5 detection inference on images, videos, directories, globs, YouTube, webcam, streams, etc.

Usage - sources:
    $ python detect.py --weights yolov5s.pt --source 0                               # webcam
                                                     img.jpg                         # image
                                                     vid.mp4                         # video
                                                     screen                          # screenshot
                                                     path/                           # directory
                                                     list.txt                        # list of images
                                                     list.streams                    # list of streams
                                                     'path/*.jpg'                    # glob
                                                     'https://youtu.be/LNwODJXcvt4'  # YouTube
                                                     'rtsp://example.com/media.mp4'  # RTSP, RTMP, HTTP stream

Usage - formats:
    $ python detect.py --weights yolov5s.pt                 # PyTorch
                                 yolov5s.torchscript        # TorchScript
                                 yolov5s.onnx               # ONNX Runtime or OpenCV DNN with --dnn
                                 yolov5s_openvino_model     # OpenVINO
                                 yolov5s.engine             # TensorRT
                                 yolov5s.mlmodel            # CoreML (macOS-only)
                                 yolov5s_saved_model        # TensorFlow SavedModel
                                 yolov5s.pb                 # TensorFlow GraphDef
                                 yolov5s.tflite             # TensorFlow Lite
                                 yolov5s_edgetpu.tflite     # TensorFlow Edge TPU
                                 yolov5s_paddle_model       # PaddlePaddle
"""

 以上是官方提供的参考

第一点 :  sources 表示需要进行检测的源文件, 可以是多种形式 webcam 相机,  image 图片, video  视频, 甚至YouTube, URL网址均可

第二点 :  formats 表示使用的权重文件, 同样可以是多种形式, 一般用的是 PyTorch 的 .pt 文件

这里给一个简单的命令行示例模板 :

python detect.py --source 0 --weights weights/yolov5s.pt

--source 0 表示使用电脑相机获取待处理的图像

--weights weights/yolov5s.pt 表示使用 yolo 项目根目录下的 weights 文件夹中的 yolov5s.pt

( 笔者是自己创建了这个 weights 文件夹, 并将 yolov5.st 放在里面, 大家可以自己安排权重文件放置的位置, 记得使用的时候要把路径写全, 如果程序找不到你的权重文件它会自己从网上下载 )

第二部分: 导入必要的库, 函数和类, 并定义 FILE 和 ROOT 路径

# 导入必要的库
import argparse
import csv
import os
import platform
import sys
from pathlib import Path
import torch

这段代码导入了该程序需要使用的库

# 定义 FILE 和 ROOT 路径
FILE = Path(__file__).resolve()    # 找到当前文件 detect.py 的绝对路径   resolve 表示将相对路径转为绝对路径
ROOT = FILE.parents[0]             # 找到 YOLOv5 根目录
# 将 YOLOv5 根目录添加到 sys.path( 如果尚未存在 )
if str(ROOT) not in sys.path:
    sys.path.append(str(ROOT))  
# 获取 YOLOv5 根目录的路径
ROOT = Path(os.path.relpath(ROOT, Path.cwd()))  

 这段代码的功能 :

1  找到当前文件 detect.py 在你电脑上的绝对路径

2  将 YOLOv5 根目录添加到 sys.path( 如果尚未存在 )

3  获取 YOLOv5 根目录的路径

# 从外部模块导入所需的函数/类
from ultralytics.utils.plotting import Annotator, colors, save_one_box
from models.common import DetectMultiBackend
from utils.dataloaders import IMG_FORMATS, VID_FORMATS, LoadImages, LoadScreenshots, LoadStreams
from utils.general import (LOGGER, Profile, check_file, check_img_size, check_imshow, check_requirements, colorstr, cv2,
                           increment_path, non_max_suppression, print_args, scale_boxes, strip_optimizer, xyxy2xywh)
from utils.torch_utils import select_device, smart_inference_mode

这段代码从外部模块导入了所需的函数/类

第三部分: 程序的入口, 以及程序对命令行参数的解析

# 程序的入口
if __name__ == '__main__':
    # 它首先使用 parse_opt 函数解析命令行选项
    opt = parse_opt()  
    # 然后,它以解析后的选项作为参数调用主函数 main
    main(opt)  

 这段代码虽然在整个文件的末尾, 却是这个程序的入口 !

程序执行的第一步便是使用 parse_opt 函数解析命令行选项

以下是 parse_opt 函数 :

它会创建一个 ArgumentParser 对象 parser, 将命令行的参数添加到 parser 中                  

因此这是我们编写命令行时的重要参考, 可以通过它知道如何编写命令行以达到我们的需求

这段代码末尾的 print_args 函数会在终端打印本次检测所使用的参数

# 解析命令行参数
def parse_opt():
    # 创建 ArgumentParser 对象 parser
    parser = argparse.ArgumentParser()

    # 对 ArgumentParser 对象 parser 添加命令行参数及其选项和说明 ( 编写命令行时的参考 )
    parser.add_argument('--weights', nargs='+', type=str, default=ROOT / 'yolov5s.pt', help='model path or triton URL')
    parser.add_argument('--source', type=str, default=ROOT / 'data/images', help='file/dir/URL/glob/screen/0(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-csv', action='store_true', help='save results in CSV format')
    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')
    parser.add_argument('--vid-stride', type=int, default=1, help='video frame-rate stride')

    # 分析命令行参数
    opt = parser.parse_args()
    # 如果 imgsz 参数只有一个值,将修改该参数为两个值,以适应 yolov5 代码的要求
    opt.imgsz *= 2 if len(opt.imgsz) == 1 else 1  # expand
    # 将在终端打印已解析的参数
    print_args(vars(opt))
    # 返回已解析的参数
    return opt

然后程序会将 parse_opt 函数解析出的结果 opt 作为参数传给主函数 main

# 主函数 main
def main(opt):
    # 检查是否满足 requirements.txt 文件中指定的要求   不包括 “tensorboard” 和 “hop”
    check_requirements(ROOT / 'requirements.txt', exclude=('tensorboard', 'thop'))
    # 使用 “opt” 参数中的选项运行 run 功能模块
    run(**vars(opt))

main 函数检查完 requirements.txt 文件中指定的要求后再将 opt 作为参数传给函数 run

第四部分: run 函数接收并检测参数信息, 做一些准备工作

@smart_inference_mode()
def run(
        weights=ROOT / 'yolov5s.pt',      # model path or triton URL                       选择本地权重文件或通过 Triton URL 网络调用权重文件
        source=ROOT / 'data/images',      # file/dir/URL/glob/screen/0(webcam)             选择要处理的图像或视频或目录或 URL 或 glob 表达式或屏幕截图或 0(相机)
        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                              设置 NMS 阈值
        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                          选择是否保存结果到 txt 文件
        save_csv=False,                   # save results to *.csv                          选择是否保存结果到 csv 文件
        save_conf=False,                  # save confidences in --save-txt labels          选择是否保存置信度到 txt 文件
        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                             选择是否使用类无关的 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              选择是否使用 FP16 半精度推理
        dnn=False,                        # use OpenCV DNN for ONNX inference              选择是否使用 OpenCV DNN 进行 ONNX 推理
        vid_stride=1,                     # video frame-rate stride                        设置视频帧率步长
):

以上代码是 run 函数在接收传给它的参数信息并设置一些之后会用到的标识符

这些标识符的含义 ( 中英文 ) 笔者在上面都写好了, 这些标识符其实都有默认的值, 大家有兴趣的话可以进行更改, 运行一下看一看效果

    # 将要处理的源的路径转换为字符串类型
    source = str(source)

    # 根据条件确定是否保存推理图像:  1. 如果 nosave 是 False   2. 如果源文件不是以 “.txt” 结尾
    save_img = not nosave and not source.endswith('.txt')
    # 检查源文件是否为支持图像或视频格式的文件
    is_file = Path(source).suffix[1:] in (IMG_FORMATS + VID_FORMATS)
    # 检查源是否是具有支持协议的 URL
    is_url = source.lower().startswith(('rtsp://', 'rtmp://', 'http://', 'https://'))
    # 根据数字检查、文件扩展名和 URL 格式确定源是否来自摄像头
    webcam = source.isnumeric() or source.endswith('.streams') or (is_url and not is_file)
    # 检查源是否用于截屏
    screenshot = source.lower().startswith('screen')
    # 如果源是 URL 和文件,将检查并可能下载该文件
    if is_url and is_file:
        source = check_file(source) 

接下来程序通过这些参数的值来判断出源的类型, 做一些检查工作, 如果是URL的话还会进行下载

    # 定义并创建一个新目录,用于保存具有递增路径的项目   目录名是 “project” 和 “name” 的组合
    # 如果目录已经存在,则会递增以避免覆盖
    # 如果 save_txt 为 True,会在 save_dir 中创建一个子目录 “labels” 以保存图片对应的文本文件( 储存标注框的信息 )
    # 如果 save_txt 为 False,则直接创建不带子目录的 save_dir 
    # 确保所有父目录都已创建( 如果它们不存在 )
    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)  

并且还会定义一个新目录,用于保存具有递增路径的项目 ( 即最终的结果文件 )

第五部分: 加载模型和数据集

    """
    - bs: 数据加载的批大小
    - device: 加载模型的设备
    - weights: 权重文件
    - dnn: 即 DNN   可能用到的深度神经网络
    - data: 数据集的配置文件
    - half: 半精度推理
    - imgsz: 输入图像的大小
    - webcam: 用于表明输入是否来自网络摄像头的标志
    - screenshot: 用于表明输入是否为屏幕截图的标志
    - source: 输入源( 例如,视频文件或图像目录 )
    - vid_stride: 用于视频处理的帧步长
    - dataset: 加载的数据集
    - view_img: 用于显示图像的标志
    - vid_path: 视频路径存储器
    - vid_writer: 视频存储器
    """

这里笔者先给出自己整理的该部分会用到的一些标识符及其含义 ( 可以先不看, 在下面遇到了再回上来看 )

    # 选择加载模型的设备 有 GPU 则选择 GPU,没有则选择 CPU
    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)

首先程序会选择加载模型的设备  有 GPU 则选择 GPU,没有则选择 CPU

再根据你的环境配置选择如何加载模型  比如: 用 .pt 文件, 则通过 pytorch 方式来加载模型

然后读取模型数据  后面需要用到

再检查输入图像的尺寸是否符合模型要求

    # 表明每次输入一张图片
    bs = 1 

    # 如果摄像头为 True,则会通过 LoadStreams 函数来进行数据集初始化
    if webcam:
        view_img = check_imshow(warn=True)
        dataset = LoadStreams(source, img_size=imgsz, stride=stride, auto=pt, vid_stride=vid_stride)
        bs = len(dataset)
    # 如果屏幕截图为 True,则会通过 LoadScreenshots 函数来进行数据集初始化
    elif screenshot:
        dataset = LoadScreenshots(source, img_size=imgsz, stride=stride, auto=pt)
    # 如果网络摄像头和屏幕截图都不是 True,则会通过 LoadImages 函数来进行数据集初始化
    else:
        dataset = LoadImages(source, img_size=imgsz, stride=stride, auto=pt, vid_stride=vid_stride)
    # 将 vid_path 和 vid_writer 初始化为长度为 bs 的列表,作为视频路径存储器和视频存储器
    vid_path, vid_writer = [None] * bs, [None] * bs

接着根据之前对源类型的判断, 使用对应的方式来加载数据集

第六部分: 运行模型, 进行推理

    # 模型预热
    model.warmup(imgsz=(1 if pt or model.triton else bs, 3, *imgsz))

    # 创建一些中间变量
    seen, windows, dt = 0, [], (Profile(), Profile(), Profile())

首先对模型进行预热  其实就是传给模型一张空白的图片先运行一遍

再创建一些中间变量  seen 用于图像的计数  windows 用于后续图像的展示  dt 用于记录处理时长

    # 遍历数据集
    for path, im, im0s, vid_cap, s in dataset:

接着遍历数据集, 每一次遍历可能会处理多张图像, 一般一次只处理一张 ( 由上文的 bs 决定 )

        # 使用配置文件 dt[0] 处理图像
        with dt[0]:
            # 将图像转换为模型支持的格式 ( 一般会将原图缩放为模型要求的尺寸 ) 并移动到处理设备( 即 GPU 或 CPU )
            im = torch.from_numpy(im).to(model.device)
            # 如果模型是 fp16,则将图像转换为半精度,否则转换为浮点
            im = im.half() if model.fp16 else im.float()  
            # 将图像的每一个像素点的值从 0-255 规格化到 0.0-1.0
            im /= 255  
        # 如果图像只有3个维度( 通道数, 高度, 宽度 ),则再增加一个维度
        if len(im.shape) == 3:
                im = im[None]

 先进行图像的预处理

        # 使用配置文件 dt[1] 处理图像   
        with dt[1]:            
            # 如果 visualize 为 True,则将保存推理过程中的特征图
            visualize = increment_path(save_dir / Path(path).stem, mkdir=True) if visualize else False
            # 进行推理并获取预测结果 pred ( 包含大量的预测框、置信度、类别等信息 ), 如果 augment 为 True,则还会对图像进行数据增强
            pred = model(im, augment=augment, visualize=visualize)
            # 最终结果格式: pred [图片数量, 预测框数量, 预测框信息(中心点x坐标, 中心点y坐标, 宽度, 高度, 置信度, 80个类别)]
            # 举例: pred [1, 10000, 85]   每一个预测框包含 85 个信息,分别是中心点x坐标、中心点y坐标、宽度、高度、置信度、80个类别

再进行预测框检测 ( 程序的核心 )

结果格式: pred [ 图片数量, 预测框数量, 预测框信息 ( x1, y1, x2, y2, 置信度, 类别 ) ]

举例: pred [ 1, 10000, 85 ] 每一个预测框包含 85 个信息,分别是中心点x坐标、中心点y坐标、宽度、高度、置信度、80个类别

        # 使用配置文件 dt[2] 处理图像   
        with dt[2]:
            # 进行 NMS 非极大值抑制 过滤掉置信度低于 conf_thres 的预测框
            pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det)
            # 最终结果格式: pred [图片数量, 预测框数量, 预测框信息(x1, y1, x2, y2、置信度、类别)]
            # 举例: pred [1, 5, 6]   每一个预测框包含 6 个信息,分别是左上角x坐标、左上角y坐标、右下角x坐标、右下角y坐标、置信度、类别

然后是 NMS 非极大值抑制 过滤掉置信度低于 conf_thres 的预测框

结果格式: pred [ 图片数量, 预测框数量, 预测框信息 ( x1, y1, x2, y2、置信度、类别 ) ]

举例: pred [ 1, 5, 6 ] 每一个预测框包含 6 个信息,分别是左上角x坐标、左上角y坐标、右下角x坐标、右下角y坐标、置信度、对应的类别

第七部分: 定义一个将数据保存到 CSV 文件的函数

        """
        将预测结果写入 CSV 文件
        参数:
            - image_name: 图像的名称
            - prediction: 预测类标签
            - confidence: 预测的置信度得分
        """

        # 定义 CSV 文件的保存路径
        csv_path = save_dir / 'predictions.csv'
        # 定义一个保存数据到 CSV 文件的函数 ( 如果 save_csv 为 True, 后面的代码则会调用该函数 )
        def write_to_csv(image_name, prediction, confidence):
            data = {'Image Name': image_name, 'Prediction': prediction, 'Confidence': confidence}
            with open(csv_path, mode='a', newline='') as f:
                writer = csv.DictWriter(f, fieldnames=data.keys())
                if not csv_path.is_file():
                    writer.writeheader()
                writer.writerow(data)

如果 save_csv 设置为 True, 则后面的代码会调用该函数, 但是程序默认设置为 false, 一般用不到, 就不详细展开了

第八部分: 保存数据, 展示处理好的图像

        # 遍历预测后的数据集中的每一张图片
        for i, det in enumerate(pred): 

因为数据集的一次处理中通常只有一个图片, 所以通常只遍历了一次

第一点: 做一些准备工作

            # 记录已处理的图片数量 起到计数作用
            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 = Path(p)
            # 创建图像文件的保存路径 ( 如果 save_img 为 True )
            save_path = str(save_dir / p.name)
            # 创建文本文件的保存路径 ( 如果 save_txt 为 True )
            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]]
            # 将检测框裁剪并保存 ( 如果 save_crop 为 True )
            imc = im0.copy() if save_crop else im0
            # 设置检测框的绘制方式
            annotator = Annotator(im0, line_width=line_thickness, example=str(names))

这部分进行了很多重要操作:

        设置了文件的保存路径

        存储了之后终端需要输出的图像尺寸

        获得原图的尺寸

        将检测框裁剪并保存 ( 如果 save_crop 为 True )

        设置检测框的绘制方式

第二点: 存储预测框信息

            # 如果这张图片存在预测框, 则遍历所有预测框并绘制保存每一个预测框的信息
            if len(det):

如果这张图片存在预测框, 则进行如下这些操作

                # 进行坐标映射  将检测框从目前图像大小重新缩放为原图对应的大小    
                det[:, :4] = scale_boxes(im.shape[2:], det[:, :4], im0.shape).round()

进行坐标映射 将检测框从目前图像大小重新缩放为原图对应的大小        

原因:  为了满足模型支持的尺寸格式要求, 先前对原图进行了缩放操作, 导致识别出的预测框相对于原图也有一定的缩放度, 现在为了在原图上画出预测框, 则必须将预测框再缩放回来

                # 储存之后要在终端输出的预测框信息 ( 即为后续的终端输出做准备 )
                for c in det[:, 5].unique():
                    # 计算每一个种类的检测框数量 n
                    n = (det[:, 5] == c).sum() 
                    # 存储每一个种类与其对应的检测框数量到字符串 s 中
                    s += f"{n} {names[int(c)]}{'s' * (n > 1)}, "  

储存之后要在终端输出的预测框信息 ( 即为后续的终端输出做准备 )

                # 存储每一个预测框的图像结果
                for *xyxy, conf, cls in reversed(det):

遍历一张图片上的所有预测框

                    # 获取当前预测框对应的种类编号
                    c = int(cls)
                    # 获取对象的标签,如果 hide_conf 为 False,则还包括置信度
                    label = names[c] if hide_conf else f'{names[c]}'
                    # 将置信度分数转换为浮点值
                    confidence = float(conf)
                    # 将置信度分数格式化为带两位小数的字符串
                    confidence_str = f'{confidence:.2f}'

保存这些预测框的标签和置信度信息, 后续绘图需要用到

                    # 保存预测结果到 CSV 文件 ( 如果 save_csv 为 True )
                    if save_csv:
                        write_to_csv(p.name, label, confidence_str)
                    # 保存预测结果到 TXT 文件 ( 如果 save_txt 为 True )
                    if save_txt:  
                        xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist()  # normalized xywh
                        line = (cls, *xywh, conf) if save_conf else (cls, *xywh)  # label format
                        with open(f'{txt_path}.txt', 'a') as f:
                            f.write(('%g ' * len(line)).rstrip() % line + '\n')
                    # 绘制检测框到原图上 ( 如果 save_img 或 save_crop 或 view_img 为 True )
                    if save_img or save_crop or view_img:  
                        # 获取当前预测框对应的种类编号
                        c = int(cls) 
                        # 获取对象的标签( 如果 hide_labels 为 False ),获取对象的置信度( 如果 hide_conf 为 False )
                        label = None if hide_labels else (names[c] if hide_conf else f'{names[c]} {conf:.2f}')
                        # 使用 box_label 函数绘制检测框到原图上
                        annotator.box_label(xyxy, label, color=colors(c, True))
                    # 保存预测框对应的截取图像 ( 如果 save_crop 为 True )
                    if save_crop:
                        save_one_box(xyxy, imc, file=save_dir / 'crops' / names[c] / f'{p.stem}.jpg', BGR=True)

根据之前设置好的标识符, 判断是否要进行各种文件的保存

重点关注 annotator.box_label(xyxy, label, color=colors(c, True)) 这一行代码, 它使用了 box_label 函数绘制了预测框到原图上

另外注意这里有对多种文件的保存, 但并没有对画好预测框的图像进行保存, 而这会在展示预测框模块里进行实现

第三点: 展示并保存已经绘制好预测框后的图像

            # 获取已经绘制好预测框后的图像
            im0 = annotator.result()

获取已经绘制好预测框后的图像

            # 如果 view_img 为 True,则会创建一个窗口展示这些图像 ( 主要使用 cv2 模块中的函数 )
            if view_img:
                if platform.system() == 'Linux' and p not in windows:
                    windows.append(p)
                    cv2.namedWindow(str(p), cv2.WINDOW_NORMAL | cv2.WINDOW_KEEPRATIO)  # allow window resize (Linux)
                    cv2.resizeWindow(str(p), im0.shape[1], im0.shape[0])
                cv2.imshow(str(p), im0)
                cv2.waitKey(1)  # 1 millisecond

如果 view_img 为 True,则会创建一个窗口展示这些图像 ( 主要使用 cv2 模块中的函数 )

            # 保存已经绘制好预测框后的图像 ( 如果 save_img 为 True )
            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)

保存已经绘制好预测框后的图像 ( 如果 save_img 为 True )

第四点: 在终端打印完成一次数据集检测, 保存, 展示的总耗时

        # 在终端打印本轮检测所花费的时间,使用 “dt” 变量转换为毫秒
        LOGGER.info(f"{s}{'' if len(det) else '(no detections), '}{dt[1].dt * 1E3:.1f}ms")

第九部分: 终端的总结和模型的更新

    # 计算平均检测时间并在终端打印
    t = tuple(x.t / 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)
    # 在终端打印保存结果的路径 ( 如果 save_img 或 save_txt 为 True )
    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}")

在完成所有数据集检测之后在终端打印平均检测时间, 保存结果的路径等信息

    # 更新模型 ( 如果 update 为 True )
    if update:
        strip_optimizer(weights[0])  # update model (to fix SourceChangeWarning)

更新模型 ( 如果 update 为 True )

至此, detect.py 文件已经完整地过了一遍, 但笔者其实也只是对主要的关键部分进行了讲解, 一些细节只能大家自己去看源码来进行理解 ( 当然我对 detect.py 文件的注释也是大家重要的参考 )

最后, 希望当大家看到笔者在哪里的讲解不够全面, 或者哪里有什么错误, 还望大家帮忙指出, 笔者将感激不尽, 感谢您的耐心阅读 !

最后再附上完整的 detect.py 文件 ( 包含笔者的注释 )

# YOLOv5 🚀 by Ultralytics, AGPL-3.0 license


################################### 如何使用 detect.py 脚本 ( 命令行模板 ) ###################################

"""
Run YOLOv5 detection inference on images, videos, directories, globs, YouTube, webcam, streams, etc.

Usage - sources:
    $ python detect.py --weights yolov5s.pt --source 0                               # webcam
                                                     img.jpg                         # image
                                                     vid.mp4                         # video
                                                     screen                          # screenshot
                                                     path/                           # directory
                                                     list.txt                        # list of images
                                                     list.streams                    # list of streams
                                                     'path/*.jpg'                    # glob
                                                     'https://youtu.be/LNwODJXcvt4'  # YouTube
                                                     'rtsp://example.com/media.mp4'  # RTSP, RTMP, HTTP stream

Usage - formats:
    $ python detect.py --weights yolov5s.pt                 # PyTorch
                                 yolov5s.torchscript        # TorchScript
                                 yolov5s.onnx               # ONNX Runtime or OpenCV DNN with --dnn
                                 yolov5s_openvino_model     # OpenVINO
                                 yolov5s.engine             # TensorRT
                                 yolov5s.mlmodel            # CoreML (macOS-only)
                                 yolov5s_saved_model        # TensorFlow SavedModel
                                 yolov5s.pb                 # TensorFlow GraphDef
                                 yolov5s.tflite             # TensorFlow Lite
                                 yolov5s_edgetpu.tflite     # TensorFlow Edge TPU
                                 yolov5s_paddle_model       # PaddlePaddle
"""


# 导入必要的库
import argparse
import csv
import os
import platform
import sys
from pathlib import Path
import torch


# 定义 FILE 和 ROOT 路径
FILE = Path(__file__).resolve()    # 找到当前文件 detect.py 的绝对路径   resolve 表示将相对路径转为绝对路径
ROOT = FILE.parents[0]             # 找到 YOLOv5 根目录
# 将 YOLOv5 根目录添加到 sys.path( 如果尚未存在 )
if str(ROOT) not in sys.path:
    sys.path.append(str(ROOT))  
# 获取 YOLOv5 根目录的路径
ROOT = Path(os.path.relpath(ROOT, Path.cwd()))  


# 从外部模块导入所需的函数/类
from ultralytics.utils.plotting import Annotator, colors, save_one_box
from models.common import DetectMultiBackend
from utils.dataloaders import IMG_FORMATS, VID_FORMATS, LoadImages, LoadScreenshots, LoadStreams
from utils.general import (LOGGER, Profile, check_file, check_img_size, check_imshow, check_requirements, colorstr, cv2,
                           increment_path, non_max_suppression, print_args, scale_boxes, strip_optimizer, xyxy2xywh)
from utils.torch_utils import select_device, smart_inference_mode


################################### detect.py 脚本的功能模块 ( detect.py 脚本的灵魂 ) ###################################

@smart_inference_mode()
def run(
        weights=ROOT / 'yolov5s.pt',      # model path or triton URL                       选择本地权重文件或通过 Triton URL 网络调用权重文件
        source=ROOT / 'data/images',      # file/dir/URL/glob/screen/0(webcam)             选择要处理的图像或视频或目录或 URL 或 glob 表达式或屏幕截图或 0(相机)
        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                              设置 NMS 阈值
        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                          选择是否保存结果到 txt 文件
        save_csv=False,                   # save results to *.csv                          选择是否保存结果到 csv 文件
        save_conf=False,                  # save confidences in --save-txt labels          选择是否保存置信度到 txt 文件
        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                             选择是否使用类无关的 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              选择是否使用 FP16 半精度推理
        dnn=False,                        # use OpenCV DNN for ONNX inference              选择是否使用 OpenCV DNN 进行 ONNX 推理
        vid_stride=1,                     # video frame-rate stride                        设置视频帧率步长
):
    # 将要处理的源的路径转换为字符串类型
    source = str(source)

    # 根据条件确定是否保存推理图像:  1. 如果 nosave 是 False   2. 如果源文件不是以 “.txt” 结尾
    save_img = not nosave and not source.endswith('.txt')
    # 检查源文件是否为支持图像或视频格式的文件
    is_file = Path(source).suffix[1:] in (IMG_FORMATS + VID_FORMATS)
    # 检查源是否是具有支持协议的 URL
    is_url = source.lower().startswith(('rtsp://', 'rtmp://', 'http://', 'https://'))
    # 根据数字检查、文件扩展名和 URL 格式确定源是否来自摄像头
    webcam = source.isnumeric() or source.endswith('.streams') or (is_url and not is_file)
    # 检查源是否用于截屏
    screenshot = source.lower().startswith('screen')
    # 如果源是 URL 和文件,将检查并可能下载该文件
    if is_url and is_file:
        source = check_file(source)  

    # 定义并创建一个新目录,用于保存具有递增路径的项目   目录名是 “project” 和 “name” 的组合
    # 如果目录已经存在,则会递增以避免覆盖
    # 如果 save_txt 为 True,会在 save_dir 中创建一个子目录 “labels” 以保存图片对应的文本文件( 储存标注框的信息 )
    # 如果 save_txt 为 False,则直接创建不带子目录的 save_dir 
    # 确保所有父目录都已创建( 如果它们不存在 )
    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)  


    ###################################### 加载模型和数据集 ######################################

    """
    - bs: 数据加载的批大小
    - device: 加载模型的设备
    - weights: 权重文件
    - dnn: 即 DNN   可能用到的深度神经网络
    - data: 数据集的配置文件
    - half: 半精度推理
    - imgsz: 输入图像的大小
    - webcam: 用于表明输入是否来自网络摄像头的标志
    - screenshot: 用于表明输入是否为屏幕截图的标志
    - source: 输入源( 例如,视频文件或图像目录 )
    - vid_stride: 用于视频处理的帧步长
    - dataset: 加载的数据集
    - view_img: 用于显示图像的标志
    - vid_path: 视频路径存储器
    - vid_writer: 视频存储器
    """

    # 选择加载模型的设备 有 GPU 则选择 GPU,没有则选择 CPU
    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)

    # 表明每次输入一张图片
    bs = 1 

    # 如果摄像头为 True,则会通过 LoadStreams 函数来进行数据集初始化
    if webcam:
        view_img = check_imshow(warn=True)
        dataset = LoadStreams(source, img_size=imgsz, stride=stride, auto=pt, vid_stride=vid_stride)
        bs = len(dataset)
    # 如果屏幕截图为 True,则会通过 LoadScreenshots 函数来进行数据集初始化
    elif screenshot:
        dataset = LoadScreenshots(source, img_size=imgsz, stride=stride, auto=pt)
    # 如果网络摄像头和屏幕截图都不是 True,则会通过 LoadImages 函数来进行数据集初始化
    else:
        dataset = LoadImages(source, img_size=imgsz, stride=stride, auto=pt, vid_stride=vid_stride)
    # 将 vid_path 和 vid_writer 初始化为长度为 bs 的列表,作为视频路径存储器和视频存储器
    vid_path, vid_writer = [None] * bs, [None] * bs


    ########################################### 运行模型 ( 主角终于来了 ) ##########################################

    # 模型预热
    model.warmup(imgsz=(1 if pt or model.triton else bs, 3, *imgsz))

    # 创建一些中间变量
    seen, windows, dt = 0, [], (Profile(), Profile(), Profile())

    # 遍历数据集
    for path, im, im0s, vid_cap, s in dataset:
        
        ############################################ 进行推理 ############################################

        # 使用配置文件 dt[0] 处理图像
        with dt[0]:
            # 将图像转换为模型支持的格式 ( 一般会将原图缩放为模型要求的尺寸 ) 并移动到处理设备( 即 GPU 或 CPU )
            im = torch.from_numpy(im).to(model.device)
            # 如果模型是 fp16,则将图像转换为半精度,否则转换为浮点
            im = im.half() if model.fp16 else im.float()  
            # 将图像的每一个像素点的值从 0-255 规格化到 0.0-1.0
            im /= 255  

        # 如果图像只有3个维度( 通道数, 高度, 宽度 ),则再增加一个维度
        if len(im.shape) == 3:
                im = im[None]

        # 使用配置文件 dt[1] 处理图像   
        with dt[1]:            
            # 如果 visualize 为 True,则将保存推理过程中的特征图
            visualize = increment_path(save_dir / Path(path).stem, mkdir=True) if visualize else False
            # 进行推理并获取预测结果 pred ( 包含大量的预测框、置信度、类别等信息 ), 如果 augment 为 True,则还会对图像进行数据增强
            pred = model(im, augment=augment, visualize=visualize)
            # 最终结果格式: pred [图片数量, 预测框数量, 预测框信息(中心点x坐标, 中心点y坐标, 宽度, 高度, 置信度, 80个类别)]
            # 举例: pred [1, 10000, 85]   每一个预测框包含 85 个信息,分别是中心点x坐标、中心点y坐标、宽度、高度、置信度、80个类别

        # 使用配置文件 dt[2] 处理图像   
        with dt[2]:
            # 进行 NMS 非极大值抑制 过滤掉置信度低于 conf_thres 的预测框
            pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det)
            # 最终结果格式: pred [图片数量, 预测框数量, 预测框信息(x1, y1, x2, y2、置信度、类别)]
            # 举例: pred [1, 5, 6]   每一个预测框包含 6 个信息,分别是左上角x坐标、左上角y坐标、右下角x坐标、右下角y坐标、置信度、类别
        

        ################################# 定义一个保存数据到 CSV 文件的函数 #################################
            
        """
        将预测结果写入 CSV 文件
        参数:
            - image_name: 图像的名称
            - prediction: 预测类标签
            - confidence: 预测的置信度得分
        """

        # 定义 CSV 文件的保存路径
        csv_path = save_dir / 'predictions.csv'
        # 定义一个保存数据到 CSV 文件的函数 ( 如果 save_csv 为 True, 后面的代码则会调用该函数 )
        def write_to_csv(image_name, prediction, confidence):
            data = {'Image Name': image_name, 'Prediction': prediction, 'Confidence': confidence}
            with open(csv_path, mode='a', newline='') as f:
                writer = csv.DictWriter(f, fieldnames=data.keys())
                if not csv_path.is_file():
                    writer.writeheader()
                writer.writerow(data)

        
        ################################################# 保存数据 ############################################
                
        # 遍历预测后的数据集中的每一张图片
        for i, det in enumerate(pred): 

            # 记录已处理的图片数量 起到计数作用
            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 = Path(p)
            # 创建图像文件的保存路径 ( 如果 save_img 为 True )
            save_path = str(save_dir / p.name)
            # 创建文本文件的保存路径 ( 如果 save_txt 为 True )
            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]]
            # 将检测框裁剪并保存 ( 如果 save_crop 为 True )
            imc = im0.copy() if save_crop else im0
            # 设置检测框的绘制方式
            annotator = Annotator(im0, line_width=line_thickness, example=str(names))


            ########################### 遍历预测框并绘制保存每一个预测框的信息 ##############################

            # 如果这张图片存在预测框, 则遍历所有预测框并绘制保存每一个预测框的信息
            if len(det):

                # 进行坐标映射  将检测框从目前图像大小重新缩放为原图对应的大小    
                det[:, :4] = scale_boxes(im.shape[2:], det[:, :4], im0.shape).round()

                # 储存之后要在终端输出的预测框信息 ( 即为后续的终端输出做准备 )
                for c in det[:, 5].unique():
                    # 计算每一个种类的检测框数量 n
                    n = (det[:, 5] == c).sum() 
                    # 存储每一个种类与其对应的检测框数量到字符串 s 中
                    s += f"{n} {names[int(c)]}{'s' * (n > 1)}, "  

                # 存储每一个预测框的图像结果
                for *xyxy, conf, cls in reversed(det):

                    # 获取当前预测框对应的种类编号
                    c = int(cls)
                    # 获取对象的标签,如果 hide_conf 为 False,则还包括置信度
                    label = names[c] if hide_conf else f'{names[c]}'
                    # 将置信度分数转换为浮点值
                    confidence = float(conf)
                    # 将置信度分数格式化为带两位小数的字符串
                    confidence_str = f'{confidence:.2f}'

                    # 保存预测结果到 CSV 文件 ( 如果 save_csv 为 True )
                    if save_csv:
                        write_to_csv(p.name, label, confidence_str)
                    # 保存预测结果到 TXT 文件 ( 如果 save_txt 为 True )
                    if save_txt:  
                        xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist()  # normalized xywh
                        line = (cls, *xywh, conf) if save_conf else (cls, *xywh)  # label format
                        with open(f'{txt_path}.txt', 'a') as f:
                            f.write(('%g ' * len(line)).rstrip() % line + '\n')
                    # 绘制检测框到原图上 ( 如果 save_img 或 save_crop 或 view_img 为 True )
                    if save_img or save_crop or view_img:  
                        # 获取当前预测框对应的种类编号
                        c = int(cls) 
                        # 获取对象的标签( 如果 hide_labels 为 False ),获取对象的置信度( 如果 hide_conf 为 False )
                        label = None if hide_labels else (names[c] if hide_conf else f'{names[c]} {conf:.2f}')
                        # 使用 box_label 函数绘制检测框到原图上
                        annotator.box_label(xyxy, label, color=colors(c, True))
                    # 保存预测框对应的截取图像 ( 如果 save_crop 为 True )
                    if save_crop:
                        save_one_box(xyxy, imc, file=save_dir / 'crops' / names[c] / f'{p.stem}.jpg', BGR=True)


            ############################# 展示并保存已经绘制好预测框后的图像 #############################
                        
            # 获取已经绘制好预测框后的图像
            im0 = annotator.result()

            # 如果 view_img 为 True,则会创建一个窗口展示这些图像 ( 主要使用 cv2 模块中的函数 )
            if view_img:
                if platform.system() == 'Linux' and p not in windows:
                    windows.append(p)
                    cv2.namedWindow(str(p), cv2.WINDOW_NORMAL | cv2.WINDOW_KEEPRATIO)  # allow window resize (Linux)
                    cv2.resizeWindow(str(p), im0.shape[1], im0.shape[0])
                cv2.imshow(str(p), im0)
                cv2.waitKey(1)  # 1 millisecond

            # 保存已经绘制好预测框后的图像 ( 如果 save_img 为 True )
            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)

        # 在终端打印本轮检测所花费的时间,使用 “dt” 变量转换为毫秒
        LOGGER.info(f"{s}{'' if len(det) else '(no detections), '}{dt[1].dt * 1E3:.1f}ms")

    # 计算平均检测时间并在终端打印
    t = tuple(x.t / 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)
    # 在终端打印保存结果的路径 ( 如果 save_img 或 save_txt 为 True )
    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}")
    # 更新模型 ( 如果 update 为 True )
    if update:
        strip_optimizer(weights[0])  # update model (to fix SourceChangeWarning)


# 解析命令行参数
def parse_opt():
    # 创建 ArgumentParser 对象 parser
    parser = argparse.ArgumentParser()

    # 对 ArgumentParser 对象 parser 添加命令行参数及其选项和说明 ( 编写命令行时的参考 )
    parser.add_argument('--weights', nargs='+', type=str, default=ROOT / 'yolov5s.pt', help='model path or triton URL')
    parser.add_argument('--source', type=str, default=ROOT / 'data/images', help='file/dir/URL/glob/screen/0(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-csv', action='store_true', help='save results in CSV format')
    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')
    parser.add_argument('--vid-stride', type=int, default=1, help='video frame-rate stride')

    # 分析命令行参数
    opt = parser.parse_args()
    # 如果 imgsz 参数只有一个值,将修改该参数为两个值,以适应 yolov5 代码的要求
    opt.imgsz *= 2 if len(opt.imgsz) == 1 else 1  # expand
    # 将在终端打印已解析的参数
    print_args(vars(opt))
    # 返回已解析的参数
    return opt


# 主函数 main
def main(opt):
    # 检查是否满足 requirements.txt 文件中指定的要求   不包括 “tensorboard” 和 “hop”
    check_requirements(ROOT / 'requirements.txt', exclude=('tensorboard', 'thop'))
    # 使用 “opt” 参数中的选项运行 run 功能模块
    run(**vars(opt))


# 程序的入口
if __name__ == '__main__':
    # 它首先使用 parse_opt 函数解析命令行选项
    opt = parse_opt()  
    # 然后,它以解析后的选项作为参数调用主函数 main
    main(opt)  

  • 27
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
### 回答1: 要在 PyCharm 安装 ultralytics 库,请按照以下步骤操作: 1. 打开 PyCharm 并创建一个新项目。 2. 在 PyCharm 打开终端窗口。可以通过点击底部的“终端”标签来打开终端窗口。 3. 在终端窗口输入以下命令来安装 ultralytics 库: ``` pip install ultralytics ``` 4. 等待安装完成后,您就可以在 PyCharm 项目使用 ultralytics 库了。 请注意,如果您使用的是虚拟环境,请确保在虚拟环境安装 ultralytics 库。 ### 回答2: 要在PyCharm安装ultralytics库,您可以按照以下步骤进行操作: 1. 首先,打开PyCharm并创建一个新的项目或打开现有项目。 2. 点击顶部菜单栏的“文件”选项,然后选择“设置”以打开项目设置。 3. 在项目设置,选择“解释器和虚拟环境”选项。 4. 在解释器和虚拟环境设置页面的右上角,点击“+”按钮。 5. 在弹出窗口,选择“搜索”选项。 6. 在搜索框输入“ultralytics”并点击右侧的放大镜图标进行搜索。 7. 在搜索结果,您将看到ultralytics库。选择它并点击“安装包”按钮。 8. PyCharm将自动下载和安装ultralytics库及其依赖项。 9. 安装完成后,您可以看到ultralytics库出现在项目解释器和虚拟环境设置页面的已安装软件包列表。 10. 点击“确定”按钮保存设置并关闭项目设置页面。 现在,您已经成功将ultralytics库安装到PyCharm。您可以在项目使用import语句来引入ultralytics库并开始使用它的功能。 ### 回答3: 要在PyCharm安装ultralytics库,可以按照以下步骤进行操作: 1. 打开PyCharm并创建一个新的Python项目或者打开现有的项目。 2. 在PyCharm的顶部菜单选择“File”(文件)>“Settings”(设置)。 3. 在设置窗口,选择“Project: [项目名称]”>“Project Interpreter”(项目解释器)。 4. 在右侧的项目解释器窗口,点击“+”图标,在弹出窗口选择“Manage Python Interpreters”(管理Python解释器)。 5. 在“Python Interpreters”(Python解释器)窗口,如果已经存在ultralytics库,可以在列表找到该库。如果没有,请点击右侧的“+”按钮。 6. 在弹出窗口,可以选择从项目依赖添加库,也可以选择通过搜索来安装库。点击搜索框并输入“ultralytics”,然后点击“Install Package”(安装包)按钮。 7. 等待安装完成后,可以关闭设置窗口。 现在,ultralytics库已经成功安装在PyCharm。您可以在代码导入该库并开始使用其提供的功能了。在代码导入ultralytics库的语句通常为: ```python import ultralytics ``` 请注意,确保您的Python解释器已经正确配置,并且与您的项目相匹配。如果您使用的是虚拟环境,请确保在正确的环境安装和使用ultralytics库。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值