YOLOX は高精度の車両検出をトレーニングします

序文

最近、プロジェクトで高精度の車両検出モデルを使用する必要があり、最初は yolov5 または yolox の coco 事前学習済みモデルを直接使用することを考えましたが、実際のシーンでは精度があまり良くないことがわかりました。 COCO データセットには 80 クラスあります。さらに、データセットには間違ったラベルがたくさんあります。無料でシーンに直接入ると、多くの誤検出や検出漏れが発生します。そのため、専用のモデルをトレーニングする必要があります。この記事では、トレーニングのプロセスを記録します。モデルのトレーニングには yolov5 または yolox を選択できます。どちらも非常に優れたパフォーマンスを備えたアルゴリズムです。この記事ではトレーニングに yolox を使用します。

1. データセット分析

当初はココのデータセットから乗用車、バス、トラックの3カテゴリを直接抽出して直接学習用の車両データセットにしようと考えていましたが、車両データセットを分離してみると理想的なデータではないことが分かり、データが間違っているので、諦めてください。次に、車両検出に関する公開されているデータ セットに目を向けました。私のビジネス シナリオは頭上監視映像に偏っていたため、トレーニングに VisDrone データ セットを使用することにしました。VisDrone データ セットは、ドローンのデータ セットに基づく検出および追跡データ セットです。おおよそのパースは以下のとおりです:
ここに画像の説明を挿入します
このようなシーンの方がニーズに合っていて、モニタリング時は基本的に俯瞰視点になりますが、他のシーンでの利用も考慮して、 VisDroneデータセット 上記にKITTIデータセットを追加 KITTIデータセットは自動運転の観点からの物体検知データセット 検知対象には歩行者や車両等が含まれるため、
ここに画像の説明を挿入します
この2つのシーンのデータココ データ セットから分離された選択された画像は、基本的にすべての車両検出シナリオを満たすことができます。上記のシナリオ データ セットがまだ満足できない場合は、さらにデータ セットを追加できます。

歩行者検知データセットの概要 1

歩行者および車両検出データセットの概要 2

関連するデータセットとコードは Baidu Cloud で提供されており、必要な友人は自分でダウンロードできます。

リンク: https://pan.baidu.com/s/1k-f61kiOiMA8yf-tqgV4GA?pwd=28hw
抽出コード: 28hw

2. データセットの準備

上記 2 種類のデータ セットの注釈形式は、私たちがよく知っている COCO 形式または VOC 形式ではないため、ダウンロード後、注釈を解析して、関連する VOC 形式の注釈ラベルを取得する必要があります。まず、2 つのデータ セットをダウンロードします。 VisDrone データ形式ラベルは txt ファイルであるため、使い慣れた VOC 形式に変換するコードが必要です。変換コードは次のとおりです。

"""
该脚本用于visdrone数据处理;
将annatations文件夹中的txt标签文件转换为XML文件;
txt标签内容为:
<bbox_left>,<bbox_top>,<bbox_width>,<bbox_height>,<score>,<object_category>,<truncation>,<occlusion>
类别:
ignored regions(0), pedestrian(1),
people(2), bicycle(3), car(4), van(5),
truck(6), tricycle(7), awning-tricycle(8),
bus(9), motor(10), others(11)
"""

import os
import cv2
import time
from xml.dom import minidom

name_dict = {
    
    '0': 'ignored regions', '1': 'pedestrian', '2': 'people',
             '3': 'bicycle', '4': 'car', '5': 'van', '6': 'truck',
             '7': 'tricycle', '8': 'awning-tricycle', '9': 'bus',
             '10': 'motor', '11': 'others'}


def transfer_to_xml(pic, txt, file_name):
    xml_save_path = r'VisDrone2019-DET-train\xml'            # 生成的xml文件存储的文件夹
    if not os.path.exists(xml_save_path):
        os.mkdir(xml_save_path)

    img = cv2.imread(pic)
    img_w = img.shape[1]
    img_h = img.shape[0]
    img_d = img.shape[2]
    doc = minidom.Document()

    annotation = doc.createElement("annotation")
    doc.appendChild(annotation)
    folder = doc.createElement('folder')
    folder.appendChild(doc.createTextNode('visdrone'))
    annotation.appendChild(folder)

    filename = doc.createElement('filename')
    filename.appendChild(doc.createTextNode(file_name))
    annotation.appendChild(filename)

    source = doc.createElement('source')
    database = doc.createElement('database')
    database.appendChild(doc.createTextNode("Unknown"))
    source.appendChild(database)

    annotation.appendChild(source)

    size = doc.createElement('size')
    width = doc.createElement('width')
    width.appendChild(doc.createTextNode(str(img_w)))
    size.appendChild(width)
    height = doc.createElement('height')
    height.appendChild(doc.createTextNode(str(img_h)))
    size.appendChild(height)
    depth = doc.createElement('depth')
    depth.appendChild(doc.createTextNode(str(img_d)))
    size.appendChild(depth)
    annotation.appendChild(size)

    segmented = doc.createElement('segmented')
    segmented.appendChild(doc.createTextNode("0"))
    annotation.appendChild(segmented)

    with open(txt, 'r') as f:
        lines = [f.readlines()]
        for line in lines:
            for boxes in line:
                box = boxes.strip('\n')
                box = box.split(',')
                x_min = box[0]
                y_min = box[1]
                x_max = int(box[0]) + int(box[2])
                y_max = int(box[1]) + int(box[3])
                object_name = name_dict[box[5]]

                # if object_name is 'ignored regions' or 'others':
                #     continue

                object = doc.createElement('object')
                nm = doc.createElement('name')
                nm.appendChild(doc.createTextNode(object_name))
                object.appendChild(nm)
                pose = doc.createElement('pose')
                pose.appendChild(doc.createTextNode("Unspecified"))
                object.appendChild(pose)
                truncated = doc.createElement('truncated')
                truncated.appendChild(doc.createTextNode("1"))
                object.appendChild(truncated)
                difficult = doc.createElement('difficult')
                difficult.appendChild(doc.createTextNode("0"))
                object.appendChild(difficult)
                bndbox = doc.createElement('bndbox')
                xmin = doc.createElement('xmin')
                xmin.appendChild(doc.createTextNode(x_min))
                bndbox.appendChild(xmin)
                ymin = doc.createElement('ymin')
                ymin.appendChild(doc.createTextNode(y_min))
                bndbox.appendChild(ymin)
                xmax = doc.createElement('xmax')
                xmax.appendChild(doc.createTextNode(str(x_max)))
                bndbox.appendChild(xmax)
                ymax = doc.createElement('ymax')
                ymax.appendChild(doc.createTextNode(str(y_max)))
                bndbox.appendChild(ymax)
                object.appendChild(bndbox)
                annotation.appendChild(object)
                with open(os.path.join(xml_save_path, file_name + '.xml'), 'w') as x:
                    x.write(doc.toprettyxml())
                x.close()
    f.close()

if __name__ == '__main__':
    t = time.time()
    print('Transfer .txt to .xml...ing....')
    txt_folder = r'VisDrone2019-DET-train\annotations'  # visdrone txt标签文件夹
    txt_file = os.listdir(txt_folder)
    img_folder = r'VisDrone2019-DET-train\images'  # visdrone 照片所在文件夹

    for txt in txt_file:
        txt_full_path = os.path.join(txt_folder, txt)
        img_full_path = os.path.join(img_folder, txt.split('.')[0] + '.jpg')

        try:
            transfer_to_xml(img_full_path, txt_full_path, txt.split('.')[0])
        except Exception as e:
            print(e)

    print("Transfer .txt to .XML sucessed. costed: {:.3f}s...".format(time.time() - t))

変換された XML アノテーション ファイルには 12 のクラスがありますが、トレーニング用に車のクラスだけを抽出したいと考えています。VisDrone の車のクラスは車、バス、トラック、バンなので、次のコードを使用してカテゴリを抽出し、新しいカテゴリを再生成します。データセット:

# VOC数据集提取某个类或者某些类
# !/usr/bin/env python
# -*- encoding: utf-8 -*-
import os
import xml.etree.ElementTree as ET
import shutil

# 根据自己的情况修改相应的路径
ann_filepath = r'Annotations/'
img_filepath = r'JPEGImages/'
img_savepath = r'imgs/'
ann_savepath = r'xmls/'
if not os.path.exists(img_savepath):
    os.mkdir(img_savepath)

if not os.path.exists(ann_savepath):
    os.mkdir(ann_savepath)

# 这是VOC数据集中所有类别
# classes = ['aeroplane', 'bicycle', 'bird', 'boat', 'bottle',
#             'bus', 'car', 'cat', 'chair', 'cow', 'diningtable',
#              'dog', 'horse', 'motorbike', 'pottedplant',
#           'sheep', 'sofa', 'train', 'person','tvmonitor']

classes = ['car','bus','truck','van']  # 这里是需要提取的类别

def save_annotation(file):
    tree = ET.parse(ann_filepath + '/' + file)
    root = tree.getroot()
    result = root.findall("object")
    bool_num = 0
    for obj in result:
        if obj.find("name").text not in classes:
            root.remove(obj)
        else:
            bool_num = 1
    if bool_num:
        tree.write(ann_savepath + file)
        return True
    else:
        return False

def save_images(file):
    name_img = img_filepath + os.path.splitext(file)[0] + ".png"
    shutil.copy(name_img, img_savepath)
    # 文本文件名自己定义,主要用于生成相应的训练或测试的txt文件
    with open('train.txt', 'a') as file_txt:
        file_txt.write(os.path.splitext(file)[0])
        file_txt.write("\n")
    return True


if __name__ == '__main__':
    for f in os.listdir(ann_filepath):
        print(f)
        if save_annotation(f):
            save_images(f)

次に、これらのカテゴリーを 1 つのクラスにマージし、car タグを統一して使用したいと考えています。カテゴリーの変更とマージのコードは次のとおりです。

#!/usr/bin/env python2
# -*- coding: utf-8 -*-

import os
import xml.etree.ElementTree as ET

origin_ann_dir = r'xmls_old/'  # 设置原始标签路径为 Annos
new_ann_dir = r'xmls_new/'  # 设置新标签路径 Annotations
for dirpaths, dirnames, filenames in os.walk(origin_ann_dir):  # os.walk游走遍历目录名
    for filename in filenames:
        print("process...")
        if os.path.isfile(r'%s%s' % (origin_ann_dir, filename)):  # 获取原始xml文件绝对路径,isfile()检测是否为文件 isdir检测是否为目录
            origin_ann_path = os.path.join(r'%s%s' % (origin_ann_dir, filename))  # 如果是,获取绝对路径(重复代码)
            new_ann_path = os.path.join(r'%s%s' % (new_ann_dir, filename))
            tree = ET.parse(origin_ann_path)  # ET是一个xml文件解析库,ET.parse()打开xml文件。parse--"解析"
            root = tree.getroot()  # 获取根节点
            for object in root.findall('object'):  # 找到根节点下所有“object”节点
                name = str(object.find('name').text)  # 找到object节点下name子节点的值(字符串)
                if (name in ["car","bus","truck","van"]):
                    object.find('name').text = "car"

            tree.write(new_ann_path)  # tree为文件,write写入新的文件中。

この時点で、VisDrone データ セットが完成し、車クラスの XML アノテーション形式を 1 つだけ含むデータ セットが取得されます。次に、KITTI データ セットを見てください。KITTI データ セットのダウンロード リンクでは、この部分のみをダウンロードする必要があります: ダウンロード後、
ここに画像の説明を挿入します
ubuntu での解凍に問題が発生する可能性があります。パニックにならないでください。答えは、次のように検索すると見つかります。エラーレポートです。7zを使用しているようです。解凍するのを忘れたので、自分で検索してください。

KITTI データ セットのラベルも txt ファイルであるため、変換する必要があります。自動車クラスと歩行者クラスをマージし、最終的に Car、Pedestrian、Cyclist の 3 つのクラスのみを保持する必要があります。

# modify_annotations_txt.py
#将原来的8类物体转换为我们现在需要的3类:Car,Pedestrian,Cyclist。
#我们把原来的Car、Van、Truck,Tram合并为Car类,把原来的Pedestrian,Person(sit-ting)合并为现在的Pedestrian,原来的Cyclist这一类保持不变。
import glob
import string
txt_list = glob.glob('kitti/data_object_image_2/training/label/label_2/*.txt')
def show_category(txt_list):
    category_list= []
    for item in txt_list:
        try:
            with open(item) as tdf:
                for each_line in tdf:
                    labeldata = each_line.strip().split(' ') # 去掉前后多余的字符并把其分开
                    category_list.append(labeldata[0]) # 只要第一个字段,即类别
        except IOError as ioerr:
            print('File error:'+str(ioerr))
    print(set(category_list)) # 输出集合
def merge(line):
    each_line=''
    for i in range(len(line)):
        if i!= (len(line)-1):
            each_line=each_line+line[i]+' '
        else:
            each_line=each_line+line[i] # 最后一条字段后面不加空格
    each_line=each_line+'\n'
    return (each_line)
print('before modify categories are:\n')
show_category(txt_list)
for item in txt_list:
    new_txt=[]
    try:
        with open(item, 'r') as r_tdf:
            for each_line in r_tdf:
                labeldata = each_line.strip().split(' ')
                if labeldata[0] in ['Truck','Van','Tram']: # 合并汽车类
                    labeldata[0] = labeldata[0].replace(labeldata[0],'Car')
                if labeldata[0] == 'Person_sitting': # 合并行人类
                    labeldata[0] = labeldata[0].replace(labeldata[0],'Pedestrian')
                if labeldata[0] == 'DontCare': # 忽略Dontcare类
                    continue
                if labeldata[0] == 'Misc': # 忽略Misc类
                    continue
                new_txt.append(merge(labeldata)) # 重新写入新的txt文件
        with open(item,'w+') as w_tdf: # w+是打开原文件将内容删除,另写新内容进去
            for temp in new_txt:
                w_tdf.write(temp)
    except IOError as ioerr:
        print('File error:'+str(ioerr))
print('\nafter modify categories are:\n')
show_category(txt_list)

新しい txt ラベル ファイルを取得します。新しいラベルには次の 3 つのカテゴリのみが含まれており、それを XML 形式のラベルに変換します。

# kitti_txt_to_xml.py
# encoding:utf-8
# 根据一个给定的XML Schema,使用DOM树的形式从空白文件生成一个XML
from xml.dom.minidom import Document
import cv2
import os

def generate_xml(name,split_lines,img_size,class_ind):
    doc = Document() # 创建DOM文档对象
    annotation = doc.createElement('annotation')
    doc.appendChild(annotation)
    title = doc.createElement('folder')
    title_text = doc.createTextNode('KITTI')
    title.appendChild(title_text)
    annotation.appendChild(title)
    img_name=name+'.png'
    title = doc.createElement('filename')
    title_text = doc.createTextNode(img_name)
    title.appendChild(title_text)
    annotation.appendChild(title)
    source = doc.createElement('source')
    annotation.appendChild(source)
    title = doc.createElement('database')
    title_text = doc.createTextNode('The KITTI Database')
    title.appendChild(title_text)
    source.appendChild(title)
    title = doc.createElement('annotation')
    title_text = doc.createTextNode('KITTI')
    title.appendChild(title_text)
    source.appendChild(title)
    size = doc.createElement('size')
    annotation.appendChild(size)
    title = doc.createElement('width')
    title_text = doc.createTextNode(str(img_size[1]))
    title.appendChild(title_text)
    size.appendChild(title)
    title = doc.createElement('height')
    title_text = doc.createTextNode(str(img_size[0]))
    title.appendChild(title_text)
    size.appendChild(title)
    title = doc.createElement('depth')
    title_text = doc.createTextNode(str(img_size[2]))
    title.appendChild(title_text)
    size.appendChild(title)
    for split_line in split_lines:
        line=split_line.strip().split()
        if line[0] in class_ind:
            object = doc.createElement('object')
            annotation.appendChild(object)
            title = doc.createElement('name')
            title_text = doc.createTextNode(line[0])
            title.appendChild(title_text)
            object.appendChild(title)

            title = doc.createElement('pose')
            title_text = doc.createTextNode("Unspecified")
            title.appendChild(title_text)
            object.appendChild(title)

            title = doc.createElement('truncated')
            title_text = doc.createTextNode(str(0))
            title.appendChild(title_text)
            object.appendChild(title)

            title = doc.createElement('difficult')
            title_text = doc.createTextNode(str(0))
            title.appendChild(title_text)
            object.appendChild(title)

            bndbox = doc.createElement('bndbox')
            object.appendChild(bndbox)
            title = doc.createElement('xmin')
            title_text = doc.createTextNode(str(int(float(line[4]))))
            title.appendChild(title_text)
            bndbox.appendChild(title)
            title = doc.createElement('ymin')
            title_text = doc.createTextNode(str(int(float(line[5]))))
            title.appendChild(title_text)
            bndbox.appendChild(title)
            title = doc.createElement('xmax')
            title_text = doc.createTextNode(str(int(float(line[6]))))
            title.appendChild(title_text)
            bndbox.appendChild(title)
            title = doc.createElement('ymax')
            title_text = doc.createTextNode(str(int(float(line[7]))))
            title.appendChild(title_text)
            bndbox.appendChild(title)
    # 将DOM对象doc写入文件
    f = open('VOCdevkit/VOCKITTI/Annotations/'+name+'.xml','w')     # xml保存路径
    f.write(doc.toprettyxml(indent='\t'))
    f.close()

if __name__ == '__main__':

    class_ind=('Pedestrian', 'Car', 'Cyclist')
    txt = "kitti/training/label"        # 新的txt
    for file_name in os.listdir(txt):
        full_path=os.path.join(txt, file_name) # 获取文件全路径
        f=open(full_path)
        split_lines = f.readlines()
        name= file_name[:-4] # 后四位是扩展名.txt,只取前面的文件名
        img_name=name+'.png'
        img_path=os.path.join('VOCdevkit/VOCKITTI/JPEGImages',img_name)   # 图片路径
        img_size=cv2.imread(img_path).shape
        print(img_path)
        generate_xml(name,split_lines,img_size,class_ind)
    print('all txts has converted into xmls')

XML アノテーション形式を取得したら、VisDrone で処理したコードを使用して Car クラスを個別に抽出し、Car ラベルを car (大文字と小文字) に変更する必要があります。もちろん、これら 2 つの手順を完了することもできます。最後に、2 つのデータセットのデータを結合し、2 つのデータセットの XML ファイルを同じフォルダーに配置し、画像を同じフォルダーに配置するだけです。その後、トレーニングを開始できます。

3. モデルのトレーニング

yolox モデルのトレーニングについては、以前の記事「 YOLOX カスタム データセットのトレーニング」を参照してください。ここでは繰り返しません。トレーニング プロセスについて簡単に説明します。

  1. データセットを分割します。
  2. 対応する構成ファイルを変更します。
  3. トレーニングを開始します。

トレーニングには、tiny と nano の 2 つのモデルを使用しました。トレーニング プロセスは比較的スムーズでした。300 ラウンドにわたってトレーニングしました。これら 2 つのデータ セットはより難しいため、トレーニングされた AP はそれほど高くないかもしれませんが、それほど高くないことに注意してください。問題は、テスト後にわかります。

VisDrone で混合した KITTI データセット (1000 枚の画像) の一部のみをトレーニングに使用しましたが、KITTI データセットは VisDrone よりもシンプルであるため、すべてのデータを使用すると、効果は nano のトレーニング効果よりも優れている可能性があります。 tiny を使用すると高くなります。数パーセントですが、nano と tiny のトレーニング結果は次のとおりです。
ここに画像の説明を挿入します
ここに画像の説明を挿入します

4. モデルのテスト

自動車検出用に coco データセットでトレーニングされた nano と tiny の精度は比較的低いため、再トレーニングされた nano と COCO を使用して事前トレーニングされた s モデルを直接比較しました。入力サイズは両方とも 832 です。最初は、によってトレーニングされた s です。 COCO. モデル、次に再トレーニング後の車両の検出に使用される nano モデル。
最初の比較セットは次のとおりです。
ここに画像の説明を挿入します
ここに画像の説明を挿入します

2 番目の比較セット:
ここに画像の説明を挿入します
ここに画像の説明を挿入します
3 番目の比較セット:
ここに画像の説明を挿入します
ここに画像の説明を挿入します
モデル間の比較は、効果が依然として良好であることを示しています。テストするために、インターネット上で密集した車両の写真をランダムに見つけました
ここに画像の説明を挿入します
ここに画像の説明を挿入します
ここに画像の説明を挿入します
ここに画像の説明を挿入します
ここに画像の説明を挿入します
ここに画像の説明を挿入します
ここに画像の説明を挿入します

おすすめ

転載: blog.csdn.net/qq_39056987/article/details/125086156