Keras深度学习框架实战(7):使用YOLOV8和KerasCV进行高效的图像物体识别

发布于:2024-06-04 ⋅ 阅读:(47) ⋅ 点赞:(0)

1、绪论

1.1 KerasCV简介

KerasCV是一个专注于计算机视觉任务的模块化组件库,基于Keras构建,可与TensorFlow、JAX或PyTorch等框架配合使用。

  1. 概念与定位

    • KerasCV是Keras API的水平扩展,提供了一系列新的第一方Keras对象,这些对象过于专业化,无法添加到核心Keras中。
    • 它获得了与核心Keras API相同级别的完善和向后兼容性保证,并由Keras团队维护。
  2. 功能与应用

    • KerasCV的API协助执行常见的计算机视觉任务,如数据增强、分类、对象检测、分割、图像生成等。
    • 它包括针对流行计算机视觉数据集的预训练模型,例如ImageNet、COCO和Pascal VOC,可用于迁移学习。
    • KerasCV还提供了一系列可视化工具,用于检查模型学习的中间表示以及可视化对象检测和分割任务的结果。
  3. 框架兼容性

    • 虽然KerasCV主要支持TensorFlow框架,但由于其模块化设计,一些组件和功能可能也可以与其他兼容Keras的框架(如JAX或某些版本的PyTorch)一起使用。
    • 这些模型、层、指标、回调等基于Keras Core构建,可以在任何框架中进行训练和序列化,并在另一个框架中重复使用,而无需进行昂贵的迁移。
  4. 应用场景

    • 应用计算机视觉工程师可以利用KerasCV为常见的计算机视觉任务快速组装生产级、最先进的训练和推理管道。
    • 在多个领域中都有广泛应用,例如自动驾驶、医学影像处理、视频监控等。

KerasCV是一个功能强大、灵活且易于使用的计算机视觉库,为开发者提供了丰富的工具和资源来构建和优化各种计算机视觉模型。

1.2 YOLOV8简介

YOLOV8(You Only Look Once version 8)是一个深度学习框架,专门用于实现实时对象检测。

  1. 算法思想

    • YOLO(You Only Look Once)算法的核心思想是只需要浏览一次图像,就可以识别出图像中物体的类别和位置。它采用了一种Region-free(无区域)或单阶段(1-stage)的方法,与需要两阶段(2-stage)的Region-based方法不同。
  2. 性能特点

    • 实时性能:YOLOV8继承了YOLO系列的实时检测特性,即使在较低的硬件配置上也能达到很高的帧率(FPS)。
    • 高准确度:通过更深更复杂的网络结构和改进的训练技巧,YOLOV8在保持高速度的同时,也大幅提高了检测的准确度。
    • 多尺度预测:YOLOV8引入了改进的多尺度预测技术,能够更好地检测不同大小的对象。
    • 自适应锚框:新版在自适应调整锚框方面做了优化,可以更准确地预测对象的位置和大小。
    • 更强的特征提取器:YOLOV8使用了更先进的特征提取网络,有助于从图像中提取出更丰富、更有区分度的特征。
  3. 功能与应用

    • YOLOV8不仅是一个目标检测算法,还结合了多算法实现多目标追踪、实例分割和姿态估计功能。
    • 它在计算机视觉领域具有广泛的应用前景,如智能监控、自动驾驶、人机交互等领域。
  4. 创新点

    • YOLOV8是在Yolo系列历史版本的基础上推出的最新版本,引入了新的功能和改进点,如新的骨干网络、新的Ancher-Free检测头和一个新的损失函数。
    • 它的可扩展性允许它不仅仅用于Yolo系列模型,还能支持非Yolo模型以及分类、分割、姿态估计等各类任务。
  5. 模型评估

    • 在COCO Val 2017数据集上,YOLOV8相比前代模型如YOLOV5,在精度上有所提升,但模型参数量和FLOPs(浮点运算次数)也相应增加。然而,与YOLOV5相比,大部分模型的推理速度可能会变慢。

YOLOV8是一个功能强大、性能优越的实时对象检测算法,具有广泛的应用前景和强大的扩展能力。

1.3 图像目标识别概述

图像目标识别是计算机视觉领域中的一个重要任务,旨在从给定的图像中自动识别和定位出其中的目标物体。

  1. 定义

    • 图像目标识别是利用计算机对图像进行处理、分析和理解,以识别各种不同模式的目标和对象的技术。
  2. 主要步骤

    • 图像预处理:对图像进行平移、旋转和缩放等几何规范,使图像识别能够更快速、准确。同时,图像滤波用于消除噪声,保持图像特征。
    • 图像分割:是实现机器视觉图像自动识别与分析的关键步骤,其分割质量对后续图像的分析具有重要意义。
    • 目标检测:在图像中定位出目标的位置,通常使用边界框来表示目标的位置和大小。
    • 目标分类:在检测到目标后,对其进行分类,识别出目标的类别。
  3. 技术特点

    • 图像目标识别通过存储的信息(记忆中存储的信息)与当前的信息(当时进入感官的信息)进行比较实现对图像的识别。
    • 前提是图像描述,即使用数字或符号表示图像或景物中各个目标的相关特征,甚至目标之间的关系。
    • 图像识别技术对图像中个性特征进行提取时,可以采用模板匹配模型等方法。
  4. 应用领域

    • 图像识别技术已广泛应用于多个领域,包括生物医学、卫星遥感、机器人视觉、货物检测、目标跟踪、自主车导航、公安、银行、交通、军事、电子商务和多媒体网络通信等。
  5. 技术发展

    • 随着技术的发展,出现了基于机器视觉的目标识别、基于深度学习的目标识别等,这些方法大大提高了图像识别的准确度和识别效率。

图像目标识别是一个复杂的任务,涉及多个步骤和技术方法。通过不断的研究和技术创新,图像目标识别的性能正在不断提高,为各个领域的应用提供了有力的支持。

2、图像物体识别过程

2.1软件安装及设置

KerasCV 是 Keras 在计算机视觉任务上的扩展。在本文中我们将看到如何使用 KerasCV 训练 YOLOv8 目标检测模型。在进行训练之前,将简要介绍软件的安装和设置。

2.1.1 软件安装
!pip install --upgrade git+https://github.com/keras-team/keras-cv -q
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
2.1.2 设置
import os
from tqdm.auto import tqdm
import xml.etree.ElementTree as ET

import tensorflow as tf
from tensorflow import keras

import keras_cv
from keras_cv import bounding_box
from keras_cv import visualization

2.2 数据预处理

2.2.1 数据加载

本文我们将使用从 roboflow 获取的自动驾驶汽车数据集。为了使数据集更易于管理,我已经提取了较大数据集的一个子集,该数据集原本包含15,000个数据样本。在此子集中,我选择了7,316个样本用于模型训练。

为了简化手头的任务并集中我们的努力,我们将使用减少数量的对象类别。具体来说,我们将考虑五个主要类别进行检测和分类:汽车、行人、交通灯、摩托车手和卡车。这些类别代表了自动驾驶汽车环境中遇到的一些最常见和最重要的对象。

通过将数据集缩小到这些特定类别,我们可以集中精力构建一个能够准确识别和分类这些重要对象的强大目标检测模型。

TensorFlow 数据集库提供了一种方便的方式来下载和使用各种数据集,包括目标检测数据集。对于想要快速开始使用数据而无需手动下载和预处理的人来说,这是一个很好的选择。

程序员可以在这里查看各种目标检测数据集:TensorFlow 数据集

然而,在本文的代码示例中,我们将演示如何从头开始使用 TensorFlow 的 tf.data 管道加载数据集。这种方法提供更多的灵活性,并允许程序员根据需要自定义预处理步骤。

使用 tf.data 管道加载在 TensorFlow 数据集库中不可用自定义数据集是使用 tf.data 管道的一个主要优势。这种方法允许程序员创建一个定制的数据预处理管道,以满足程序员对数据集的特定需求和要求。

2.2.2 设置超参数
SPLIT_RATIO = 0.2
BATCH_SIZE = 4
LEARNING_RATE = 0.001
EPOCH = 5
GLOBAL_CLIPNORM = 10.0
2.2.3 创建数据字典

在图像目标识别任务中,建立一个详细的数据字典是一个至关重要的步骤,它确保了模型能够准确地将图像中的目标映射到对应的类别。数据字典,通常被称为标签字典或类别字典,是一个将目标类别名称与唯一标识符相关联的集合。以下是建立和使用数据字典的详细过程:

首先,需要明确你的数据集中包含哪些目标类别。这些类别通常是根据你的具体应用场景和需求来定义的,例如,在自动驾驶系统中,类别可能包括“汽车”、“行人”、“交通灯”等;在动物识别应用中,类别可能包括“猫”、“狗”、“鸟”等。

接下来,你需要列出所有的目标类别,并确保每个类别名称都是唯一的,没有拼写错误或混淆的可能性。这一步是为了确保在后续的数据处理和模型训练过程中,每个类别都能够被正确地识别和区分。

然后,为每个类别分配一个唯一的标识符。这个标识符通常是一个整数或字符串,用于在数据处理和模型训练过程中代替类别名称。整数标识符在处理速度和内存使用方面通常更具优势,因此在大多数图像目标识别任务中更为常用。你可以按照某种顺序(如字母顺序或自定义顺序)为类别分配标识符,只要确保每个类别都有一个唯一的标识符即可。

现在,你可以创建一个数据字典,将类别名称作为键,将对应的标识符作为值。在创建数据字典时,可以使用编程语言(如Python)中的字典数据结构来实现。这个数据字典将成为你在整个项目过程中处理类别标签的参考依据。

在训练、验证和测试过程中,你需要使用数据字典来编码和解码类别标签。编码是指将类别名称转换为对应的标识符,以便模型能够处理这些标签。解码是指将模型输出的标识符转换回可读的类别名称,以便你能够理解模型的预测结果。在编码和解码过程中,你只需要查找数据字典中相应的键值对即可。

需要注意的是,在整个项目过程中,你需要确保始终使用相同的数据字典。不要更改字典中的映射关系,除非你有充分的理由这样做。此外,如果你的数据集将来需要添加新的类别,你需要确保你的数据字典可以轻松地扩展以包含新的映射关系。一种常见的做法是在数据字典中预留一些额外的标识符,以便将来添加新的类别时使用。

通过建立和使用一个详细的数据字典,你可以确保在图像目标识别任务中准确地处理类别标签,从而提高模型的识别精度和性能。

物体类别映射

class_ids = [
    "car",
    "pedestrian",
    "trafficLight",
    "biker",
    "truck",
]
class_mapping = dict(zip(range(len(class_ids)), class_ids))

# 图像和注释的路径
path_images = "/kaggle/input/dataset/data/images/"
path_annot = "/kaggle/input/dataset/data/annotations/"

# 获取 path_annot 中所有 XML 文件路径并排序
xml_files = sorted(
    [
        os.path.join(path_annot, file_name)
        for file_name in os.listdir(path_annot)
        if file_name.endswith(".xml")
    ]
)

# 获取 path_images 中所有 JPEG 图像文件路径并排序
jpg_files = sorted(
    [
        os.path.join(path_images, file_name)
        for file_name in os.listdir(path_images)
        if file_name.endswith(".jpg")
    ]
)

下面定义的函数读取 XML 文件,找到图像名称和路径,然后遍历 XML 文件中的每个对象,提取每个对象的边界框坐标和类别标签。

该函数返回三个值:图像路径、边界框列表(每个边界框表示为四个浮点数的列表:xmin, ymin, xmax, ymax),以及与每个边界框对应的类别 ID 列表(表示为整数)。类别 ID 是通过使用名为 class_mapping 的字典将类别标签映射到整数值来获得的。

def parse_annotation(xml_file):
    tree = ET.parse(xml_file)
    root = tree.getroot()

    image_name = root.find("filename").text
    image_path = os.path.join(path_images, image_name)

    boxes = []
    classes = []
    for obj in root.iter("object"):
        cls = obj.find("name").text
        classes.append(cls)

        bbox = obj.find("bndbox")
        xmin = float(bbox.find("xmin").text)
        ymin = float(bbox.find("ymin").text)
        xmax = float(bbox.find("xmax").text)
        ymax = float(bbox.find("ymax").text)
        boxes.append([xmin, ymin, xmax, ymax])

    class_ids = [
        list(class_mapping.keys())[list(class_mapping.values()).index(cls)]
        for cls in classes
    ]
    return image_path, boxes, class_ids

image_paths = []
bbox = []
classes = []
for xml_file in tqdm(xml_files):
    image_path, boxes, class_ids = parse_annotation(xml_file)
    image_paths.append(image_path)
    bbox.append(boxes)
    classes.append(class_ids)

建立张量

这里我们使用 tf.ragged.constantbboxclasses 列表创建不规则张量。不规则张量是一种可以处理一个或多个维度上长度不等数据的张量类型。这在处理具有可变长度序列的数据时非常有用,例如文本或时间序列数据。

classes = [
    [8, 8, 8, 8, 8],      # 5 classes
    [12, 14, 14, 14],     # 4 classes
    [1],                  # 1 class
    [7, 7],               # 2 classes
    ...
]

bbox = [
    [[199.0, 19.0, 390.0, 401.0],
    [217.0, 15.0, 270.0, 157.0],
    [393.0, 18.0, 432.0, 162.0],
    [1.0, 15.0, 226.0, 276.0],
    [19.0, 95.0, 458.0, 443.0]], # 第一张图有 4 个对象
    [[52.0, 117.0, 109.0, 177.0]], # 第二张图有 1 个对象
    [[88.0, 87.0, 235.0, 322.0],
    [113.0, 117.0, 218.0, 471.0]], # 第三张图有 2 个对象
    ...
]

在这个例子中,bboxclasses 列表对每个图像的长度不同,这取决于图像中的对象数量以及相应的边界框和类别。为了处理这种可变性,我们使用不规则张量而不是常规张量。

稍后,这些不规则张量被用来创建一个 tf.data.Dataset,使用 from_tensor_slices 方法。该方法通过沿第一维切分输入张量来创建数据集。通过使用不规则张量,数据集可以处理每个图像长度不等的数据,并为进一步处理提供灵活的输入管道。

bbox = tf.ragged.constant(bbox)
classes = tf.ragged.constant(classes)
image_paths = tf.ragged.constant(image_paths)

data = tf.data.Dataset.from_tensor_slices((image_paths, classes, bbox))

2.2.4 分割训练和验证数据

在图像目标识别任务中,分割训练和验证数据是确保模型性能评估和避免过拟合的关键步骤。

首先,你需要明确你的数据集,包括图像的总数、每个类别的图像数量以及标签的详细程度。接着,大部分数据(通常70%到80%)被用作训练集,用于训练模型学习如何从图像中识别目标。剩余的数据(20%到30%)则被用作验证集,它不参与训练过程,但用于在训练过程中评估模型的性能,以便调整超参数或提前停止训练。

在分割数据时,必须确保训练集和验证集中的类别分布相似,这有助于确保模型在验证集上的性能能够反映其在未见过的数据上的性能。同时,要特别注意避免数据泄露,即确保验证集中的图像在训练过程中是完全不可见的。任何形式的数据泄露都可能导致模型在验证集上表现出过高的性能,但在实际应用中表现不佳。

为了提高模型的泛化能力,可以考虑对训练集进行数据增强,如旋转、缩放、翻转等操作。然而,验证集应保持原始状态,以便准确评估模型的性能。在多次实验或研究中,为了获得更稳定的验证结果,可以考虑多次随机分割数据集并计算平均验证性能。

最后,务必记录你如何分割数据的信息,包括分割的比例、使用的随机数种子等,以便其他人能够复现你的结果或进行进一步的研究。

合理分割训练和验证数据是图像目标识别任务中不可或缺的一环,它有助于确保模型能够在未见过的数据上展现出良好的性能。

数据分割

# 确定验证样本的数量
num_val = int(len(xml_files) * SPLIT_RATIO)

# 将数据集分割为训练集和验证集
val_data = data.take(num_val)
train_data = data.skip(num_val)

边界框格式化

让我们来谈谈数据加载和边界框格式化,以启动事情。KerasCV 中的边界框有一个预定的格式。为此,你必须将你的边界框捆绑成一个字典,该字典符合下面列出的要求:

bounding_boxes = {
    # num_boxes 可能是一个不规则维度
    'boxes': Tensor(shape=[batch, num_boxes, 4]),
    'classes': Tensor(shape=[batch, num_boxes])
}

字典有两个键,'boxes''classes',每个键都映射到一个 TensorFlow 不规则张量或张量对象。'boxes' 张量的形状为 [batch, num_boxes, 4],其中 batch 是批次中的图像数量,num_boxes 是任何图像
中边界框的最大数量。4 表示定义边界框所需的四个值:xmin, ymin, xmax, ymax。

'classes' 张量的形状为 [batch, num_boxes],其中的每个元素代表 'boxes' 张量中相应边界框的类别标签。num_boxes 维度可能是不规则的,这意味着批次中图像的数量可能不同。

最终的字典应该是:

{"images": images, "bounding_boxes": bounding_boxes}
def load_image(image_path):
    image = tf.io.read_file(image_path)
    image = tf.image.decode_jpeg(image, channels=3)
    return image

def load_dataset(image_path, classes, bbox):
    # 读取图像
    image = load_image(image_path)
    bounding_boxes = {
        "classes": tf.cast(classes, dtype=tf.float32),
        "boxes": bbox,
    }
    return {"images": tf.cast(image, tf.float32), "bounding_boxes": bounding_boxes}

在这里,我们创建一个层,将图像调整为 640x640 像素,同时保持原始纵横比。与图像相关联的边界框指定为 xyxy 格式。如果需要,调整大小的图像将用零填充,以保持原始纵横比。

KerasCV 支持的边界框格式:

  1. CENTER_XYWH
  2. XYWH
  3. XYXY
  4. REL_XYXY
  5. REL_XYWH
  6. YXYX
  7. REL_YXYX

格式转换方法

此外,可以在任何两对格式之间执行格式转换:

boxes = keras_cv.bounding_box.convert_format(
    bounding_box,
    images=image,
    source="xyxy",  # 原始格式
    target="xywh",  # 目标格式(我们要转换的格式)
)

2.2.5 数据增强

构建目标检测管道时最具挑战性的任务之一是数据增强。它涉及对输入图像应用各种转换,以增加训练数据的多样性,并提高模型的泛化能力。然而,当处理目标检测任务时,情况变得更加复杂,因为这些转换需要了解底层的边界框,并相应地更新它们。

KerasCV 提供了对边界框增强的原生支持。KerasCV 提供了一系列专门设计用于处理边界框的数据增强层。这些层在图像转换时智能地调整边界框坐标,确保边界框保持准确并与增强图像对齐。

通过利用 KerasCV 的功能,开发人员可以方便地将边界框友好的数据增强集成到他们的目标检测管道中。通过在 tf.data 管道中执行即时增强,该过程变得无缝高效,从而实现更好的训练和更准确的目标检测结果。

augmenter = keras.Sequential(
    layers=[
        keras_cv.layers.RandomFlip(mode="horizontal", bounding_box_format="xyxy"),
        keras_cv.layers.RandomShear(
            x_factor=0.2, y_factor=0.2, bounding_box_format="xyxy"
        ),
        keras_cv.layers.JitteredResize(
            target_size=(640, 640), scale_factor=(0.75, 1.3), bounding_box_format="xyxy"
        ),
    ]
)

2.2.6 创建训练数据集

在图像目标识别任务中,对训练数据集进行适当的预处理和增强是至关重要的步骤。以下代码片段展示了如何使用TensorFlow的tf.data API对train_data数据集进行一系列的处理,以准备用于模型训练。

首先,train_data.map(load_dataset, num_parallel_calls=tf.data.AUTOTUNE)这行代码将load_dataset函数应用于train_data数据集中的每一个元素,该函数通常负责读取图像文件、解码图像数据、进行必要的预处理(如归一化、裁剪等)以及加载标签等操作。通过num_parallel_calls=tf.data.AUTOTUNE,TensorFlow会自动决定并行调用load_dataset函数的最佳数量,以优化性能。

接着,train_ds.shuffle(BATCH_SIZE * 4)对数据集进行随机打乱,以防止模型在训练过程中记住数据的特定顺序,从而提高模型的泛化能力。打乱缓冲区的大小设置为BATCH_SIZE * 4,这意味着TensorFlow会从数据集中随机选择BATCH_SIZE * 4个元素放入缓冲区,并从该缓冲区中随机选择一个元素作为下一个输出。

然后,train_ds.ragged_batch(BATCH_SIZE, drop_remainder=True)将数据集分割成大小为BATCH_SIZE的批次。由于使用ragged_batch,这个方法可以处理可能具有不同长度的元素(如不同大小的图像)。但是,由于drop_remainder=True,如果数据集中的元素数量不能被BATCH_SIZE整除,那么剩余的元素将被丢弃。

最后,train_ds.map(augmenter, num_parallel_calls=tf.data.AUTOTUNE)这行代码将augmenter函数应用于数据集的每一个批次,进行数据增强操作,如随机旋转、缩放、翻转等,以增加模型的泛化能力。同样,num_parallel_calls=tf.data.AUTOTUNE允许TensorFlow自动决定并行调用augmenter函数的最佳数量。

整个处理流程的目的是生成一个适用于图像目标识别模型训练的、经过预处理和增强的数据集train_ds

train_ds = train_data.map(load_dataset, num_parallel_calls=tf.data.AUTOTUNE)
train_ds = train_ds.shuffle(BATCH_SIZE * 4)
train_ds = train_ds.ragged_batch(BATCH_SIZE, drop_remainder=True)
train_ds = train_ds.map(augmenter, num_parallel_calls=tf.data.AUTOTUNE)

2.2.7 创建验证数据集

在图像目标识别任务中,对验证数据集(val_data)进行适当的预处理和增强是评估模型性能的关键步骤。以下代码片段使用keras_cv.layers.JitteredResize层和其他tf.data API方法来处理验证数据集val_data

首先,定义了一个JitteredResizeresizing,用于在验证数据集中随机调整图像的大小。该层将图像的目标大小设置为640x640像素,并通过scale_factor参数在0.75到1.3的范围内随机缩放图像,以此作为数据增强的一种手段。这种随机缩放有助于模型学习对尺度变化的鲁棒性。此外,bounding_box_format="xyxy"指定了边界框的格式,确保在缩放过程中边界框的位置和大小得到正确的调整。

接下来,通过val_data.map(load_dataset, num_parallel_calls=tf.data.AUTOTUNE)load_dataset函数被应用于验证数据集的每个元素。这个函数负责加载图像文件、解码图像数据,并进行必要的预处理(如解码标签、归一化等)。num_parallel_calls=tf.data.AUTOTUNE允许TensorFlow自动确定并行调用的最佳数量,以提高数据加载的效率。

然后,val_ds.shuffle(BATCH_SIZE * 4)对验证数据集进行随机打乱。打乱操作有助于防止模型在评估过程中受到数据顺序的影响,从而得到更准确的评估结果。打乱缓冲区的大小设置为BATCH_SIZE * 4,意味着TensorFlow将从数据集中随机选择BATCH_SIZE * 4个元素进行打乱。

接着,val_ds.ragged_batch(BATCH_SIZE, drop_remainder=True)将打乱后的数据集分割成大小为BATCH_SIZE的批次。由于使用了ragged_batch,这个方法可以处理可能具有不同长度的元素(尽管在验证集中这种情况可能较少见)。drop_remainder=True确保如果数据集中的元素数量不能被BATCH_SIZE整除,则剩余的元素将被丢弃,以确保每个批次的大小一致。

最后,通过val_ds.map(resizing, num_parallel_calls=tf.data.AUTOTUNE)resizing层被应用于验证数据集的每个批次。这个步骤将前面定义的随机大小调整操作应用于验证集中的每个图像,以实现数据增强。同样,num_parallel_calls=tf.data.AUTOTUNE允许TensorFlow自动确定并行调用的最佳数量,以提高处理效率。

整个处理流程的目的是生成一个经过适当预处理和增强的验证数据集val_ds,以便在模型训练过程中用于评估模型的性能。

resizing = keras_cv.layers.JitteredResize(
    target_size=(640, 640),
    scale_factor=(0.75, 1.3),
    bounding_box_format="xyxy",
)

val_ds = val_data.map(load_dataset, num_parallel_calls=tf.data.AUTOTUNE)
val_ds = val_ds.shuffle(BATCH_SIZE * 4)
val_ds = val_ds.ragged_batch(BATCH_SIZE, drop_remainder=True)
val_ds = val_ds.map(resizing, num_parallel_calls=tf.data.AUTOTUNE)

2.2.8 可视化

以下码定义了一个名为visualize_dataset的函数,该函数用于可视化数据集中的图像及其对应的边界框。随后,该函数被用于可视化训练数据集train_ds和验证数据集val_ds

visualize_dataset函数中,首先通过next(iter(inputs.take(1)))从输入的数据集inputs中取出一个批次的数据。这个批次的数据通常是一个字典,包含了图像(images)和对应的边界框(bounding_boxes)。

接下来,函数使用visualization.plot_bounding_box_gallery来绘制一个包含多个图像的网格(或称为画廊),每个图像上都标出了边界框。value_range参数定义了图像像素值的范围(在此例中为0到255,对应于标准的8位RGB图像),而rowscols参数定义了网格中的行数和列数。y_true参数是真实的边界框数据,用于在图像上绘制边界框。scalefont_scale参数分别用于控制图像大小和字体大小。bounding_box_format参数定义了边界框的格式(在这里使用"xyxy"格式,表示边界框由左上角的(x, y)坐标和右下角的(x, y)坐标定义)。class_mapping参数用于将类别索引映射到实际的类别名称,但在这个函数定义中并未给出class_mapping的具体内容或传递方式,我们假设它在使用该函数之前已经被定义或作为全局变量存在。

在函数定义之后,visualize_dataset函数被两次调用,分别用于可视化train_dsval_ds数据集。在这两个调用中,除了数据集本身外,其他参数都相同:边界框格式设置为"xyxy",像素值范围设置为0到255,网格的行数和列数都设置为2。这意味着每个网格将包含2x2=4个图像。

代码的目的是为了直观地展示训练数据集和验证数据集中的图像及其对应的边界框,以便于开发者检查数据的质量和预处理效果。

def visualize_dataset(inputs, value_range, rows, cols, bounding_box_format):
    inputs = next(iter(inputs.take(1)))
    images, bounding_boxes = inputs["images"], inputs["bounding_boxes"]
    visualization.plot_bounding_box_gallery(
        images,
        value_range=value_range,
        rows=rows,
        cols=cols,
        y_true=bounding_boxes,
        scale=5,
        font_scale=0.7,
        bounding_box_format=bounding_box_format,
        class_mapping=class_mapping,
    )

visualize_dataset(
    train_ds, bounding_box_format="xyxy", value_range=(0, 255), rows=2, cols=2
)

visualize_dataset(
    val_ds, bounding_box_format="xyxy", value_range=(0, 255), rows=2, cols=2
)

然后通过使用map和prefetch函数对训练数据集train_ds和验证数据集val_ds进行了优化处理。首先,通过map函数将数据集中的每个字典项转换为元组形式,简化了数据格式并提高了模型访问数据的效率。然后,使用prefetch函数在后台异步地加载数据,以便在模型需要新的数据批次时能够立即提供,从而减少了数据加载的时间,提高了整体的训练性能。在这个过程中,tf.data.AUTOTUNE被用于自动选择最佳的预取缓冲区大小,以在可用资源之间达到最佳平衡。

def dict_to_tuple(inputs):
    return inputs["images"], inputs["bounding_boxes"]

train_ds = train_ds.map(dict_to_tuple, num_parallel_calls=tf.data.AUTOTUNE)
train_ds = train_ds.prefetch(tf.data.AUTOTUNE)

val_ds = val_ds.map(dict_to_tuple, num_parallel_calls=tf.data.AUTOTUNE)
val_ds = val_ds.prefetch(tf.data.AUTOTUNE)

2.3 创建模型

YOLOv8 是一种用于各种计算机视觉任务的最新 YOLO 模型,如目标检测、图像分类和实例分割。Ultraalytics,YOLOv5 的创建者,也开发了 YOLOv8,它在架构和开发者体验方面相比前身进行了多项改进和变更。YOLOv8 是业界高度评价的最新技术模型。

下面的表格比较了五种不同大小(以像素为单位)的 YOLOv8 模型的性能指标:YOLOv8n、YOLOv8s、YOLOv8m、YOLOv8l 和 YOLOv8x。指标包括不同交并比(IoU)阈值下验证数据的平均精度均值(mAP)值、CPU 上 ONNX 格式的推理速度和 A100 TensorRT 的速度、参数数量和浮点运算次数(FLOPs)(分别为百万和十亿)。

模型 大小 (像素) mAPval 50-95 速度 CPU ONNX (ms) 速度 A100 TensorRT (ms) 参数 (M) FLOPs (B)
YOLOv8n 640 37.3 80.4 0.99 3.2 8.7
YOLOv8s 640 44.9 128.4 1.20 11.2 28.6
YOLOv8m 640 50.2 234.7 1.83 25.9 78.9
YOLOv8l 640 52.9 375.2 2.39 43.7 165.2
YOLOv8x 640 53.9 479.1 3.53 68.2 257.8
2.3.1 创建backbone 实例

在图像目标识别任务中,当使用KerasCV这样的框架时,为YOLOV8选择一个合适的backbone并加载COCO预训练权重是构建高效模型的关键步骤。首先,你需要从KerasCV中选择一个经过预训练的backbone,这个backbone通常基于深度卷积神经网络(CNN)如ResNet、EfficientNet等,并已经针对图像分类或目标检测任务进行了优化。其次,确保这个backbone提供了COCO数据集的预训练权重,因为这将有助于加速训练过程并提高模型的性能。

在选定backbone后,你可以使用KerasCV提供的工具或API来加载这个backbone以及对应的COCO预训练权重。这个步骤将确保backbone中的权重已经被正确地初始化为经过大量数据训练得到的值,从而提高了模型在目标识别任务上的泛化能力。

一旦backbone被加载并初始化,你可以将其整合到YOLOV8模型中。YOLOV8是一个流行的目标检测算法,它依赖于backbone来提取图像中的特征。将backbone整合到YOLOV8模型中时,需要确保backbone的输出与YOLOV8的输入要求相匹配,以便能够正确地处理这些特征并输出目标边界框和类别。

最后,你可以使用整合了backbone的YOLOV8模型进行训练和评估。通过调整模型的参数和配置,你可以进一步优化模型的性能,以满足特定应用场景的需求。在整个过程中,保持对backbone和YOLOV8模型的理解和熟悉将是非常重要的,这将有助于你更好地利用这些工具来解决图像目标识别任务。KerasCV 中可用的 YOLOV8 Backbones:

无权重:

1.   yolo_v8_xs_backbone
2.   yolo_v8_s_backbone
3.   yolo_v8_m_backbone
4.   yolo_v8_l_backbone
5.   yolo_v8_xl_backbone

带有预训练 coco 权重:

backbone = keras_cv.models.YOLOV8Backbone.from_preset(
    "yolo_v8_s_backbone_coco"  # 我们将使用带有 coco 权重的 yolov8 小型 backbone
)
Downloading data from https://storage.googleapis.com/keras-cv/models/yolov8/coco/yolov8_s_backbone.h5
20596968/20596968 [==============================] - 0s 0us/step
2.3.2 构建YOLOV8 模型

接下来,让我们使用 YOLOV8Detector 构建一个 YOLOV8 模型,它接受一个特征提取器作为 backbone 参数,一个 num_classes 参数,该参数根据 class_mapping 列表的大小指定要检测的目标类别数量,一个 bounding_box_format 参数,该参数告知模型数据集中边界框的格式,最后,特征金字塔网络(FPN)深度由 fpn_depth 参数指定。

在 KerasCV中,使用上述任何一个 backbone 构建 YOLOV8 都非常简单。

yolo = keras_cv.models.YOLOV8Detector(
    num_classes=len(class_mapping),
    bounding_box_format="xyxy",
    backbone=backbone,
    fpn_depth=1,
)

2.4 编译模型

2.4.1YOLOV8 使用的损失函数:
  1. 分类损失:此损失函数计算预期类别概率和实际类别概率之间的差异。在这种情况下,使用了 binary_crossentropy,这是解决二元分类问题的著名解决方案。我们使用二元交叉熵,因为每个被识别的对象要么被归类为某个特定类别(如人、汽车等),要么不是。

  2. 边界框损失:box_loss 是用来衡量预测边界框和真实边界框之间差异的损失函数。在这种情况下,使用了完整的 IoU(CIoU)指标,它不仅衡量预测和真实边界框之间的重叠,还考虑了纵横比、中心距离和框大小的差异。这些损失函数共同帮助优化模型的目标检测,通过最小化预测和真实类别概率以及边界框之间的差异。

在以下的Python代码中,配置一个用于训练YOLOv8模型的优化器,并编译模型以使用特定的损失函数。

  1. 优化器配置

    • tf.keras.optimizers.Adam(...):这里您正在创建一个Adam优化器的实例,它通常用于深度学习中参数的优化。
      • learning_rate=LEARNING_RATE:学习率决定了参数更新的步长。LEARNING_RATE应该是一个预先定义的变量,表示您想要设置的学习率值。
      • global_clipnorm=GLOBAL_CLIPNORM:这是梯度裁剪的一个参数。它用于确保梯度的L2范数不会超过指定的阈值,从而避免梯度爆炸的问题。GLOBAL_CLIPNORM应该是一个预先定义的变量,表示您想要设置的梯度裁剪阈值。
  2. 模型编译

    • yolo.compile(...):这是Keras模型的一个方法,用于配置训练过程。在编译时,您需要指定损失函数和优化器(以及其他可选的参数,如指标)。
      • optimizer=optimizer:这里您传递了之前创建的Adam优化器实例。
      • classification_loss="binary_crossentropy":对于分类任务,您使用了二元交叉熵损失(binary crossentropy)。这是因为YOLOv8为每个类别都使用了一个独立的sigmoid分类器,因此每个类别的预测都可以被视为一个二分类问题。
      • box_loss="ciou":对于边界框的回归,您选择了CIOU(Complete Intersection over Union)损失。CIOU是IOU的一个扩展,它增加了宽度、高度比的损失以及预测框和目标框中心点距离的惩罚项,从而提高了边界框回归的精度。
optimizer = tf.keras.optimizers.Adam(
    learning_rate=LEARNING_RATE,
    global_clipnorm=GLOBAL_CLIPNORM,
)

yolo.compile(
    optimizer=optimizer, classification_loss="binary_crossentropy", box_loss="ciou"
)
2.4.2 COCO 指标回调

我们将使用 KerasCV 中的 BoxCOCOMetrics 来评估模型并计算平均精度均值(Map)得分、召回率和精确度。当 mAP 分数提高时,我们还会保存我们的模型。

示例代码定义了一个名为 EvaluateCOCOMetricsCallback 的类,它继承自 Keras 的 Callback 类,用于在 Keras 模型训练过程中评估模型在 COCO 数据集上的指标,并在 mAP(mean Average Precision)提高时保存模型。以下是对代码的详细解读:

  • 导入依赖

虽然代码中没有直接显示导入的依赖,但我们可以假设已经导入了 keraskeras_cv.metrics.BoxCOCOMetrics。其中,keras 是深度学习库 Keras,而 keras_cv.metrics.BoxCOCOMetrics 可能是一个第三方库或自定义库,用于计算 COCO 数据集上的目标检测指标。

  • 类定义

    • EvaluateCOCOMetricsCallback(keras.callbacks.Callback):定义了一个继承自 Keras Callback 的类,用于在模型训练过程中执行特定的操作。
  • 初始化方法

    • __init__(self, data, save_path):初始化方法接受两个参数,datasave_path。其中,data 是用于评估的数据集,而 save_path 是保存最佳模型的路径。
    • self.metrics = keras_cv.metrics.BoxCOCOMetrics(...):创建一个 BoxCOCOMetrics 对象,用于计算 COCO 数据集上的指标。这里设置 bounding_box_format 为 “xyxy”,表示边界框的格式是 (x1, y1, x2, y2),其中 (x1, y1) 是左上角坐标,(x2, y2) 是右下角坐标。evaluate_freq=1e9 表示每次更新都会计算指标,但通常这个参数应该设置为一个较小的值,如 1,表示每个批次都计算指标,但在这种情况下,由于我们在 on_epoch_end 中手动处理,所以这个值设置为 1e9 是合适的。
    • self.best_map = -1.0:初始化最佳 mAP 值为 -1.0,用于后续比较。
  • on_epoch_end 方法

    • 这个方法在每个训练周期结束时被调用。
    • self.metrics.reset_state():重置指标的状态,为新的评估周期做准备。
    • 遍历 self.data 中的每个批次,使用模型进行预测,并使用 self.metrics.update_state(y_true, y_pred) 更新指标的状态。
    • metrics = self.metrics.result(force=True):计算并获取最终的指标结果。force=True 表示强制计算结果,即使没有达到 evaluate_freq 指定的更新频率。
    • logs.update(metrics):将计算得到的指标结果更新到 Keras 的日志中,这样可以在训练过程中跟踪这些指标。
    • 检查当前的 mAP 值是否比之前保存的最佳 mAP 值高,如果是,则保存模型并更新最佳 mAP 值。
class EvaluateCOCOMetricsCallback(keras.callbacks.Callback):
    def __init__(self, data, save_path):
        super().__init__()
        self.data = data
        self.metrics = keras_cv.metrics.BoxCOCOMetrics(
            bounding_box_format="xyxy",
            evaluate_freq=1e9,
        )

        self.save_path = save_path
        self.best_map = -1.0

    def on_epoch_end(self, epoch, logs):
        self.metrics.reset_state()
        for batch in self.data:
            images, y_true = batch[0], batch[1]
            y_pred = self.model.predict(images, verbose=0)
            self.metrics.update_state(y_true, y_pred)

        metrics = self.metrics.result(force=True)
        logs.update(metrics)

        current_map = metrics["MaP"]
        if current_map > self.best_map:
            self.best_map = current_map
            self.model.save(self.save_path)  # 当 mAP 提高时保存模型

        return logs

2.5 训练模型

示例代码使用YOLO的fit方法来训练模型。

yolo.fit(
    train_ds,
    validation_data=val_ds,
    epochs=3,
    callbacks=[EvaluateCOCOMetricsCallback(val_ds, "model.h5")],
)

yolo.fit(...):

  • 这表明你正在调用一个名为yolo的对象的fit方法。这个对象很可能是一个深度学习模型(如Keras的Model类或其子类)。

train_ds:

  • 这是fit方法的第一个参数,代表训练数据集。train_ds很可能是一个数据加载器或数据生成器,它为模型提供训练时所需的输入数据和对应的标签。

validation_data=val_ds:

  • 这是一个关键字参数,用于指定验证数据集。在训练过程中,模型会使用val_ds来评估其在验证集上的性能,但不会基于验证集的数据来更新权重。这有助于监控模型是否出现过拟合。

epochs=3:

  • 这指定了训练的轮数(或迭代次数)。在这里,模型将遍历整个train_ds数据集3次。

callbacks=[EvaluateCOCOMetricsCallback(val_ds, "model.h5")]:

  • callbacks是一个列表,其中可以包含多个回调函数。这些回调函数在训练的不同阶段(如每个epoch结束后)被调用,以执行某些操作,如保存模型、记录日志等。
  • 在这里,你定义了一个名为EvaluateCOCOMetricsCallback的回调函数,并将其实例化,传递了val_ds(验证数据集)和"model.h5"(可能是模型权重文件的保存路径)作为参数。

2.6 可视化预测结果

当使用深度学习模型(如YOLO)进行目标检测时,我们通常希望可视化模型的预测结果以评估其性能。以下是一个步骤说明,描述了如何执行这一过程:

首先,我们定义一个名为visualize_detections的函数,该函数接受三个参数:model(训练好的模型),dataset(用于获取输入图像的数据集)以及bounding_box_format(边界框的格式,如"xyxy")。

在函数内部,我们使用next(iter(dataset.take(1)))从数据集中获取一个批次的图像和对应的真实标签。然后,我们利用model.predict(images)对图像进行预测,得到预测的边界框信息。

接下来,我们可能需要将预测的边界框信息转换为一种可视化工具所需的格式。但请注意,这一步的具体实现取决于你使用的可视化库或方法。假设这里存在一个bounding_box模块,它有一个to_ragged方法可以将预测结果转换为所需的格式(尽管在标准的深度学习库中并没有直接这样的方法)。

一旦我们有了可视化所需的图像、真实标签和预测边界框,我们就可以调用一个可视化函数(如visualization.plot_bounding_box_gallery)来绘制包含真实边界框和预测边界框的图像。这个函数通常接受一系列参数,如图像、真实标签、预测标签、边界框格式等,并生成一个可视化结果。

最后,我们调用visualize_detections函数,传入已训练的YOLO模型、验证数据集以及边界框格式(“xyxy”)。这将触发上述过程,生成一个可视化结果,显示模型在验证数据集上的预测效果。

def visualize_detections(model, dataset, bounding_box_format):
    images, y_true = next(iter(dataset.take(1)))
    y_pred = model.predict(images)
    y_pred = bounding_box.to_ragged(y_pred)
    visualization.plot_bounding_box_gallery(
        images,
        value_range=(0, 255),
        bounding_box_format=bounding_box_format,
        y_true=y_true,
        y_pred=y_pred,
        scale=4,
        rows=2,
        cols=2,
        show=True,
        font_scale=0.7,
        class_mapping=class_mapping,
    )

visualize_detections(yolo, dataset=val_ds, bounding_box_format="xyxy")

3、总结

本文介绍了如何使用 KerasCV 库来训练 YOLOv8 目标检测模型。KerasCV 是 Keras 的扩展,专为计算机视觉任务设计,提供了预训练模型、可视化工具以及对迁移学习的支持。

  • 环境设置与数据准备

首先,安装了 KerasCV 库,并导入了必要的 TensorFlow 和 KerasCV 模块。接着,定义了超参数,包括数据集分割比例、批量大小、学习率、训练周期数和全局剪切范数。为了处理 PASCAL VOC 数据集,编写了解析 XML 注释文件的函数,用于提取图像路径、边界框和类别 ID。

  • 数据处理

利用 TensorFlow 的 tf.data API 加载和处理数据,创建了不规则张量来处理边界框和类别标签的不同长度,并划分了训练集和验证集。

  • 数据增强

介绍了数据增强的重要性,并展示了 KerasCV 提供的数据增强层,这些层能够智能调整边界框坐标,以适应图像变换。

  • 模型构建

详细描述了 YOLOv8 模型的不同变体(YOLOv8n、YOLOv8s、YOLOv8m、YOLOv8l 和 YOLOv8x)的性能指标,包括平均精度均值(mAP)、推理速度、参数数量和浮点运算次数。然后,使用 KerasCV 的 YOLOV8Detector 类构建了 YOLOv8 模型,指定了类别数量、边界框格式、backbone 和 FPN 深度。

  • 模型编译与训练

讨论了用于 YOLOv8 的损失函数,包括分类损失和边界框损失,并编译了模型。使用自定义的回调函数 EvaluateCOCOMetricsCallback 训练模型,并在每个周期结束时评估 mAP 分数,还在 mAP 提高时保存模型。

  • 结果可视化

提供了可视化训练和验证数据集的函数,以及可视化模型预测结果的函数,这有助于理解模型性能和进行故障排除。

通过本文读者可以了解到如何从头开始使用 KerasCV 训练定制的 YOLOv8 目标检测模型,包括数据准备、增强、模型构建、编译、训练和结果可视化的完整流程。


网站公告

今日签到

点亮在社区的每一天
去签到