秋天,是大地回馈辛勤耕耘者的季节,金黄的稻田、硕果累累的果园、还有那一片片郁郁葱葱的花生地,共同绘制出一幅幅丰收的画卷。对于农民而言,秋收不仅仅是收获的季节,更是他们与土地情感交织、汗水与希望交织的见证。花生,作为重要的经济作物之一,其采摘过程历来是农业生产中的重要环节。然而,传统的花生采摘方式大多依赖人工,劳动强度大、效率低,且伴随着不小的浪费问题。在往昔的秋日里,农民们需先小心翼翼地松土,再缓缓拔出花生植株,抖落泥土,一颗颗地将花生果实摘下,装入编织袋中。这种纯手工的方式,虽承载着世代传承的智慧与汗水,却也难以掩盖其低效与辛劳。随着科技的飞速发展,特别是人工智能、大模型、具身智能机器人等技术的不断突破,农业生产正迎来一场前所未有的变革。
近年来,越来越多的传统行业开始探索AI技术的应用,以期提升生产效率、优化作业模式。在农业领域,这一趋势尤为明显。以花生采摘为例,传统作业中常见的场景是:收割机作业后,仍有大量花生遗落在田间,需要人工弯腰捡拾,这不仅耗时费力,还容易遗漏,造成资源浪费。针对这一问题,AI智能化采摘机器人的出现,为花生采摘带来了新的解决方案。智能化采摘机器人,集成了环境感知、图像处理、机械臂操作等多项先进技术。它们能够通过环境交互式感知系统,精准定位并挖掘地里的花生。机器人头部的高清摄像头则扮演着“眼睛”的角色,负责捕捉花生植株的图像信息。这些图像数据随后被送入预先训练好的目标检测模型中,该模型能够高效、精确地识别并定位花生果实,甚至能进行计数,确保每一颗花生都不被遗漏。
为了实现这一目标,关键在于构建高效高精度的目标检测模型。这要求我们在软件开发阶段,广泛收集田间地头花生采摘的真实场景图像,并进行细致的人工标注。这些标注数据是模型训练的基石,它们帮助模型学习到花生果实在不同光照、角度、遮挡情况下的特征,从而提升模型的泛化能力和识别精度。通过深度学习算法的不断迭代优化,最终得到的模型能够在复杂多变的田间环境中,准确识别并定位花生果实,为机械臂的精准采摘提供可靠依据。本文正是基于这样的背景思考下,考虑基于轻量级的检测模型来实践开发花生果实检测计数系统,在前面的博文中我们已经进行了相关的开发实践,感兴趣的话可以自行移步阅读:
《助力花生作物智能化采摘,基于嵌入式端超轻量级模型LeYOLO全系列【n/s/m/l】参数模型开发构建花生种植采摘场景下花生果实智能检测计数系统》
《助力花生作物智能化采摘,基于YOLOv5全系列【n/s/m/l/x】参数模型开发构建花生种植采摘场景下花生果实智能检测计数系统》
《助力花生作物智能化采摘,基于YOLOv7全系列【tiny/l/x】参数模型开发构建花生种植采摘场景下花生果实智能检测计数系统》
《助力花生作物智能化采摘,基于YOLOv8全系列【n/s/m/l/x】参数模型开发构建花生种植采摘场景下花生果实智能检测计数系统》
《助力花生作物智能化采摘,基于YOLOv10全系列【n/s/m/b/l/x】参数模型开发构建花生种植采摘场景下花生果实智能检测计数系统》
《助力花生作物智能化采摘,基于YOLOv11全系列【n/s/m/l/x】参数模型开发构建花生种植采摘场景下花生果实智能检测计数系统》
《助力花生作物智能化采摘,基于最新脉冲目标检测模型SpikeYOLO全系列【n/s/m/l/x】参数模型开发构建花生种植采摘场景下花生果实智能检测计数系统》
本文则是想要基于YOLO系列最新发表的融合超图计算的目标检测模型HyperYOLO全系列的参数模型来进行开发实践,首先看下实例效果:
接下来看下实例数据:
Hyper-YOLO是一种新的目标检测方法,它集成了超图计算,以捕捉视觉特征之间复杂的、高阶的相关性。传统的YOLO模型虽然功能强大,但在颈部设计方面存在局限性,限制了跨层特征的整合以及高阶特征相互关系的利用。为了应对这些挑战,我们提出了超图计算赋能的语义收集与散射(HGC-SCS)框架,该框架将视觉特征图转换为语义空间,并构建超图以进行高阶消息传递。这使得模型能够获取语义和结构信息,超越了传统的以特征为中心的学习。Hyper-YOLO在其主干中集成了我们提出的混合聚合网络(MANet),以增强特征提取,并在其颈部引入了基于超图的跨层和跨位置表示网络(HyperC2Net)。HyperC2Net在五个尺度上运行,并摆脱了传统的网格结构,允许在不同层级和位置之间进行复杂的高阶交互。这些组件的协同作用使得Hyper-YOLO成为一种先进的架构,这一点在COCO数据集上的优越表现中得到了证实。Hyper-YOLO-N显著优于先进的YOLOv8-N,AP^{val}提升了12%。与SOTA的Gold-YOLO-N相比,Hyper-YOLO-N在仅使用72%参数的情况下,AP^{val}提升了5%。
数据集格式层面保持与YOLOv8系列完全相同的格式,这里如果有问题的话可以直接看我前面的系列博文:
《基于YOLOv8开发构建目标检测模型超详细教程【以焊缝质量检测数据场景为例》
训练代码如下:
import os
import sys
from copy import copy
import numpy as np
sys.path.append(os.getcwd())
from ultralytics.data import build_dataloader, build_yolo_dataset
from ultralytics.engine.trainer import BaseTrainer
from ultralytics.models import yolo
from ultralytics.nn.tasks import DetectionModel
from ultralytics.utils import DEFAULT_CFG, LOGGER, RANK
from ultralytics.utils.plotting import plot_images, plot_labels, plot_results
from ultralytics.utils.torch_utils import de_parallel, torch_distributed_zero_first
class DetectionTrainer(BaseTrainer):
def build_dataset(self, img_path, mode="train", batch=None):
gs = max(int(de_parallel(self.model).stride.max() if self.model else 0), 32)
return build_yolo_dataset(
self.args,
img_path,
batch,
self.data,
mode=mode,
rect=mode == "val",
stride=gs,
)
def get_dataloader(self, dataset_path, batch_size=16, rank=0, mode="train"):
assert mode in ["train", "val"]
with torch_distributed_zero_first(rank):
dataset = self.build_dataset(dataset_path, mode, batch_size)
shuffle = mode == "train"
if getattr(dataset, "rect", False) and shuffle:
LOGGER.warning(
"WARNING ⚠️ 'rect=True' is incompatible with DataLoader shuffle, setting shuffle=False"
)
shuffle = False
workers = self.args.workers if mode == "train" else self.args.workers * 2
return build_dataloader(
dataset, batch_size, workers, shuffle, rank
) # return dataloader
def preprocess_batch(self, batch):
batch["img"] = batch["img"].to(self.device, non_blocking=True).float() / 255
return batch
def set_model_attributes(self):
self.model.nc = self.data["nc"]
self.model.names = self.data["names"]
self.model.args = self.args
def get_model(self, cfg=None, weights=None, verbose=True):
model = DetectionModel(cfg, nc=self.data["nc"], verbose=verbose and RANK == -1)
if weights:
model.load(weights)
return model
def get_validator(self):
self.loss_names = "box_loss", "cls_loss", "dfl_loss"
return yolo.detect.DetectionValidator(
self.test_loader, save_dir=self.save_dir, args=copy(self.args)
)
def label_loss_items(self, loss_items=None, prefix="train"):
keys = [f"{prefix}/{x}" for x in self.loss_names]
if loss_items is not None:
loss_items = [round(float(x), 5) for x in loss_items]
return dict(zip(keys, loss_items))
else:
return keys
def progress_string(self):
return ("\n" + "%11s" * (4 + len(self.loss_names))) % (
"Epoch",
"GPU_mem",
*self.loss_names,
"Instances",
"Size",
)
def plot_training_samples(self, batch, ni):
plot_images(
images=batch["img"],
batch_idx=batch["batch_idx"],
cls=batch["cls"].squeeze(-1),
bboxes=batch["bboxes"],
paths=batch["im_file"],
fname=self.save_dir / f"train_batch{ni}.jpg",
on_plot=self.on_plot,
)
def plot_metrics(self):
plot_results(file=self.csv, on_plot=self.on_plot)
def plot_training_labels(self):
boxes = np.concatenate(
[lb["bboxes"] for lb in self.train_loader.dataset.labels], 0
)
cls = np.concatenate([lb["cls"] for lb in self.train_loader.dataset.labels], 0)
plot_labels(
boxes,
cls.squeeze(),
names=self.data["names"],
save_dir=self.save_dir,
on_plot=self.on_plot,
)
def train(cfg=DEFAULT_CFG, use_python=False):
model = cfg.model
data = cfg.data
device = cfg.device if cfg.device is not None else ""
args = dict(model=model, data=data, device=device)
if use_python:
from ultralytics import YOLO
YOLO(model).train(**args)
else:
trainer = DetectionTrainer(overrides=args)
trainer.train()
if __name__ == "__main__":
train()
这里我们依次选择n、s、m、l和x五款不同参数量级的模型来进行开发。
这里给出HyperYOLO的模型文件如下:
# Parameters
nc: 1 # number of classes
scales: # model compound scaling constants, i.e. 'model=hyper-yolon.yaml' will call hyper-yolo.yaml with scale 'n'
# [depth, width, max_channels, threshold]
n: [0.33, 0.25, 1024, 6]
s: [0.33, 0.50, 1024, 8]
m: [0.67, 0.75, 768, 10]
l: [1.00, 1.00, 512, 10]
x: [1.00, 1.25, 512, 12]
# Hyper-YOLO backbone
backbone:
- [-1, 1, Conv, [64, 3, 2]] # 0-B1/2
- [-1, 1, Conv, [128, 3, 2]] # 1
- [-1, 3, MANet, [128, True, 2, 3]] # 2-B2/4
- [-1, 1, Conv, [256, 3, 2]] # 3
- [-1, 6, MANet, [256, True, 2, 5]] # 4-B3/8
- [-1, 1, Conv, [512, 3, 2]] # 5
- [-1, 6, MANet, [512, True, 2, 5]] # 6-B4/16
- [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32
- [-1, 3, MANet, [1024, True, 2, 3]] # 8
- [-1, 1, SPPF, [1024, 5]] # 9-B5/32
# Hyper-YOLO head
head:
# Semantic Collecting
- [0, 1, nn.AvgPool2d, [8, 8, 0]] # 10
- [2, 1, nn.AvgPool2d, [4, 4, 0]] # 11
- [4, 1, nn.AvgPool2d, [2, 2, 0]] # 12
- [9, 1, nn.Upsample, [None, 2, 'nearest']] # 13
- [[10, 11, 12, 6, 13], 1, Concat, [1]] # cat 14
# Hypergraph Computation
- [-1, 1, Conv, [512, 1, 1]] # 15
- [-1, 1, HyperComputeModule, [512]] # 16
- [-1, 3, MANet, [512, True, 2, 3]] # 17
# Semantic Collecting
- [-1, 1, nn.AvgPool2d, [2, 2, 0]] # 18
- [[-1, 9], 1, Concat, [1]] # cat 19
- [-1, 1, Conv, [1024, 1, 1]] # 20 P5
- [[17, 6], 1, Concat, [1]] # cat 21
- [-1, 3, MANet, [512, False, 2, 3]] # 22 P4
- [17, 1, nn.Upsample, [None, 2, 'nearest']] # 23
- [[-1, 4], 1, Concat, [1]] # cat 24
- [-1, 3, MANet, [256, False, 2, 3]] # 25 P3/N3
- [-1, 1, Conv, [256, 3, 2]] # 26
- [[-1, 22], 1, Concat, [1]] # 27 cat
- [-1, 3, MANet, [512, False, 2, 3]] # 28 N4
- [-1, 1, Conv, [512, 3, 2]] # 29
- [[-1, 20], 1, Concat, [1]] # 30 cat
- [-1, 3, C2f, [1024, False]] # 31 N5
- [[25, 28, 31], 1, Detect, [nc]] # Detect(N3, N4, N5)
实验阶段我们保持了相同的参数设置,等待长时期的训练过程结束之后我们来对全系列五款不同参数量级的模型进行纵向的对比分析,如下:
【Precision曲线】
精确率曲线(Precision Curve)是一种用于评估二分类模型在不同阈值下的精确率性能的可视化工具。它通过绘制不同阈值下的精确率和召回率之间的关系图来帮助我们了解模型在不同阈值下的表现。
精确率(Precision)是指被正确预测为正例的样本数占所有预测为正例的样本数的比例。召回率(Recall)是指被正确预测为正例的样本数占所有实际为正例的样本数的比例。
绘制精确率曲线的步骤如下:
使用不同的阈值将预测概率转换为二进制类别标签。通常,当预测概率大于阈值时,样本被分类为正例,否则分类为负例。
对于每个阈值,计算相应的精确率和召回率。
将每个阈值下的精确率和召回率绘制在同一个图表上,形成精确率曲线。
根据精确率曲线的形状和变化趋势,可以选择适当的阈值以达到所需的性能要求。
通过观察精确率曲线,我们可以根据需求确定最佳的阈值,以平衡精确率和召回率。较高的精确率意味着较少的误报,而较高的召回率则表示较少的漏报。根据具体的业务需求和成本权衡,可以在曲线上选择合适的操作点或阈值。
精确率曲线通常与召回率曲线(Recall Curve)一起使用,以提供更全面的分类器性能分析,并帮助评估和比较不同模型的性能。
【Recall曲线】
召回率曲线(Recall Curve)是一种用于评估二分类模型在不同阈值下的召回率性能的可视化工具。它通过绘制不同阈值下的召回率和对应的精确率之间的关系图来帮助我们了解模型在不同阈值下的表现。
召回率(Recall)是指被正确预测为正例的样本数占所有实际为正例的样本数的比例。召回率也被称为灵敏度(Sensitivity)或真正例率(True Positive Rate)。
绘制召回率曲线的步骤如下:
使用不同的阈值将预测概率转换为二进制类别标签。通常,当预测概率大于阈值时,样本被分类为正例,否则分类为负例。
对于每个阈值,计算相应的召回率和对应的精确率。
将每个阈值下的召回率和精确率绘制在同一个图表上,形成召回率曲线。
根据召回率曲线的形状和变化趋势,可以选择适当的阈值以达到所需的性能要求。
通过观察召回率曲线,我们可以根据需求确定最佳的阈值,以平衡召回率和精确率。较高的召回率表示较少的漏报,而较高的精确率意味着较少的误报。根据具体的业务需求和成本权衡,可以在曲线上选择合适的操作点或阈值。
召回率曲线通常与精确率曲线(Precision Curve)一起使用,以提供更全面的分类器性能分析,并帮助评估和比较不同模型的性能。
【loss曲线】
在深度学习的训练过程中,loss函数用于衡量模型预测结果与实际标签之间的差异。loss曲线则是通过记录每个epoch(或者迭代步数)的loss值,并将其以图形化的方式展现出来,以便我们更好地理解和分析模型的训练过程。
【F1值曲线】
F1值曲线是一种用于评估二分类模型在不同阈值下的性能的可视化工具。它通过绘制不同阈值下的精确率(Precision)、召回率(Recall)和F1分数的关系图来帮助我们理解模型的整体性能。
F1分数是精确率和召回率的调和平均值,它综合考虑了两者的性能指标。F1值曲线可以帮助我们确定在不同精确率和召回率之间找到一个平衡点,以选择最佳的阈值。
绘制F1值曲线的步骤如下:
使用不同的阈值将预测概率转换为二进制类别标签。通常,当预测概率大于阈值时,样本被分类为正例,否则分类为负例。
对于每个阈值,计算相应的精确率、召回率和F1分数。
将每个阈值下的精确率、召回率和F1分数绘制在同一个图表上,形成F1值曲线。
根据F1值曲线的形状和变化趋势,可以选择适当的阈值以达到所需的性能要求。
F1值曲线通常与接收者操作特征曲线(ROC曲线)一起使用,以帮助评估和比较不同模型的性能。它们提供了更全面的分类器性能分析,可以根据具体应用场景来选择合适的模型和阈值设置。
【mAP0.5】
mAP0.5,也被称为mAP@0.5或AP50,指的是当Intersection over Union(IoU)阈值为0.5时的平均精度(mean Average Precision)。IoU是一个用于衡量预测边界框与真实边界框之间重叠程度的指标,其值范围在0到1之间。当IoU值为0.5时,意味着预测框与真实框至少有50%的重叠部分。
在计算mAP0.5时,首先会为每个类别计算所有图片的AP(Average Precision),然后将所有类别的AP值求平均,得到mAP0.5。AP是Precision-Recall Curve曲线下面的面积,这个面积越大,说明AP的值越大,类别的检测精度就越高。
mAP0.5主要关注模型在IoU阈值为0.5时的性能,当mAP0.5的值很高时,说明算法能够准确检测到物体的位置,并且将其与真实标注框的IoU值超过了阈值0.5。
【mAP0.5:0.95】
mAP0.5:0.95,也被称为mAP@[0.5:0.95]或AP@[0.5:0.95],表示在IoU阈值从0.5到0.95变化时,取各个阈值对应的mAP的平均值。具体来说,它会在IoU阈值从0.5开始,以0.05为步长,逐步增加到0.95,并在每个阈值下计算mAP,然后将这些mAP值求平均。
这个指标考虑了多个IoU阈值下的平均精度,从而更全面、更准确地评估模型性能。当mAP0.5:0.95的值很高时,说明算法在不同阈值下的检测结果均非常准确,覆盖面广,可以适应不同的场景和应用需求。
对于一些需求比较高的场合,比如安全监控等领域,需要保证高的准确率和召回率,这时mAP0.5:0.95可能更适合作为模型的评价标准。
综上所述,mAP0.5和mAP0.5:0.95都是用于评估目标检测模型性能的重要指标,但它们的关注点有所不同。mAP0.5主要关注模型在IoU阈值为0.5时的性能,而mAP0.5:0.95则考虑了多个IoU阈值下的平均精度,从而更全面、更准确地评估模型性能。
综合五款不同参数量级模型的开发实验对比结果来看:n系列的模型效果最次,其余四款模型性能相近,综合对比考虑最终选择使用s系列的模型来作为线上推理模型。
接下来看下s系列模型的详细情况。
【离线推理实例】
【Batch实例】
【混淆矩阵】
【F1值曲线】
【Precision曲线】
【PR曲线】
【Recall曲线】
【训练可视化】
AI智能化采摘机器人的应用必将极大地提高花生采摘的效率,减轻了农民的劳动强度,还有效减少了作物浪费,促进了农业资源的合理利用。农业生产智能化、精准化是当前传统农业发展和变革的潜力赛道。展望未来,随着AI技术的持续进步和应用的不断深化,我们有理由相信,农业生产将变得更加高效、智能、环保。智能化采摘机器人,作为科技与农业融合的典范,将引领农业生产方式的新一轮变革,让每一份辛勤耕耘都能收获更加丰硕的成果,让农业成为更加有尊严、有希望的行业。