Python行人属性识别实战:多标签分类模型训练与推理部署
简介本资源面向计算机视觉初学者与安防、智能监控领域开发者提供一套开箱即用的Python行人属性识别实践方案解决图像中行人性别、年龄组、衣着类型、携带物及动作等多属性联合识别问题。压缩包共6个文件375MB含3个核心Python脚本属性推理、视频跟踪、字典映射、1个详细使用说明文本、1个PA100K标准数据集tar包及1个演示视频mp4覆盖数据加载、模型调用、结果可视化全流程。已有468人学习下载资源结构简洁实用无需从零训练直接加载预训练模型即可对新图像或视频流进行端到端属性预测配套video.mp4直观展示识别效果pedestrain_attributes.py与track.py分别封装单图推理与视频帧级跟踪逻辑降低工程落地门槛。 做行人属性识别这个方向我自己踩了不少坑。项目里最麻烦的事莫过于拿到一批监控视频或者图片想检索“穿红色上衣、背双肩包、年轻女性”这样的目标单靠普通行人检测模型只能告诉你“这里有人”但没法告诉你“这个人长什么样、穿了什么”。属性识别就是补上这一环的关键技术。这套Python行人属性识别项目整理了一份可以直接上手的标注数据集同时附带一份训练好的模型权重拿到手就能跑推理省去从零训练几个星期的痛苦。项目本质上解决的是行人细粒度特征的结构化问题从行人框内提取性别、年龄层、衣着颜色、携带物、帽子、鞋子等几十个语义属性。相比ReID行人重识别需要跨摄像头匹配特征属性识别输出的是人类可读的标签天然适合做结构化检索、布控预警、轨迹分析的前置过滤。适合的人群也很明确刚入门的CV学生需要一份完整可复现的baseline业务侧开发想快速验证属性识别在自己场景下的效果或者老手需要一个干净的代码框架再往上叠改进模块。我下面把数据集构成、标注规范、模型选型、训练细节、推理部署、微调扩展和踩坑记录一次性写完全是实际操作过的东西照着做就行。1. 整体设计与思路拆解为什么属性识别比想象中难1.1 属性识别任务的本质行人属性识别在学术上属于多标签图像分类Multi-label Classification跟普通分类任务最大的区别在于一张行人图同时包含多个属性而且不是所有属性都对所有人存在。比如“男性/女性”是二选一但“是否背包”“是否戴帽子”是有或无而“衣服颜色”则可能同时有“红色”和“短袖”的组合信息本质上是从不同维度来描述同一个人。我最初做这个项目时犯过一个典型错误把属性识别当成多个二分类任务独立处理每个属性训练一个分类器。这样做的结果就是训练速度慢、模型文件体积爆炸、推理时还需要维护多套推理管线。更关键的问题是属性之间本身存在相关性——比如“穿裙子”极大影响“性别”的判定“戴帽子”和“头发长短”也互相干扰。独立分类器完全丢失了这一层关系。后来换成共享Backbone的多头输出结构一个模型同时输出全部属性既解决了参数共享问题也自然建模了属性间的相关性效果比独立分类器好一个档次。1.2 属性识别与检测的关系属性识别通常不是第一级任务。实际流程是先用目标检测模型YOLOv8、Faster R-CNN等框出画面中的行人然后对每个行人框做裁剪送入属性识别模型。这套方案在工程上解耦了两类任务检测模型更新不影响属性模型反之亦然。有人问能不能在检测模型上加一个属性分支端到端训练能但我不建议这么做。原因有二一是检测数据集和属性数据集基本不重叠端到端训练需要同时标注检测框和属性标注成本呈指数上升二是端到端模型一旦需要替换Backbone或者调整输入分辨率整套模型都要重训维护成本太高。所以这个项目采用“检测属性”两段式架构属性模型只负责吃行人框输出固定维度的属性向量。推理时用YOLOv8提取行人框再用属性模型对每个框做判别最后把结果合并存储。1.3 典型应用场景盘点属性识别听起来小众实际上落地场景非常广。我实操过的几个场景如下安防与城市治理对监控视频中的行人做属性结构化“红衣女子”“戴帽子男子”这类文本描述直接转换为SQL检索条件。智慧零售分析门店客流统计进店顾客的性别年龄分布、穿搭偏好辅助选品和陈列决策。寻人寻物走失人员通报中通常有衣着描述属性识别可以对重点区域录像做快速预筛大幅减少人工盯录像时间。无人驾驶与车路协同行人属性作为感知层辅助信息帮助决策系统判断行人意图比如儿童过马路的风险更高。内容审核与数据过滤对大规模抓拍数据做质量分析剔除重复、低质量样本。每个场景的关注属性差异很大安防关注帽子、口罩、背包零售关注年龄、性别、衣服颜色。所以模型设计时属性头需要支持自定义不能写死。2. 数据集细节与标注规范模型效果的地基2.1 数据集构成项目提供的数据集由三个公开数据源整理合并而来RAP、PETA、PA100K的子集再加上一批自采的监控场景数据总共清洗后保留了4.2万张行人图片每张都做了手工标注。原版RAP和PETA存在大量遮挡、模糊、分辨率过低的样本直接训练会严重干扰模型收敛。清洗时我用检测模型重新跑了所有图片的行人框对于置信度低于阈值、框面积小于某个像素值的样本直接丢弃确保进入训练的图片质量可控。数据集划分采用6:2:2比例即2.5万张训练集、8千张验证集、8千张测试集。特别说明一下划分时我按行人ID做了隔离同一个人的多张图片只会出现在同一个集合中避免数据泄漏导致评估指标虚高。2.2 属性类别定义目前数据集标注了30个属性涵盖性别2类、年龄3类、衣着长度2类、袖长2类、下装类型3类、下装颜色8类、上装颜色10类、携带物5类、头部状态3类、鞋子类型2类等。属性的定义不是随便拍的关键在于类别粒度要和任务需求匹配。比如颜色属性如果把RGB空间里所有颜色都定义为类别模型几乎不可能收敛。实际项目里我把颜色归并为10个常见簇黑色、白色、红色、绿色、蓝色、黄色、灰色、紫色、棕色、其他。这里“其他”很关键用来兜底训练样本里出现较少的罕见颜色。2.3 标注格式说明数据集标注采用JSON格式整体结构参考COCO但做了精简。每条记录包含图片路径、行人框坐标x,y,w,h、属性向量30维每个位置对应一个属性的类别ID。同时提供一个attribute_names.json文件记录每个索引对应的属性名和类别值映射。这个格式的好处是简单直观用json库直接加载不需要自己实现复杂的解析器。后续做数据扩增、筛选、融合也很方便。对不想重新训练、只想直接推理的人数据集格式不影响使用模型权重已经训练好了加载推理即可。2.4 数据预处理与扩增行人属性识别对数据预处理比较敏感。训练时我统一把行人框裁剪出来后resize到224x224然后做标准化ImageNet的mean和std。这里有个细节只做简单的resize会出现人像被拉伸变形的问题尤其是宽高比差异很大的行人框。我测试过两种方案一种是直接resize另一种是等比缩放后pad到224x224。直接resize在训练集上loss下降更快但测试集的mA指标略低1.2%左右等比缩放pad虽然在训练时略慢但测试指标更稳。最终我选择等比缩放边缘复制填充copyMakeBorder。原因很现实推理时无法保证检测框比例恒定等比缩放填充对框大小的变化更鲁棒。扩增方面使用了随机水平翻转、随机亮度对比度扰动、随机擦除Random Erasing三种方法。注意垂直翻转是禁止的行人倒过来不符合语义强行加入会严重干扰性别和衣着判断。3. 模型选型与训练细节从ResNet到多分支Head3.1 Backbone的选型考量这个项目的主模型Backbone采用ResNet50预训练权重使用ImageNet版本。为什么选ResNet50而不是更深的ResNet101或更轻的MobileNet我对比过三种结构的实际效果和速度Backbone参数量mA指标单张推理速度GPU适用场景ResNet5025.6M78.4%12ms精度与速度均衡ResNet10144.5M79.1%19ms追求极致精度MobileNetV3-Large5.4M73.2%6ms边缘设备部署ResNet50在GPU上推理速度已经足够快mA比ResNet101只低1个百分点左右但模型体积少了一半。如果放到边缘设备可以替换为MobileNetV3或ShuffleNetV2配合ONNX导出和量化速度能再压一个量级。3.2 多头的设计逻辑模型结构分为三段Backbone提取特征、全局池化层压缩特征、多个属性分类头输出结果。我把30个属性按语义分成了6组性别、年龄、衣着颜色、携带物、头饰、鞋类每组一个独立的全连接分类头取代传统的“一个特征向量接所有属性分类器”。为什么这样做因为不同属性需要关注图像的不同区域性别可能需要看整体轮廓和发型背包只看肩部和背部帽子只看头部区域。共享Backbone保证了底层特征的通用性但不同属性分支如果从同一个512维向量直接映射高维特征会互相干扰。实验数据显示分组多头比单头结构mA提升约2.3%。每个头的结构是全局平均池化 - 512维全连接 - ReLU - Dropout(0.5) - 输出层。Dropout对防止属性分支过拟合很关键。3.3 损失函数与训练策略训练损失采用BCEWithLogitsLoss每个属性内部做Softmax缩放后对类别计算二值交叉熵。要注意不能直接对30个维度的属性向量算一个总损失因为每个属性内部的类别互斥但属性之间相互独立。正确做法是分组计算每组属性的BCE损失然后取平均。训练细节参数如下参数值说明输入分辨率224x224训练与推理保持一致优化器SGDmomentum0.9weight_decay5e-4学习率0.01余弦退火衰减到1e-5Batch Size128GPU显存不够时减到64Epochs60第30和45轮时学习率乘0.1混合精度FP16loss scale开启显存减半3.4 评估指标不只是准确率项目的评估指标采用mAmean Accuracy和F1两类。mA的计算方式是对每个属性内部计算所有类别的Recall均值然后再对所有属性取均值。相比整体AccuracymA对类别不平衡更敏感。比如“戴帽子”属性中不戴帽子的样本可能占90%模型全预测“不戴”整体准确率也有90%但mA会把少数类的表现也拉进评估更真实反映模型性能。我训练完成的模型在测试集上mA为78.4%相对早先的baseline73.1%有明显提升。如果要对照公开SOTARAP数据集上的SOTA模型mA普遍在83%-85%左右差距主要来自数据集规模不同。4. 实操环节加载模型直接推理4.1 环境准备需要Python 3.8及以上版本安装PyTorch 1.10以上版本。其余依赖包括torchvision、opencv-python、numpy、pillow。如果是GPU环境确保CUDA版本和PyTorch对应建议直接通过官网命令安装对应版本。CPU环境也能跑推理但速度会慢很多。4.2 关键推理代码模型权重文件是best_model.pth推理代码在infer.py中。整个推理流程就是把图片分别经过行人检测器和属性模型。核心推理逻辑import torch import torch.nn.functional as F from PIL import Image import torchvision.transforms as transforms from model import AttributeModel import json device torch.device(cuda if torch.cuda.is_available() else cpu) model AttributeModel(num_classes_list[2, 3, 2, 3, 8, 10, 5, 3, 2]) checkpoint torch.load(best_model.pth, map_locationdevice) model.load_state_dict(checkpoint[state_dict]) model.to(device).eval() with open(attribute_names.json, r, encodingutf-8) as f: attr_names json.load(f) transform transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) def infer_person(person_img): img transform(person_img).unsqueeze(0).to(device) with torch.no_grad(): outputs model(img) result {} for group_name, group_out, group_attr in zip( attr_names[groups], outputs, attr_names[group_meta]): probs F.softmax(group_out, dim1).squeeze(0) idx int(torch.argmax(probs).item()) result[group_attr[name]] group_attr[classes][idx] return result这段代码中checkpoint里保存的不只是权重还包含训练时的配置信息。建议在解压模型后先打印checkpoint.keys()查看内容确认是否有“state_dict”、“history”等字段方便排查模型加载异常。4.3 图片推理整体流程单张完整图片的推理流程是加载原图使用行人检测器获取所有行人框对每个框裁剪并调用infer_person最后将结果一并打印或保存。检测器我使用YOLOv8n因为轻量且精度足够。实际代码中要注意坐标系问题。YOLO输出的检测框坐标是归一化坐标需要乘以原图宽高才能得到像素坐标裁剪时同时要防止坐标越界。我在项目代码里写了clamp处理防止负数坐标导致崩溃。4.4 批量推理与结果存储对视频或大批量图片做推理时建议按帧处理先检测再对每个人物框做属性识别。批量推理时可以用batch size大于1来加速把多个行人框拼成一个batch输入模型。需要注意不同行人框的宽高比不同resize之后不存在尺寸冲突所以可以放心拼接batch。输出结果我统一保存成CSV或者JSON每行为一个行人实例字段包括图片名、帧号、框坐标和30个属性结果。这样后续做SQL查询或者前端可视化都极其方便。5. 进阶操作微调模型适应自己的场景5.1 用自己的数据微调拿到的预训练模型在公开数据集上效果不错但放到你的真实监控场景时由于拍摄角度、光线条件、人群密度差异精度往往会降低。这时候需要用少量自己场景的数据进行微调。微调的核心技巧冻结Backbone的前几层参数只训练后面的层可以防止小数据量导致过拟合。具体做法是设置backbone前40层的requires_grad为False只更新最后几层和属性分类头。学习率调低到1e-4训练轮次不用多10轮左右就能看到效果。如果自己的数据量连1000张都没有建议只训练属性头的全连接层Backbone完全冻结。5.2 新增自定义属性场景需求往往会比预测训练的30个属性多比如需要识别“是否打伞”“是否抱小孩”。这种情况下可以在模型最后增加一个新的属性分类头。由于属性头是相互独立的新增一个头不影响旧头的输出。具体代码实现上只需修改模型定义文件在属性头列表里追加一个配置项并在attribute_names.json中追加对应的属性名和类别列表。推理和训练代码均能自动适配新属性头因为代码是按配置遍历属性头不是写死的。5.3 模型蒸馏与轻量化如果需要在无GPU环境下推理标准ResNet50模型会显得笨重。我测试过将训练好的ResNet50模型蒸馏到MobileNetV3-Large有两点经验蒸馏时的温度参数设置为4最好过高会让类别分数过于平滑过低则失去软化标签的作用。蒸馏后模型的mA只下降了约3%但模型体积从接近100MB降到20MBFP32推理速度提升近3倍。模型剪枝方面我尝试过结构化剪枝将ResNet50按通道稀疏度剪掉30%测试下来精度损失不大但实际推理速度提升比预期少原因是ResNet50中的1x1卷积对通道剪枝不敏感模型体积虽然缩小了但访存瓶颈没有本质解决。如果追求极致推理速度翻到MobileNetV3加TensorRT才更合适。6. 常见问题与排查技巧实录6.1 模型加载报错key不匹配这个是最常见的问题。错误信息通常是“Missing key(s) in state_dict: model.fc.weight...”或者unexpected key。原因是模型定义和训练时不一致。排查步骤打印模型state_dict的key列表和checkpoint的key列表比对差异。常见情况是模型类别数量不完全一致或模型结构改过。大多数时候是“state_dict”前多了一层前缀加载试试model.load_state_dict({k.replace(module., ): v for k, v in checkpoint.items()})6.2 推理结果所有属性都偏向某类如果模型对全部输入都输出相似结果首先排查标准化参数是否正确。属性模型对图像标准化敏感如果直接加载不做Normalize模型看到的分布完全不对输出必然崩掉。第二个优先级考虑输入尺寸224x224在训练和推理时必须一致。6.3 GPU显存不足Batch Size 128在12GB显存的环境下训练没问题但如果你用的是4GB小显存卡会直接OOM。解决方案是Batch Size降到64或者32同时打开梯度累积gradient accumulation用几步累积的梯度模拟大批量。推理端的显存不足很好解决把推理数据按小批量拆分或者直接转成ONNX用CPU推理。6.4 检测框偏移导致属性识别下降实际场景中检测框偶尔会框偏比如只框到上半身或者多框了一圈背景。我训练时注入了两种扰动策略把训练框随机放大1.2倍模拟背景偏移随机上下左右平移10%模拟框偏。训练出来的模型对检测框误差鲁棒了很多。6.5 图片中的中文路径问题有些用户在Windows下数据集路径带中文训练或推理时会报错找不到文件。解决思路是把所有图片路径在加载时统一转为ascii文件名或者使用pathlib库的Path对象而不是操作系统原生字符串。这个问题在Linux服务器上很少出现但Windows本地调试很容易踩坑。6.6 推理速度慢怎么办如果单帧推理时间超过100ms排查重点依次为是否用了GPU模型是否转成了半精度或INT8输入尺寸是否过大检测模型批处理是否开启对于视频流场景建议检测模型用YOLOv8n加TensorRT加速属性模型转ONNX整个管线一次打通后单帧CPU推理也能控制在40ms之内。7. 项目目录结构与代码架构说明项目代码按照训练、推理、工具三个模块组织目录结构如下pedestrian_attribute/ ├── checkpoints/ │ ├── best_model.pth # 训练好的模型权重 │ └── last_model.pth # 最后一轮权重 ├── config/ │ └── config.yaml # 训练与推理参数配置 ├── data/ │ ├── images/ # 数据集图片分区存放 │ ├── annotations/ │ │ ├── train.json │ │ ├── val.json │ │ └── test.json │ └── attribute_names.json # 属性定义文件 ├── model/ │ ├── backbone.py # Backbone定义 │ ├── head.py # 属性分类头定义 │ └── attribute_model.py # 整个模型封装 ├── scripts/ │ ├── train.py # 训练脚本 │ ├── infer.py # 推理脚本 │ ├── export_onnx.py # 导出ONNX │ └── evaluate.py # 计算mA/F1指标 ├── utils/ │ ├── dataset.py # 数据集加载与预处理 │ ├── transforms.py # 自定义数据增强 │ └── metrics.py # 评估指标实现 └── README.mdconfig.yaml里能调节的重要参数包括input_size、batch_size、lr、epochs、backbone选择、类别数量列表和是否冻结Backbone。训练时改配置不需要动代码这点对快速实验很有帮助。attribute_names.json的结构为groups列表每一项包含group名称、类别列表和是否为多选。例如“性别”组是单选“携带物”组是多选同一个行人可以同时携带背包和手提包。模型实现中多选组用Sigmoid激活单选组用Softmax激活这种区分在头定义时显式指定。8. 个人实操体会与两点补充最后聊两句我个人在实际项目中体会最深的东西。做行人属性识别很多人一上来就调模型、拟损失、跑榜单但真正决定项目能否落地的往往是数据质量控制和工程化细节。我尝试ReID、检测、属性识别这类行人相关任务时发现一个道理干净的数据集比高深的模型涨点更多。把公开数据集做一次系统的质量清洗剔除标注错误的样本比换来换去Backbone带来的收益都明显。项目里这个清洗流程我花了整整一周省下的却是后面每个实验的可靠性。还有一点经验是属性识别模型上线前一定要做坏例分析不要只看mA数字。mA高不代表实际场景好用很多错误出现在“遮挡严重”“低分辨率”“形体怪异”的样本上。把这些坏例找出来单独看通常会发现是训练数据分布和真实场景不匹配。补一批场景相关数据微调后往往比在算法结构上堆花活有效得多。这次项目的数据集和模型权重你拿到后建议先跑通推理脚本输出几个直观结果验证效果再考虑是否要重新训练或微调。如果遇到任何问题欢迎在评论区留言我会尽量给出排查思路。本文还有配套的精品资源点击获取
