使用Tensorflow Object Detection Api与与训练模型进行交通图像分类

写在前面

最近要参加一个比赛,是做图像识别的,简单说下数据集,这是一些交通图片,jpg格式,像素为1280*720,类似这样:
在这里插入图片描述
每个图片对应的标签数据是一个txt文件,内容长这样:
在这里插入图片描述
每行是5个数字,第一个数字是物体类别的编号,第二第三个数字是物体在图片中左上角点的位置,第四第五是物体在图片中右下角点的位置,这样不方便看,于是用ChatGPT写了一个小工具用来看这个训练数据和标签数据的情况,工具不放出来了,就是根据标签数据在图片上画框框:
在这里插入图片描述
这里心疼标注人员一秒钟

训练数据集分为白天的和晚上的,我目前只看了白天的数据,3万多张,先拿这个训练,其它的再说吧

我准备使用Tensorflow Object Detection Api中的预训练模型进行训练,一是自己没有用过,二是想看看效果,所以跟着各路教程搭建一下这个环境

目前已有的环境是:Win11,Anaconda Python 3.8.19, tensorflow=2.10, CUDA Toolkit=11.2, cuDNN=8.1, 显卡GeForce RTX 3080

来吧,开干

第一步 安装Tensorflow Object Detection API

这里先参照这篇文章进行安装,这篇文章前面还介绍了Anaconda和Tensorflow的安装过程,我没看,因为我装好了,所以也不知道写得好不好

1. 下载Tensorflow Object Detection API代码

首先第一步,下载Tensorflow Object Detection API代码,有git用git,没git从项目页面直接下载zip解压也可以

我在我的Python项目里建了一个TensorFlow文件夹,在里面克隆代码git clone https://github.com/tensorflow/models

目录结构如下:
在这里插入图片描述

2. 安装Protobuf

这个Protobuf是个编译工具,用来编译上一步里下载的代码,还是从git链接上下载zip包就可以,按照文档安装3.12.3版本,在Assets里选择protoc-3.12.3-win64.zip
在这里插入图片描述

下载之后解压,里面会有一个bin目录,这个目录添加到系统环境变量Path中即可
在这里插入图片描述

打开cmd,运行protoc --version出现版本号信息就表示安装好了
在这里插入图片描述

3. 编译代码

然后就用protoc去编译Tensorflow Object Detection Api里的代码

cmd进入Tensorflow Object Detection Api的models/research目录,文档上有这么一个提示框:

在这里插入图片描述
大概的意思是如果在Windows上使用3.5以上版本的Protobuf,需要执行下面这个命令,那说的不就是我吗,复制,粘贴,运行
在这里插入图片描述
也没个SUCCESS之类的提示,心慌,但愿没有消息就是好消息,继续

4. 安装COCO API

有关coco api的说明,可以参考(一) COCO Python API - 使用篇

大概是使用COCO数据集的API,COCO数据集是一种图片标注数据集,既然模型封装了,所以数据集也得处理成模型认识的格式,我们先装,后面再处理

注意这个东西依赖Visual C++ 2015以上版本进行编译,所以需要把Visual C++ 2015添加到环境变量里,文档给了安装包链接

进入conda,在对应的Python环境里安装cython和pycocotools两个包
pip install cython
pip install pycocotools,文档里从git上下载安装,我遇到了报错问题,就参考这个直接安装了

5. 安装Object Detection API依赖

cmd进入Tensorflow Object Detection Api代码目录,进入models/research文件夹,执行:

copy object_detection\packages\tf2\setup.py .
python -m pip install .

因为我用的是Windows,所以路径斜杠与Linux不同,使用的也是copy命令而不是cp命令

这个过程有点漫长,可以来一把轻松愉快的Warcraft III

吐槽一下人工智能这些个工具包,版本依赖问题太恶心了,装完这个装那个,各路大佬整理的资料都不一样,太难了

6. 测试安装效果

确保在models/research目录中,执行python object_detection/builders/model_builder_tf2_test.py

妈的果然就出错了
在这里插入图片描述
参考装一下protobuf库pip install protobuf==3.20.3,之前用的3.19版本有问题

在这里插入图片描述
测试的过程中GPU也是能跑起来的
在这里插入图片描述

好了,至此这个环境就算搭建好了,再顺便骂一遍这些个开发者,BUG满天飞!

第二步 转换数据集

这一步我没有成功!可以直接跳到第三步去

因为这是一个预训练模型,因此数据集的输入格式跟我的训练数据格式是不一样的,所以需要进行转换,这个地方我用了ChatGPT,以下步骤基本是在ChatGPT-4o指导下进行的

1. 编写代码把图片和标签数据转换为TFRecord格式

这里先说一下我的目录结构
在这里插入图片描述
白天的训练集在dataset/trainA目录下,images是图片数据,labels里面是标签数据,和文章最开头展示的一样

TFODAPI/Tensorflow目录是第一步里面我们搭建环境的那个目录

main.py是我的代码,内容是:

import os
import random
import tensorflow as tf
from PIL import Image
from sklearn.model_selection import train_test_split

# 设置随机种子以便结果可重复
random.seed(42)


def create_tf_example(image_path, label_path):
    with tf.io.gfile.GFile(image_path, 'rb') as fid:
        encoded_image = fid.read()

    image = Image.open(image_path)
    width, height = image.size

    with open(label_path, 'r') as file:
        lines = file.readlines()

    xmins = []
    xmaxs = []
    ymins = []
    ymaxs = []
    classes_text = []
    classes = []

    for line in lines:
        parts = line.strip().split()
        class_id = int(parts[0])
        xmin = float(parts[1]) / width
        ymin = float(parts[2]) / height
        xmax = float(parts[3]) / width
        ymax = float(parts[4]) / height

        xmins.append(xmin)
        xmaxs.append(xmax)
        ymins.append(ymin)
        ymaxs.append(ymax)
        classes_text.append(str(class_id).encode('utf8'))
        classes.append(class_id)

    tf_example = tf.train.Example(features=tf.train.Features(feature={
        'image/height': tf.train.Feature(int64_list=tf.train.Int64List(value=[height])),
        'image/width': tf.train.Feature(int64_list=tf.train.Int64List(value=[width])),
        'image/filename': tf.train.Feature(bytes_list=tf.train.BytesList(value=[image_path.encode('utf8')])),
        'image/source_id': tf.train.Feature(bytes_list=tf.train.BytesList(value=[image_path.encode('utf8')])),
        'image/encoded': tf.train.Feature(bytes_list=tf.train.BytesList(value=[encoded_image])),
        'image/format': tf.train.Feature(bytes_list=tf.train.BytesList(value=['jpg'.encode('utf8')])),
        'image/object/bbox/xmin': tf.train.Feature(float_list=tf.train.FloatList(value=xmins)),
        'image/object/bbox/xmax': tf.train.Feature(float_list=tf.train.FloatList(value=xmaxs)),
        'image/object/bbox/ymin': tf.train.Feature(float_list=tf.train.FloatList(value=ymins)),
        'image/object/bbox/ymax': tf.train.Feature(float_list=tf.train.FloatList(value=ymaxs)),
        'image/object/class/text': tf.train.Feature(bytes_list=tf.train.BytesList(value=classes_text)),
        'image/object/class/label': tf.train.Feature(int64_list=tf.train.Int64List(value=classes)),
    }))

    return tf_example


def create_tfrecord(output_path, image_label_pairs):
    writer = tf.io.TFRecordWriter(output_path)
    i = 0
    for image_path, label_path in image_label_pairs:
        tf_example = create_tf_example(image_path, label_path)
        writer.write(tf_example.SerializeToString())
        i += 1
        print(i)

    writer.close()

if __name__ == '__main__':

    # 获取所有图片和标签文件路径
    image_dir = '../dataset/trainA/train_dataset/images'
    label_dir = '../dataset/trainA/train_dataset/labels'

    image_files = [os.path.join(image_dir, f) for f in os.listdir(image_dir) if f.endswith('.jpg')]
    label_files = [os.path.join(label_dir, f.replace('.jpg', '.txt')) for f in os.listdir(image_dir) if f.endswith('.jpg')]

    # 分割训练集和验证集
    train_images, val_images, train_labels, val_labels = train_test_split(image_files, label_files, test_size=0.2,
                                                                          random_state=42)

    # 创建TFRecord文件
    create_tfrecord('train.tfrecord', zip(train_images, train_labels))
    # 创建验证集文件
    create_tfrecord('val.tfrecord', zip(val_images, val_labels))

这段代码实际上是读取images文件夹和labels文件夹里的文件,然后生成两个文件,分别是train.trfecordval.tfrecord,第一个是训练集数据,第二个是验证集数据

接下来写一个类别映射文件label_map.pbtxt,因为这个比赛图片中有9中类别,所以文件内容写的是

item {
  id: 0
  name: 'bus'
}

item {
  id: 1
  name: 'traffic_light'
}

item {
  id: 2
  name: 'traffic_sign'
}

item {
  id: 3
  name: 'person'
}

item {
  id: 4
  name: 'bike'
}

item {
  id: 5
  name: 'truck'
}

item {
  id: 6
  name: 'motor'
}

item {
  id: 7
  name: 'car'
}

item {
  id: 8
  name: 'rider'
}

在这里插入图片描述

2. 下载模型并修改配置文件

然后下载TensorFlow Object Detection API提供的与训练模型,因为我用的windows,所以直接从链接下载,下载完以后用7-Zip解压
在这里插入图片描述

把这个efficientdet_d0_coco17_tpu-32文件夹整个放在我的main.py同级目录下
在这里插入图片描述
然后修改pipeline.config中的内容
在这里插入图片描述
这里需要修改的都是路径,路径我是相对于pipeline.config文件来写的

3. 训练模型

这里遇到了非常多的他妈的坑,我猜都是GPT给我挖的

在conda里进入TFODAPI目录,这个目录里efficientdet_d0_coco17_tpu-32是模型,Tensorflow是第一步安装的Tensorflow Object Detection API

执行命令python TensorFlow\models\research\object_detection\model_main_tf2.py --pipeline_config_path=efficientdet_d0_coco17_tpu-32\pipeline.config --model_dir=efficientdet_d0_coco17_tpu-32\checkpoint --alsologtostderr

然后报错
在这里插入图片描述
这里看着是编码问题,我检查了pipeline.configlabel_map.pbtxt、标签txt数据,都是utf-8呀

看报错是label_map_util.py报错,所以我猜是label_map.pbtxt文件有问题,但是能有什么问题呢?

于是我做了两步修改,首先把label_map.pbtxt里面的id从1开始,而不再从0开始

item {
  id: 1
  name: 'bus'
}

item {
  id: 2
  name: 'traffic_light'
}

item {
  id: 3
  name: 'traffic_sign'
}

item {
  id: 4
  name: 'person'
}

item {
  id: 5
  name: 'bike'
}

item {
  id: 6
  name: 'truck'
}

item {
  id: 7
  name: 'motor'
}

item {
  id: 8
  name: 'car'
}

item {
  id: 9
  name: 'rider'
}

第二步修改pipeline.config中的路径为绝对路径,因为我实在不知道是相对的谁,如果是相对的model_main_tf2.py那真的是恶心死,所以先写绝对路径,随后再去尝试相对路径吧
在这里插入图片描述
再次运行python TensorFlow\models\research\object_detection\model_main_tf2.py --pipeline_config_path=efficientdet_d0_coco17_tpu-32\pipeline.config --model_dir=efficientdet_d0_coco17_tpu-32\checkpoint --alsologtostderr

报错更新
在这里插入图片描述
这是说检查点路径Checkpoint dir和模型路径model_dir不能相同,需要修改,但是这个至少说明pipeline.config文件是读出来了,所以之前的问题可能是读错文件了吧

解决上面的报错,在efficientdet_d0_coco17_tpu-32新建一个文件夹train_output,用做model_dir
在这里插入图片描述
修改指令为:python TensorFlow\models\research\object_detection\model_main_tf2.py --pipeline_config_path=efficientdet_d0_coco17_tpu-32\pipeline.config --model_dir=efficientdet_d0_coco17_tpu-32\train_output --alsologtostderr

训练过程中提示
在这里插入图片描述
毕竟他妈3w多张图片,光train.tfrecord文件就1.8个G
在这里插入图片描述
于是问ChatGPT咋办,它说是GPU内存不足导致的,先修改批处理大小,在pipeling.config中找到train_configbatch_size配置项,原先默认是128,按照GPT说的改成4先试试

在这里插入图片描述
重新运行前,需要先删掉train_output目录下自动生成的train文件夹

遇到新问题
在这里插入图片描述
问GPT,他不知道,在stackoverflow上找到一个帖子,有人回复说修改pineline.config里面的一个配置项可以解决
在这里插入图片描述
我也修改
在这里插入图片描述
删掉train_output目录下自动生成的train文件夹,重新运行训练命令

然后继续出错
在这里插入图片描述
我裂开了,我心好累,我决定不跟着GPT玩了,继续看文档去吧

第三步 跟着文档训练模型

人工智障GPT,跟着文档Train Custom Object Detector来玩吧,老老实实撸英文

文档里说,这并不像看上去那样简单

在这里插入图片描述
我们需要做这么6个步骤

1. 准备工作空间

首先,在第一步的TensorFlow里新建一个文件夹,名为addons,这个后面要放labelImg,然后新建一个workspace文件夹,在workspace目录下面新建一个training_demo文件夹,如图:
在这里插入图片描述
文档里说这个training_demo是一个训练文件夹,每个不同的训练任务(应当是指在不同数据集上)都应当有独立的目录,每个训练目录下面基本包含这么些个文件:
在这里插入图片描述
文档里给每个目录有较为详细的说明,我们也跟着创建
在这里插入图片描述

2. 准备数据集

2.1. 注释数据集

2.1.1. 安装LabelImg

直接在conda上用pip安装即可pip isntall labelImg,这是一个做图像标注的工具

直接在conda中敲命令labelImg查看安装效果
在这里插入图片描述
哇哦居然会打开一个小窗口

TensorFlowaddons目录下新建文件夹labelImg
在这里插入图片描述
然后文档让我们把图片放到images目录里,运行lableImg对图片进行标注,嗷!原来你是在教我标注啊,对不起我的数据是标注好的,我得看看怎么导入,我选择直接去问GPT

在这里插入图片描述
GPT说lableImg不支持导入txt文件,但是可以写一个脚本把txt文件转换成xml,以下是脚本

import os
import glob
from xml.etree.ElementTree import Element, SubElement, tostring
from xml.dom.minidom import parseString

def create_voc_xml(image_path, txt_path, output_path, img_width=1280, img_height=720):
    image_name = os.path.basename(image_path)
    txt_name = os.path.basename(txt_path).split('.')[0]

    root = Element('annotation')

    folder = SubElement(root, 'folder')
    folder.text = 'images'

    filename = SubElement(root, 'filename')
    filename.text = image_name

    path = SubElement(root, 'path')
    path.text = image_path

    source = SubElement(root, 'source')
    database = SubElement(source, 'database')
    database.text = 'Unknown'

    size = SubElement(root, 'size')
    width = SubElement(size, 'width')
    width.text = str(img_width)
    height = SubElement(size, 'height')
    height.text = str(img_height)
    depth = SubElement(size, 'depth')
    depth.text = '3'

    segmented = SubElement(root, 'segmented')
    segmented.text = '0'

    with open(txt_path, 'r') as f:
        for line in f:
            values = line.strip().split()
            class_id, xmin, ymin, xmax, ymax = map(int, values)

            obj = SubElement(root, 'object')
            name = SubElement(obj, 'name')
            name.text = str(class_id)  # Assuming class_id is used as class name
            pose = SubElement(obj, 'pose')
            pose.text = 'Unspecified'
            truncated = SubElement(obj, 'truncated')
            truncated.text = '0'
            difficult = SubElement(obj, 'difficult')
            difficult.text = '0'
            bndbox = SubElement(obj, 'bndbox')
            x1 = SubElement(bndbox, 'xmin')
            x1.text = str(xmin)
            y1 = SubElement(bndbox, 'ymin')
            y1.text = str(ymin)
            x2 = SubElement(bndbox, 'xmax')
            x2.text = str(xmax)
            y2 = SubElement(bndbox, 'ymax')
            y2.text = str(ymax)

    dom = parseString(tostring(root))
    with open(os.path.join(output_path, txt_name + '.xml'), 'w') as f:
        f.write(dom.toprettyxml(indent='\t'))

def convert_all(txt_dir, img_dir, output_dir):
    if not os.path.exists(output_dir):
        os.makedirs(output_dir)

    txt_files = glob.glob(os.path.join(txt_dir, '*.txt'))
    for txt_file in txt_files:
        img_file = os.path.join(img_dir, os.path.basename(txt_file).replace('.txt', '.jpg'))
        if os.path.exists(img_file):
            create_voc_xml(img_file, txt_file, output_dir)

if __name__ == "__main__":
    txt_directory = 'dataset/trainA/train_dataset/labels'  # 原txt格式标签路径
    img_directory = 'dataset/trainA/train_dataset/images'  # 训练图片文件夹路径
    output_directory = 'TFODAPI/TensorFlow/workspace/training_demo/images/train/'  # xml标签文件输出路径
    convert_all(txt_directory, img_directory, output_directory)

只需要去修改txt_directoryimg_directoryoutput_directory 这三个路径即可,路径是相对于这个py脚本的

脚本可以用,打开一个生成的xml文件,发现并不难看懂
在这里插入图片描述
由于这个Object Detection模型还能做动作识别,所以会有一些其它的标签,比如pose,应该是指动作,但是咱不管,咱们的标签数据没那么多信息,一个xml文件里会有很多个object标签,对应着原txt标签的一行数据

不过看文档的意思,他的图片文件和xml标签文件都是在同一个目录下的,所以我需要把数据集都放到training_demo/images/train目录下

复制完了,目录里面的文件长这样
在这里插入图片描述
在labelImg窗口中点击Open Dir,选择training_demo/images/train目录,就可以查看到标注好的数据了
在这里插入图片描述
可以点击Next Image查看下一张图,鼠标移动到图片上,可以看到被标注的物体有高亮显示,右侧会显示物体类别信息,还是很容易理解的

这个脚本GPT写的不错

2.2. 数据集分区

这里实际上就是划分训练集和测试集,防止过拟合,也可以在测试集上查看模型的效果,一般来说训练集和测试集的比例是9:1,由于我们的训练数据目前都在training_demo/images/train目录下,因此从里面随机挑选10%的数据放到training_demo/images/test目录即可,这里我们也可以写一个脚本来实现,还是让GPT来写脚本,这点小事他还是能干的
在这里插入图片描述
我在training_demo文件夹里新建了一个脚本

import os
import random
import shutil

def move_files_for_test(train_dir, test_dir, test_ratio=0.1):
    if not os.path.exists(test_dir):
        os.makedirs(test_dir)

    # 获取所有jpg文件和对应的xml文件
    images = [f for f in os.listdir(train_dir) if f.endswith('.jpg')]
    xmls = [f for f in os.listdir(train_dir) if f.endswith('.xml')]

    # 确保每张图片都有对应的xml文件
    paired_files = [(img, img.replace('.jpg', '.xml')) for img in images if img.replace('.jpg', '.xml') in xmls]

    # 打乱顺序并选择10%的文件
    random.shuffle(paired_files)
    num_test = int(len(paired_files) * test_ratio)
    test_files = paired_files[:num_test]

    # 移动文件到test目录
    for img, xml in test_files:
        shutil.move(os.path.join(train_dir, img), os.path.join(test_dir, img))
        shutil.move(os.path.join(train_dir, xml), os.path.join(test_dir, xml))

    print(f'Moved {num_test} images and their XML files to {test_dir}')

if __name__ == "__main__":
    train_directory = 'images/train'  # 替换为你的train文件夹路径
    test_directory = 'images/test'    # 替换为你的test文件夹路径

    move_files_for_test(train_directory, test_directory)

可以运行

在这里插入图片描述
这样我们就有了训练集和测试集

2.3. 创建标签映射

这里就是写一个.pbtxt文件,里面写着物体的类别,这个在第二步里实际上做过,这里的坑是id需要从1开始而不是从0开始,但是我的标签数据里的物体编号是从0开始,不晓得这里会不会遇到问题

training_demo/annotations目录下新建label_map.pbtxt文件,写入:

item {
  id: 1
  name: 'bus'
}

item {
  id: 2
  name: 'traffic_light'
}

item {
  id: 3
  name: 'traffic_sign'
}

item {
  id: 4
  name: 'person'
}

item {
  id: 5
  name: 'bike'
}

item {
  id: 6
  name: 'truck'
}

item {
  id: 7
  name: 'motor'
}

item {
  id: 8
  name: 'car'
}

item {
  id: 9
  name: 'rider'
}

2.4. 创建tfrecord数据集

这个tfrecord实际上就是这个Object Detection模型接受的输入数据格式,上面的labelImg标注的xml格式还是太大,这个tfrecord格式是个二进制文件,在第二步里也有涉及,当时使用GPT写的脚本把txt标签转换成了tfrecord格式,所以我觉得直接拿过来用就可以了,因为文档是写了个脚本把xml标签文件转换成了额tfrecord,这不就是中间商赚差价吗

但是因为我不清楚第二步里到底哪里有问题,所以决定还是跟着文档走,在TensorFlow文件夹中新建scripts/preprocessing文件夹,再新建一个generate_tfrecord.py文件,写入文档中的脚本,文档中也提供了脚本的下载,行数有点多我就不贴在这里了,脚本路径如下:
在这里插入图片描述

在conda中进入TensorFlow/scripts/preprocessing文件夹,执行脚本的格式为python generate_tfrecord.py -x [A]\train -l [B]\label_map.pbtxt -o [B]\train.record
其中[A]是图片路径,也就是images那个文件夹,[B]是annotations那个文件夹

首先创建训练集的tfrecord
python generate_tfrecord.py -x ..\..\workspace\training_demo\images\train -l ..\..\workspace\training_demo\annotations\label_map.pbtxt -o ..\..\workspace\training_demo\annotations\train.record

执行居然报错了
在这里插入图片描述
看了一下文档中的脚本,大致了解了问题所在

label_map.pbtxt的本意,是id是类别编号,而name是标签名,也就说我原本的txt标签数据中,第一个数字值的就是物体类别编号,而文档的意思是第一个应当不是一个数字,而是物体的类别,例如bike 120 230 560 460,而我的是8 120 230 560 460,所以,这样的话我的label_map.pbtxt中的类别英文就是凭空出现的,所以找不到Key

修改label_map.pbtxt内容为

item {
  id: 1
  name: '0'
}

item {
  id: 2
  name: '1'
}

item {
  id: 3
  name: '2'
}

item {
  id: 4
  name: '3'
}

item {
  id: 5
  name: '4'
}

item {
  id: 6
  name: '5'
}

item {
  id: 7
  name: '6'
}

item {
  id: 8
  name: '7'
}

item {
  id: 9
  name: '8'
}

这里id是label_map的编号,从1开始,name是我标签数据中的标签名,从0开始,按字符串写,如果想知道每个标签编号对应的物体类别,参照下面这个字典吧

LABEL_ID_OBJECT_MAPPING = {
    "0": "bus",
    "1": "traffic_light",
    "2": "traffic_sign",
    "3": "person",
    "4": "bike",
    "5": "truck",
    "6": "motor",
    "7": "car",
    "8": "rider"
}

再次运行生成tfrecord的脚本python generate_tfrecord.py -x ..\..\workspace\training_demo\images\train -l ..\..\workspace\training_demo\annotations\label_map.pbtxt -o ..\..\workspace\training_demo\annotations\train.record,就可以跑起来了,如果心慌的话可以适当在脚本中层架一些打印,告诉自己程序再跑,不然30000多文件真的跑好慢

生成完训练集的tfrecord文件后,再生成测试集的,执行python generate_tfrecord.py -x ..\..\workspace\training_demo\images\test -l ..\..\workspace\training_demo\annotations\label_map.pbtxt -o ..\..\workspace\training_demo\annotations\test.record
在这里插入图片描述
成功生成了train.recordtest.record两个文件,目录架构如图:
在这里插入图片描述

3. 训练准备工作

3.1. 下载预训练模型

从这里开始与文档稍有分歧,文档提到不会从头训练模型,而是采用预训练模型,文档采用的是SSD ResNet50 V1 FPN 640x640模型,而我准备继续用第二步中失败了的efficientdet_d0_coco17_tpu-32模型

首先从tensorflow的git上下载预训练模型,放到pre-trained-models目录中,那我就从链接中下载efficientdet_d0_coco17_tpu-32模型的tar包,用7-zip解压,复制到对应目录中,如图:
在这里插入图片描述
文档中说如果想搞多个预训练模型,都可以放在pre-trained-models目录中,一个模型一个文件夹即可

3.2. 修改pipeline.config

首先,在workspace目录中的models文件夹下面,新建一个和预训练模型同名的文件夹,并把pipeline.config复制过去,这个models目录存放的是我们自己训练的模型,如图
在这里插入图片描述
然后按照文档修改pipeline.config文件
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
跟着文档改完,明白了这里的相对路径都是相对于training_demo文件夹的

4. 训练模型

好激动,好激动

首先把TensorFlow/models/research/object_detection/model_main_tf2.py这个文件复制到training_demo文件夹
在这里插入图片描述
training_demo路径中执行命令python model_main_tf2.py --model_dir=models\efficientdet_d0_coco17_tpu-32 --pipeline_config_path=models\efficientdet_d0_coco17_tpu-32\pipeline.config,这里就是指定了模型路径和pipeline配置文件路径

好了,又报错了,这次是跟第二步中最后让我崩溃的报错一样
在这里插入图片描述
环境变量是有的,所以找不到文件的报错纯是在跟我扯淡

GPT说用CPU训练试试,于是在model_main_tf2.py的引用后面加入两行代码
在这里插入图片描述
再运行python model_main_tf2.py --model_dir=models\efficientdet_d0_coco17_tpu-32 --pipeline_config_path=models\efficientdet_d0_coco17_tpu-32\pipeline.config,嘿,跑起来了
在这里插入图片描述
只不过拿CPU跑的,GPU闲置严重,就这样训练了一晚上7个小时了还没有训练完
在这里插入图片描述
GPU的问题之后再解决吧,今天六一,陪娃陪一天,就让我的破i5继续超负荷吧

【六一晚上更新】

这样下去发现不是办法,用cpu训练从上面的信息里可以看到每步的耗时为2.7秒左右,然后我查看pipeline.config里面,总步数有30万步
在这里插入图片描述
那我得训练300000*2.7/60/60/24=9.375天,完蛋去吧!!这个问题必须解决

首先是问GPT,GPT说要么禁用JIT编译,要么修改GPU缓存策略,然而这两个解决办法都没卵用,我就步记录过程了

然后问谷歌,幸运的是在github上有人遇到了这个问题,这人遇到的问题跟我一模一样,环境变量都配置了,就是不行,回答问题的人也不知道问题原理是什么,但是在训练前执行export XLA_FLAGS=--xla_gpu_cuda_data_dir=<PATH_TO_CUDA>就好了,这里的<PATH_TO_CUDA>是CUDA的安装路径,实际上就是CUDA_DIR或者CUDA_PATH的路径,这个应当是安装完CUDA就自动配好的,如果是windows系统,把export命令改为set命令,需要注意的是这个指令并不会永久保存,所以每次重开conda窗口都需要重新输入,否则就是把这个配置写在环境变量中
在这里插入图片描述
在这里插入图片描述
不要忘记注释掉之前禁用GPU的那两行代码

执行了这个命令之后,再次运行训练指令python model_main_tf2.py --model_dir=models\efficientdet_d0_coco17_tpu-32 --pipeline_config_path=models\efficientdet_d0_coco17_tpu-32\pipeline.config

他妈的成功了成功了成功了!!!!!
在这里插入图片描述
我的3080动起来了!!
在这里插入图片描述
每步的耗时骤降至0.3秒左右,这样总步数30万的话,我需要训练300000*0.3/60/60/24≈1.04天

虽然还是很长的,不过总比10天强

那就先训练吧,等训练完了再看看模型效果好了!

【六月三日早上更新】

训练了一整天,终于训练完成了!
在这里插入图片描述
models/efficientdet_d0_coco17_tpu-32目录里多了不少文件
在这里插入图片描述
写到这里突然意识到,第二步里我完全跟着ChatGPT走,最终遇到的问题就是找不到GPU的问题,当时没有搜索到github上的issue,这么看来GPT还是很牛的,虽然第二步不了了之,但我还是保留,以记录我踩的坑

5. 评估模型

训练过程中会有一些指标被随时记录(默认每300秒一次),这些ckpt-*开头的文件都是

执行评估脚本来查看效果,在training_demo目录中执行python model_main_tf2. py --model_dir=models\efficientdet_d0_coco17_tpu-32 --pipeline_config_path=models\efficientdet_d0_coco17_tpu-32\pipeline.config --checkpoint_dir=models\efficientdet_d0_coco17_tpu-32

这个命令跟训练命令很像,但是这次models文件夹中有训练的过程文件了,因此执行起来就是在测试集上验证了
在这里插入图片描述
在这里插入图片描述
这个过程也是吃GPU的,
在这里插入图片描述
验证的速度还算快,几分钟,验证完成后会在模型文件夹中自动创建一个eval文件夹
在这里插入图片描述
然后用TensorBoard来查看训练过程

进入conda中的环境,在training_demo目录中执行tensorboard --logdir=models\efficientdet_d0_coco17_tpu-32,会显示一个地址
在这里插入图片描述
在浏览器输入这个地址就可以查看一个可视化的界面
在这里插入图片描述
导航到scalars标签页,可以查看模型在测试集上的各项评估指标
在这里插入图片描述
这个界面可能会因tensorflow版本不同而不同,至于怎么看,请查阅其它资料

6. 导出模型

首先把TensorFlow/models/research/object_detection/exporter_main_v2.py中的脚本复制到training_demo文件夹中
在这里插入图片描述
然后在training_demo目录中执行python exporter_main_v2.py --input_type image_tensor --pipeline_config_path models\efficientdet_d0_coco17_tpu-32\pipeline.config --trained_checkpoint_dir models\efficientdet_d0_coco17_tpu-32 --output_directory exported-models\my_model

这个指令实际上就是设置了这么几个参数

  • input_type,输入类型
  • pipeline_config_path,pipeline.config文件路径
  • trained_checkpoint_dir,训练模型检查点路径
  • output_directory,输出路径,这里最后的my_model是可以自己任意命名的

输出完成后目录结构如下:
在这里插入图片描述

7. 调用模型

最后就是调用模型去对其它图片做识别了,我这里直接问GPT,让它给写了个脚本

import tensorflow as tf
import numpy as np
import cv2
import matplotlib.pyplot as plt
from object_detection.utils import visualization_utils as viz_utils
from object_detection.utils import label_map_util

# 配置路径
saved_model_dir = 'exported-models/my_model/saved_model'
label_map_path = 'annotations/label_map.pbtxt'
image_test_id = 1504
image_path = f'images/test/train_A_{image_test_id}.jpg'

# 加载导出的模型
detect_fn = tf.saved_model.load(saved_model_dir)

# 加载标签映射
category_index = label_map_util.create_category_index_from_labelmap(label_map_path, use_display_name=True)

# 读取并处理输入图片
image_np = cv2.imread(image_path)
image_np = cv2.cvtColor(image_np, cv2.COLOR_BGR2RGB)
image_np = cv2.resize(image_np, (1280, 720))  # 确保图片大小为1280x720
image_np_expanded = np.expand_dims(image_np, axis=0)

# 将图片转换为TensorFlow张量
input_tensor = tf.convert_to_tensor(image_np_expanded, dtype=tf.uint8)

# 进行物体检测
detections = detect_fn(input_tensor)

# 提取检测结果
num_detections = int(detections.pop('num_detections'))
detections = {key: value[0, :num_detections].numpy() for key, value in detections.items()}
detections['num_detections'] = num_detections

# 检测到的类别ID和分数
detections['detection_classes'] = detections['detection_classes'].astype(np.int64)

# 可视化结果
label_id_offset = 1
image_np_with_detections = image_np.copy()

viz_utils.visualize_boxes_and_labels_on_image_array(
    image_np_with_detections,
    detections['detection_boxes'],
    detections['detection_classes'] + label_id_offset,
    detections['detection_scores'],
    category_index,
    use_normalized_coordinates=True,
    max_boxes_to_draw=200,
    min_score_thresh=.30,
    agnostic_mode=False)

# 保存检测结果图像
output_image = cv2.cvtColor(image_np_with_detections, cv2.COLOR_RGB2BGR)
output_image_path = f"my_test/train_A_{image_test_id}_mytest.jpg"
cv2.imwrite(output_image_path, output_image)

# 显示图片
# plt.figure(figsize=(12, 8))
# plt.imshow(image_np_with_detections)
# plt.show()

首先是在training_demo里建了一个名为my_test的文件夹,用来存放预测后的结果,只需修改image_test_id 这个参数为图片编号即可运行,我先是拿着一个测试集的数据查看效果
在这里插入图片描述
上面的图片是模型预测的结果,下面的图片是测试集和标注信息叠加的图片,可以看出来这个准确率还是可以的,至少该有的东西就都有了

剩下的工作就是写脚本,去按照比赛要求的格式输出物体了

实际上我这里只训练了白天的数据,还需要再去训练夜晚的数据,参照上面的步骤执行就可以了,至于如何融合两个模型,我等问问GPT,再写一篇博客吧

很欣慰,搞了一个周末终于是训练出来了,上班去了呜呜呜

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值