★★★ 本文源自AlStudio社区精品项目,【点击此处】查看更多精品内容 >>>

本项目是针对环形路口的车辆检测任务,环形路口的车流量大,行人和自行车流动频繁,深度学习技术在其中有十分广阔的应用前景。

模型整体情况如下:

模型名称mAP(0.50, 11point)
ppyoloe_plus_crn_x_80e_coco94.41%
ppyoloe_plus_crn_l_80e_coco93.76%
ppyoloe_plus_distill_x_distill_l95.59%

经过知识蒸馏,模型约有1.83%的涨点效果,最终我们模型的mAP(0.50, 11point)能够达到95.59%。

一、项目背景

当前,环形路口的车辆检测是交通监控中的一个重要问题。在环形路口,由于车流量大,行人和自行车流动频繁,很难对车辆进行准确的检测。传统的车辆检测方法主要依靠摄像头和视频分析技术,但是这些方法存在误检率高、计算量大等问题,难以满足实时性和高精度的要求。

为了解决这个问题,深度学习技术被广泛应用于车辆检测领域。深度学习可以通过模拟人类视觉系统的方式进行图像识别和特征提取,具有高效、准确、快速的优点。基于计算机视觉的车辆检测方法主要包括两种:基于图像特征的方法和基于深度学习的方法。

基于图像特征的方法主要包括HOG(方向梯度直方图)、SIFT(小波变换的自相似性特征)、LBP(边缘保留的位图)等方法。这些方法通过对图像的局部特征进行提取,可以有效地检测出车辆的位置和大小信息。然而,这些方法需要对图像进行预处理和特征提取,计算量和实时性都较差。

基于深度学习的方法则是利用神经网络模型对图像进行特征学习和分类。其中,最著名的是卷积神经网络(CNN)和循环神经网络(RNN)。CNN模型通过对图像进行卷积操作和池化操作,可以提取出图像的高级特征。RNN模型则可以对时间序列数据进行学习,可以对车辆的运动状态进行建模和预测。

在环形路口车辆检测中,由于车辆在环形路口中的行驶轨迹是由多个参数描述的,因此可以通过对车辆的运动状态进行建模和预测,从而实现对车辆的准确检测。基于深度学习的方法可以通过对图像进行特征提取和分类,实现对环形路口车辆检测的高效、准确和实时处理。

综上所述,深度学习技术在环形路口车辆检测中具有广泛的应用前景,可以有效地提高车辆检测的准确性和实时性,为交通监控领域的发展提供了新的思路和方法。

二、数据集简介

本项目数据集是无人机拍摄的西班牙环形交叉路口航拍图像。该数据集总共包含 246315 个实例:236850 辆汽车、4899 辆自行车、2262 辆卡车、1752 辆公共汽车和 552 个空环形交叉路口,采用 15474 张 1920x1080 像素的 JPG 图像。这些图像是从具有不同交通流的 8 个环形交叉路口中提取的。

Roundabout (scenes)FramesCarTruckCycleBusEmpty
1 (00001)1,99634,55804,22900
2 (00002)514743000157
3 (00003-00017)1,7954,82258000
4 (00018-00033)1,0276,6150000
5 (00034-00049)1,2612,2480550081
6 (00050-00052)2,0365,789562022692
7 (00053)1,99634,55804,22900
8 (00054)1,3441,7332220150222
Total15,474236,8502,2624,8991,752552

部分数据如下:

三、数据预处理

Step01: 解压数据集到/home/aistudio/work目录下。

!unzip /home/aistudio/data/data206266/archive.zip -d /home/aistudio/work/

Step02: 分开存放数据集中的正负样本。

运行以下代码块,我们可以看到该数据集存在552个负样本,PaddleDetection暂不支持负样本训练,因此我们需要将这些数据单独存放。

处理思路:

  1. 在/home/aistudio/work/original/original目录下新建两个文件夹Unlabeled和Empty_xml。
    • Unlabeled:用于存放负样本的图片。
    • Empty_xml:用于存放负样本对应的标注文件。
  2. 通过object字段是否存在判断是否是负样本。
%cd /home/aistudio/work/original/original
!mkdir Unlabeled
!mkdir Empty_xml
import os
import shutil
import xml.dom.minidom


def ReadXml(XMLPATH, IMGPATH):
    if os.path.exists(XMLPATH) is False:
        return None
    dom = xml.dom.minidom.parse(XMLPATH)
    root_ = dom.documentElement
    object_ = root_.getElementsByTagName('object')
    if len(object_) == 0:
        shutil.move(XMLPATH, "/home/aistudio/work/original/original/Empty_xml")
        shutil.move(IMGPATH, "/home/aistudio/work/original/original/Unlabeled")
        print(XMLPATH)
        print(IMGPATH)
    info = []
    for object_1 in object_:
        name = object_1.getElementsByTagName("name")[0].firstChild.data
        bndbox = object_1.getElementsByTagName("bndbox")[0]
        xmin = int(bndbox.getElementsByTagName("xmin")[0].firstChild.data)
        ymin = int(bndbox.getElementsByTagName("ymin")[0].firstChild.data)
        xmax = int(bndbox.getElementsByTagName("xmax")[0].firstChild.data)
        ymax = int(bndbox.getElementsByTagName("ymax")[0].firstChild.data)
        info.append([xmin, ymin, xmax, ymax, name])
    return info


XMLDir = "/home/aistudio/work/original/original/annotations"
IMGDir = "/home/aistudio/work/original/original/imgs"
LabelNum = {}

for root, dirs, files in os.walk(XMLDir):
    for file in files:
        if file[-3:] == 'xml':
            locations = ReadXml(root + "/" + file, IMGDir + "/" + file[:-3] + "jpg")
            if len(locations) not in LabelNum:
                LabelNum[len(locations)] = 1
            else:
                LabelNum[len(locations)] += 1

print(LabelNum)

Step03: 划分数据集。

首先安装PaddleX。

!pip install paddlex
!mv imgs/ JPEGImages/
!mv annotations/ Annotations/

然后通过split_dataset这个API按照0.9:0.1的比例划分训练集和验证集。

!paddlex --split_dataset --format VOC --dataset_dir /home/aistudio/work/original/original --val_value 0.1

四、代码实现

4.1 环境配置

首先,安装PaddleDetection。

# 克隆PaddleDetection仓库
%cd /home/aistudio/
!git clone https://gitee.com/PaddlePaddle/PaddleDetection.git

# 安装其他依赖
%cd PaddleDetection
!pip install -r requirements.txt --user

# 编译安装paddledet
!python setup.py install

然后,将数据集移动到/home/aistudio/PaddleDetection/dataset目录下。

!mv /home/aistudio/work/original/* /home/aistudio/PaddleDetection/dataset/

4.2 数据集分析

Step01: 标签类别数目分析

该数据集总共包含5个标签,各类标签的数量分别为:

  • vehicle: 236250
  • bus: 1752
  • truck: 2262
  • cycle: 4899
  • van: 600
import os
from unicodedata import name
import xml.etree.ElementTree as ET
import glob

def count_num(indir):
    # 提取xml文件列表
    os.chdir(indir)
    annotations = os.listdir('.')
    annotations = glob.glob(str(annotations) + '*.xml')

    dict = {} # 新建字典,用于存放各类标签名及其对应的数目
    for i, file in enumerate(annotations): # 遍历xml文件
       
        # actual parsing
        in_file = open(file, encoding = 'utf-8')
        tree = ET.parse(in_file)
        root = tree.getroot()

        # 遍历文件的所有标签
        for obj in root.iter('object'):
            name = obj.find('name').text
            if(name in dict.keys()): dict[name] += 1 # 如果标签不是第一次出现,则+1
            else: dict[name] = 1 # 如果标签是第一次出现,则将该标签名对应的value初始化为1

    # 打印结果
    print("各类标签的数量分别为:")
    for key in dict.keys(): 
        print(key + ': ' + str(dict[key]))            

indir='/home/aistudio/PaddleDetection/dataset/original/Annotations/'   # xml文件所在的目录
count_num(indir) # 调用函数统计各类标签数目

Step02: 图像尺寸分析

通过图像尺寸分析,我们可以看到该数据集图片的尺寸,均为[1920, 1080]。

import os
from unicodedata import name
import xml.etree.ElementTree as ET
import glob

def Image_size(indir):
    # 提取xml文件列表
    os.chdir(indir)
    annotations = os.listdir('.')
    annotations = glob.glob(str(annotations) + '*.xml')
    width_heights = []

    for i, file in enumerate(annotations): # 遍历xml文件
        # actual parsing
        in_file = open(file, encoding = 'utf-8')
        tree = ET.parse(in_file)
        root = tree.getroot()
        width = int(root.find('size').find('width').text)
        height = int(root.find('size').find('height').text)
        if [width, height] not in width_heights: width_heights.append([width, height])
    print("数据集中,有{}种不同的尺寸,分别是:".format(len(width_heights)))
    for item in width_heights:
        print(item)

indir='/home/aistudio/PaddleDetection/dataset/original/Annotations/'   # xml文件所在的目录
Image_size(indir)

Step03: 检测框高宽比分析

通过绘制检测框高宽比分布直方图反映当前检测框宽高比的分布情况。

import os
from unicodedata import name
import xml.etree.ElementTree as ET
import glob
import matplotlib.pyplot as plt

def ratio(indir):
    # 提取xml文件列表
    os.chdir(indir)
    annotations = os.listdir('.')
    annotations = glob.glob(str(annotations) + '*.xml')
    # count_0, count_1, count_2, count_3 = 0, 0, 0, 0 # 举反例,不要这么写
    count = [0 for i in range(20)]

    for i, file in enumerate(annotations): # 遍历xml文件
        # actual parsing
        in_file = open(file, encoding = 'utf-8')
        tree = ET.parse(in_file)
        root = tree.getroot()

        # 遍历文件的所有检测框
        for obj in root.iter('object'):
            xmin = obj.find('bndbox').find('xmin').text
            ymin = obj.find('bndbox').find('ymin').text
            xmax = obj.find('bndbox').find('xmax').text
            ymax = obj.find('bndbox').find('ymax').text
            Aspect_ratio = (int(ymax)-int(ymin)) / (int(xmax)-int(xmin))
            if int(Aspect_ratio/0.25) < 19:
                count[int(Aspect_ratio/0.25)] += 1
            else:
                count[-1] += 1
    sign = [0.25*i for i in range(20)]
    plt.bar(x=sign, height=count)
    plt.savefig("/home/aistudio/work/hw.png") 
    plt.show()
    print(count)

indir='/home/aistudio/PaddleDetection/dataset/original/Annotations/'   # xml文件所在的目录
ratio(indir)

结果如下:

4.3 模型选择

4.4 模型训练

执行以下指令训练PP-YOLOE+。

这里我们需要训练两个模型 PP-YOLOE+_x 和 PP-YOLOE+_l ,在任务中我们主要关注的是 PP-YOLOE+_l 模型,PP-YOLOE+_x 主要用于作为 Teacher Model 对 PP-YOLOE+_l 进行知识蒸馏。

PP-YOLOE+_x:

# 替换配置文件
!rm /home/aistudio/PaddleDetection/configs/ppyoloe/ppyoloe_plus_crn_x_80e_coco.yml
!rm /home/aistudio/PaddleDetection/configs/datasets/voc.yml
!rm /home/aistudio/PaddleDetection/configs/runtime.yml
!rm /home/aistudio/PaddleDetection/configs/ppyoloe/_base_/optimizer_80e.yml
!rm /home/aistudio/PaddleDetection/configs/ppyoloe/_base_/ppyoloe_plus_crn.yml
!rm /home/aistudio/PaddleDetection/configs/ppyoloe/_base_/ppyoloe_plus_reader.yml
!cp /home/aistudio/config/ppyoloe_plus_crn_x_80e_coco.yml /home/aistudio/PaddleDetection/configs/ppyoloe/
!cp /home/aistudio/config/voc.yml /home/aistudio/PaddleDetection/configs/datasets/
!cp /home/aistudio/config/runtime.yml /home/aistudio/PaddleDetection/configs/
!cp /home/aistudio/config/optimizer_80e.yml /home/aistudio/PaddleDetection/configs/ppyoloe/_base_/
!cp /home/aistudio/config/ppyoloe_plus_crn.yml /home/aistudio/PaddleDetection/configs/ppyoloe/_base_/
!cp /home/aistudio/config/ppyoloe_plus_reader.yml /home/aistudio/PaddleDetection/configs/ppyoloe/_base_/
%cd /home/aistudio/PaddleDetection
!python tools/train.py -c configs/ppyoloe/ppyoloe_plus_crn_x_80e_coco.yml --eval

PP-YOLOE+_l:

# 替换配置文件
!rm /home/aistudio/PaddleDetection/configs/ppyoloe/ppyoloe_plus_crn_l_80e_coco.yml
!cp /home/aistudio/config/ppyoloe_plus_crn_l_80e_coco.yml /home/aistudio/PaddleDetection/configs/ppyoloe/
!python tools/train.py -c configs/ppyoloe/ppyoloe_plus_crn_l_80e_coco.yml --eval

4.5 模型评估

执行以下命令在单个GPU上评估验证集。

PP-YOLOE+_x:

!python tools/eval.py -c configs/ppyoloe/ppyoloe_plus_crn_x_80e_coco.yml -o weights=output/ppyoloe_plus_crn_x_80e_coco/best_model.pdparams

模型的各项指标如下所示:

  • mAP(0.50, 11point) = 94.41%
  • Total sample number: 1492, average FPS: 18.123835279456372

PP-YOLOE+_l:

!python tools/eval.py -c configs/ppyoloe/ppyoloe_plus_crn_l_80e_coco.yml -o weights=output/ppyoloe_plus_crn_l_80e_coco/best_model.pdparams

模型的各项指标如下所示:

  • mAP(0.50, 11point) = 93.76%
  • Total sample number: 1492, average FPS: 18.653238786294843

4.6 知识蒸馏

知识蒸馏是一种模型压缩常见方法,指的是在teacher-student框架中,将复杂、学习能力强的网络(teacher)学到的特征表示"知识"蒸馏出来,传递给参数量小、学习能力弱的网络(student)。
在训练过程中,往往以最优化训练集的准确率作为训练目标,但真实目标其实应该是最优化模型的泛化能力。显然如果能直接以提升模型的泛化能力为目标进行训练是最好的,但这需要正确的关于泛化能力的信息,而这些信息通常不可用。如果我们使用由大型模型产生的所有类概率作为训练小模型的目标,就可以让小模型得到不输大模型的性能。这种把大模型的知识迁移到小模型的方式就是蒸馏。

PaddleDetection提供了对PPYOLOE+ 进行模型蒸馏的方案,结合了logits蒸馏和feature蒸馏。

  • logits蒸馏指的是在softmax时使用较高的温度系数,提升负标签的信息,然后使用Student和Teacher在高温softmax下logits的KL散度作为loss。
  • feature蒸馏指的是是强迫Student去学习Teacher某些中间层的特征,直接匹配中间的特征或学习特征之间的转换关系。

在这里我们将 PP-YOLOE+_x 作为 Teacher Model ,将 PP-YOLOE+_l 作为Student Model。我们可以运行以下代码块进行知识蒸馏。

# 替换配置文件
!rm /home/aistudio/PaddleDetection/configs/ppyoloe/distill/ppyoloe_plus_crn_l_80e_coco_distill.yml
!cp /home/aistudio/config/ppyoloe_plus_crn_l_80e_coco_distill.yml /home/aistudio/PaddleDetection/configs/ppyoloe/distill/
!rm /home/aistudio/PaddleDetection/configs/slim/distill/ppyoloe_plus_distill_x_distill_l.yml
!cp /home/aistudio/config/ppyoloe_plus_distill_x_distill_l.yml /home/aistudio/PaddleDetection/configs/slim/distill/
!python tools/train.py -c configs/ppyoloe/distill/ppyoloe_plus_crn_l_80e_coco_distill.yml --slim_config configs/slim/distill/ppyoloe_plus_distill_x_distill_l.yml

损失函数如图所示:

!python tools/eval.py -c configs/ppyoloe/distill/ppyoloe_plus_crn_l_80e_coco_distill.yml -o weights=output/ppyoloe_plus_distill_x_distill_l/best_model.pdparams

模型的各项指标如下所示:

  • mAP(0.50, 11point) = 95.59%
  • Total sample number: 1492, average FPS: 17.57711827893251
!python tools/infer.py -c configs/ppyoloe/distill/ppyoloe_plus_crn_l_80e_coco_distill.yml -o weights=output/ppyoloe_plus_distill_x_distill_l/best_model.pdparams --infer_dir=dataset/original/JPEGImages

部分可视化效果如下:

4.7 模型导出

PP-YOLOE+在GPU上部署或者速度测试需要通过tools/export_model.py导出模型。

!python tools/export_model.py -c configs/ppyoloe/ppyoloe_plus_crn_l_80e_coco.yml -o weights=output/ppyoloe_plus_distill_x_distill_l/best_model.pdparams

五、Gradio部署模型

Step01: 测试我们的推理代码是否存在错误。

import numpy as np
import cv2
from PIL import Image, ImageDraw

from paddle.inference import Config
from paddle.inference import create_predictor


def resize(img, target_size):
    if not isinstance(img, np.ndarray):
        raise TypeError('image type is not numpy.')
    im_shape = img.shape
    im_size_min = np.min(im_shape[0:2])
    im_size_max = np.max(im_shape[0:2])
    im_scale_x = float(target_size) / float(im_shape[1])
    im_scale_y = float(target_size) / float(im_shape[0])
    img = cv2.resize(img, None, None, fx=im_scale_x, fy=im_scale_y)
    return img


def normalize(img, mean, std):
    img = img / 255.0
    mean = np.array(mean)[np.newaxis, np.newaxis, :]
    std = np.array(std)[np.newaxis, np.newaxis, :]
    img -= mean
    img /= std
    return img


def preprocess(img, img_size):
    mean = [0., 0., 0.]
    std = [1, 1, 1]
    img = resize(img, img_size)
    img = img[:, :, ::-1].astype('float32') 
    img = normalize(img, mean, std)
    img = img.transpose((2, 0, 1))  
    img = img[np.newaxis, :]
    return img


def draw_box(img,result,threshold=0.5):
    for res in result:
        cat_id, score, bbox = res[0], res[1], res[2:]
        if score < threshold:
            continue
        xmin, ymin, xmax, ymax = int(bbox[0]),int(bbox[1]),int(bbox[2]),int(bbox[3])
        print('category id is {}, bbox is {}'.format(cat_id, bbox))
        if cat_id == 0:
            cv2.rectangle(img,(xmin,ymax),(xmax,ymin),(255,0,0),2)
        elif cat_id == 1:
            cv2.rectangle(img,(xmin,ymax),(xmax,ymin),(0,255,0),2)
        elif cat_id == 2:
            cv2.rectangle(img,(xmin,ymax),(xmax,ymin),(0,0,255),2)
        elif cat_id == 3:
            cv2.rectangle(img,(xmin,ymax),(xmax,ymin),(255,0,255),2)
        else:
            cv2.rectangle(img,(xmin,ymax),(xmax,ymin),(0,255,255),2)


def init_predictor(model_file, params_file):
    config = Config(model_file, params_file)
    config.enable_memory_optim()
    config.enable_use_gpu(1000, 0)
    predictor = create_predictor(config)

    return predictor


def run(predictor, img):
    input_names = predictor.get_input_names()
    for i, name in enumerate(input_names):
        input_tensor = predictor.get_input_handle(name)
        input_tensor.reshape(img[i].shape)
        input_tensor.copy_from_cpu(img[i].copy())

    predictor.run()

    results = []
    output_names = predictor.get_output_names()
    for i, name in enumerate(output_names):
        output_tensor = predictor.get_output_handle(name)
        output_data = output_tensor.copy_to_cpu()
        results.append(output_data)
    return results


if __name__ == '__main__':
    img_name = '/home/aistudio/work/demo.jpg'
    save_img_name = '/home/aistudio/work/infer.jpg'
    im_size = 640
    pred = init_predictor("/home/aistudio/launch/output_inference/ppyoloe_plus_crn_l_80e_coco/model.pdmodel", "/home/aistudio/launch/output_inference/ppyoloe_plus_crn_l_80e_coco/model.pdiparams")
    img = cv2.imread(img_name)
    data = preprocess(img, im_size)
    scale_factor = np.array([im_size * 1. / img.shape[0], im_size * 1. / img.shape[1]]).reshape((1, 2)).astype(np.float32)
    result = run(pred, [data, scale_factor])
    draw_box(img, result[0])
    cv2.imwrite(save_img_name, img)

Step02: 运行/home/aistudio/launch目录下的demo.gradio.py即可。

六、总结与提高

模型整体情况如下:

模型名称mAP(0.50, 11point)
ppyoloe_plus_crn_x_80e_coco94.41%
ppyoloe_plus_crn_l_80e_coco93.76%
ppyoloe_plus_distill_x_distill_l95.59%

经过知识蒸馏,模型约有1.83%的涨点效果,最终我们模型的mAP(0.50, 11point)能够达到95.59%。

注意:我们训练 ppyoloe_plus_crn_x_80e_coco 、 ppyoloe_plus_crn_l_80e_coco 和 ppyoloe_plus_distill_x_distill_l 需要在相同划分的数据集下进行。假设在训练 ppyoloe_plus_distill_x_distill_l 时我们重新通过paddlex划分数据集后,你在训练 ppyoloe_plus_distill_x_distill_l 模型时该图像可能属于训练集,但是训练 ppyoloe_plus_crn_x_80e_coco 和 ppyoloe_plus_crn_l_80e_coco 时它就可能属于验证集,这会影响我们判断蒸馏的效果。

此文章为搬运
原项目链接

Logo

学大模型,用大模型上飞桨星河社区!每天8点V100G算力免费领!免费领取ERNIE 4.0 100w Token >>>

更多推荐