Bootstrap

PaddleClas学习2——使用PPLCNet模型对车辆朝向进行识别(python)

1. 配置PaddlePaddle,PaddleClas环境

安装:请先参考文档 环境准备 配置 PaddleClas 运行环境。

2. 准备数据

首先从VeRi数据集官网中申请并下载数据。

2.1 标注数据格式

参考:基于PP-Vehicle的交通监控分析系统
VeRi数据集标注了车辆的10种属性,其中10种车辆颜色,9种车型属性,具体如下:

# 车辆颜色
- "yellow"
- "orange"
- "green"
- "gray"
- "red"
- "blue"
- "white"
- "golden"
- "brown"
- "black"
# 车型
- "sedan"
- "suv"
- "van"
- "hatchback"
- "mpv"
- "pickup"
- "bus"
- "truck"
- "estate"

在标注文件中使用长度为19的序列来表示上述属性。
举例:
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]

前10位中,位序号0的值为1,表示车辆颜色为"yellow"。

后9位中,位序号11的值为1,表示车型为"suv"。
现在定义一种新的属性车辆朝向,包含forward、sideward、backward三个属性,因此要在上述19位标志的后面增加3位分别对应上面的3个方向。
新的属性定义变成了[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1].(括号内为新增属性位,最后一位为1,表示朝向为backward)

2.2 标注数据

(1)创建list_direction.txt,添加内容:

1 forward
2 sideward
3 backward

(2)修改train_label.xmltest_label.xml,添加directionID字段,如下:

在这里插入图片描述
其中directionID1表示车头朝前,2表示车辆水平,3表示车头朝后

(3)转换label,生成train_list.txt
可以在python终端中执行下面的命令,也可以将其写入一个文件,然后使用python convert.py的方式运行该文件。

import os
from xml.dom.minidom import parse

vehicleids = []

def convert_annotation(input_fp, output_fp, subdir):
    in_file = open(input_fp)
    list_file = open(output_fp, 'w')
    tree = parse(in_file)

    root = tree.documentElement

    for item in root.getElementsByTagName("Item"):  
        label = ['0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0']
        if item.hasAttribute("imageName"):
            name = item.getAttribute("imageName")
        if item.hasAttribute("vehicleID"):
            vehicleid = item.getAttribute("vehicleID")
            if vehicleid not in vehicleids :
                vehicleids.append(vehicleid)
            vid = vehicleids.index(vehicleid)
        if item.hasAttribute("colorID"):
            colorid = int (item.getAttribute("colorID"))
            label[colorid-1] = '1'
        if item.hasAttribute("typeID"):
            typeid = int (item.getAttribute("typeID"))
            label[typeid+9] = '1'
        if item.hasAttribute("directionID"):
            typeid = int (item.getAttribute("directionID"))
            label[typeid+18] = '1'
        label = ','.join(label)
        list_file.write(os.path.join(subdir, name)  + "\t" + label + "\n")

    list_file.close()

convert_annotation('train_label.xml', 'train_list.txt', 'image_train')  #imagename vehiclenum colorid typeid
convert_annotation('test_label.xml', 'test_list.txt', 'image_test')

执行上述命令后,VeRi目录中具有以下数据:

VeRi
├── image_train
│   ├── 0001_c001_00016450_0.jpg
│   ├── 0001_c001_00016460_0.jpg
│   ├── 0001_c001_00016470_0.jpg
...
├── image_test
│   ├── 0002_c002_00030600_0.jpg
│   ├── 0002_c002_00030605_1.jpg
│   ├── 0002_c002_00030615_1.jpg
...
...
├── train_list.txt
├── test_list.txt
├── train_label.xml
├── test_label.xml

其中train/test/分别为训练集和验证集。train_list.txttest_list.txt分别为训练集和验证集的转换后用于训练的标签文件。

3. 模型训练

官网: PULC 车辆属性识别模型
标注好的数据,放在PaddleClasdataset目录下,数据集目录名为VeRi

3.1 修改配置文件

ppcls/configs/PULC/vehicle_attribute/PPLCNet_x1_0.yaml
(1)训练属性种类

修改Arch下的class_num为22,对应上面最终属性总位数。

Arch:
  name: "PPLCNet_x1_0"
  pretrained: True
  use_ssld: True
  class_num: 22           #属性种类数量

(2)训练数据路径

修改DataLoaderTrainEval下的 image_rootcls_label_path。分别表示训练数据跟路径和标注文件路径。

需要注意的是标注文件train_list.txt中的图片路径是相对于image_root目录的相对路径。

DataLoader:
  Train:
    dataset:
      name: MultiLabelDataset
      image_root: "dataset/VeRi/"                     # the root path of training images
      cls_label_path: "dataset/VeRi/train_list.txt"   # the location of the training list file
      label_ratio: True
      transform_ops:
        ...
  Eval:
    dataset:
      name: MultiLabelDataset
      image_root: "dataset/VeRi/"                     # the root path of evaluation images
      cls_label_path: "dataset/VeRi/val_list.txt"     # the location of the evaluation list file
      label_ratio: True
      transform_ops:
         ...

3.2 训练、评估

上述准备工作完成后,可以执行以下命令进行训练:

python tools/train.py -c ./ppcls/configs/PULC/vehicle_attribute/PPLCNet_x1_0.yaml

训练完成后可以执行以下命令进行性能评估:

python tools/eval.py -c ./ppcls/configs/PULC/vehicle_attribute/PPLCNet_x1_0.yaml \
        -o Global.pretrained_model=./output/PPLCNet_x1_0/best_model

3.3 模型导出

python3 tools/export_model.py \
    -c ./ppcls/configs/PULC/vehicle_attribute/PPLCNet_x1_0.yaml \
    -o Global.pretrained_model=output/PPLCNet_x1_0/best_model \
    -o Global.save_inference_dir=deploy/models/PPLCNet_x1_0_vehicle_attribute_model

4 模型预测

修改后处理文件ppcls\data\postprocess\attr_rec.py

class VehicleAttribute(object):
    def __init__(self, color_threshold=0.5, type_threshold=0.5, direction_threshold=0.5):
        self.color_threshold = color_threshold
        self.type_threshold = type_threshold
        self.direction_threshold = direction_threshold
        self.color_list = [
            "yellow", "orange", "green", "gray", "red", "blue", "white",
            "golden", "brown", "black"
        ]
        self.type_list = [
            "sedan", "suv", "van", "hatchback", "mpv", "pickup", "bus",
            "truck", "estate"
        ]
        self.direction_list = [
            "forward", "sideward", "backward"]

    def __call__(self, x, file_names=None):
        ......
        for idx, res in enumerate(x):
            res = res.tolist()
            label_res = []
            color_idx = np.argmax(res[:10])
            type_idx = np.argmax(res[10:19])
            direction_idx = np.argmax(res[19:])

            print(color_idx, type_idx)
            if res[color_idx] >= self.color_threshold:
                color_info = f"Color: ({self.color_list[color_idx]}, prob: {res[color_idx]})"
            else:
                color_info = "Color unknown"

            if res[type_idx + 10] >= self.type_threshold:
                type_info = f"Type: ({self.type_list[type_idx]}, prob: {res[type_idx + 10]})"
            else:
                type_info = "Type unknown"
            
            if res[direction_idx + 19] >= self.direction_threshold:
                direction_info = f"Direction: ({self.direction_list[direction_idx]}, prob: {res[direction_idx + 19]})"
            else:
                direction_info = "Direction unknown"
            label_res = f"{color_info}, {type_info}, {direction_info}"

            threshold_list = [self.color_threshold
                              ] * 10 + [self.type_threshold] * 9 + [self.direction_threshold] * 3
            pred_res = (np.array(res) > np.array(threshold_list)
                        ).astype(np.int8).tolist()
            batch_res.append({
                "attr": label_res,
                "pred": pred_res,
                "file_name": file_names[idx]
            })
        return batch_res

执行以下命令进行预测:

python tools/infer.py -c ./ppcls/configs/PULC/vehicle_attribute/PPLCNet_x1_0.yaml -o Global.pretrained_model=output/PPLCNet_x1_0/best_model

输出结果如下:

[{'attr': 
'Color: (yellow, prob: 0.9991264939308167), 
Type: (hatchback, prob: 0.99992835521698), 
Direction: (backward, prob: 0.9999992847442627)', 
'pred': [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1], 'file_name': './deploy/images/PULC/vehicle_attribute/0002_c002_00030670_0.jpg'}]

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;