基于深度学习的HRNet-YOLO的花卉图像识别系统

1.研究背景与意义

项目参考AAAI Association for the Advancement of Artificial Intelligence

研究背景与意义

随着计算机视觉技术的快速发展,图像识别已经成为了人工智能领域的一个重要研究方向。图像识别技术在各个领域都有广泛的应用,其中之一就是花卉图像识别。花卉图像识别可以帮助人们快速准确地识别花卉的种类和特征,对于植物学研究、园艺种植、生态环境保护等方面都具有重要的意义。

传统的花卉图像识别方法主要依赖于手工设计的特征提取算法和分类器,这些方法在一定程度上可以实现花卉图像的识别,但是存在着一些问题。首先,手工设计的特征提取算法往往需要人工经验和专业知识,对于不同的花卉种类和特征可能需要不同的特征提取算法,这给算法的设计和实现带来了一定的困难。其次,传统的分类器在处理大规模的花卉图像数据时,往往需要大量的计算资源和时间,无法满足实时性的要求。此外,传统的方法对于花卉图像中的遮挡、光照变化等问题也比较敏感,识别准确率有限。

基于深度学习的图像识别方法在解决上述问题上具有很大的优势。深度学习模型可以自动学习图像的特征表示,不需要手工设计特征提取算法,大大减轻了算法设计的难度。同时,深度学习模型可以通过大规模的训练数据进行训练,可以充分利用计算资源,提高识别的准确率和效率。此外,深度学习模型对于图像中的遮挡、光照变化等问题也具有一定的鲁棒性。

HRNet-YOLO是一种基于深度学习的目标检测算法,它结合了高分辨率网络(HRNet)和You Only Look Once(YOLO)的思想,可以实现对花卉图像中花卉目标的准确检测和识别。HRNet-YOLO具有多尺度特征融合和全局上下文信息的建模能力,可以有效地解决花卉图像中的遮挡、光照变化等问题,提高识别的准确率和鲁棒性。

因此,基于深度学习的HRNet-YOLO的花卉图像识别系统具有重要的研究意义和应用价值。通过研究该系统,可以实现对花卉图像的自动化识别和分类,为植物学研究、园艺种植、生态环境保护等领域提供有力的支持。此外,该系统还可以应用于花卉市场、花卉展览等场景,提供快速准确的花卉识别服务,提升用户体验和商业价值。因此,研究基于深度学习的HRNet-YOLO的花卉图像识别系统具有重要的理论和实践意义。

2.图片演示

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.视频演示

基于深度学习的HRNet-YOLO的花卉图像识别系统_哔哩哔哩_bilibili

4.数据集的采集&标注和整理

图片的收集

首先,我们需要收集所需的图片。这可以通过不同的方式来实现,例如使用现有的公开数据集FlowerDatasets。
在这里插入图片描述

下面是一个简单的方法是使用Python脚本,该脚本读取分类图片文件,然后将其转换为所需的格式。

import os
import shutil
import random

# 指定输入和输出文件夹的路径
input_dir = 'train'
output_dir = 'output'

# 确保输出文件夹存在
if not os.path.exists(output_dir):
    os.makedirs(output_dir)

# 遍历输入文件夹中的所有子文件夹
for subdir in os.listdir(input_dir):
    input_subdir_path = os.path.join(input_dir, subdir)

    # 确保它是一个子文件夹
    if os.path.isdir(input_subdir_path):
        output_subdir_path = os.path.join(output_dir, subdir)

        # 在输出文件夹中创建同名的子文件夹
        if not os.path.exists(output_subdir_path):
            os.makedirs(output_subdir_path)

        # 获取所有文件的列表
        files = [f for f in os.listdir(input_subdir_path) if os.path.isfile(os.path.join(input_subdir_path, f))]

        # 随机选择四分之一的文件
        files_to_move = random.sample(files, len(files) // 4)

        # 移动文件
        for file_to_move in files_to_move:
            src_path = os.path.join(input_subdir_path, file_to_move)
            dest_path = os.path.join(output_subdir_path, file_to_move)
            shutil.move(src_path, dest_path)

print("任务完成!")


整理数据文件夹结构

我们需要将数据集整理为以下结构:

-----dataset
	-----dataset
           |-----train
           |   |-----class1
           |   |-----class2
           |   |-----.......
           |
           |-----valid
           |   |-----class1
           |   |-----class2
           |   |-----.......
           |
           |-----test
           |   |-----class1
           |   |-----class2
           |   |-----.......

模型训练
 Epoch   gpu_mem       box       obj       cls    labels  img_size
 1/200     20.8G   0.01576   0.01955  0.007536        22      1280: 100%|██████████| 849/849 [14:42<00:00,  1.04s/it]
           Class     Images     Labels          P          R     mAP@.5 mAP@.5:.95: 100%|██████████| 213/213 [01:14<00:00,  2.87it/s]
             all       3395      17314      0.994      0.957      0.0957      0.0843

 Epoch   gpu_mem       box       obj       cls    labels  img_size
 2/200     20.8G   0.01578   0.01923  0.007006        22      1280: 100%|██████████| 849/849 [14:44<00:00,  1.04s/it]
           Class     Images     Labels          P          R     mAP@.5 mAP@.5:.95: 100%|██████████| 213/213 [01:12<00:00,  2.95it/s]
             all       3395      17314      0.996      0.956      0.0957      0.0845

 Epoch   gpu_mem       box       obj       cls    labels  img_size
 3/200     20.8G   0.01561    0.0191  0.006895        27      1280: 100%|██████████| 849/849 [10:56<00:00,  1.29it/s]
           Class     Images     Labels          P          R     mAP@.5 mAP@.5:.95: 100%|███████   | 187/213 [00:52<00:00,  4.04it/s]
             all       3395      17314      0.996      0.957      0.0957      0.0845

5.核心代码讲解

5.1 cls_hrnet.py


def conv3x3(in_planes, out_planes, stride=1):
    """3x3 convolution with padding"""
    return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
                     padding=1, bias=False)


class BasicBlock(nn.Module):
    expansion = 1

    def __init__(self, inplanes, planes, stride=1, downsample=None):
        super(BasicBlock, self).__init__()
        self.conv1 = conv3x3(inplanes, planes, stride)
        self.bn1 = nn.BatchNorm2d(planes, momentum=BN_MOMENTUM)
        self.relu = nn.ReLU(inplace=True)
        self.conv2 = conv3x3(planes, planes)
        self.bn2 = nn.BatchNorm2d(planes, momentum=BN_MOMENTUM)
        self.downsample = downsample
        self.stride = stride

    def forward(self, x):
        residual = x

        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.bn2(out)

        if self.downsample is not None:
            residual = self.downsample(x)

        out += residual
        out = self.relu(out)

        return out


class Bottleneck(nn.Module):
    expansion = 4

    def __init__(self, inplanes, planes, stride=1, downsample=None):
        super(Bottleneck, self).__init__()
        self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False)
        self.bn1 = nn.BatchNorm2d(planes, momentum=BN_MOMENTUM)
        self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride,
                               padding=1, bias=False)
        self.bn2 = nn.BatchNorm2d(planes, momentum=BN_MOMENTUM)
        self.conv3 = nn.Conv2d(planes, planes * self.expansion, kernel_size=1,
                               bias=False)
        self.bn3 = nn.BatchNorm2d(planes * self.expansion,
                               momentum=BN_MOMENTUM)
        self.relu = nn.ReLU(inplace=True)
        self.downsample = downsample
        self.stride = stride

    def forward(self, x):
        residual = x

        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.bn2(out)
        out = self.relu(out)

        out = self.conv3(out)
        out = self.bn3(out)

        if self.downsample is not None:
            residual = self.downsample(x)

        out += residual
        out = self.relu(out)

        return out


class HighResolutionModule(nn.Module):
    def __init__(self, num_branches, blocks, num_blocks, num_inchannels,
                 num_channels, fuse_method, multi_scale_output=True):
        super(HighResolutionModule, self).__init__()
        self._check_branches(
            num_branches, blocks, num_blocks, num_inchannels, num_channels)

        self.num_inchannels = num_inchannels
        self.fuse_method = fuse_method
        self.num_branches = num_branches

        self.multi_scale_output = multi_scale_output

        self.branches = self._make_branches(
            num_branches, blocks, num_blocks, num_channels)
        self.fuse_layers = self._make_fuse_layers()
        self.relu = nn.ReLU(False)

    def _check_branches(self, num_branches, blocks, num_blocks,
                        num_inchannels, num_channels):
        if num_branches != len(num_blocks):
            error_msg = 'NUM_BRANCHES({}) <> NUM_BLOCKS({})'.format(
                num_branches, len(num_blocks))
            logger.error(error_msg)
            raise ValueError(error_msg)

        if num_branches != len(num_channels):
            error_msg = 'NUM_BRANCHES({}) <> NUM_CHANNELS({})'.format(
                num_branches, len(num_channels))
            logger.error(error_msg)
            raise ValueError(error_msg)

        if num_branches != len(num_inchannels):
            error_msg = 'NUM_BRANCHES({}) <> NUM_INCHANNELS({})'.format(
                num_branches, len(num_inchannels))
            logger.error(error_msg)
            raise ValueError(error_msg)

    def _make_one_branch(self, branch_index, block, num_blocks, num_channels,
                         stride=1):
        downsample = None
        if stride != 1 or \
           self.num_inchannels[branch_index] != num_channels[branch_index] * block.expansion:
            downsample = nn.Sequential(
                nn.Conv2d(self.num_inchannels[branch_index],
                          num_channels[branch_index] * block.expansion,
                          kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(num_channels[branch_index] * block.expansion,
                            momentum=BN_MOMENTUM),
            )

        layers = []
        layers.append(block(self.num_inchannels[branch_index],
                            num_channels[branch_index], stride, downsample))
        self.num_inchannels[branch_index] = \
            num_channels[branch_index] * block.expansion
        for i in range(1, num_blocks[branch_index]):
            layers.append(block(self.num_inchannels[branch_index],
                                num_channels[branch_index]))

        return nn.Sequential(*layers)

    def _make_branches(self, num_branches, block, num_blocks, num_channels):
        branches = []

        for i in range(num_branches):
            branches.append(
                self._make_one_branch(i, block, num_blocks, num_channels))

        return nn.ModuleList(branches)

    def _make_fuse_layers(self):
        if self.num_branches == 1:
            return None

        num_branches = self.num_branches
        num_inchannels = self.num_inchannels
        fuse_layers = []
        for i in range(num_branches if self.multi_scale_output else 1):
            fuse_layer = []
            for j in range(num_branches):
                if j > i:
                    fuse_layer.append(nn.Sequential(
                        nn.Conv2d(num_inchannels[j],
                                  num_inchannels[i],
                                  1,
                                  1,
                                  0,
                                  bias=False),
                        nn.BatchNorm2d(num_inchannels[i], 
                                       momentum=BN_MOMENTUM),
                        nn.Upsample(scale_factor=2**(j-i), mode='nearest')))
                elif j == i:
                    fuse_layer.append(None)
                else:
                    conv3x3s = []
                    for k in range(i-j):
                        if k == i - j - 1:
                            num_outchannels_conv3x3 = num_inchannels[i]
                            conv3x3s.append(nn.Sequential(
                                nn.Conv2d(num_inchannels[j],
                                          num_outchannels_conv3x3,
                                          3, 2, 1, bias=False),
                                nn.BatchNorm2d(num_outchannels_conv3x3, 
                                            momentum=BN_MOMENTUM)))
                        else:
                            num_outchannels_conv3x3 = num_inchannels[j]
                            conv3x3s.append(nn.Sequential(
                                nn.Conv2d(num_inchannels[j],
                                          num_outchannels_conv3x3,
                                          3, 2, 1, bias=False),
                                nn.BatchNorm2d(num_outchannels_conv3x3,
                                            momentum=BN_MOMENTUM),
                                nn.ReLU(False)))
                    fuse_layer.append(nn.Sequential(*conv3x3s))
            fuse_layers.append(nn.ModuleList(fuse_layer))

        return nn.ModuleList(fuse_layers)

    def get_num_inchannels(self):
        return self.num_inchannels

    def forward(self, x):
        if self.num_branches == 1:
            return [self.branches[0](x[0])]

        for i in range(self.num_branches):
            x[i] = self.branches[i](x[i])

        x_fuse = []
        for i in range(len(self.fuse_layers)):
            y = x[0] if i == 0 else self.fuse_layers[i][0](x[0])
            for j in range(1, self.num_branches):
                if i == j:
                    y = y + x[j]
                else:
                    y = y + self.fuse_layers[i][j](x[j])
            x_fuse.append(self.relu(y))

        return x_fuse


blocks_dict = {
    'BASIC': BasicBlock,
    'BOTTLENECK': Bottleneck
}


class HighResolutionNet(nn.Module):

    def __init__(self, cfg, **kwargs):
        super(HighResolutionNet, self

该程序文件是一个HRNet模型的实现,包含了多个模块和类。

文件中定义了一些辅助函数和常量,如conv3x3函数用于创建3x3的卷积层,BasicBlockBottleneck类分别定义了HRNet中的基本块和瓶颈块。

HighResolutionModule类定义了HRNet的高分辨率模块,其中包含了多个分支和融合层。_make_one_branch函数用于创建一个分支,_make_branches函数用于创建多个分支,_make_fuse_layers函数用于创建融合层。forward函数用于前向传播。

HighResolutionNet类定义了整个HRNet模型,包含了多个阶段和层。__init__函数用于初始化模型,_make_layer函数用于创建一个阶段,forward函数用于前向传播。

总体来说,该程序文件实现了HRNet模型的各个组件和整体结构。

5.2 evaluate.py


class Accuracy:
    def __init__(self, topk=(1,)):
        self.topk = topk

    def compute(self, output, target):
        """Computes the precision@k for the specified values of k"""
        with torch.no_grad():
            maxk = max(self.topk)
            batch_size = target.size(0)

            _, pred = output.topk(maxk, 1, True, True)
            pred = pred.t()
            correct = pred.eq(target.view(1, -1).expand_as(pred))

            res = []
            for k in self.topk:
                correct_k = correct[:k].view(-1).float().sum(0, keepdim=True)
                res.append(correct_k.mul_(100.0 / batch_size))
            return res

这个程序文件名为evaluate.py,它包含了一个名为accuracy的函数。这个函数用于计算给定的输出和目标的准确率。具体来说,它计算了给定的topk值(默认为1)的准确率。

函数首先使用torch.no_grad()上下文管理器,以确保在计算准确率时不会进行梯度计算。然后,它确定了最大的topk值和批次大小。接下来,它使用output.topk()函数找到输出中的前k个最大值,并将其转置。然后,它将预测值与目标值进行比较,并计算出预测正确的数量。

最后,函数将计算的准确率以列表的形式返回。列表中的每个元素都是一个torch.Tensor对象,表示对应的topk值的准确率。这些准确率是通过将正确预测的数量除以批次大小并乘以100得到的。

5.3 function.py

class AverageMeter(object):
    """Computes and stores the average and current value"""
    def __init__(self):
        self.reset()

    def reset(self):
        self.val = 0
        self.avg = 0
        self.sum = 0
        self.count = 0

    def update(self, val, n=1):
        self.val = val
        self.sum += val * n
        self.count += n
        self.avg = self.sum / self.count

class Trainer(object):
    def __init__(self, config, train_loader, model, criterion, optimizer, epoch, output_dir, tb_log_dir, writer_dict):
        self.config = config
        self.train_loader = train_loader
        self.model = model
        self.criterion = criterion
        self.optimizer = optimizer
        self.epoch = epoch
        self.output_dir = output_dir
        self.tb_log_dir = tb_log_dir
        self.writer_dict = writer_dict

    def train(self):
        batch_time = AverageMeter()
        data_time = AverageMeter()
        losses = AverageMeter()
        top1 = AverageMeter()
        top5 = AverageMeter()

        # switch to train mode
        self.model.train()

        end = time.time()
        for i, (input, target) in enumerate(self.train_loader):
            # measure data loading time
            data_time.update(time.time() - end)
            #target = target - 1 # Specific for imagenet

            # compute output
            output = self.model(input)
            target = target.cuda(non_blocking=True)

            loss = self.criterion(output, target)

            # compute gradient and do update step
            self.optimizer.zero_grad()
            loss.backward()
            self.optimizer.step()

            # measure accuracy and record loss
            losses.update(loss.item(), input.size(0))

            prec1, prec5 = accuracy(output, target, (1, 5))

            top1.update(prec1[0], input.size(0))
            top5.update(prec5[0], input.size(0))

            # measure elapsed time
            batch_time.update(time.time() - end)
            end = time.time()

            if i % self.config.PRINT_FREQ == 0:
                msg = 'Epoch: [{0}][{1}/{2}]\t' \
                      'Time {batch_time.val:.3f}s ({batch_time.avg:.3f}s)\t' \
                      'Speed {speed:.1f} samples/s\t' \
                      'Data {data_time.val:.3f}s ({data_time.avg:.3f}s)\t' \
                      'Loss {loss.val:.5f} ({loss.avg:.5f})\t' \
                      'Accuracy@1 {top1.val:.3f} ({top1.avg:.3f})\t' \
                      'Accuracy@5 {top5.val:.3f} ({top5.avg:.3f})\t'.format(
                          self.epoch, i, len(self.train_loader), batch_time=batch_time,
                          speed=input.size(0)/batch_time.val,
                          data_time=data_time, loss=losses, top1=top1, top5=top5)
                logger.info(msg)

                if self.writer_dict:
                    writer = self.writer_dict['writer']
                    global_steps = self.writer_dict['train_global_steps']
                    writer.add_scalar('train_loss', losses.val, global_steps)
                    writer.add_scalar('train_top1', top1.val, global_steps)
                    self.writer_dict['train_global_steps'] = global_steps + 1

class Validator(object):
    def __init__(self, config, val_loader, model, criterion, output_dir, tb_log_dir, writer_dict=None):
        self.config = config
        self.val_loader = val_loader
        self.model = model
        self.criterion = criterion
        self.output_dir = output_dir
        self.tb_log_dir = tb_log_dir
        self.writer_dict = writer_dict

    def validate(self):
        batch_time = AverageMeter()
        losses = AverageMeter()
        top1 = AverageMeter()
        top5 = AverageMeter()

        # switch to evaluate mode
        self.model.eval()

        with torch.no_grad():
            end = time.time()
            for i, (input, target) in enumerate(self.val_loader):
                # compute output
                output = self.model(input)

                target = target.cuda(non_blocking=True)

                loss = self.criterion(output, target)

                # measure accuracy and record loss
                losses.update(loss.item(), input.size(0))
                prec1, prec5 = accuracy(output, target, (1, 5))
                top1.update(prec1[0], input.size(0))
                top5.update(prec5[0], input.size(0))

                # measure elapsed time
                batch_time.update(time.time() - end)
                end = time.time()

            msg = 'Test: Time {batch_time.avg:.3f}\t' \
                  'Loss {loss.avg:.4f}\t' \
                  'Error@1 {error1:.3f}\t' \
                  'Error@5 {error5:.3f}\t' \
                  'Accuracy@1 {top1.avg:.3f}\t' \
                  'Accuracy@5 {top5.avg:.3f}\t'.format(
                      batch_time=batch_time, loss=losses, top1=top1, top5=top5,
                      error1=100-top1.avg, error5=100-top5.avg)
            logger.info(msg)

            if self.writer_dict:
                writer = self.writer_dict['writer']
                global_steps = self.writer_dict['valid_global_steps']
                writer.add_scalar('valid_loss', losses.avg, global_steps)
                writer.add_scalar('valid_top1', top1.avg, global_steps)
                self.writer_dict['valid_global_steps'] = global_steps + 1

这个程序文件名为model.py,主要功能是使用timm库中的模型来进行推理和计算模型的FLOPS和参数数量。

程序首先导入了torch、timm和thop库。然后使用timm.list_models()函数列出了所有可用的模型,并打印出来。

接下来,程序判断当前设备是否支持CUDA,并创建了一个大小为(1, 3, 640, 640)的随机输入张量dummy_input,并将其发送到设备上。

然后,程序使用timm.create_model()函数创建了一个名为’hrnet_w64’的模型,设置pretrained为False,并只返回特征部分。接着将模型发送到设备上,并设置为评估模式。

程序使用model.feature_info.channels()函数打印出模型的特征通道信息。然后,对dummy_input进行推理,并打印出每个特征的大小。

最后,程序使用thop库的profile函数计算模型在给定输入上的FLOPS和参数数量,并使用clever_format函数格式化输出。最后打印出总的FLOPS和参数数量。

5.3 train.py


def train(hyp,  # path/to/hyp.yaml or hyp dictionary
          opt,
          device,
          callbacks
          ):
    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

    # 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()))

    # Save run settings
    with open(save_dir / 'hyp.yaml', 'w') as f:
        yaml.safe_dump(hyp, f, sort_keys=False)
    with open(save_dir / 'opt.yaml', 'w') as f:
        yaml.safe_dump(vars(opt), f, sort_keys=False)
    data_dict = None

    # Loggers
    if RANK in [-1, 0]:
        loggers = Loggers(save_dir, weights, opt, hyp, LOGGER)  # loggers instance
        if loggers.wandb:
            data_dict = loggers.wandb.data_dict
            if resume:
                weights, epochs, hyp = opt.weights, opt.epochs, opt.hyp

        # Register actions
        for k in methods(loggers):
            callbacks.register_action(k, callback=getattr(loggers, k))

    # Config
    plots = not evolve  # create plots
    cuda = device.type != 'cpu'
    init_seeds(1 + RANK)
    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 = ['item'] if single_cls and len(data_dict['names']) != 1 else data_dict['names']  # class names
    assert len(names) == nc, f'{len(names)} names found for nc={nc} dataset in {data}'  # check
    is_coco = data.endswith('coco.yaml') and nc == 80  # 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=device)  # load checkpoint
        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

    # Freeze
    freeze = [f'model.{x}.' for x in range(freeze)]  # layers to freeze
    for k, v in model.named_parameters():
        v.requires_grad = True  # train all layers
        if any(x in k for x in freeze):
            print(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)

    # 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
    LOGGER.info(f"Scaled weight_decay = {hyp['weight_decay']}")

    g0, g1, g2 = [], [], []  # optimizer parameter groups
    for v in model.modules():
        if hasattr(v, 'bias') and isinstance(v.bias, nn.Parameter):  # bias
            g2.append(v.bias)
        if isinstance(v, nn.BatchNorm2d):  # weight (no decay)
            g0.append(v.weight)
        elif hasattr(v, 'weight') and isinstance(v.weight, nn.Parameter):  # weight (with decay)
            g1.append(v.weight)

    if opt.adam:
        optimizer = Adam(g0, lr=hyp['lr0'], betas=(hyp['momentum'], 0.999))  # adjust beta1 to momentum
    else:
        optimizer = SGD(g0, lr=hyp['lr0'], momentum=hyp['momentum'], nesterov=True)

    optimizer.add_param_group({'params': g1, 'weight_decay': hyp['weight_decay']})  # add g1 with weight_decay
    optimizer.add_param_group({'params': g2})  # add g2 (biases)
    LOGGER.info(f"{colorstr('optimizer:')} {type(optimizer).__name__} with parameter groups "
                f"{len(g0)} weight, {len(g1)} weight (no decay), {len(g2)} bias")
    del g0, g1, g2

    # Scheduler
    if opt.linear_lr:
        lf = lambda x: (1 - x / (epochs - 1)) *

该程序文件是用于训练一个YOLOv5模型的。它包含了训练的主要逻辑和一些辅助函数。程序文件的名称是train.py。该文件的主要功能是加载数据集、创建模型、定义优化器和学习率调度器、训练模型、保存模型等。程序文件使用了一些外部库,如argparse、logging、torch等。

5.4 ui.py


class ImageClassifier:
    def __init__(self, weights, source, data, imgsz, device, view_img, save_txt, nosave, augment, visualize, update, project, name, exist_ok, half, dnn, vid_stride):
        self.weights = weights
        self.source = source
        self.data = data
        self.imgsz = imgsz
        self.device = device
        self.view_img = view_img
        self.save_txt = save_txt
        self.nosave = nosave
        self.augment = augment
        self.visualize = visualize
        self.update = update
        self.project = project
        self.name = name
        self.exist_ok = exist_ok
        self.half = half
        self.dnn = dnn
        self.vid_stride = vid_stride

    def run(self):
        source = str(self.source)
        save_img = not self.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

        # Directories
        save_dir = increment_path(Path(self.project) / self.name, exist_ok=self.exist_ok)  # increment run
        (save_dir / 'labels' if self.save_txt else save_dir).mkdir(parents=True, exist_ok=True)  # make dir

        # Load model
        device = select_device(self.device)
        model = DetectMultiBackend(self.weights, device=device, dnn=self.dnn, data=self.data, fp16=self.half)
        stride, names, pt = model.stride, model.names, model.pt
        imgsz = check_img_size(self.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, transforms=classify_transforms(imgsz[0]), vid_stride=self.vid_stride)
            bs = len(dataset)
        elif screenshot:
            dataset = LoadScreenshots(source, img_size=imgsz, stride=stride, auto=pt)
        else:
            dataset = LoadImages(source, img_size=imgsz, transforms=classify_transforms(imgsz[0]), vid_stride=self.vid_stride)
        vid_path, vid_writer = [None] * bs, [None] * bs

        # Run inference
        model.warmup(imgsz=(1 if pt 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.Tensor(im).to(model.device)
                im = im.half() if model.fp16 else im.float()  # uint8 to fp16/32
                if len(im.shape) == 3:
                    im = im[None]  # expand for batch dim

            # Inference
            with dt[1]:
                results = model(im)

            # Post-process
            with dt[2]:
                pred = F.softmax(results, dim=1)  # probabilities

            # Process predictions
            for i, prob in enumerate(pred):  # per image
                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)  # to Path
                save_path = str(save_dir / p.name)  # im.jpg
                txt_path = str(save_dir / 'labels' / p.stem) + ('' if dataset.mode == 'image' else f'_{frame}')  # im.txt

                s += '%gx%g ' % im.shape[2:]  # print string
                annotator = Annotator(im0, example=str(names), pil=True)

                # Print results
                top5i = prob.argsort(0, descending=True)[:5].tolist()  # top 5 indices
                s += f"{', '.join(f'{names[j]} {prob[j]:.2f}' for j in top5i)}, "

                # Write results
                text = '\n'.join(f'{prob[j]:.2f} {names[j]}' for j in top5i)
                classname = names[top5i[0]]
                if classname == 'daisy':
                    ui.printf('检测到雏菊')
                if classname == 'dandelion':
                    ui.printf('检测到蒲公英')
                if classname == 'roses':
                    ui.printf('检测到玫瑰花')
                if classname == 'sunflowers':
                    ui.printf('检测到向日葵')
                if classname == 'tulips':
                    ui.printf('检测到郁金香')

                if save_img or view_img:  # Add bbox to image
                    annotator.text((32, 32), text, txt_color=(255, 255, 255))
                if self.save_txt:  # Write to file
                    with open(f'{txt_path}.txt', 'a') as f:
                        f.write(text + '\n')

                # Stream results
                ......

该程序文件是一个使用YOLOv5模型进行目标检测的程序。它使用PyQt5库创建了一个用户界面,可以选择输入图像或视频进行目标检测,并显示检测结果。

程序首先导入了所需的库和模块,然后定义了一些全局变量和函数。接下来,定义了一个名为run的函数,该函数接受一些参数,包括模型权重路径、输入源、数据集路径等。该函数首先加载模型并选择设备,然后根据输入源的类型加载数据。接着,对输入数据进行预处理、推理和后处理,最后将结果保存或显示出来。

程序还定义了一个名为parse_opt的函数,该函数用于解析命令行参数,并返回解析结果。

整个程序的主要功能是使用YOLOv5模型进行目标检测,并将检测结果保存或显示出来。

5.5 classify\predict.py



class YOLOv5Classifier:
    def __init__(self, weights, source, data, imgsz, device, view_img, save_txt, nosave, augment, visualize, update,
                 project, name, exist_ok, half, dnn, vid_stride):
        self.weights = weights
        self.source = source
        self.data = data
        self.imgsz = imgsz
        self.device = device
        self.view_img = view_img
        self.save_txt = save_txt
        self.nosave = nosave
        self.augment = augment
        self.visualize = visualize
        self.update = update
        self.project = project
        self.name = name
        self.exist_ok = exist_ok
        self.half = half
        self.dnn = dnn
        self.vid_stride = vid_stride

    def run(self):
        source = str(self.source)
        save_img = not self.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

        # Directories
        save_dir = increment_path(Path(self.project) / self.name, exist_ok=self.exist_ok)  # increment run
        (save_dir / 'labels' if self.save_txt else save_dir).mkdir(parents=True, exist_ok=True)  # make dir

        # Load model
        device = select_device(self.device)
        model = DetectMultiBackend(self.weights, device=device, dnn=self.dnn, data=self.data, fp16=self.half)
        stride, names, pt = model.stride, model.names, model.pt
        imgsz = check_img_size(self.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, transforms=classify_transforms(imgsz[0]),
                                  vid_stride=self.vid_stride)
            bs = len(dataset)
        elif screenshot:
            dataset = LoadScreenshots(source, img_size=imgsz, stride=stride, auto=pt)
        else:
            dataset = LoadImages(source, img_size=imgsz, transforms=classify_transforms(imgsz[0]),
                                 vid_stride=self.vid_stride)
        vid_path, vid_writer = [None] * bs, [None] * bs

        # Run inference
        model.warmup(imgsz=(1 if pt 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.Tensor(im).to(model.device)
                im = im.half() if model.fp16 else im.float()  # uint8 to fp16/32
                if len(im.shape) == 3:
                    im = im[None]  # expand for batch dim

            # Inference
            with dt[1]:
                results = model(im)

            # Post-process
            with dt[2]:
                pred = F.softmax(results, dim=1)  # probabilities

            # Process predictions
            for i, prob in enumerate(pred):  # per image
                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)  # to Path
                save_path = str(save_dir / p.name)  # im.jpg
                txt_path = str(save_dir / 'labels' / p.stem) + ('' if dataset.mode == 'image' else f'_{frame}')  # im.txt

                s += '%gx%g ' % im.shape[2:]  # print string
                annotator = Annotator(im0, example=str(names), pil=True)

                # Print results
                top5i = prob.argsort(0, descending=True)[:5].tolist()  # top 5 indices
                s += f"{', '.join(f'{names[j]} {prob[j]:.2f}' for j in top5i)}, "

                # Write results
                text = '\n'.join(f'{prob[j]:.2f} {names[j]}' for j in top
                ......

这个程序文件是一个用于运行YOLOv5分类推理的脚本。它可以用于对图像、视频、目录、YouTube、网络摄像头等进行分类推理。

程序文件中定义了一些命令行参数,用于指定模型路径、数据源、推理尺寸、设备等。它还包含了一些辅助函数和工具类,用于加载模型、处理输入数据、进行推理和后处理等操作。

在主函数中,程序首先解析命令行参数,然后调用run函数进行推理。run函数根据命令行参数设置模型、数据加载器和推理参数,然后进行推理。推理过程中,程序会将推理结果保存到文件或显示在屏幕上。

最后,程序会打印推理速度和保存结果的路径。

整体来说,这个程序文件是一个用于运行YOLOv5分类推理的脚本,可以方便地对图像、视频等进行分类推理,并保存推理结果。

6.系统整体结构

根据以上分析,该程序是一个基于深度学习的HRNet-YOLO的花卉图像识别系统。它包含了目标检测、分类和分割三个主要功能模块。程序的整体构架如下:

  1. 目标检测模块:

    • cls_hrnet.py:实现HRNet-YOLO模型的网络结构。
    • evaluate.py:评估模型在测试集上的性能。
    • export.py:将模型导出为TorchScript或ONNX格式。
    • function.py:定义了一些辅助函数,如计算准确率等。
    • model.py:定义了HRNet模型的整体结构。
    • train.py:训练模型的主程序。
    • ui.py:用户界面,用于交互式操作模型。
  2. 分类模块:

    • classify/predict.py:使用训练好的模型进行图像分类预测。
    • classify/train.py:训练图像分类模型。
    • classify/val.py:验证图像分类模型的性能。
  3. 分割模块:

    • segment/predict.py:使用训练好的模型进行图像分割预测。
    • segment/train.py:训练图像分割模型。
    • segment/val.py:验证图像分割模型的性能。
  4. 其他工具模块:

    • models/common.py:定义了一些通用的模型组件。
    • models/experimental.py:定义了一些实验性的模型组件。
    • models/tf.py:与TensorFlow相关的模型组件。
    • models/yolo.py:YOLO模型的定义。
    • models/init.py:模型模块的初始化文件。
    • utils/:包含了一系列辅助函数和工具类,用于数据处理、模型训练、模型评估等。

下面是每个文件的功能概述:

文件名功能
cls_hrnet.py实现HRNet-YOLO模型的网络结构
evaluate.py评估模型在测试集上的性能
export.py将模型导出为TorchScript或ONNX格式
function.py定义了一些辅助函数,如计算准确率等
model.py定义了HRNet模型的整体结构
train.py训练模型的主程序
ui.py用户界面,用于交互式操作模型
classify/predict.py使用训练好的模型进行图像分类预测
classify/train.py训练图像分类模型
classify/val.py验证图像分类模型的性能
segment/predict.py使用训练好的模型进行图像分割预测
segment/train.py训练图像分割模型
segment/val.py验证图像分割模型的性能
models/common.py定义了一些通用的模型组件
models/experimental.py定义了一些实验性的模型组件
models/tf.py与TensorFlow相关的模型组件
models/yolo.pyYOLO模型的定义
models/init.py模型模块的初始化文件
utils/activations.py激活函数相关的辅助函数
utils/augmentations.py数据增强相关的辅助函数
utils/autoanchor.py自动锚框相关的辅助函数
utils/autobatch.py自动批处理相关的辅助函数
utils/callbacks.py回调函数相关的辅助函数
utils/dataloaders.py数据加载器相关的辅助函数
utils/downloads.py下载数据集和模型权重的辅助函数
utils/general.py通用的辅助函数
utils/loss.py损失函数相关的辅助函数
utils/metrics.py评估指标相关的辅助函数
utils/plots.py绘图相关的辅助函数
utils/torch_utils.pyPyTorch相关的辅助函数
utils/triton.pyTriton Inference Server相关的辅助函数
utils/init.py工具模块的初始化文件
utils/aws/resume.pyAWS相关的辅助函数
utils/aws/init.pyAWS模块的初始化文件
utils/flask_rest_api/example_request.pyFlask REST API的示例请求文件
utils/flask_rest_api/restapi.pyFlask REST API的实现文件
utils/loggers/init.py日志记录器模块的初始化文件
utils/loggers/clearml/clearml_utils.pyClearML日志记录器的辅助函数
utils/loggers/clearml/hpo.pyClearML日志记录器的超参数优化辅助函数
utils/loggers/clearml/init.pyClearML日志记录器模块的初始化文件
utils/loggers/comet/comet_utils.pyComet日志记录器的辅助函数
utils/loggers/comet/hpo.pyComet日志记录器的超参数优化辅助函数
utils/loggers/comet/init.pyComet日志记录器模块的初始化文件
utils/loggers/wandb/wandb_utils.pyWandB日志记录器的辅助函数
utils/loggers/wandb/init.pyWandB日志记录器模块的初始化文件
utils/segment/augmentations.py图像分割数据增强相关的辅助函数
utils/segment/dataloaders.py图像分割数据加载器相关的辅助函数
utils/segment/general.py图像分割通用的辅助函数
utils/segment/loss.py图像分割损失函数相关的辅助函数
utils/segment/metrics.py图像分割评估指标相关的辅助函数
utils/segment/plots.py图像分割绘图相关的辅助函数
utils/segment/init.py图像分割模块的初始化文件

7.HRNet骨干网络简介

HRNet是微软亚洲研究院的团队完成的,打通图像分类、图像分割、目标检测、人脸对齐、姿态识别、风格迁移、Image Inpainting、超分、optical flow、Depth estimation、边缘检测等网络结构。
在这里插入图片描述

王老师在ValseWebinar《物体和关键点检测》中亲自讲解了HRNet,讲解地非常透彻。以下文章主要参考了王老师在演讲中的解读,配合论文+代码部分,来为各位读者介绍这个全能的Backbone-HRNet。
在人体姿态识别这类的任务中,需要生成一个高分辨率的heatmap来进行关键点检测。这就与一般的网络结构比如VGGNet的要求不同,因为VGGNet最终得到的feature map分辨率很低,损失了空间结构。
在这里插入图片描述

获取高分辨率的方式大部分都是如上图所示,采用的是先降分辨率,然后再升分辨率的方法。U-Net、SegNet、DeconvNet、Hourglass本质上都是这种结构。
在这里插入图片描述

核心

普通网络都是这种结构,不同分辨率之间是进行了串联

在这里插入图片描述

王井东老师则是将不同分辨率的feature map进行并联:

在这里插入图片描述

在并联的基础上,添加不同分辨率feature map之间的交互(fusion)。

在这里插入图片描述

具体fusion的方法如下图所示:

在这里插入图片描述

同分辨率的层直接复制。
需要升分辨率的使用bilinear upsample + 1x1卷积将channel数统一。
需要降分辨率的使用strided 3x3 卷积。
三个feature map融合的方式是相加。
至于为何要用strided 3x3卷积,这是因为卷积在降维的时候会出现信息损失,使用strided 3x3卷积是为了通过学习的方式,降低信息的损耗。所以这里没有用maxpool或者组合池化。

在这里插入图片描述

另外在读HRNet的时候会有一个问题,有四个分支的到底如何使用这几个分支呢?论文中也给出了几种方式作为最终的特征选择。
在这里插入图片描述

(a)图展示的是HRNetV1的特征选择,只使用分辨率最高的特征图。

(b)图展示的是HRNetV2的特征选择,将所有分辨率的特征图(小的特征图进行upsample)进行concate,主要用于语义分割和面部关键点检测。

©图展示的是HRNetV2p的特征选择,在HRNetV2的基础上,使用了一个特征金字塔,主要用于目标检测网络。
在这里插入图片描述

再补充一个(d)图

(d)图展示的也是HRNetV2,采用上图的融合方式,主要用于训练分类网络。

总结一下HRNet创新点:

(1)将高低分辨率之间的链接由串联改为并联。
(2)在整个网络结构中都保持了高分辨率的表征(最上边那个通路)。
(3)在高低分辨率中引入了交互来提高模型性能。

8.训练结果可视化分析

为了对基于深度学习的 HRNet-YOLO 花卉图像识别系统的实验结果进行全面分析,必须从多个角度处理数据。此分析应包含系统架构的概述、用于培训和测试的数据集、性能指标以及对所提供图像中所示结果的详细从不同角度详细分析。

系统架构

HRNet(高分辨率网络)与 YOLO(You Only Look Once)相结合提出了一种创新方法,其中 HRNet 在通过网络维持高分辨率表示方面的鲁棒性与 YOLO 的速度和检测功能相结合。这种融合可能旨在增强系统通过复杂细节识别各种花朵的能力,这对于准确分类至关重要。

数据集和训练

任何深度学习模型的成功很大程度上取决于数据集的质量和数量。对于花卉图像识别系统,数据集必须包括不同生长阶段、光照条件和方向的各种花卉。从图像中,我们可以推断数据集包括玫瑰、郁金香、向日葵、雏菊和蒲公英。平衡的数据集将包含每个类别相同数量的图像,具有不同的背景和位置,以最大限度地减少任何偏差。

评价指标

图像识别系统的关键性能指标包括准确度、精确度、召回率和 F1 分数。准确性将衡量模型正确分类花卉的整体有效性。精确度和召回率分别表示系统正确标记花朵图像的能力以及正确识别特定花朵的所有实例的程度。F1 分数将提供精确度和召回率之间的平衡,这是数据集不平衡时的一个重要因素。
该数据集包含有关基于深度学习的 HRNet-YOLO 花卉图像识别系统在不同时期的性能信息。这些列包括:

epoch:训练过程中的纪元号。
train/loss:每个时期训练期间产生的损失。
test/loss:每个时期测试期间产生的损失。
metrics/accuracy_top1:top-1 准确度指标,指示模型最高置信度预测正确的测试样本的比例。
metrics/accuracy_top5:top-5 准确度指标,指示正确标签属于模型前 5 个预测的测试样本的比例。
lr/0:每个时期的学习率。
为了可视化这些数据,我们可以为每个时期的这些指标创建图表。这将帮助我们分析模型的性能和学习率如何随时间演变。

结果可视化
import matplotlib.pyplot as plt

# Setting up the plots
fig, axes = plt.subplots(3, 2, figsize=(15, 12))
fig.suptitle('HRNet-YOLO Flower Image Recognition System Performance Analysis', fontsize=16)

# Plotting training loss
axes[0, 0].plot(data['epoch'], data['train/loss'], label='Training Loss', color='blue')
axes[0, 0].set_title('Training Loss per Epoch')
axes[0, 0].set_xlabel('Epoch')
axes[0, 0].set_ylabel('Loss')
axes[0, 0].grid(True)

# Plotting testing loss
axes[0, 1].plot(data['epoch'], data['test/loss'], label='Testing Loss', color='red')
axes[0, 1].set_title('Testing Loss per Epoch')
axes[0, 1].set_xlabel('Epoch')
axes[0, 1].set_ylabel('Loss')
axes[0, 1].grid(True)

# Plotting top-1 accuracy
axes[1, 0].plot(data['epoch'], data['metrics/accuracy_top1'], label='Top-1 Accuracy', color='green')
axes[1, 0].set_title('Top-1 Accuracy per Epoch')
axes[1, 0].set_xlabel('Epoch')
axes[1, 0].set_ylabel('Accuracy')
axes[1, 0].grid(True)

# Plotting top-5 accuracy
axes[1, 1].plot(data['epoch'], data['metrics/accuracy_top5'], label='Top-5 Accuracy', color='orange')
axes[1, 1].set_title('Top-5 Accuracy per Epoch')
axes[1, 1].set_xlabel('Epoch')
axes[1, 1].set_ylabel('Accuracy')
axes[1, 1].grid(True)

# Plotting learning rate
axes[2, 0].plot(data['epoch'], data['lr/0'], label='Learning Rate', color='purple')
axes[2, 0].set_title('Learning Rate per Epoch')
axes[2, 0].set_xlabel('Epoch')
axes[2, 0].set_ylabel('Learning Rate')
axes[2, 0].grid(True)

# Adjust layout
plt.tight_layout(rect=[0, 0.03, 1, 0.95])

# Show the plots
plt.show()

在这里插入图片描述

训练结果分析
1.训练与测试损失趋势分析

训练损失分析:该指标反映了模型在学习过程中对训练数据的识别与失踪能力。从初始的0.48046逐渐下降至0.28212,表明模型在学习过程中逐步提升了对数据训练的识别与失踪能力。下降趋势通常是模型优化过程中的正常现象,但这种也需要清醒的则可能,即模型在训练数据上表现得非常优秀,而在未知的数据上可能失效。
测试损失分析:测试损失最初的波动,从0.80446降到0.33867再升到0.44810,随后逐渐稳定并下降至0.23084,这可能表明模型在开始未见数据的支撑不强,但随着训练的深入研究,模型的泛化能力得到了提升。

2.准确率分析

Top-1准确率:这个选择指标是模型训练的第一个正确的概率。从0.80507梯度提升至0.92626,说明模型对于图像布置的识别能力随着逐渐增强。这种持续提升的也反映改进了的HRNet骨干网络和YOLOv5结合带来的功效。
Top-5准确率:由于数据中Top-5准确率始终为1,这可能表明在实验中该指标不是主要关注点,或者模型对于预留类别的区分能力相对关系,即使不是最佳预测,也能在前五个名称中包含正确的类别。

3.学习率与模型优化

学习率从0.00099逐渐下降到0.00095,这种缓慢恢复的策略可能是为了在初期快速恢复,在后期通过较小的学习步长进行精细调整,避免过大的步长导致模型在最优化解附近震荡而不是收敛。

4.改进的HRNet骨干网络对YOLOv5的影响

HRNet强调在多尺度特征的融合,这对于保留图像识别可能非常重要,因为保留的形状、颜色和纹理在不同尺度下融合表现出各异。通过改进的HRNet骨干网络,YOLOv5可能更有效地提取和这些特征,从而提高识别准确率。

5.模型泛化能力和鲁棒性分析

模型的泛化能力通过测试损失和测试准确率来体现。从数据中可以看出,随着训练的深入,模型在未知数据集上的表现逐渐稳定并提升,表明其具有良好的泛化能力。 测试损失的下降趋势和准确率的提升表明模型在多次迭代后对数据的下降和鲁棒性损失增强。
在这里插入图片描述

6.未来改进方向

根据目前的实验结果,未来的研究可以探索不同的学习率调整策略,例如使用更动态的学习率调整(如学习率回调机制),以期进一步提高模型的学习效率和泛化能力。
可以探索更多的数据修复和增强技术,以提高模型对于初始阶段不同特征的认知和鲁棒性。
还可以考虑对HRNet和YOLOv5的结合方式进行进一步的优化,例如通过改变网络结构或引入新的特征机制融合,以提高模型的整体性能。

9.系统整合

下图完整源码&数据集&环境部署视频教程&自定义UI界面

在这里插入图片描述

参考博客《基于深度学习的HRNet-YOLO的花卉图像识别系统》

10.参考文献


[1]邓忠豪,陈晓东.基于深度卷积神经网络的肺结节检测算法[J].计算机应用.2019,(7).DOI:10.11772/j.issn.1001-9081.2019010056 .

[2]关胤.基于残差网络迁移学习的花卉识别系统[J].计算机工程与应用.2019,(1).DOI:10.3778/j.issn.1002-8331.1709-0322 .

[3]曾燕,陈岳林,蔡晓东.结合全局与局部池化的深度哈希人脸识别算法[J].西安电子科技大学学报(自然科学版).2018,(5).DOI:10.3969/j.issn.1001-2400.2018.05.026 .

[4]郭子琰,舒心,刘常燕,等.基于ReLU函数的卷积神经网络的花卉识别算法[J].计算机技术与发展.2018,(5).DOI:10.3969/j.issn.1673-629X.2018.05.035 .

[5]王威,刘小翠,王新.基于综合特征的花卉种类识别方法研究[J].湖南城市学院学报(自然科学版).2018,(4).DOI:10.3969/j.issn.1672-7304.2018.04.0009 .

[6]沈萍,赵备.基于深度学习模型的花卉种类识别[J].科技通报.2017,(3).DOI:10.13774/j.cnki.kjtb.2017.03.014 .

[7]苗金泉,曹卫群.可扩展的花卉种类识别[J].中国图象图形学报.2014,(11).DOI:10.11834/jig.20141111 .

[8]柯逍,陈小芬,李绍滋.基于多特征融合的花卉图像检索[J].计算机科学.2010,(11).DOI:10.3969/j.issn.1002-137X.2010.11.068 .

[9]王爽.基于机器学习的花卉识别算法的研究与实现[J].电子科技大学.2018.

[10]Raul Orus Perez.Using TensorFlow-based Neural Network to estimate GNSS single frequency ionospheric delay (IONONet)[J].Advances in Space Research.2019,63(5).1607-1618.