目标检测经典模型之YOLOV5-detect.py源码解析(持续更新)

07-21 1179阅读

detect文件框架

  • 一、导入模块包
  • 二、定义run函数
    • 1. 归一化操作
      • 代码解析
        • uint8
        • 精度转换
        • 归一化
        • 2. 扩展维度
          • 为什么扩展维度?
          • 代码解释
          • 3. 对检测结果类别计数
            • 检查是否有检测结果
            • 统计每个类别的出现次数
            • 构建描述性字符串
            • 三、定义命令行参数
            • 四、主函数

              本帖是YOLOV5推理部分代码的中文逐行注释。由于AI注释的缘故,可能与源码会有小部分出入,所以不建议复制粘贴替换源码的detect.py文件。本贴的初衷是YOLOV5源码逻辑的学习,后续会不断修正该代码和加入新的注释。

              目标检测经典模型之YOLOV5-detect.py源码解析(持续更新)
              (图片来源网络,侵删)

              一、导入模块包

              import argparse  # 引入argparse库,用于解析命令行参数
              import os  # 引入os库,用于进行操作系统相关的操作
              import platform  # 引入platform库,用于获取平台信息
              import sys  # 引入sys库,用于操作Python运行时环境
              from pathlib import Path  # 引入Path库,用于处理文件和目录路径
              import cv2  # 引入OpenCV库,用于图像处理
              import torch  # 引入PyTorch库,用于深度学习
              FILE = Path(__file__).resolve()  # 获取当前文件的绝对路径
              ROOT = FILE.parents[0]  # 获取当前文件的父目录路径
              if str(ROOT) not in sys.path:  # 如果当前文件的父目录路径不在系统路径中
                  sys.path.append(str(ROOT))  # 将当前文件的父目录路径添加到系统路径中
              ROOT = Path(os.path.relpath(ROOT, Path.cwd()))  # 将ROOT路径相对于当前工作目录进行转换
              from ultralytics.utils.plotting import Annotator, colors, save_one_box
              from models.common import DetectMultiBackend  # 从models.common模块中导入DetectMultiBackend类
              from utils.dataloaders import LoadImages, LoadStreams  # 从utils.dataloaders模块中导入LoadImages和LoadStreams类
              from utils.general import (  # 从utils.general模块中导入多个函数和类
                  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 # 从utils.torch_utils模块中导入select_device和time_sync和smart_inference_mode函数
              

              二、定义run函数

              @smart_inference_mode()
              def run(
                      weights='yolov5s.pt',  # 模型权重文件路径,默认值为'yolov5s.pt'
                      source='data/images',  # 输入源,可以是文件、目录、URL或摄像头,默认值为'data/images'
                      data='data/coco128.yaml',  # 数据集配置文件路径,默认值为'data/coco128.yaml'
                      imgsz=640,  # 输入图像尺寸,默认值为640
                      conf_thres=0.25,  # 置信度阈值,默认值为0.25
                      iou_thres=0.45,  # 非极大值抑制的IoU阈值,默认值为0.45
                      max_det=1000,  # 每张图像的最大检测数量,默认值为1000
                      device='',  # 使用的设备,可以是'cpu'或'cuda:0',默认值为''
                      view_img=False,  # 是否显示检测结果,默认值为False
                      save_txt=False,  # 是否将检测结果保存到文本文件,默认值为False
                      save_conf=False,  # 是否保存置信度,默认值为False
                      save_crop=False,  # 是否保存裁剪后的检测框,默认值为False
                      nosave=False,  # 是否保存图像/视频,默认值为False
                      classes=None,  # 按类别过滤,例如0或0 2 3,默认值为None
                      agnostic_nms=False,  # 是否使用类别无关的NMS,默认值为False
                      augment=False,  # 是否使用增强推理,默认值为False
                      visualize=False,  # 是否可视化特征,默认值为False
                      update=False,  # 是否更新所有模型,默认值为False
                      project='runs/detect',  # 保存结果的项目路径,默认值为'runs/detect'
                      name='exp',  # 保存结果的文件夹名称,默认值为'exp'
                      exist_ok=False,  # 是否允许现有项目名称,默认值为False
                      line_thickness=3,  # 边界框的厚度(像素),默认值为3
                      hide_labels=False,  # 是否隐藏标签,默认值为False
                      hide_conf=False,  # 是否隐藏置信度,默认值为False
                      half=False,  # 是否使用FP16半精度推理,默认值为False
                      dnn=False,  # 是否使用OpenCV DNN进行ONNX推理,默认值为False
                      vid_stride=1,  # 视频帧率步幅,默认值为1
              ):
              	# 将source变量转换为字符串
              	source = str(source)
              	
              	# 判断是否需要保存推理后的图像,除非指定了--nosave或source是文本文件
              	save_img = not nosave and not source.endswith(".txt")  
              	
              	# 判断source是否是一个图像或视频文件
              	is_file = Path(source).suffix[1:] in (IMG_FORMATS + VID_FORMATS)
              	
              	# 判断source是否是一个网络链接
              	is_url = source.lower().startswith(("rtsp://", "rtmp://", "http://", "https://"))
              	
              	# 判断source是否是一个网络摄像头流或屏幕截图指令或一个有效的URL
              	webcam = source.isnumeric() or source.endswith(".streams") or (is_url and not is_file)
              	
              	# 判断source是否是一个屏幕截图指令
              	screenshot = source.lower().startswith("screen")
              	
              	# 如果source是一个有效的URL并且指向一个文件,下载这个文件
              	if is_url and is_file:
              	    source = check_file(source)  
              	
              	# 创建保存结果的目录,如果存在则覆盖或增量命名
              	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)  
              	
              	# 加载模型并选择设备(CPU或GPU)
              	device = select_device(device)
              	model = DetectMultiBackend(weights, device=device, dnn=dnn, data=data, fp16=half)
              	
              	# 获取模型的步幅、类别名以及模型是否是PyTorch模型
              	stride, names, pt = model.stride, model.names, model.pt
              	
              	# 检查并调整图像尺寸以适应模型的步幅
              	imgsz = check_img_size(imgsz, s=stride)  
              	
              	# 设置batch_size为1,因为通常推理是单张图像进行
              	bs = 1  
              	
              	# 根据source类型选择数据加载方式
              	if webcam:
              	    # 对于网络摄像头流,检查是否可以显示图像
              	    view_img = check_imshow(warn=True)
              	    # 加载网络摄像头流数据
              	    dataset = LoadStreams(source, img_size=imgsz, stride=stride, auto=pt, vid_stride=vid_stride)
              	    # 确定batch_size
              	    bs = len(dataset)
              	elif screenshot:
              	    # 加载屏幕截图数据
              	    dataset = LoadScreenshots(source, img_size=imgsz, stride=stride, auto=pt)
              	else:
              	    # 加载普通图像或视频数据
              	    dataset = LoadImages(source, img_size=imgsz, stride=stride, auto=pt, vid_stride=vid_stride)
              	
              	# 初始化视频路径和视频写入器列表
              	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(device=device), Profile(device=device), Profile(device=device))
              	
              	# 遍历数据集中的每一张图片
              	for path, im, im0s, vid_cap, s in dataset:
              	    # 测量预处理时间
              	    with dt[0]:
              	        # 将图像转换为Tensor并移至适当设备
              	        im = torch.from_numpy(im).to(model.device)
              	        # 调整数据类型和归一化
              	        im = im.half() if model.fp16 else im.float()  
              	        im /= 255  
              	        # 扩展维度以匹配batch_size
              	        if len(im.shape) == 3:
              	            im = im[None]  
              	        # 如果模型是XML格式且batch_size大于1,将图像拆分为多个部分
              	        if model.xml and im.shape[0] > 1:
              	            ims = torch.chunk(im, im.shape[0], 0)
              	
              	    # 执行推理
              	    with dt[1]:
              	        # 可视化模式,保存可视化结果
              	        visualize = increment_path(save_dir / Path(path).stem, mkdir=True) if visualize else False
              	        # 如果模型是XML格式且batch_size大于1,逐个执行推理
              	        if model.xml and im.shape[0] > 1:
              	            pred = None
              	            for image in ims:
              	                if pred is None:
              	                    pred = model(image, augment=augment, visualize=visualize).unsqueeze(0)
              	                else:
              	                    pred = torch.cat((pred, model(image, augment=augment, visualize=visualize).unsqueeze(0)), dim=0)
              	            pred = [pred, None]
              	        # 否则直接执行推理
              	        else:
              	            pred = model(im, augment=augment, visualize=visualize)
              	
              	    # 进行非最大值抑制
              	    with dt[2]:
              	        pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det)
              	
              	    # 定义CSV文件路径
              	    csv_path = save_dir / "predictions.csv"
              	
              	    # 定义函数将预测数据写入CSV文件
              	    def write_to_csv(image_name, prediction, confidence):
              	        """将预测数据写入CSV文件,如果文件不存在则创建新文件。"""
              	        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:  
              	            p, im0, frame = path[i], im0s[i].copy(), dataset.count
              	            s += f"{i}: "  
              	        else:
              	            p, im0, frame = path, im0s.copy(), getattr(dataset, "frame", 0)
              	
              	        # 将路径转换为Path对象
              	        p = Path(p)  
              	
              	        # 构建保存图像的路径
              	        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 = Annotator(im0, line_width=line_thickness, example=str(names))
              	
              	        # 如果有检测结果
              	        if len(det):
              	            # 对每个类别进行计数
              	            for c in det[:, 5].unique():
              	                n = (det[:, 5] == c).sum()  
              	                s += f"{n} {names[int(c)]}{'s' * (n > 1)}, "  
              	
              	            # 写入预测结果到CSV文件
              	            if save_csv:
              	                write_to_csv(p.name, label, confidence_str)
              	
              	            # 将检测框坐标从模型输出大小缩放回原图大小
              	            det[:, :4] = scale_boxes(im.shape[2:], det[:, :4], im0.shape).round()
              	
              	            # 将检测结果写入文件或在图像上绘制
              	            for *xyxy, conf, cls in reversed(det):
              	                c = int(cls)  # 整数类别
              	                label = names[c] if hide_conf else f"{names[c]}"  
              	                confidence = float(conf)
              	                confidence_str = f"{confidence:.2f}"
              	
              	                # 如果需要保存CSV文件,写入数据
              	                if save_csv:
              	                    write_to_csv(p.name, label, confidence_str)
              	
              	                # 如果需要保存文本标签文件,写入数据
              	                if save_txt:  
              	                    xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist()  
              	                    line = (cls, *xywh, conf) if save_conf else (cls, *xywh)  
              	                    with open(f"{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)
              	
              	        # 绘制结果
              	        im0 = annotator.result()
              	
              	        # 如果需要显示图像,在窗口中显示
              	        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)  
              	                cv2.resizeWindow(str(p), im0.shape[1], im0.shape[0])
              	            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 = str(Path(save_path).with_suffix(".mp4"))  
              	                    vid_writer[i] = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*"mp4v"), fps, (w, h))
              	                vid_writer[i].write(im0)
              	
              	    # 输出单张图像的推理时间
              	    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)  
              	LOGGER.info(f"Speed: %.1fms pre-process, %.1fms inference, %.1fms NMS per image at shape {(1, 3, *imgsz)}")
              	
              	# 输出保存结果的信息
              	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[0])  
              

              1. 归一化操作

                  # 遍历数据集中的每一张图片
              	for path, im, im0s, vid_cap, s in dataset:
              	    # 测量预处理时间
              	    with dt[0]:
              	        # 将图像转换为Tensor并移至适当设备
              	        im = torch.from_numpy(im).to(model.device)
              	        # 调整数据类型和归一化
              	        im = im.half() if model.fp16 else im.float()  
              	        im /= 255  
              

              这段代码是针对深度学习模型输入预处理的一部分,特别是在使用PyTorch框架时。这里是针对YOLOv5或类似的模型,它说明了如何将输入图像从uint8格式转换为适合模型输入的格式,即fp16(半精度浮点数,16位)或fp32(单精度浮点数,32位)。

              代码解析

              uint8

              uint8类型用于数字图像时,每个像素的颜色通道(如红、绿、蓝)通常使用uint8类型表示,每个通道的值范围从0(黑色)到255(最饱和的颜色)。

              精度转换
              im = im.half() if model.fp16 else im.float()
              

              这段代码检查模型是否支持半精度(fp16)计算。如果model.fp16为True,则im.half()将图像张量从uint8转换为fp16(半精度浮点数)。如果model.fp16为False,则im.float()将图像张量转换为fp32(单精度浮点数)。

              注意:uint8到fp16或fp32的转换是隐式的,即当从uint8类型转换到浮点类型时,原本的整数值会被转换为相应的浮点数值,但不会改变其数值大小。例如,uint8的255在转换为fp32后仍然是255.0。

              归一化
              im /= 255  # 0 - 255 to 0.0 - 1.0
              

              这行代码将图像张量的像素值从uint8的0到255范围归一化到fp16或fp32的0.0到1.0之间。这是深度学习模型输入预处理中常见的一步,帮助模型在训练和推断时获得更好的数值稳定性,同时使不同亮度和对比度的图像在模型眼中更加“平等”。

              这段代码的关键在于它确保了输入图像被适当地格式化和归一化,以供模型进行有效处理。模型是否使用半精度计算取决于模型自身的配置(model.fp16),这通常在模型训练时为了提高计算效率和减少内存使用而设定。归一化步骤则是深度学习图像处理中普遍采用的预处理步骤,确保模型输入的一致性和数值稳定性。

              2. 扩展维度

              # 扩展维度以匹配batch_size
              if len(im.shape) == 3:
              	im = im[None]  
              

              在深度学习中,尤其是使用卷积神经网络(CNN)进行图像处理时,通常需要处理的是一批图像而非单一图像。这是因为现代GPU架构设计为并行处理大量数据,处理一批图像比一次处理一张图像更有效率。因此,即使输入的是单张图像,也需要将其形状转换为适用于模型的批次输入格式。

              为什么扩展维度?

              当你的图像数据im的形状是三维的(例如,形状为(height, width, channels)),这意味着你只有一个图像。然而,大多数深度学习框架和模型期望输入数据的形状至少是四维的,即(batch_size, height, width, channels)(对于TensorFlow)或(batch_size, channels, height, width)(对于PyTorch)。

              在YOLOv5的情况下,模型预期的输入是四维的,即 (batch_size, channels, height, width)。因此,如果你的im是一个单独的图像,它的形状会是 (channels, height, width),需要在前面增加一个维度来代表batch_size,这样形状就会变成 (1, channels, height, width)。这就是为什么使用im = im[None]来扩展维度,None在这里等价于np.newaxis,它会在数组中插入一个新的轴。

              代码解释

              if len(im.shape) == 3:
                  im = im[None]  # 扩展维度,使形状从 (channels, height, width) 变为 (1, channels, height, width)
              

              这条语句检查im的形状,如果它只有三个维度,那么就使用None来扩展其第一个维度,从而匹配模型期望的输入形状。扩展维度是为了将单张图像转换为批次格式,以便模型能够正确处理。这是深度学习实践中一个常见的预处理步骤,尤其在使用CNN进行图像处理时。

              3. 对检测结果类别计数

              # 如果有检测结果
              	        if len(det):
              	            # 对每个类别进行计数
              	            for c in det[:, 5].unique():
              	                n = (det[:, 5] == c).sum()  
              	                s += f"{n} {names[int(c)]}{'s' * (n > 1)}, " 
              

              这段代码是在处理YOLOv5模型输出的检测结果时,用于统计每个类别出现的次数,并生成一个描述性的字符串,用于后续的日志记录或输出。让我们逐步分解这段代码:

              检查是否有检测结果

              if len(det):
              

              det是一个二维张量,包含了所有检测到的对象的信息,每一行代表一个检测到的对象,列中包含位置信息(如边界框坐标)、类别ID和置信度得分。如果det非空(即有检测结果),则执行以下操作。

              统计每个类别的出现次数

              for c in det[:, 5].unique():
                  n = (det[:, 5] == c).sum()  
              
              • det[:, 5]访问的是检测结果中每一行的第6个元素(索引为5,因为Python中索引从0开始),这通常对应于检测到对象的类别ID。
              • unique()函数返回所有独特的类别ID,这样我们就可以迭代每一个类别。
              • 对于每一个类别c,(det[:, 5] == c)会产生一个布尔掩码,表示哪些检测结果属于这个类别。
              • sum()函数计算这个布尔掩码中True的个数,即该类别在检测结果中出现的次数,结果存储在变量n中。

                构建描述性字符串

                s += f"{n} {names[int(c)]}{'s' * (n > 1)}, " 
                
                • f"{n} {names[int(c)]}"使用f-string(格式化字符串字面量)来构建一个描述,其中n是检测到的类别数量,names[int(c)]是从模型的类别名列表中获取该类别的名称。
                • 's' * (n > 1)检查如果n大于1,就在类别名称后面加上一个s,这样可以正确地复数化名词。
                • 最后,结果字符串后面加上一个逗号和空格,以便在下一个类别出现时可以继续拼接。

                  这段代码的结果是一个描述检测结果的字符串,其中包含了每个类别及其出现的次数。例如,如果有两个狗和一个猫被检测到,结果可能是 "2 dogs, 1 cat"。这个字符串通常用于在控制台或日志中提供一个直观的反馈,说明检测到了什么以及数量。

                  三、定义命令行参数

                  def parse_opt():
                      parser = argparse.ArgumentParser()  # 创建ArgumentParser对象
                      parser.add_argument('--weights', nargs='+', type=str, default='yolov5s.pt', help='model path(s)')  # 添加权重参数
                      parser.add_argument('--source', type=str, default='data/images', help='file/dir/URL/glob, 0 for webcam')  # 添加输入源参数
                      parser.add_argument('--data', type=str, default='data/coco128.yaml', help='(optional) dataset.yaml path')  # 添加数据集参数
                      parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=640, help='inference size (pixels)')  # 添加图像大小参数
                      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')  # 添加IoU阈值参数
                      parser.add_argument('--max-det', type=int, default=1000, help='maximum detections per image')  # 添加最大检测数量参数
                      parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')  # 添加设备参数
                      parser.add_argument('--view-img', action='store_true', help='show results')  # 添加显示图像参数
                      parser.add_argument('--save-txt', action='store_true', help='save results to *.txt')  # 添加保存文本参数
                      parser.add_argument('--save-conf', action='store_true', help='save confidences in --save-txt labels')  # 添加保存置信度参数
                      parser.add_argument('--save-crop', action='store_true', help='save cropped prediction boxes')  # 添加保存裁剪框参数
                      parser.add_argument('--nosave', action='store_true', help='do not save images/videos')  # 添加不保存图像/视频参数
                      parser.add_argument('--classes', nargs='+', type=int, help='filter by class: --class 0, or --class 0 2 3')  # 添加类别过滤参数
                      parser.add_argument('--agnostic-nms', action='store_true', help='class-agnostic NMS')  # 添加类别无关的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='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')  # 添加OpenCV DNN推理参数
                      parser.add_argument('--vid-stride', type=int, default=1, help='video frame-rate stride')  # 添加视频帧率步幅参数
                      opt = parser.parse_args()  # 解析命令行参数
                      return opt  # 返回解析结果
                  

                  四、主函数

                  def main(opt):
                      check_requirements(exclude=('tensorboard', 'thop'))  # 检查运行所需的库
                      run(**vars(opt))  # 运行检测程序
                  if __name__ == "__main__":
                      opt = parse_opt()  # 解析命令行参数
                      main(opt)  # 运行主程序
                  
VPS购买请点击我

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

目录[+]