Advertisement

(2-3)目标检测和识别:基于深度学习的YOLO算法

阅读量:

2.3 基于深度学习的YOLO算法

YOLO(You Only Look Once)是一种基于深度学习的目标检测算法,在图像或视频中具备高效的实时处理能力,并且能够实现高准确率的目标识别与分类任务。该算法利用单一神经网络架构完成对目标物体的定位与分类识别过程,并能在较短的时间内完成大量目标物体的检测工作。

2.3.1 YOLO 算法介绍

随着时间的推移,YOLO系列算法不断演进。其中YOLO v5(You Only Look Once v2)作为一种广受欢迎的实现版本,在基础架构上进行了核心优化和改进措施。本书将重点介绍和分析YOLO v5这一主流模型。相较于原始版本,在检测精度和速度方面均有明显提升。作为一种改进版本,它不仅提升了性能指标还优化了计算效率使其成为广泛应用于目标检测领域的主流选择。此外随着技术的发展后续 versions like YOLOv3 YOLOv4 and YOLOv5 were introduced to further enhance detection capabilities and adapt to diverse application scenarios。这些 version improvements mainly focused on network architecture feature extraction and prediction mechanisms ensuring they meet the demands of varied operational environments。

在Python中使用YOLO v5前需要先通过如下命令安装:

pip install ultralytics

初学人员无需额外配置即可直接使用YOLO v5提供的预先训练好的模型。该预先训练好的YOLO v5模型可通过多种途径获取。常见获取渠道包括但不限于官方网站、第三方平台以及社区资源等。

Darknet官方网站上, YOLO v5是由Joseph Redmon开发的一个Darknet框架的部分内容。您可以通过官方网站下载YOLO v2的预训练模型。访问网站地址:https://pjreddie.com/darknet/yolo/

YOLO官方GitHub页面:YOLO官方GitHub仓库也提供了YOLO v2的预训练模型。访问https://github.com/pjreddie/darknet/releases页面即可获取预训练模型的下载链接。

第三方平台或社区:除了官方渠道或平台外,在这些资源中包含YOLOv2预训练模型下载功能。其中比较常见的几种资源平台包括Model Zoo、PyTorch Hub以及Hugging Face Model Hub等多种选项。

在下载预训练的YOLO v2模型时,请务必选择可信赖的来源,并仔细查阅模型的许可协议与使用条款。预训练模型的下载通常涉及注册、登录或同意特定条件的过程,请根据具体来源确定所需步骤。

注意:考虑到YOLO v2模型在不同的深度学习框架中可能具有的具体实现和权重格式差异性较大,因此在下载模型前必须确保所选模型与当前使用的深度学习框架存在良好的兼容性配置。通常情况下,官方发布的预训练模型会与主流支持的深度学习框架之间具备较好的兼容性和适应性。

2.3.2 使用YOLO v5算法实现图像目标检测

以案例说明YOLOv5技术的应用场景及其核心优势时

*实例2-3:使用YOLO v5实现模型训练、验证和预测(源码路径:codes\2\yolov5*

在本实例中, 开发用于目标检测任务的Python脚本, 该脚本可执行YOLOv5模型的推理过程, 支持从不同来源加载并运行模型, 并通过自动获取最新发布版本的方式获取模型文件, 将推断结果存储于特定目录中. 该脚本的具体实现包括以下几个步骤: 首先, 确保环境配置正确; 其次, 导入必要的库模块; 然后, 初始化YOLOv5模型; 接着, 加载待检测图像; 最后, 执行推断操作并保存结果.

****1.****准备工作

声明了一个名为run的函数, 该函数负责执行目标检测任务.

复制代码
 def run(

    
     weights=ROOT / 'yolov5s.pt',  # model path or triton URL
    
     source=ROOT / 'data/images',  # file/dir/URL/glob/screen/0(webcam)
    
     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
    
     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
    
     save_conf=False,  # save confidences in --save-txt labels
    
     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
    
     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
    
     dnn=False,  # use OpenCV DNN for ONNX inference
    
     vid_stride=1,  # video frame-rate stride
    
 ):

在上述代码中,各个参数的具体说明如下:

  1. weights:模型的路径或Triton URL,默认值为yolov5s.pt,表示模型的权重文件路径。
  2. source:推理的来源,可以是文件、目录、URL、通配符、屏幕截图或者摄像头。默认值为data/images,表示推理的来源为data/images目录。
  3. data:数据集的配置文件路径,默认值为data/coco128.yaml,表示使用COCO128数据集的配置文件。
  4. imgsz:推理时的图像尺寸,默认为(640, 640),表示推理时将图像调整为高度和宽度都为640的尺寸。
  5. conf_thres:置信度阈值,默认值为0.25,表示只保留置信度大于该阈值的检测结果。
  6. iou_thres:NMS(非极大值抑制)的IOU(交并比)阈值,默认值为0.45,用于去除重叠度较高的重复检测结果。
  7. max_det:每张图像的最大检测数量,默认值为1000,表示每张图像最多保留1000个检测结果。
  8. device:设备类型,默认为空字符串,表示使用默认设备(GPU或CPU)进行推理。
  9. view_img:是否显示结果图像,默认值为False,表示不显示结果图像。
  10. save_txt:是否将结果保存为文本文件,默认值为False。
  11. save_conf:是否将置信度保存在保存的文本标签中,默认值为False。
  12. save_crop:是否保存裁剪的预测框,默认值为False。
  13. nosave:是否禁止保存图像或视频,默认值为False。
  14. classes:根据类别进行过滤,默认为None,表示不进行类别过滤。
  15. agnostic_nms:是否使用类别不可知的NMS,默认值为False。
  16. augment:是否进行增强推理,默认值为False。
  17. visualize:是否可视化特征,默认值为False。
  18. update:是否更新所有模型,默认值为False。
  19. project:保存结果的项目路径,默认为runs/detect。
  20. name:保存结果的名称,默认为exp。
  21. exist_ok:是否允许存在的项目/名称,如果为True,则不递增项目/名称,默认值为False。
  22. line_thickness:边界框线条的粗细,默认为3个像素。
  23. hide_labels:是否隐藏标签,默认值为False。
  24. hide_conf:是否隐藏置信度,默认值为False。
  25. half:是否使用FP16的半精度推理,默认值为False。
  26. dnn:是否使用OpenCV DNN进行ONNX推理,默认值为False。
  27. vid_stride:视频帧率步长,默认值为1。

其中列出的各项参数可根据具体需求进行设置,以便于应对各类目标检测场景的需求

(2)执行目标检测推断过程,并针对来自不同输入源的数据进行相应的处理和运算。具体实现的代码片段如下所示。

复制代码
    source = str(source)

    
     save_img = not nosave and not source.endswith('.txt')  # save inference images
    
     is_file = Path(source).suffix[1:] in (IMG_FORMATS + VID_FORMATS)
    
     is_url = source.lower().startswith(('rtsp://', 'rtmp://', 'http://', 'https://'))
    
     webcam = source.isnumeric() or source.endswith('.streams') or (is_url and not is_file)
    
     screenshot = source.lower().startswith('screen')
    
     if is_url and is_file:
    
     source = check_file(source)  # download
    
  
    
     save_dir = increment_path(Path(project) / name, exist_ok=exist_ok)  # increment run
    
     (save_dir / 'labels' if save_txt else save_dir).mkdir(parents=True, exist_ok=True)  # make dir
    
  
    
     # 加载模型
    
     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)  # check image size
    
  
    
     # Dataloader
    
     bs = 1  # batch_size
    
     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)
    
     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
    
  
    
     # Run inference
    
     model.warmup(imgsz=(1 if pt or model.triton else bs, 3, *imgsz))  # warmup
    
     seen, windows, dt = 0, [], (Profile(), Profile(), Profile())
    
     for path, im, im0s, vid_cap, s in dataset:
    
     with dt[0]:
    
         im = torch.from_numpy(im).to(model.device)
    
         im = im.half() if model.fp16 else im.float()  # uint8 to fp16/32
    
         im /= 255  # 0 - 255 to 0.0 - 1.0
    
         if len(im.shape) == 3:
    
             im = im[None]  # expand for batch dim
    
  
    
     # Inference
    
     with dt[1]:
    
         visualize = increment_path(save_dir / Path(path).stem, mkdir=True) if visualize else False
    
         pred = model(im, augment=augment, visualize=visualize)
    
  
    
     # NMS
    
     with dt[2]:
    
         pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_

对上述代码的具体说明如下:

  1. 首先,对输入的source进行了一系列判断和处理。将source转换为字符串类型,并根据条件判断是否保存推理图像。接着,判断source是文件路径还是URL,以及是否为摄像头或屏幕截图。如果source是URL且为文件路径,则会进行文件下载操作。
  2. 接下来,创建保存结果的目录,根据project和name参数生成保存结果的路径,并在指定路径下创建目录。如果save_txt为True,则在目录下创建一个名为'labels'的子目录,否则直接创建主目录。
  3. 然后,加载模型并选择设备。根据指定的设备类型,选择相应的设备进行推理。同时,实例化DetectMultiBackend类的对象model,并获取其步长(stride)、类别名称(names)和模型(pt)。
  4. 接下来,检查图像尺寸并创建数据加载器。根据不同的输入来源,选择相应的数据加载器对象:LoadStreams用于摄像头输入,LoadScreenshots用于屏幕截图输入,LoadImages用于图像或视频输入。同时,根据是否为摄像头输入,确定批处理大小(bs)。
  5. 接下来,进行推理过程。首先,通过调用model.warmup()方法进行模型预热,其中输入图像尺寸根据模型类型进行调整。然后,使用迭代器遍历数据加载器,获取输入图像及相关信息。将图像转换为PyTorch张量,并根据模型的精度要求进行数据类型和范围的调整。如果输入图像维度为3维,则添加一个批处理维度。
  6. 然后,进行推理过程。根据是否需要可视化结果,确定是否保存推理结果的路径,并调用model对象的__call__()方法进行推理。得到推理结果后,根据设定的置信度阈值、IOU阈值和其他参数,进行非最大值抑制(NMS)操作。
  7. 最后,对推理结果进行处理。包括计算推理的时间、保存推理结果图像和输出结果信息。其中,推理时间分为3个阶段,即数据准备、模型推理和NMS操作。保存推理结果图像的路径根据是否需要可视化和输入图像的文件名进行生成。然后,根据设置的参数决定是否将推理结果保存为文本文件。

****2.****模型训练

以下是对原文的改写版本

开发名为train()的功能以实现模型训练,在执行以下代码之前进行必要的准备工作:涉及参数解析、目录创建、超参数加载以及日志记录器搭建等关键步骤。这些准备措施确保了后续训练过程能够顺利开展。

复制代码
 def train(hyp, opt, device, callbacks):  # hyp is path/to/hyp.yaml or hyp dictionary

    
     save_dir, epochs, batch_size, weights, single_cls, evolve, data, cfg, resume, noval, nosave, workers, freeze = \
    
     Path(opt.save_dir), opt.epochs, opt.batch_size, opt.weights, opt.single_cls, opt.evolve, opt.data, opt.cfg, \
    
     opt.resume, opt.noval, opt.nosave, opt.workers, opt.freeze
    
     callbacks.run('on_pretrain_routine_start')
    
  
    
     # Directories
    
     w = save_dir / 'weights'  # weights dir
    
     (w.parent if evolve else w).mkdir(parents=True, exist_ok=True)  # make dir
    
     last, best = w / 'last.pt', w / 'best.pt'
    
  
    
     # Hyperparameters
    
     if isinstance(hyp, str):
    
     with open(hyp, errors='ignore') as f:
    
         hyp = yaml.safe_load(f)  # load hyps dict
    
     LOGGER.info(colorstr('hyperparameters: ') + ', '.join(f'{k}={v}' for k, v in hyp.items()))
    
     opt.hyp = hyp.copy()  # for saving hyps to checkpoints
    
  
    
     # Save run settings
    
     if not evolve:
    
     yaml_save(save_dir / 'hyp.yaml', hyp)
    
     yaml_save(save_dir / 'opt.yaml', vars(opt))
    
  
    
     # Loggers
    
     data_dict = None
    
     if RANK in {-1, 0}:
    
     loggers = Loggers(save_dir, weights, opt, hyp, LOGGER)  # loggers instance
    
  
    
     # Register actions
    
     for k in methods(loggers):
    
         callbacks.register_action(k, callback=getattr(loggers, k))
    
  
    
     # Process custom dataset artifact link
    
     data_dict = loggers.remote_dataset
    
     if resume:  # If resuming runs from remote artifact
    
         weights, epochs, hyp, batch_size = opt.weights, opt.epochs, opt.hyp, opt.batch_size

对上述代码的具体说明如下:

  1. 随后,在函数体内提取并设置参数与选项。
  2. 生成存储文件夹,并将权重文件路径设为w。
  3. 首先检查hyp是否为字符串;如果是,则从指定位置读取超参数字典并将其存入opt属性hyp部分。
  4. 当evolve设置为False时(即非进化训练),需记录运行环境信息并将配置信息分别以yaml格式保存到对应的存储位置。
  5. 创建日志记录器(Loggers)实例;若当前进程属于主进程(RANK=-1或RANK=0),则建立日志记录器并对回调函数进行注册。
  6. 当从远程存储的artifact复现运行状态时,请更新当前模型的权重值、迭代轮次以及相关配置参数。

在继续进行模型训练之前所需进行的各项准备工作涉及多个关键步骤和参数设置。这些步骤包括但不限于系统参数配置、模型加载操作以及相关的初始化设置等细节内容。具体而言,在准备阶段需要完成以下几项工作:首先是网络架构与数据路径的确认与配置;其次是权重初始化与数据预处理的操作;接着是对神经网络各层参数的选择与设定;随后是GPU资源分配与内存管理的工作;此外还需要对优化算法相关的超参数进行合理设置;最后还需完成学习率调整策略的设计与实现等基础性工作。以上所有准备环节均需确保其准确性和完整性,并且应在满足特定性能指标的基础上完成各项操作以确保后续训练过程的有效性与稳定性

复制代码
     plots = not evolve and not opt.noplots  # create plots

    
     cuda = device.type != 'cpu'
    
     init_seeds(opt.seed + 1 + RANK, deterministic=True)
    
     with torch_distributed_zero_first(LOCAL_RANK):
    
     data_dict = data_dict or check_dataset(data)  # check if None
    
     train_path, val_path = data_dict['train'], data_dict['val']
    
     nc = 1 if single_cls else int(data_dict['nc'])  # number of classes
    
     names = {0: 'item'} if single_cls and len(data_dict['names']) != 1 else data_dict['names']  # class names
    
     is_coco = isinstance(val_path, str) and val_path.endswith('coco/val2017.txt')  # COCO dataset
    
  
    
     # Model
    
     check_suffix(weights, '.pt')  # check weights
    
     pretrained = weights.endswith('.pt')
    
     if pretrained:
    
     with torch_distributed_zero_first(LOCAL_RANK):
    
         weights = attempt_download(weights)  # download if not found locally
    
     ckpt = torch.load(weights, map_location='cpu')  # load checkpoint to CPU to avoid CUDA memory leak
    
     model = Model(cfg or ckpt['model'].yaml, ch=3, nc=nc, anchors=hyp.get('anchors')).to(device)  # create
    
     exclude = ['anchor'] if (cfg or hyp.get('anchors')) and not resume else []  # exclude keys
    
     csd = ckpt['model'].float().state_dict()  # checkpoint state_dict as FP32
    
     csd = intersect_dicts(csd, model.state_dict(), exclude=exclude)  # intersect
    
     model.load_state_dict(csd, strict=False)  # load
    
     LOGGER.info(f'Transferred {len(csd)}/{len(model.state_dict())} items from {weights}')  # report
    
     else:
    
     model = Model(cfg, ch=3, nc=nc, anchors=hyp.get('anchors')).to(device)  # create
    
     amp = check_amp(model)  # check AMP
    
  
    
     # Freeze
    
     freeze = [f'model.{x}.' for x in (freeze if len(freeze) > 1 else range(freeze[0]))]  # layers to freeze
    
     for k, v in model.named_parameters():
    
     v.requires_grad = True  # train all layers
    
     # v.register_hook(lambda x: torch.nan_to_num(x))  # NaN to 0 (commented for erratic training results)
    
     if any(x in k for x in freeze):
    
         LOGGER.info(f'freezing {k}')
    
         v.requires_grad = False
    
  
    
     # Image size
    
     gs = max(int(model.stride.max()), 32)  # grid size (max stride)
    
     imgsz = check_img_size(opt.imgsz, gs, floor=gs * 2)  # verify imgsz is gs-multiple
    
  
    
     # Batch size
    
     if RANK == -1 and batch_size == -1:  # single-GPU only, estimate best batch size
    
     batch_size = check_train_batch_size(model, imgsz, amp)
    
     loggers.on_params_update({'batch_size': batch_size})
    
  
    
     # Optimizer
    
     nbs = 64  # nominal batch size
    
     accumulate = max(round(nbs / batch_size), 1)  # accumulate loss before optimizing
    
     hyp['weight_decay'] *= batch_size * accumulate / nbs  # scale weight_decay
    
     optimizer = smart_optimizer(model, opt.optimizer, hyp['lr0'], hyp['momentum'], hyp['weight_decay'])
    
  
    
     # Scheduler
    
     if opt.cos_lr:
    
     lf = one_cycle(1, hyp['lrf'], epochs)  # cosine 1->hyp['lrf']
    
     else:
    
     lf = lambda x: (1 - x / epochs) * (1.0 - hyp['lrf']) + hyp['lrf']  # linear
    
     scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf)  # plot_lr_scheduler(optimizer, scheduler, epochs)
    
  
    
     # EMA
    
     ema = ModelEMA(model) if RANK in {-1, 0} else None
    
  
    
     # Resume
    
     best_fitness, start_epoch = 0.0, 0
    
     if pretrained:
    
     if resume:
    
         best_fitness, start_epoch, epochs = smart_resume(ckpt, optimizer, ema, weights, epochs, resume)
    
     del ckpt, csd

(3)使用深度学习框架PyTorch训练目标检测模型,具体实现代码如下所示。

复制代码
     train_loader, dataset = create_dataloader(train_path,

    
                                           imgsz,
    
                                           batch_size // WORLD_SIZE,
    
                                           gs,
    
                                           single_cls,
    
                                           hyp=hyp,
    
                                           augment=True,
    
                                           cache=None if opt.cache == 'val' else opt.cache,
    
                                           rect=opt.rect,
    
                                           rank=LOCAL_RANK,
    
                                           workers=workers,
    
                                           image_weights=opt.image_weights,
    
                                           quad=opt.quad,
    
                                           prefix=colorstr('train: '),
    
                                           shuffle=True,
    
                                           seed=opt.seed)
    
     labels = np.concatenate(dataset.labels, 0)
    
     mlc = int(labels[:, 0].max())  # max label class
    
     assert mlc < nc, f'Label class {mlc} exceeds nc={nc} in {data}. Possible class labels are 0-{nc - 1}'
    
  
    
     # Process 0
    
     if RANK in {-1, 0}:
    
     val_loader = create_dataloader(val_path,
    
                                    imgsz,
    
                                    batch_size // WORLD_SIZE * 2,
    
                                    gs,
    
                                    single_cls,
    
                                    hyp=hyp,
    
                                    cache=None if noval else opt.cache,
    
                                    rect=True,
    
                                    rank=-1,
    
                                    workers=workers * 2,
    
                                    pad=0.5,
    
                                    prefix=colorstr('val: '))[0]
    
  
    
     if not resume:
    
         if not opt.noautoanchor:
    
             check_anchors(dataset, model=model, thr=hyp['anchor_t'], imgsz=imgsz)  # run AutoAnchor
    
         model.half().float()  # pre-reduce anchor precision
    
  
    
     callbacks.run('on_pretrain_routine_end', labels, names)
    
  
    
     # DDP mode
    
     if cuda and RANK != -1:
    
     model = smart_DDP(model)
    
  
    
     # Model attributes
    
     nl = de_parallel(model).model[-1].nl  # number of detection layers (to scale hyps)
    
     hyp['box'] *= 3 / nl  # scale to layers
    
     hyp['cls'] *= nc / 80 * 3 / nl  # scale to classes and layers
    
     hyp['obj'] *= (imgsz / 640) ** 2 * 3 / nl  # scale to image size and layers
    
     hyp['label_smoothing'] = opt.label_smoothing
    
     model.nc = nc  # attach number of classes to model
    
     model.hyp = hyp  # attach hyperparameters to model
    
     model.class_weights = labels_to_class_weights(dataset.labels, nc).to(device) * nc  # attach class weights
    
     model.names = names
    
  
    
     # Start training
    
     t0 = time.time()
    
     nb = len(train_loader)  # number of batches
    
     nw = max(round(hyp['warmup_epochs'] * nb), 100)  # number of warmup iterations, max(3 epochs, 100 iterations)
    
     # nw = min(nw, (epochs - start_epoch) / 2 * nb)  # limit warmup to < 1/2 of training
    
     last_opt_step = -1
    
     maps = np.zeros(nc)  # mAP per class
    
     results = (0, 0, 0, 0, 0, 0, 0)  # P, R, mAP@.5, mAP@.5-.95, val_loss(box, obj, cls)
    
     scheduler.last_epoch = start_epoch - 1  # do not move
    
     scaler = torch.cuda.amp.GradScaler(enabled=amp)
    
     stopper, stop = EarlyStopping(patience=opt.patience), False
    
     compute_loss = ComputeLoss(model)  # init loss class
    
     callbacks.run('on_train_start')
    
     LOGGER.info(f'Image sizes {imgsz} train, {imgsz} val\n'
    
             f'Using {train_loader.num_workers * WORLD_SIZE} dataloader workers\n'
    
             f"Logging results to {colorstr('bold', save_dir)}\n"
    
             f'Starting training for {epochs} epochs...')
    
     for epoch in range(start_epoch, epochs):  # epoch ------------------------------------------------------------------
    
     callbacks.run('on_train_epoch_start')
    
     model.train()
    
  
    
     # Update image weights (optional, single-GPU only)
    
     if opt.image_weights:
    
         cw = model.class_weights.cpu().numpy() * (1 - maps) ** 2 / nc  # class weights
    
         iw = labels_to_image_weights(dataset.labels, nc=nc, class_weights=cw)  # image weights
    
         dataset.indices = random.choices(range(dataset.n), weights=iw, k=dataset.n)  # rand weighted idx
    
  
    
     # Update mosaic border (optional)
    
     # b = int(random.uniform(0.25 * imgsz, 0.75 * imgsz + gs) // gs * gs)
    
     # dataset.mosaic_border = [b - imgsz, -b]  # height, width borders
    
  
    
     mloss = torch.zeros(3, device=device)  # mean losses
    
     if RANK != -1:
    
         train_loader.sampler.set_epoch(epoch)
    
     pbar = enumerate(train_loader)
    
     LOGGER.info(('\n' + '%11s' * 7) % ('Epoch', 'GPU_mem', 'box_loss', 'obj_loss', 'cls_loss', 'Instances', 'Size'))
    
     if RANK in {-1, 0}:
    
         pbar = tqdm(pbar, total=nb, bar_format=TQDM_BAR_FORMAT)  # progress bar
    
     optimizer.zero_grad()
    
     for i, (imgs, targets, paths, _) in pbar:  # batch -------------------------------------------------------------
    
         callbacks.run('on_train_batch_start')
    
         ni = i + nb * epoch  # number integrated batches (since train start)
    
         imgs = imgs.to(device, non_blocking=True).float() / 255  # uint8 to float32, 0-255 to 0.0-1.0
    
  
    
         # Warmup
    
         if ni <= nw:
    
             xi = [0, nw]  # x interp
    
             # compute_loss.gr = np.interp(ni, xi, [0.0, 1.0])  # iou loss ratio (obj_loss = 1.0 or iou)
    
             accumulate = max(1, np.interp(ni, xi, [1, nbs / batch_size]).round())
    
             for j, x in enumerate(optimizer.param_groups):
    
                 # bias lr falls from 0.1 to lr0, all other lrs rise from 0.0 to lr0
    
                 x['lr'] = np.interp(ni, xi, [hyp['warmup_bias_lr'] if j == 0 else 0.0, x['initial_lr'] * lf(epoch)])
    
                 if 'momentum' in x:
    
                     x['momentum'] = np.interp(ni, xi, [hyp['warmup_momentum'], hyp['momentum']])
    
  
    
         # Multi-scale
    
         if opt.multi_scale:
    
             sz = random.randrange(int(imgsz * 0.5), int(imgsz * 1.5) + gs) // gs * gs  # size
    
             sf = sz / max(imgs.shape[2:])  # scale factor
    
             if sf != 1:
    
                 ns = [math.ceil(x * sf / gs) * gs for x in imgs.shape[2:]]  # new shape (stretched to gs-multiple)
    
                 imgs = nn.functional.interpolate(imgs, size=ns, mode='bilinear', align_corners=False)
    
  
    
         # Forward
    
         with torch.cuda.amp.autocast(amp):
    
             pred = model(imgs)  # forward
    
             loss, loss_items = compute_loss(pred, targets.to(device))  # loss scaled by batch_size
    
             if RANK != -1:
    
                 loss *= WORLD_SIZE  # gradient averaged between devices in DDP mode
    
             if opt.quad:
    
                 loss *= 4.
    
  
    
         # Backward
    
         scaler.scale(loss).backward()
    
  
    
         # Optimize - https://pytorch.org/docs/master/notes/amp_examples.html
    
         if ni - last_opt_step >= accumulate:
    
             scaler.unscale_(optimizer)  # unscale gradients
    
             torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=10.0)  # clip gradients
    
             scaler.step(optimizer)  # optimizer.step
    
             scaler.update()
    
             optimizer.zero_grad()
    
             if ema:
    
                 ema.update(model)
    
             last_opt_step = ni
    
  
    
         # Log
    
         if RANK in {-1, 0}:
    
             mloss = (mloss * i + loss_items) / (i + 1)  # update mean losses
    
             mem = f'{torch.cuda.memory_reserved() / 1E9 if torch.cuda.is_available() else 0:.3g}G'  # (GB)
    
             pbar.set_description(('%11s' * 2 + '%11.4g' * 5) %
    
                                  (f'{epoch}/{epochs - 1}', mem, *mloss, targets.shape[0], imgs.shape[-1]))
    
             callbacks.run('on_train_batch_end', model, ni, imgs, targets, paths, list(mloss))
    
             if callbacks.stop_training:
    
                 return
    
         # end batch -------------------------------

对上述代码的具体说明如下:

首先,在数据预处理阶段创建了训练数据集的数据加载器,并从数据集中提取了标签信息。
随后根据不同的训练模式执行相应的操作。
如果当前采用的是分布式训练模式且部署在多块GPU上,则采用torch.nn.DataParallel对模型进行封装以实现多GPU并行训练;而如果当前未开启同步BatchNorm功能并且不在分布式环境下,则将模型中的所有BatchNorm层转换为SyncBatchNorm类型层以便实现跨GPU同步。
接着设置了一些模型属性参数包括调节损失函数权重以及附加各类别的权重、类别名称等信息到模型中。
在开始正式的训练过程之前设置了包括学习率调节策略与Early Stopping机制在内的若干超参数并初始化了必要的优化器与损失函数。
随后系统会进入多轮次的迭代学习过程每一轮迭代都会遍历整个数据集的不同批次输入样本并在每一批次样本上执行前向传播计算输出结果并基于计算出的目标值与预测结果之间的差异计算相应的损失值。
完成前向传播后系统会对网络中的各个参数执行反向传播运算并对优化器所指定的学习率参数进行相应更新最终使得网络能够逐步逼近预期的目标状态。
在此过程中每当累计一定数量的批次之后系统会触发一次全量更新操作从而保证网络参数能够在各设备之间保持一致从而确保异步同步的整体收敛性。

在每次 epoch 开始时都会输出相关信息(例如 current epoch, GPU memory usage, and loss values), 并且会在每个 epoch 开始前和结束后的阶段均会调用特定的 callbacks. 同时, entire training process 将连续进行若干个 epochs 直至完成预定轮次的任务.

(4)训练模型,使用循环训练目标检测模型。具体实现代码如下所示。

复制代码
         fi = fitness(np.array(results).reshape(1, -1))  # weighted combination of [P, R, mAP@.5, mAP@.5-.95]

    
         stop = stopper(epoch=epoch, fitness=fi)  # early stop check
    
         if fi > best_fitness:
    
             best_fitness = fi
    
         log_vals = list(mloss) + list(results) + lr
    
         callbacks.run('on_fit_epoch_end', log_vals, epoch, best_fitness, fi)
    
  
    
         # Save model
    
         if (not nosave) or (final_epoch and not evolve):  # if save
    
             ckpt = {
    
                 'epoch': epoch,
    
                 'best_fitness': best_fitness,
    
                 'model': deepcopy(de_parallel(model)).half(),
    
                 'ema': deepcopy(ema.ema).half(),
    
                 'updates': ema.updates,
    
                 'optimizer': optimizer.state_dict(),
    
                 'opt': vars(opt),
    
                 'git': GIT_INFO,  # {remote, branch, commit} if a git repo
    
                 'date': datetime.now().isoformat()}
    
  
    
             # Save last, best and delete
    
             torch.save(ckpt, last)
    
             if best_fitness == fi:
    
                 torch.save(ckpt, best)
    
             if opt.save_period > 0 and epoch % opt.save_period == 0:
    
                 torch.save(ckpt, w / f'epoch{epoch}.pt')
    
             del ckpt
    
             callbacks.run('on_model_save', last, epoch, final_epoch, best_fitness, fi)
    
  
    
     # EarlyStopping
    
     if RANK != -1:  # if DDP training
    
         broadcast_list = [stop if RANK == 0 else None]
    
         dist.broadcast_object_list(broadcast_list, 0)  # broadcast 'stop' to all ranks
    
         if RANK != 0:
    
             stop = broadcast_list[0]
    
     if stop:
    
         break  # must break all DDP ranks
    
  
    
     # end epoch ----------------------------------------------------------------------------------------------------
    
     # end training -----------------------------------------------------------------------------------------------------
    
     if RANK in {-1, 0}:
    
     LOGGER.info(f'\n{epoch - start_epoch + 1} epochs completed in {(time.time() - t0) / 3600:.3f} hours.')
    
     for f in last, best:
    
         if f.exists():
    
             strip_optimizer(f)  # strip optimizers
    
             if f is best:
    
                 LOGGER.info(f'\nValidating {f}...')
    
                 results, _, _ = validate.run(
    
                     data_dict,
    
                     batch_size=batch_size // WORLD_SIZE * 2,
    
                     imgsz=imgsz,
    
                     model=attempt_load(f, device).half(),
    
                     iou_thres=0.65 if is_coco else 0.60,  # best pycocotools at iou 0.65
    
                     single_cls=single_cls,
    
                     dataloader=val_loader,
    
                     save_dir=save_dir,
    
                     save_json=is_coco,
    
                     verbose=True,
    
                     plots=plots,
    
                     callbacks=callbacks,
    
                     compute_loss=compute_loss)  # val best model with plots
    
                 if is_coco:
    
                     callbacks.run('on_fit_epoch_end', list(mloss) + list(results) + lr, epoch, best_fitness, fi)
    
  
    
     callbacks.run('on_train_end', last, best, epoch, results)
    
  
    
     torch.cuda.empty_cache()
    
     return results

对上述代码的具体说明如下:

  1. fi = fitness(np.array(results).reshape(1, -1)): 计算模型在验证集上的综合指标。results是一个包含模型在验证集上表现的元组,通过调用fitness函数计算综合指标。
  2. stop = stopper(epoch=epoch, fitness=fi): 判断是否满足停止训练的条件。stopper是一个用于判断是否进行early stopping的对象,根据当前的训练轮数epoch和综合指标fitness来判断是否停止训练。
  3. if fi > best_fitness: best_fitness = fi: 更新最佳的综合指标best_fitness,如果当前的综合指标大于最佳指标。
  4. log_vals = list(mloss) + list(results) + lr: 将当前的损失值、验证集上的结果和学习率组成一个列表log_vals,用于记录训练过程中的日志。
  5. ckpt = {...}: 创建一个字典ckpt,保存训练过程中的相关信息,包括当前的轮数epoch、最佳综合指标best_fitness、模型参数、优化器状态等。
  6. torch.save(ckpt, last): 将ckpt保存到文件last,这里保存的是最后一轮的模型。
  7. if best_fitness == fi: torch.save(ckpt, best): 如果当前的综合指标等于最佳指标,将ckpt保存到文件best,这里保存的是最佳的模型。
  8. if opt.save_period > 0 and epoch % opt.save_period == 0: torch.save(ckpt, w / f'epoch{epoch}.pt'): 如果设置了保存周期save_period且当前轮数是保存周期的倍数,将ckpt保存到文件epoch{epoch}.pt,用于定期保存模型。
  9. callbacks.run('on_model_save', last, epoch, final_epoch, best_fitness, fi): 运行回调函数on_model_save,将保存的模型文件路径、当前轮数、是否是最后一轮、最佳综合指标和当前综合指标等参数传递给回调函数。
  10. if stop: break: 如果满足停止训练的条件,跳出训练循环,结束训练。
  11. if RANK in {-1, 0}: LOGGER.info(f'\n{epoch - start_epoch + 1} epochs completed in {(time.time() - t0) / 3600:.3f} hours.'): 如果是主进程(RANK为-1或0),打印训练完成的信息,包括训练轮数和所花费的时间。
  12. for f in last, best: ...: 遍历最后一轮的模型和最佳模型。
  13. if f.exists(): ...: 如果模型文件存在。
  14. strip_optimizer(f): 去除模型文件中的优化器信息。
  15. if f is best: ...: 如果是最佳模型,运行验证函数对模型进行评估。
  16. callbacks.run('on_fit_epoch_end', list(mloss) + list(results) + lr, epoch, best_fitness, fi): 运行回调函数on_fit_epoch_end,将损失值、验证集结果和学习率等参数传递给回调函数。
  17. callbacks.run('on_train_end', last, best, epoch, results): 运行回调函数on_train_end,将最后一轮模型、最佳模型、当前轮数和验证集结果等参数传递给回调函数。
  18. torch.cuda.empty_cache(): 清空GPU缓存。

****3.****模型验证

接下来开始讲解本实例的模型验证过程,在深度学习中,"val" 通常指的是验证数据集,验证数据集是在训练模型过程中用于评估模型性能和调整超参数的数据集。训练过程通常分为训练集、验证集和测试集三部分。验证集用于在训练过程中评估模型的性能,并根据验证结果调整模型的超参数,以优化模型的泛化能力。与训练集用于训练模型不同,验证集的目的是评估模型在未见过的数据上的性能,以避免过拟合和选择合适的模型。在本项目中,编写文件val.py实现模型验证功能,具体实现流程如下所示。

(1)创建名为save_one_txt的函数。其主要作用是将目标检测模型的预测结果存储为文本文件。实现过程包括通过归一化增益计算来处理预测框的位置信息,并将其从原始xyxy格式转换为归一化的xywh坐标。

  1. 对每个预测框进行遍历,并依据是否保留置信度值来决定输出格式。
    2. 将结果存储到文本文件中,每条预测结果单独成行。

函数save_one_txt()的具体实现代码如下所示。

复制代码
 def save_one_txt(predn, save_conf, shape, file):

    
     # Save one txt result
    
     gn = torch.tensor(shape)[[1, 0, 1, 0]]  # normalization gain whwh
    
     for *xyxy, conf, cls in predn.tolist():
    
     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(file, 'a') as f:
    
         f.write(('%g ' * len(line)).rstrip() % line + '\n')

(2)实现函数save_one_json的主要职责是将目标检测模型的预测结果以JSON格式的形式存储到文件中。其详细描述了操作步骤。

  1. 从输入文件路径中提取图像ID。
  2. 将其预测框的坐标由xyxy格式转为xywh格式,并将中心坐标的表示方式由相对转为绝对位置。
  3. 依次处理每一个预测框,并将预测结果按照字典形式整合到列表中。
  4. 该字典包含四个要素:图像ID、类别标识符、边界框位置以及置信度值。

函数save_one_json()的具体实现代码如下所示。

复制代码
 def save_one_json(predn, jdict, path, class_map):

    
     # Save one JSON result {"image_id": 42, "category_id": 18, "bbox": [258.15, 41.29, 348.26, 243.78], "score": 0.236}
    
     image_id = int(path.stem) if path.stem.isnumeric() else path.stem
    
     box = xyxy2xywh(predn[:, :4])  # xywh
    
     box[:, :2] -= box[:, 2:] / 2  # xy center to top-left corner
    
     for p, b in zip(predn.tolist(), box.tolist()):
    
     jdict.append({
    
         'image_id': image_id,
    
         'category_id': class_map[int(p[5])],
    
         'bbox': [round(x, 3) for x in b],
    
         'score': round(p[4], 5)})

(2)实现过程batch的函数编写;其功能为根据预测框与标签框计算出准确的预测矩阵;详细步骤如下:

初始化一个全零矩阵correct用于记录预测框与标签框的匹配关系。
计算出预测框与标签框的交并比IoU。
针对每一个设定的IoU阈值,找出那些同时满足IoU高于阈值且类别一致的预测框。
将匹配结果以布尔类型存入correct矩阵中。
最终返回一个correct张量,在其中每一行代表一个特定的预测框,在每一列对应着不同的IoU阈值设置,并用True或False标识两者之间的匹配关系。

函数process_batch()的具体实现代码如下所示。

复制代码
 def process_batch(detections, labels, iouv):

    
     correct = np.zeros((detections.shape[0], iouv.shape[0])).astype(bool)
    
     iou = box_iou(labels[:, 1:], detections[:, :4])
    
     correct_class = labels[:, 0:1] == detections[:, 5]
    
     for i in range(len(iouv)):
    
     x = torch.where((iou >= iouv[i]) & correct_class)  # IoU > threshold and classes match
    
     if x[0].shape[0]:
    
         matches = torch.cat((torch.stack(x, 1), iou[x[0], x[1]][:, None]), 1).cpu().numpy()  # [label, detect, iou]
    
         if x[0].shape[0] > 1:
    
             matches = matches[matches[:, 2].argsort()[::-1]]
    
             matches = matches[np.unique(matches[:, 1], return_index=True)[1]]
    
             # matches = matches[matches[:, 2].argsort()[::-1]]
    
             matches = matches[np.unique(matches[:, 0], return_index=True)[1]]
    
         correct[matches[:, 1].astype(int), i] = True
    
     return torch.tensor(correct, dtype=torch.bool, device=iouv.device)

通过运行下面的命令可以展示识别结果:

复制代码
    python detect.py --weights yolov5s.pt --img 640 --conf 0.25 --source data/images

4. SSD****目标检测

SSD(Single Shot MultiBox Detector)是一种广泛应用的目标检测算法,在一次快速扫描中即可识别图像中的多个目标。该算法通过融合特征提取网络与多尺度卷积层相结合的方式,在不同尺寸的目标上实现了精准检测功能。例如,在实际应用中可以通过SSD模型对现有素材图片进行高效的目标识别操作。

实例2-5:使用SSD模型对现有的素材图像实现目标检测(源码路径:codes\2\tu.py

实例文件tu.py的具体实现代码如下所示。

复制代码
 import cv2

    
  
    
 # 加载预训练的模型和标签
    
 model = cv2.dnn.readNetFromCaffe('deploy.prototxt', 'model.caffemodel')
    
 with open('labels.txt', 'r') as f:
    
     labels = f.read().splitlines()
    
  
    
 # 读取图像
    
 image = cv2.imread('999.jpg')
    
  
    
 # 创建一个blob(二进制大对象)从图像进行前处理
    
 blob = cv2.dnn.blobFromImage(image, 0.007843, (300, 300), (127.5, 127.5, 127.5), swapRB=True, crop=False)
    
  
    
 # 将blob输入到模型中进行推理
    
 model.setInput(blob)
    
 detections = model.forward()
    
  
    
 # 处理检测结果
    
 for i in range(detections.shape[2]):
    
     confidence = detections[0, 0, i, 2]
    
     if confidence > 0.1:  # 设定置信度阈值为0.5
    
     class_id = int(detections[0, 0, i, 1])
    
     label = labels[class_id]
    
     x1 = int(detections[0, 0, i, 3] * image.shape[1])
    
     y1 = int(detections[0, 0, i, 4] * image.shape[0])
    
     x2 = int(detections[0, 0, i, 5] * image.shape[1])
    
     y2 = int(detections[0, 0, i, 6] * image.shape[0])
    
  
    
     # 在图像上绘制检测结果
    
     cv2.rectangle(image, (x1, y1), (x2, y2), (0, 255, 0), 2)
    
     cv2.putText(image, label, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2)
    
  
    
 # 显示图像
    
 cv2.imshow('SSD Object Detection', image)
    
 cv2.waitKey(0)
    
 cv2.destroyAllWindows()

对上述代码的具体说明如下:

通过cv2.imread()函数读取指定路径下的目标图像文件,并将其加载至内存中的image变量中进行处理。
生成一个Blob对象并对其进行初始化配置,默认设置包括尺寸归一化、通道转换以及像素均值减除等预处理操作。
将该Blob对象作为输入传递给目标检测模型进行推理计算,并获取其输出预测结果向量。
从预测结果向量中解析出每个目标对应的置信度值、类别标签以及各目标的具体边界框坐标参数。
根据设定的置信度阈值标准筛选出具有较高置信度的目标检测结果并保存以便后续处理使用。
在原始图像上基于筛选得到的结果信息绘制对应的目标边界框和相应的类别标签标识符。
调用cv2.imshow()函数展示包含目标检测信息的图像界面,并等待按下任意键后自动关闭该窗口以释放系统资源。

执行后将会使用矩形线条标注处图片中的目标区域,如图10-1所示。

图10-1 标注出目标区域

全部评论 (0)

还没有任何评论哟~