精准提升:从94.5%到99.4%——目标检测调优全纪录
🚀 目标检测模型调优过程记录
在进行目标检测模型的训练过程中,我们面对了许多挑战与迭代。从初始模型的训练结果到最终的调优优化,每一步的实验和调整都有其独特的思路和收获。本文记录了我在优化目标检测模型的过程中进行的几次尝试,并详细分析了每次调整带来的效果提升。🔧
一开始,我训练了一个目标检测器,目标检测任务包括了 26个类别,初始的 mAP50 在 94.5% 左右。尽管初步结果较为理想,但在混淆矩阵中仍然能看到一些类别之间存在较大的误识别和混淆,尤其是在类别间数据不平衡的情况下,部分小类别的识别效果较差。为了进一步提升模型性能,减少误识别,后续进行了几轮迭代和优化。
接下来的调优尝试,我采取了不同的策略,每次都根据前一次的分析结果,针对性地进行调整。以下是我尝试的几种方法,以及每次尝试的详细效果记录。📊
1️⃣ 第一次尝试:将目标检测拆解为单目标检测 + 图像分类
在最初的尝试中,我将原本的单一目标检测器拆解为单目标检测与图像分类的结合方式。具体可以参考我这篇文章:联合目标检测与图像分类提升数据不平衡场景下的准确率,方法的核心是将每个目标先单独识别,然后通过图像分类进一步确认类别。🎯
经过调整后,虽然 Top1的准确率 提高至 99%,但是从混淆矩阵来看,部分类别仍然存在较多的混淆,尤其是一些相似类别之间的错误识别仍然较为显著。🧐
2️⃣ 第二次尝试:统一缩放目标区域
在第一次尝试后,我发现将目标区域进行标准化处理可能会对模型训练有所帮助。因此,我采用了将目标检测出来的子图强制缩放至统一大小的策略。使用 cv2.resize(object_region, (classification_size, classification_size))
对每个检测到的目标区域进行缩放。🔄
经过训练后,模型在一些类别上的识别效果有所改善,尤其是在一些小物体的检测上,误差略有减少。📉
3️⃣ 第三次尝试:统一缩放原始子图
在第二次尝试的基础上,我进一步扩大了缩放操作的范围,将原始图像中的所有子图按统一的缩放尺寸进行训练。这一操作提高了模型在不同尺度上的泛化能力,特别是对于不同尺寸的目标检测表现有所提升,训练过程中也能更好地对齐各类别的特征分布。📏
import os
from pathlib import Path
import cv2
from multiprocessing import Pool
from functools import partial
from tqdm import tqdm
def find_image_files(root_dir, image_extensions={'.jpg', '.jpeg', '.png', '.bmp', '.gif'}):
"""
递归查找根目录下所有图片文件
"""
root_path = Path(root_dir)
image_files = [p for p in root_path.rglob('*') if p.suffix.lower() in image_extensions]
return image_files
def process_image(image_path, root_dir, output_dir, size=(224, 224)):
"""
处理单张图片:读取、调整大小、保存到目标目录
"""
try:
# 计算相对路径并构造目标路径
relative_path = image_path.relative_to(root_dir)
target_path = Path(output_dir) / relative_path
# 创建目标文件夹(如果不存在)
target_path.parent.mkdir(parents=True, exist_ok=True)
# 读取图像
img = cv2.imread(str(image_path))
if img is None:
raise ValueError("图像无法读取,可能是文件损坏或格式不支持。")
# 调整大小
resized_img = cv2.resize(img, size)
# 保存图像,保持原始格式
success = cv2.imwrite(str(target_path), resized_img)
if not success:
raise IOError("图像保存失败。")
except Exception as e:
print(f"处理图片 {image_path} 时出错: {e}")
def main():
"""
主函数:查找图片并使用多进程处理
"""
# 设置根目录和输出目录
ROOT_DIR = "datasets/device_cls_merge_manual_with_21w_1218_train_val" # 替换为你的根目录路径
OUTPUT_DIR = "datasets/device_cls_merge_manual_with_21w_1218_train_val_224" # 替换为你想保存的目标目录路径
SIZE = (224, 224) # 调整后的图片大小
NUM_PROCESSES = 16 # 使用的进程数
# 查找所有图片文件
image_files = find_image_files(ROOT_DIR)
print(f"找到 {len(image_files)} 张图片。")
if len(image_files) == 0:
print("未找到任何图片文件,请检查根目录路径和图片格式。")
return
# 确保输出目录存在
Path(OUTPUT_DIR).mkdir(parents=True, exist_ok=True)
# 使用部分函数固定一些参数
process_func = partial(process_image, root_dir=ROOT_DIR, output_dir=OUTPUT_DIR, size=SIZE)
# 使用多进程处理图片,并显示进度条
with Pool(processes=NUM_PROCESSES) as pool:
list(tqdm(pool.imap_unordered(process_func, image_files), total=len(image_files)))
print("所有图片处理完成!")
if __name__ == "__main__":
main()
4️⃣ 第四次尝试:引入额外数据
为了进一步提升模型的识别效果,我额外引入了一批新的数据,并使用已经训练好的模型打出类别及置信度分数,只保留置信度大于 0.9 的类别子图。这一策略显著提升了模型的准确性,特别是在一些模糊的检测任务中,置信度较高的预测结果帮助排除了很多误识别情况。📈
5️⃣ 第五次尝试:调整数据预处理和关闭自动增强
接下来,我调整了 YOLOv8 的预处理策略,并关闭了自动数据增强(auto_augment=False
)。这一调整基于数据集的实际情况,考虑到某些预处理和数据增强方法可能会导致过拟合或过度处理,关闭自动增强后,模型在训练过程中更加专注于数据本身的特征,效果得到了明显的改善。💡
6️⃣ 第六次尝试:数据可视化与欠采样
最后,在进行数据可视化分析后,我发现部分类别的样本数过大,导致了模型训练时出现类别不平衡问题。因此,我对这些过多的类别进行了欠采样处理,减少了它们在训练中的占比,从而使得训练数据的分布更加均衡。📉
具体可以参考这篇文章:图像分类实用脚本:数据可视化与高数量类别截断,欠采样后,训练集中的数据分布更加合理,模型的鲁棒性也得到了提高。🔍
经过这些优化后,模型的表现有了显著提升。最终,mAP50 虽然略有提升至 99.4% 左右,但最重要的是,误识别率已经非常低,整体性能非常稳定。🎉
总结
通过这几轮的调优和实验,我在目标检测任务中取得了较为显著的进展。从最初的 94.5% 到最终的 99.4%,虽然提升幅度不算极其惊人,但每一轮的调整都有其明确的目的和成效,尤其是在类别不平衡、尺度差异、数据增强等方面的优化。最终,模型在实际测试中的误识别问题大幅度减少,几乎达到了理想状态。✨
在后续的工作中,我还将继续关注数据集的多样性与模型的泛化能力,进一步探索更多的调优方案。🔮