行人属性识别(Pedestrian Attributes Recognition)或人类属性识别(Human Attributes Recognition),从预定义的属性列表中预测一组属性来描述行人的特征。
利用开源项目Rethinking_of_PAR开展行人属性的数据构建、训练及测试流程。
目录
一.数据构建
1.标注工具x-anylabeling
地址:https://github.com/CVHub520/X-AnyLabeling
X-AnyLabeling v2 版本引入了全新的多标签属性标注功能。首次推出的功能包括基于百度飞浆开源的 PULC 中的车辆属性(Vehicle Attribute)和行人属性(Person Attribute)模型。整体的用户界面(UI)设计以及标注范式遵循 CVAT 开源工具箱的标准,为用户提供更加一致和友好的体验。
(1)运行anylabeling/app.py
(2)配置页面
配置属性文件:点击Import->Import-Attributes-File,选择“assets/attributes”中的属性文件。
选择要处理的图片数据(点击File->Open Dir)及生成标签数据的存放位置(File->Change Output Dir)。
选中人像后,便可在右侧属性栏进行标注。
2.数据转换
x-anylabeling标注后的数据为json格式,后续训练所需的文件格式为txt格式,转换代码如下
import os
import json
import re
'''in_path为文件目录'''
in_path='D:/X-AnyLabeling-main/data/file/'
json_path=in_path+'jsons/'
label_path=in_path+'label.txt'
save_file=open(label_path,"w")
clas_name_select = ['女性', '手提包', '单肩包', '双肩包', '短袖', '长袖', '长裤', '短裤', '行李箱',
'上衣白色', '上衣黑色', '上衣蓝色', '上衣红色', '上衣绿色', '上衣黄色', '上衣紫色', '上衣粉色', '上衣灰色',
'下装白色', '下装黑色', '下装蓝色', '下装红色', '下装绿色', '下装黄色', '下装紫色', '下装粉色', '下装灰色',
'年龄儿童', '年龄成年', '长发', '短发',
'光头', '帽子', '下装长裙', '下装短裙', '墨镜', '口罩', '围巾']
print(len(clas_name_select))
# 根据数字的大小对文件名进行排序
json_datas=os.listdir(json_path)
sorted_json_datas=sorted(json_datas,key=lambda x :int(re.search(r'\d+',x).group()))
for data in sorted_json_datas:
print(json_path+data)
with open (json_path+data,"r",encoding="UTF-8") as f:
json_data=json.load(f)
img_name=data[:-5]+'.jpg'
labels=['0']*38
# print(json_data)
attris=json_data["shapes"][0]["attributes"]
for attri in attris:
if attris[attri] not in ["No","None"]:
if attris[attri]=="Yes":
index=clas_name_select.index(attri)
labels[index]='1'
else:
index = clas_name_select.index(attris[attri])
labels[index] = '1'
labels_str=','.join(labels)
save_file.write(img_name + " " + labels_str + "\n")
save_file.close()
二.训练
加入轻量化网络进行训练,以repvgg为例,加入mobilenet、shufflenet等网络配置的方法相同。
1.添加新网络repvgg
新网络repvgg与已有网络同样是卷积+全连接结构,构建时只需要替换backbone即可。
(1)在models/backbone中添加repvgg.py文件地址链接,包括:RepVGG_A0、RepVGG_A0_m。
(2)在models/model_factory.py中添加,RepVGG_A0和RepVGG_A0_m 网络最后层输出的对应通道数。
(3)在train.py中导入repvgg。
(4)修改yaml配置文件,添加repvgg_a0网络。
2.自定义数据集
pa100k数据集中包含了26种属性,从中选择其中18个属性进行训练,标注txt数据如下。
(1)生成训练所需的.pkl文件
根据dataset/pedes_attr/preprocess/format_pa100k.py编写format_mydata.py文件。
import os
import numpy as np
import random
import pickle
from easydict import EasyDict
from scipy.io import loadmat
np.random.seed(0)
random.seed(0)
classes_name = ['Female','AgeOver60','Age18-60','AgeLess18','Front','Side','Back','Hat','Glasses','HandBag','ShoulderBag','Backpack'
,'ShortSleeve','LongSleeve','LongCoat','Trousers','Shorts','Skirt&Dress'] # 属性类别
# 需要对新属性进行重排,属性重排后的索引顺序
group_order = [7, 8, 12, 13, 14, 15, 16, 17, 9, 10, 11, 1, 2, 3, 0, 4, 5, 6]
# clas_name = ['Hat','Glasses','ShortSleeve','LongSleeve','LongCoat','Trousers','Shorts','Skirt&Dress','HandBag','ShoulderBag','Backpack'
# ,'AgeOver60','Age18-60','AgeLess18','Female','Front','Side','Back']
def make_dir(path):
if os.path.exists(path):
pass
else:
os.mkdir(path)
def generate_data_description(save_dir, label_txt,reorder):
"""
create a dataset description file, which consists of images, labels
"""
image_name = []
image_label = []
file = open(label_txt,"r")
for line in file.readlines():
'''2023-07-14'''
name = line.split("\t")[0].split('/')[-1]
print('name:',name)
label = line.split('\t')[1].split('\n')[0].split(',')
label = list(map(int, label))
image_name.append(name)
image_label.append(label)
dataset = EasyDict()
dataset.description = 'pa100k'
dataset.reorder = 'group_order'
dataset.root = os.path.join(save_dir, 'data')
dataset.image_name = image_name
dataset.label = np.array(image_label)
dataset.attr_name = classes_name
dataset.label_idx = EasyDict()
dataset.label_idx.eval = list(range(len(classes_name)))
if reorder:
dataset.label_idx.eval = group_order
dataset.partition = EasyDict()
# 数据集数量划分,自己根据自己数据集来,这是pa100k的划分
dataset.partition.train = np.arange(0, 80000) # np.array(range(80000))
dataset.partition.val = np.arange(80000, 90000) # np.array(range(80000, 90000))
dataset.partition.test = np.arange(90000, 100000) # np.array(range(90000, 100000))
dataset.partition.trainval = np.arange(0, 90000) # np.array(range(90000))
dataset.weight_train = np.mean(dataset.label[dataset.partition.train], axis=0).astype(np.float32)
dataset.weight_trainval = np.mean(dataset.label[dataset.partition.trainval], axis=0).astype(np.float32)
with open(os.path.join(save_dir, 'dataset_all.pkl'), 'wb+') as f:
pickle.dump(dataset, f)
if __name__ == "__main__":
# save_dir = '/mnt/data1/jiajian/datasets/attribute/PA100k/'
# 数据集图片存放路径 MyData/data MyData/dataset_all.pkl
save_dir = 'C:/Rethinking_of_PAR-master/data/PA100k/MyData/'
label_txt = "C:/Rethinking_of_PAR-master/data/PA100k/MyData/label.txt"
# train.txt test.txt val.txt 合并成label.txt
generate_data_description(save_dir, label_txt,reorder=True)
(2)修改完成后需要到“dataset/pedes_attr/pedes.py”中添加自己数据集名字MyData。
在configs/pedes_baseline目录下新建mydata.yaml配置文件,内容根据pa100k.yaml修改,加入新的网络和数据集。
3.训练
指定数据集的yaml配置文件进行训练。
三.测试
测试代码如下。
import argparse
import json
import os
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
import pickle
from dataset.augmentation import get_transform
from dataset.multi_label.coco import COCO14
from metrics.pedestrian_metrics import get_pedestrian_metrics
from models.model_factory import build_backbone, build_classifier
import numpy as np
import torch
from torch.utils.data import DataLoader
from tqdm import tqdm
from PIL import Image
from configs import cfg, update_config
from dataset.pedes_attr.pedes import PedesAttr
from metrics.ml_metrics import get_map_metrics, get_multilabel_metrics
from models.base_block import FeatClassifier
# from models.model_factory import model_dict, classifier_dict
from tools.function import get_model_log_path, get_reload_weight
from tools.utils import set_seed, str2bool, time_str
from models.backbone import swin_transformer, resnet, bninception,repvgg
set_seed(605)
clas_name = ['帽子', '眼镜', '短袖', '长袖', '长外套', '长裤',
'短裤', '短裙或裙子', '手提包', '单肩包','双肩包', '大于60',
'18-60', '小于18', '女性', '朝前', '侧面', '朝后']
def main(cfg, args):
exp_dir = os.path.join('exp_result', cfg.DATASET.NAME)
model_dir, log_dir = get_model_log_path(exp_dir, cfg.NAME)
train_tsfm, valid_tsfm = get_transform(cfg)
print(valid_tsfm)
backbone, c_output = build_backbone(cfg.BACKBONE.TYPE, cfg.BACKBONE.MULTISCALE)
classifier = build_classifier(cfg.CLASSIFIER.NAME)(
nattr=18,
c_in=c_output,
bn=cfg.CLASSIFIER.BN,
pool=cfg.CLASSIFIER.POOLING,
scale =cfg.CLASSIFIER.SCALE
)
model = FeatClassifier(backbone, classifier)
if torch.cuda.is_available():
model = torch.nn.DataParallel(model).cuda()
# 修改此处的模型名字
model = get_reload_weight(model_dir, model, pth='best.pth')
model.eval()
with torch.no_grad():
for name in os.listdir(args.test_img):
print(name)
img = Image.open(os.path.join(args.test_img,name))
img = valid_tsfm(img).cuda()
img = img.view(1, *img.size())
valid_logits, attns = model(img)
valid_probs = torch.sigmoid(valid_logits[0]).cpu().numpy()
valid_probs = valid_probs[0]>0.5
res = []
for i,val in enumerate(valid_probs):
if val:
res.append(clas_name[i])
if i ==14 and val==False:
res.append("男性")
print(res)
def argument_parser():
parser = argparse.ArgumentParser(description="attribute recognition",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument(
"--test_img", help="test images", type=str,
default="./test_imgs",
)
parser.add_argument(
"--cfg", help="decide which cfg to use", type=str,
)
parser.add_argument("--debug", type=str2bool, default="true")
args = parser.parse_args()
return args
if __name__ == '__main__':
args = argument_parser()
update_config(cfg, args)
main(cfg, args)
部分结果。