视觉目标检测之小目标检测技术调研与实验
定义
小目标检测
是计算机视觉领域中的一个极具挑战性的问题。小目标检测广义是指在图像中检测和识别尺寸较小、面积较小的目标物体。
通常来说,小目标的定义取决于具体的应用场景,但一般可以认为小目标是指尺寸小于 32*32 像素的物体,如下图 COCO 公开数据集的定义。当然,对于不同的任务和应用,小目标的尺寸和面积要求可能会有所不同。
挑战
基于上述描述以及近年来研究的进展,小目标检测面临的技术挑战主要体现在以下几个方面:
-
特征提取难度大
- 区域太小,信息有限:小目标在图像中仅占据极少的像素区域,深度学习模型在卷积过程中经过多次下采样后,很容易丢失细粒度信息,使得特征提取效果大打折扣。
- 细节不足:相比于常规尺寸目标,小目标往往缺乏丰富的纹理、颜色和形状细节,导致模型难以学习到充分的判别特征。
一般解决方案:增大输入图像分辨率。将输入图像的分辨率提高,以便更好地捕捉目标的细节。
-
背景干扰与噪声问题
- 易被误判为噪声:由于尺寸较小,小目标在特征图中可能与背景噪声难以区分,容易被误认为是背景中的随机噪点,增加了误检和漏检的风险。
- 复杂背景环境:在一些复杂场景(如室内会议、街景等),背景中大量冗余信息可能会淹没小目标的特征,进一步降低检测精度。
-
尺度不匹配与多尺度检测
- 尺度不平衡:现有检测网络往往在设计时侧重于中、大尺寸目标,小目标所对应的特征层分辨率较低,难以准确定位和识别。
- 多尺度融合困难:虽然一些方法采用了多尺度特征融合策略,但如何在不同尺度间有效传递和聚合信息,仍然是一个亟待解决的问题。
一般解决方案:多尺度特征融合。由于小目标的尺寸较小,其特征信息往往分布在图像的多个尺度中,因此需要在多个尺度的特征图中进行融合,以提高模型对小目标的感知能力。常见的多尺度特征融合方法包括 Feature Pyramid Networks( FPN) 和 Path Aggregation Network( PAN) 等。
-
检测器设计的局限性
- 锚框匹配问题:常见的锚框(Anchor)机制在面对小目标时,预设的尺寸往往过大或位置不匹配,导致匹配不到合适的候选区域。
- 区域提议算法不足:对于小目标,传统的区域提议方法(如 RPN)可能难以生成足够多且精确的候选框,从而影响后续的检测效果。
一般方案:无锚点设计,不再预测锚框。
-
数据标注与样本不平衡
- 标注精度问题:由于小目标尺寸较小,在数据标注时容易出现不准确的情况,进一步影响模型的训练效果。
- 正负样本比例失衡:在训练数据中,小目标的正样本数量往往较少,正负样本比例严重失衡,容易导致模型偏向于检测大目标或对小目标的识别不足。
-
实际应用场景的限制
- 视角与位置问题:例如在室内会议场景中,如果摄像头位于固定角度(如左上角),远离镜头的区域(如对角线位置)的小目标检测效果往往更差,这也暴露了模型对不同视角和区域的适应性不足。
- 实时性与效率要求:在一些实时监控或自动驾驶等应用中,要求模型不仅要具备高精度,还需在有限的计算资源下快速响应,这对小目标检测算法提出了更高的挑战。
常见技术方案
- 图像输入分辨率与样本均衡等,或场景分析,比如特定区域裁剪。
- 多尺度特征融合。多尺度特征融合是提高检测精度(特别是小目标检测)的一项关键技术。
- 注意力机制。可以理解成nlp的attention.yolo11有个block是attention机制。 常见的注意力机制包括SENet、SKNet等。
- 长跳跃连接。将不同层级的特征图进行融合的一种方法,可以帮助模型更好地捕捉不同层级的特征信息。众所周知,浅层特征图的细节信息丰富但语义信息较弱,深层特征图则与之相反。因此,在小目标检测中,可以将低层级的特征图和高层级的特征图进行融合,以增强对小目标的定位能力。
- 数据增强、尺度变换、随机裁剪、高级组合。通过对数据进行随机变换来增加数据样本的数量和多样性,如将原始图像缩小一定比例,从而得到多个尺寸较小的图像样本。在不改变目标位置的情况下,可以通过随机裁剪的方式得到多个不同的图像样本,以增加数据的多样性。Mosaic 增强,其由多张原始图像拼接而成,这样每张图像会有更大概率包含小目标。
- 大图切分Tiling预处理。
- SAHI,即切片辅助超级推理,是一个专用于小目标检测的推理框架,理论上可以集成到任意的目标检测器上。小目标检测的切片辅助推理介绍链接
- 其他技术优化,如损失函数。或专门针对小目标进行二次检测。
yolo在小目标识别上的改进
以下是YOLO系列主要是从V1到V11各版本的对比分析,重点聚焦小目标识别能力的改进:
1. YOLOv1(2015)
- 核心特点:首次提出单阶段检测框架,直接回归边界框坐标和类别概率,但网格划分粗糙(7×7),每个网格仅预测2个框。
- 小目标问题:网格划分稀疏,难以检测密集或小目标;全连接层导致空间信息丢失,小目标特征易被忽略。
- 改进方向:未针对小目标优化,召回率低。
2. YOLOv2(2016)
- 关键改进:
- 引入Anchor Boxes(通过k-means聚类生成5个先验框)和Passthrough层(融合高分辨率浅层特征)。
- 多尺度训练(输入图像尺寸动态调整)。
- 小目标优化:
- Passthrough层通过拆分和拼接特征图,增强小目标的细节保留。
- 但Anchor Box数量有限,对小目标的覆盖仍不足。
3. YOLOv3(2018)
- 突破性改进:
- 多尺度预测:结合FPN(特征金字塔网络),在13×13、26×26、52×52三种尺度上检测目标,分别对应大、中、小物体。
- 更密集的Anchor Boxes:9个聚类生成的锚框,覆盖更多尺寸比例。
- 小目标优化:
- 52×52的细粒度特征图显著提升小目标检测能力。
- 残差网络(Darknet53)增强了深层特征的表达能力。
4. YOLOv4(2020)
- 核心技术:
- Mosaic数据增强:拼接四张图像,模拟小目标密集场景。
- SPP模块(空间金字塔池化):融合不同感受野的特征,增强多尺度特征提取。
- PANet:自顶向下和自底向上的双向特征融合,优化小目标特征传递。
- 小目标优化:
- 通过多尺度数据增强和更复杂的特征融合机制,提升小目标的鲁棒性。
5. YOLOv5(2020)
- 改进点:
- 自适应锚框计算:根据数据集自动调整锚框尺寸,减少人工干预。
- Focus结构:切片操作保留高分辨率信息,减少计算量。
- 小目标优化:
- 输入分辨率提升至640×640,捕捉更多细节。
- 跨网格匹配策略(匹配目标中心附近的3个网格),增加小目标的候选框数量。
6. YOLOv6(2022)
- 创新设计:
- RepConv重参数化卷积:提升推理速度的同时保持精度。
- SimOTA动态标签分配:优化正样本选择,减少漏检。
- 小目标优化:
- 引入EfficientRep骨干网络,通过轻量化设计减少浅层特征丢失。
7. YOLOv7(2022)
- 关键技术:
- ELAN模块(高效层聚合网络):增强特征复用和梯度传播。
- 动态正样本分配(OTA):根据IoU和分类得分动态选择最佳匹配。
- 小目标优化:
- 通过浅层特征的高效聚合,保留更多小目标细节。
8. YOLOv8(2023)
- 核心改进:
- C2F模块:结合跨阶段特征和残差连接,优化梯度流。
- 解耦检测头:分离分类和回归任务,减少干扰。
- 小目标优化:
- TAL标签分配策略:基于对齐度量(align_metric)动态筛选正样本,提升小目标定位精度。
9. YOLOv9(2024)
- 突破性技术:
- PGI(程序化梯度信息):解决深层网络梯度消失问题,保留小目标的关键特征。
- GELAN网络:通过梯度路径规划优化特征融合,增强多尺度表达能力。
- 小目标优化:
- 在COCO数据集上,小目标检测精度(AP_S)提升约5%。
10. YOLOv10(2024)
- 创新设计:
- 双标签分配策略:同时优化分类和回归任务,减少漏检。
- 空间-通道解耦下采样:减少下采样过程中的信息丢失。
- 小目标优化:
- 消除NMS后处理,直接输出最优检测框,减少冗余计算对小目标的影响。
- 清华大学构建的源码
10. YOLOv11(2024)
- 创新设计:
- 增强的特征提取能力:通过改进Backbone和Neck架构,新增了C3k2和C2PSA等组件,提升了目标检测的精度。
- 优化效率和精度:重新设计了架构,优化了训练流程,提高了处理速度。YOLO11m在COCO 数据集上实现更高mAP,且参数滅少 22%。
- 小目标优化:
- C3k2和C2PSA组件,特征提取在多目标检测和遮挡方面变现更好。
- 正统官方Ultralytics实现。也分大中小预训练模型。
- 技术参考链接:YOLO11来啦 | 详细解读YOLO11的改进模块!
小目标识别能力总结
版本 | 关键改进技术 | 对小目标的优化效果 |
---|---|---|
v1-v2 | 粗糙网格、有限锚框 | 效果差,召回率低 |
v3 | 多尺度预测、FPN | 显著提升小目标检测能力 |
v4 | Mosaic增强、SPP+PAN | 增强多尺度特征融合 |
v5 | 高分辨率输入、自适应锚框 | 细节捕捉能力提升 |
v7-v8 | 动态标签分配、解耦检测头 | 减少漏检,提升定位精度 |
v9-v10 | PGI、GELAN、空间-通道解耦 | 深层特征保留,减少信息丢失 |
yolo系列补充
YOLOv10 和 YOLOX 是 YOLO 系列中两个不同的分支,它们在设计理念、技术实现和应用场景上有显著区别。以下是两者的详细对比:
1. 设计理念
-
YOLOv10:
- 是 YOLO 系列的正统延续,基于 YOLOv8 和 YOLOv9 的改进,专注于 精度与速度的平衡。
- 强调 无锚点设计 和 后处理优化,减少冗余计算,提升小目标检测能力。
- 目标是成为通用目标检测的标杆模型。
-
YOLOX:
- 是 YOLO 系列的一个 创新分支,由旷视科技(Megvii)团队开发。
- 强调 无锚点设计 和 解耦检测头,简化了传统 YOLO 的锚框机制。
- 目标是 轻量化 和 高效推理,适合实时检测和边缘计算。
4. 优缺点对比
YOLOv10
- 优点:
- 无锚点设计简化了模型结构,减少超参数调优。
- 消除NMS后处理,提升推理效率。
- 在小目标检测上表现优异。
- 缺点:
- 模型复杂度较高,对硬件资源要求较高。
- 训练和调优需要更多计算资源。
YOLOX
- 优点:
- 无锚点设计和解耦检测头使其在速度和精度之间取得良好平衡。
- 适合实时检测和边缘计算场景。
- 训练和部署相对简单。
- 缺点:
- 对小目标的检测能力略逊于 YOLOv10。
- 仍然依赖NMS后处理,可能存在冗余计算。
5. 适用场景
-
YOLOv10:
- 适合对 精度要求高 的场景,尤其是小目标检测任务。
- 适合通用目标检测,如自动驾驶、安防监控等。
-
YOLOX:
- 适合 实时检测 和 边缘计算 场景,如移动端应用、无人机检测等。
- 适合对速度和轻量化要求较高的任务。
-
如果你需要 高精度检测,尤其是小目标检测,且硬件资源充足,推荐 YOLOv10。
-
如果你需要 实时检测 或 轻量化部署,且对精度要求适中,推荐 YOLOX。
关注yolo模型 V8与V10性能对比
综合实验
综合上述分析,结合最新yolo v11特性,很明显,V11添加的算法更适合小目标识别。后续以V11作为base进行实验研究。顺便对比V8
- ultralytics官方提供的平均精度值与响应时延对比图。y轴为目标检测coco数据集中,在定位精度交并比loU为0.5-95阈值下,模型对80类目标的平均检测精度。
- 此次实验选择下载官方的中杯模型yolo11m.pt,实验数据集是自己摄像头收集的人形检测。
- 主要观测的测试图片如下,里面远侧有三个小人。一个骑电车、一个跑步、一个在画廊施工。
v8对比v11实验结果
- yolo-V8下,直接按数据集进行微调后,推理效果,实际是漏检的。
- yolo v11m,在源模型上二次微调的效果,实际多检出一个人。
yolov11 --model.train()函数的参数(Train Settings)
参数 | 类型 | 默认值 | 描述 |
---|---|---|---|
model | str | None | 指定用于训练的模型文件。接受 .pt 预训练模型或 .yaml 配置文件的路径。用于定义模型结构或初始化权重。 |
data | str | None | 数据集配置文件的路径(如 coco8.yaml )。该文件包含数据集特定的参数,包括训练和验证数据的路径、类别名称和类别数量。 |
epochs | int | 100 | 训练的总轮数。每轮代表对整个数据集的一次完整遍历。调整此值会影响训练时间和模型性能。 |
time | float | None | 训练的最大时间(小时)。如果设置,将覆盖 epochs 参数,训练在指定时间后自动停止。适用于时间受限的训练场景。 |
patience | int | 100 | 在验证指标没有改善的情况下,等待的轮数,之后提前停止训练。有助于防止过拟合,当性能停滞时停止训练。 |
batch | int | 16 | 批量大小,支持三种模式:设置为整数(如 batch=16 ),自动模式(使用 60% GPU 内存,batch=-1 ),或指定利用率分数的自动模式(如 batch=0.70 )。 |
imgsz | int 或 list | 640 | 训练的目标图像尺寸。所有图像在输入模型前会被调整为此尺寸。影响模型精度和计算复杂度。 |
save | bool | True | 是否保存训练检查点和最终模型权重。适用于恢复训练或模型部署。 |
save_period | int | -1 | 保存模型检查点的频率(以轮数为单位)。值为 -1 时禁用此功能。适用于长时间训练时保存中间模型。 |
cache | bool | False | 是否在内存(True/ram )、磁盘(disk )中缓存数据集图像,或禁用缓存(False )。通过减少磁盘 I/O 提高训练速度,但会增加内存使用。 |
device | int 或 str 或 list | None | 指定训练的计算设备:单个 GPU(device=0 ),多个 GPU(device=0,1 ),CPU(device=cpu ),或 Apple silicon 的 MPS(device=mps )。 |
workers | int | 8 | 数据加载的工作线程数(多 GPU 训练时为每个 RANK 设置)。影响数据预处理和输入模型的速度,尤其适用于多 GPU 设置。 |
project | str | None | 保存训练输出的项目目录名称。用于组织不同实验的输出。 |
name | str | None | 训练运行的名称。用于在项目文件夹中创建子目录,保存训练日志和输出。 |
exist_ok | bool | False | 如果为 True ,允许覆盖现有的项目/名称目录。适用于迭代实验时无需手动清除之前的输出。 |
pretrained | bool | True | 是否从预训练模型开始训练。可以是布尔值或指定模型的路径。有助于提高训练效率和模型性能。 |
optimizer | str | 'auto' | 训练使用的优化器。选项包括 SGD、Adam、AdamW、NAdam、RAdam、RMSProp 等,或 auto 根据模型配置自动选择。影响收敛速度和稳定性。 |
seed | int | 0 | 设置训练的随机种子,确保在相同配置下结果可复现。 |
deterministic | bool | True | 强制使用确定性算法,确保结果可复现,但可能因限制非确定性算法而影响性能和速度。 |
single_cls | bool | False | 在多类别数据集中将所有类别视为单一类别进行训练。适用于二分类任务或仅关注目标存在性的任务。 |
classes | list[int] | None | 指定要训练的类别 ID 列表。适用于过滤并仅关注某些类别的训练。 |
rect | bool | False | 启用矩形训练,优化批次组合以减少填充。可提高效率和速度,但可能影响模型精度。 |
multi_scale | bool | False | 启用多尺度训练,在训练期间将 imgsz 增加/减少最多 0.5 倍。训练模型在推理时对多种尺寸的图像更准确。 |
cos_lr | bool | False | 使用余弦学习率调度器,根据余弦曲线调整学习率。有助于更好地管理学习率以实现收敛。 |
close_mosaic | int | 10 | 在最后 N 轮禁用马赛克数据增强以稳定训练。设置为 0 时禁用此功能。 |
resume | bool | False | 从上次保存的检查点恢复训练。自动加载模型权重、优化器状态和轮数,无缝继续训练。 |
amp | bool | True | 启用自动混合精度(AMP)训练,减少内存使用并可能加速训练,对精度影响最小。 |
fraction | float | 1.0 | 指定用于训练的数据集比例。允许在完整数据集的子集上进行训练,适用于实验或资源有限的情况。 |
profile | bool | False | 启用 ONNX 和 TensorRT 速度分析,适用于优化模型部署。 |
freeze | int 或 list | None | 冻结模型的前 N 层或指定索引的层,减少可训练参数数量。适用于微调或迁移学习。 |
lr0 | float | 0.01 | 初始学习率(如 SGD=1E-2,Adam=1E-3)。调整此值对优化过程至关重要,影响模型权重的更新速度。 |
lrf | float | 0.01 | 最终学习率作为初始学习率的一部分(lr0 * lrf ),与调度器结合使用以调整学习率。 |
momentum | float | 0.937 | SGD 的动量因子或 Adam 的 beta1,影响当前更新中过去梯度的结合。 |
weight_decay | float | 0.0005 | L2 正则化项,惩罚大权重以防止过拟合。 |
warmup_epochs | float | 3.0 | 学习率预热的轮数,逐渐将学习率从低值增加到初始学习率,以稳定早期训练。 |
warmup_momentum | float | 0.8 | 预热阶段的初始动量,逐渐调整到设定的动量。 |
warmup_bias_lr | float | 0.1 | 预热阶段偏置参数的学习率,帮助稳定模型训练的初始阶段。 |
box | float | 7.5 | 损失函数中边界框损失部分的权重,影响对准确预测边界框坐标的重视程度。 |
cls | float | 0.5 | 总损失函数中分类损失的权重,影响正确类别预测相对于其他部分的重要性。 |
dfl | float | 1.5 | 分布焦点损失的权重,用于某些 YOLO 版本中的细粒度分类。 |
pose | float | 12.0 | 姿态估计模型中姿态损失的权重,影响对准确预测姿态关键点的重视程度。 |
kobj | float | 2.0 | 姿态估计模型中关键点目标性损失的权重,平衡检测置信度与姿态准确性。 |
nbs | int | 64 | 损失归一化的名义批量大小。 |
overlap_mask | bool | True | 确定是否将对象掩码合并为单个掩码进行训练,或为每个对象保留单独的掩码。在重叠情况下,较小的掩码会在合并时覆盖较大的掩码。 |
mask_ratio | int | 4 | 分割掩码的下采样比例,影响训练期间使用的掩码分辨率。 |
dropout | float | 0.0 | 分类任务中的 dropout 率,通过随机忽略单元防止过拟合。 |
val | bool | True | 是否在训练期间启用验证,允许在单独数据集上定期评估模型性能。 |
plots | bool | False | 生成并保存训练和验证指标的图表以及预测示例,提供对模型性能和学习进展的直观洞察。 |
特别补充:imgsz必须是32的整数倍,且输入resize成正方形
,不可以像(1024, 768)。原因是如下:
将输入图像resize到一个固定的尺寸这件事贯穿了DL在目标检测领域的始终:
- 在r-cnn刚提出的阶段,由于网络结构的限制,进入全连接层的输入维度必须是固定的,那么一个最简单的解决方案就是把输入图像归一化到固定的尺寸,得到输出后反变换回去。这种resize的做法实际上是为了适应卷积神经网络的一种让步。
- 随着人们对检测精度的追求,大家开始关注resize所带来的损失。不论是sppnet还是roi pooling,他们都致力于将任意维度的特征图转化成固定的维度以适应全连接的输入,这一思路从理论上解除了整个检测网络对输入的尺寸依赖,这也是fast r-cnn的一个改进点,从此输入图像可以是任意宽高。
- 然而各路算法在使用gpu做加速的过程中遇到了新的问题,每一个batch的计算必须拥有相同的size,这是算法本身之外的一个限制,但这个限制却成为了并行加速计算的拦路虎。yolo的作者针对这个情况,在不改变原始图像宽高比的前提下进行了resize操作,具体的做法是对不符合原图比例的区域进行padding.
- 下图是yolov11图像变换源码位置,实际是用插值法缩放成正方形。
数据增强设置和超参数
数据增强技术对于提高 YOLO 模型的鲁棒性和性能至关重要,它通过引入训练数据的变异性,帮助模型更好地泛化到未见过的数据。下表概述了每个增强参数的目的和效果:
参数 | 类型 | 默认值 | 范围 | 描述 |
---|---|---|---|---|
hsv_h | float | 0.015 | 0.0 - 1.0 | 调整图像的色调(色轮的一部分),引入颜色变异性。帮助模型在不同光照条件下泛化。 |
hsv_s | float | 0.7 | 0.0 - 1.0 | 改变图像的饱和度(颜色的强度),模拟不同的环境条件。 |
hsv_v | float | 0.4 | 0.0 - 1.0 | 修改图像的亮度(明暗程度),帮助模型在各种光照条件下表现良好。 |
degrees | float | 0.0 | -180 - +180 | 在指定范围内随机旋转图像,提高模型识别不同方向对象的能力。 |
translate | float | 0.1 | 0.0 - 1.0 | 在水平和垂直方向上平移图像(图像大小的一部分),帮助模型学习检测部分可见的对象。 |
scale | float | 0.5 | >=0.0 | 按增益因子缩放图像,模拟对象与相机不同距离的情况。 |
shear | float | 0.0 | -180 - +180 | 按指定角度剪切图像,模拟从不同角度观察对象的效果。 |
perspective | float | 0.0 | 0.0 - 0.001 | 对图像应用随机透视变换,增强模型理解 3D 空间对象的能力。 |
flipud | float | 0.0 | 0.0 - 1.0 | 以指定概率上下翻转图像,增加数据变异性而不影响对象的特征。 |
fliplr | float | 0.5 | 0.0 - 1.0 | 以指定概率左右翻转图像,有助于学习对称对象并增加数据集的多样性。 |
bgr | float | 0.0 | 0.0 - 1.0 | 以指定概率将图像通道从 RGB 转换为 BGR,提高对错误通道顺序的鲁棒性。 |
mosaic | float | 1.0 | 0.0 - 1.0 | 将四张训练图像组合成一张,模拟不同的场景组合和对象交互。对于复杂场景理解非常有效。 |
mixup | float | 0.0 | 0.0 - 1.0 | 混合两张图像及其标签,创建复合图像。通过引入标签噪声和视觉变异性,增强模型的泛化能力。 |
copy_paste | float | 0.0 | 0.0 - 1.0 | 在图像之间复制和粘贴对象,增加对象实例并学习对象遮挡。需要分割标签。 |
copy_paste_mode | str | flip | - | 选择 Copy-Paste 增强方法,选项包括 flip 和 mixup 。 |
auto_augment | str | randaugment | - | 自动应用预定义的增强策略(如 randaugment 、autoaugment 、augmix ),通过多样化视觉特征优化分类任务。 |
erasing | float | 0.4 | 0.0 - 0.9 | 在分类训练期间随机擦除图像的一部分,鼓励模型关注不太明显的特征进行识别。 |
crop_fraction | float | 1.0 | 0.1 - 1.0 | 将分类图像裁剪为其大小的一部分,以强调中心特征并适应对象尺度,减少背景干扰。 |
数据集格式,voc、yolo、coco数据集标准
在目标检测这块,yolo支持很多种数据格式,包括经典的coco、新版coco8、VOC等格式。我们重点关注常用目标检测格式经典COCO和VOC.
yolo兼容coco和voc, 只要吧dataset的yaml文件填写正确即可。
对于yolo的输入voc格式
对于yolo输入的coco格式
yolo的dataset配置文件yaml样例
以上是对于yolo来说的数据集格式,一般就是新增一个yaml文件即可。而标准的coco数据格式并不是完全一致,下面以coco数据集举例。
coco标准数据集(目标检测部分)
coco经典数据集,除了image文件夹外,就是annotation文件夹,里面配备的json标注文件。coco.json标注信息如下图,核心文件夹在就行,三个文件夹categories用于表明class类别,images下每个dict说清每张图片信息等,annotations里就是标注信息啦,比如矩形框或多边形框的坐标。
coco和yolo数据集互转代码
# -*- encoding: utf-8 -*-
"""
@File : coco_annotations_json_builder.py
@Description : YOLO 格式的数据集转化为 COCO 格式的数据集
@Author : 一只特立独行的羱
@Contact : 未知
@License : (C)Copyright 2019-2030,xx
@Modify Time @Version
------------ --------
2025/2/18 14:26 1.0
"""
import os
import cv2
import json
import yaml
import pathlib
from tqdm import tqdm
from typing import List
from pycocotools.coco import COCO
import os
import json
from pycocotools.coco import COCO
from tqdm import tqdm
def coco2yolo(coco_annotation_file, split_name='train'):
root_path = pathlib.Path(coco_annotation_file).resolve().parent
output_dir = root_path.joinpath(f"labels/{split_name}")
image_dir = root_path.joinpath('images')
name, stem = pathlib.Path(coco_annotation_file).name, pathlib.Path(coco_annotation_file).stem
pathlib.Path(output_dir).parent.mkdir(parents=True, exist_ok=True)
# 加载COCO标注文件
coco = COCO(coco_annotation_file)
# 获取所有图像的ID
image_ids = coco.getImgIds()
# 遍历每张图像
for image_id in tqdm(image_ids):
# 获取图像信息
image_info = coco.loadImgs(image_id)[0]
image_width = image_info['width']
image_height = image_info['height']
image_filename = image_info['file_name']
# 获取该图像的所有标注
annotation_ids = coco.getAnnIds(imgIds=image_id)
annotations = coco.loadAnns(annotation_ids)
# 创建YOLO格式的标注文件
yolo_annotation_file = os.path.join(output_dir, os.path.splitext(image_filename)[0] + '.txt')
with open(yolo_annotation_file, 'w') as f:
for ann in annotations:
# 获取类别ID
class_id = ann['category_id']
# 获取边界框信息
bbox = ann['bbox']
x_min, y_min, width, height = bbox
# 计算中心点坐标和归一化值
x_center = (x_min + width / 2) / image_width
y_center = (y_min + height / 2) / image_height
norm_width = width / image_width
norm_height = height / image_height
# 写入YOLO格式的标注
f.write(f"{class_id} {x_center} {y_center} {norm_width} {norm_height}\n")
# 生产数据划分的test.txt
image_relative_path = pathlib.Path(image_dir).joinpath(split_name).relative_to(root_path)
with open(f"{root_path}/{stem}.txt", 'w') as f:
for image_id in image_ids:
image_info = coco.loadImgs(image_id)[0]
image_filename = image_info['file_name']
image_path = f"./{image_relative_path}/{image_filename}"
f.write(f"{image_path}\n")
def yolo2coco(config_path, splitset: List[str]):
if splitset is None:
splitset = ['train', 'val']
root_path = pathlib.Path(config_path).resolve().parent
assert os.path.exists(str(root_path))
# 读取 YAML 文件
with open(config_path, "r") as file:
config = yaml.safe_load(file)
classes = config['names']
dataset_catgories = [] # 选择的数据集划分类别
for per in splitset:
catgory = config[per]
ph = pathlib.Path.joinpath(root_path, catgory)
dataset_catgories.append(ph)
print("Loading data from ", root_path)
# 标注信息
for cat in dataset_catgories:
print(f"builder coco json by {cat}")
# categories
coco_json = {'categories': [], 'annotations': [], 'images': []}
for i, cls in classes.items():
coco_json['categories'].append({'id': i, 'name': cls, 'supercategory': None})
contents = cat.read_text(encoding='utf8').splitlines()
# 标注的id
ann_id_cnt = 0
for index, k in enumerate(tqdm(contents)):
# 图片名称和图片全称
name, stem = pathlib.Path(k).name, pathlib.Path(k).stem
# 获取图片和标签
img_path = root_path.joinpath(k)
label_path = pathlib.Path(str(root_path.joinpath(k).with_suffix('.txt')).replace("/images/", "/labels/"))
# 读取图像的宽和高
im = cv2.imread(str(img_path))
height, width, _ = im.shape
# 添加图像的信息
coco_json['images'].append({'file_name': name,
'id': index,
'width': width,
'height': height})
# 添加标签
if not os.path.exists(label_path):
# 如没标签,跳过,只保留图片信息。
print(f"该标签路径不存在:{label_path}")
continue
labelList = label_path.read_text(encoding='utf8').splitlines()
for label in labelList:
label = label.strip().split()
x = float(label[1])
y = float(label[2])
w = float(label[3])
h = float(label[4])
# convert x,y,w,h to x1,y1,x2,y2
H, W, _ = im.shape
x1 = (x - w / 2) * W
y1 = (y - h / 2) * H
x2 = (x + w / 2) * W
y2 = (y + h / 2) * H
# 标签序号从0开始计算。
cls_id = int(label[0])
width = max(0, x2 - x1)
height = max(0, y2 - y1)
coco_json['annotations'].append({
'area': width * height,
'bbox': [x1, y1, width, height],
'category_id': cls_id,
'id': ann_id_cnt,
'image_id': index,
'iscrowd': 0,
# mask, 矩形是从左上角点按顺时针的四个顶点
'segmentation': [[x1, y1, x2, y1, x2, y2, x1, y2]]
})
ann_id_cnt += 1
# 保存结果
json_name = f"{cat.stem}_coco_annotation.json"
output_path = f"{root_path}/output/{json_name}"
pathlib.Path(output_path).parent.mkdir(parents=True, exist_ok=True)
with open(output_path, 'w') as f:
json.dump(coco_json, f, allow_nan=True, ensure_ascii=True, indent=2)
print('Save annotation to {}'.format(json_name))
if __name__ == "__main__":
# arg = "/Volumes/storage/PythonProjects/LocalProcess/data/ytdzwl_coco/ytdzwl_coco.yaml"
# yolo2coco(config_path=arg,splitset=['test'])
cfg_path = "/Volumes/storage/PythonProjects/LocalProcess/data/slice/val_coco.json"
coco2yolo(coco_annotation_file=cfg_path, split_name='val')
coco标注数据查看结果代码
# -*- encoding: utf-8 -*-
"""
@File : coco_view.py
@Description : None
@Author : 一只特立独行的羱
@Contact : 未知
@License : (C)Copyright 2019-2030,xx
@Modify Time @Version
------------ --------
2025/2/19 09:31 1.0
"""
import json
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from PIL import Image
import numpy as np
from pycocotools.coco import COCO
# 设置路径
annotation_path = "/Volumes/storage/PythonProjects/LocalProcess/data/slice/annotation/test_coco.json"
image_dir = "/Volumes/storage/PythonProjects/LocalProcess/data/slice/images/test"
# 初始化COCO API
coco = COCO(annotation_path)
# 获取所有图像ID,或指定特定ID
image_ids = coco.getImgIds()
selected_id = image_ids[1] # 选择第一张图像
# 加载图像信息
image_info = coco.loadImgs(selected_id)[0]
image_path = f"{image_dir}/{image_info['file_name']}"
image = Image.open(image_path)
# 加载标注信息
annotation_ids = coco.getAnnIds(imgIds=selected_id)
annotations = coco.loadAnns(annotation_ids)
# 创建画布
fig, ax = plt.subplots(1)
ax.imshow(image)
print('标注个数:', annotations.__len__())
# 绘制标注
for ann in annotations:
# 获取bbox坐标(COCO格式: [x, y, width, height])
x, y, w, h = ann['bbox']
# 创建矩形框
rect = patches.Rectangle((x, y), w, h, linewidth=1, edgecolor='r', facecolor='none')
ax.add_patch(rect)
# 添加类别标签
category = coco.loadCats(ann['category_id'])[0]['name']
label = f"{category}"
ax.text(x, y - 5, label, color='r', fontsize=8, backgroundcolor='white')
plt.axis('off')
plt.show()
实验结果记录
鱼塘训练和测试集的情况说明
- 数据格式为经典coco格式,共有2个类别,person、boat,核心是person。
- train有339条,val有81条(从标注中随机抽取的),test有31条。
- 单张图像信息为:图像尺寸宽高: 2560x1440;通道数: 3;像素总数: 3686400;
基于yolov11m.yaml的框架,没有base下训练,在test集中表现(混淆矩阵),有两个类别person、boat,主要关注person:
基于yolov11m.pt的base上训练,在test集中表现:
基于yolov8.yaml的训练,在test集中表现:
基于yolov11m.pt的base上训练,并且调整入模resize为1056,在test集中表现:
基于yolov11m.pt的base上训练,并且调整入模resize为2112(按显卡24G显存下,只能设置batch=1),在test集中表现:
基于yolov11m.pt的base上训练,对数据集进行按960分辨率切片,一张原图切片成6个slice,并且调整入模resize为960。在不去除backgroud下直接使用,评测如下(此份结果不一定准确):
基于yolov11m.pt的base上训练,对数据集进行按960分辨率切片,一张原图切片成6个slice,并且调整入模resize为960。对数据集进行数据清洗,保证background在10%以下:
undo
显存不足,如何解决?
- 降低batch_size。如果batch太小,容易陷入局部最优,导致泛化差。批量大小的设置不仅受显存限制,还会影响训练速度、梯度稳定性、泛化能力、收敛速度等多个方面。合理选择批量大小需要综合考虑显存容量、数据集大小、任务复杂度等因素,并通过实验进行调优。常见的批量大小值包括 16、32、64、128 等,具体选择取决于任务需求和硬件条件。
- 多卡并行。yolo支持多卡数据并行的方式,即如果总批量大小为 64,使用 4 张 GPU,则每张 GPU 处理 16 个样本。因为要卡通信使用nccl,部分显卡不一定能通信成功。
- 混合精度训练。使用 FP16(半精度浮点数)代替 FP32(单精度浮点数),显存占用减少一半。在 YOLOv11 中,可以通过设置 --amp 参数启用混合精度训练。一般来说,yolo要按基础模型做一遍前向传播检测兼容性。
结论
分析
- 小目标必须分辨率足够,肉眼可见人形。但由于模型输入是经过resize的,所以也要保证resize后能捕捉到人形,所以,resize越大,卷积捕捉的信息越大。需要让输入尽量匹配训练的size.
- 因便于并行和cnn,yolo都是采用正方形resize,这会导致形变,如果长宽比相差过大,需要考虑裁剪。
- 滑动窗口可以很好解决高分辨率下的小目标识别问题,但由于训练的数据并非切分滚动的数据,所以会导致很多检出很多目标。
- 训练过程,resize多尺寸可以增强识别力,但在分辨率和显存不足的情况下,作用比太小。
方案
- 尽量把输入的resize设大,让模型匹配输入像素。
- 如果关键区域能确定,可以直接裁剪训练集,让输入是监控重点区域的正方形。
- 如果输入分辨率很大,建议使用图像分区,比如长方形,切割成4块正方形区域,并构建训练集。推理时也同样方式构建。加工方式使用SAHI框架。
- 要增加负样本,比如没有目标的图像background,大概占比10%,可以增加鲁棒性。
- 数据不足时,因预训练模型中也有人形检测,在base基础上叠加训练,效果更好。
<文档还在更新完善中…>