pphuman_attribute.md 11.0 KB
Newer Older
1 2
简体中文 | [English](./pphuman_attribute_en.md)

W
wangguanzhong 已提交
3 4 5 6
# 属性识别任务二次开发

## 数据准备

7 8 9 10 11 12 13 14
### 数据格式

格式采用PA100K的属性标注格式,共有26位属性。

这26位属性的名称、位置、种类数量见下表。

| Attribute |  index    |  length   |
|:----------|:----------|:----------|
Z
zhiboniu 已提交
15 16 17 18 19 20 21 22 23
| 'Hat','Glasses'   |  [0, 1]   |  2    |
| 'ShortSleeve','LongSleeve','UpperStride','UpperLogo','UpperPlaid','UpperSplice'  | [2, 3, 4, 5, 6, 7]   |  6    |
| 'LowerStripe','LowerPattern','LongCoat','Trousers','Shorts','Skirt&Dress'   | [8, 9, 10, 11, 12, 13]   |  6    |
| 'boots'   | [14, ]   |  1   |
| 'HandBag','ShoulderBag','Backpack','HoldObjectsInFront'   | [15, 16, 17, 18]   | 4    |
| 'AgeOver60', 'Age18-60', 'AgeLess18'    | [19, 20, 21]    | 3    |
| 'Female'    | [22, ]    | 1    |
| 'Front','Side','Back'    | [23, 24, 25]    |  3    |

24 25 26

举例:

Z
zhiboniu 已提交
27 28 29
[0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0]

第一组,位置[0, 1]数值分别是[0, 1],表示'no hat'、'has glasses'。
30

Z
zhiboniu 已提交
31
第二组,位置[22, ]数值分别是[0, ], 表示gender属性是'male', 否则是'female'。
32

Z
zhiboniu 已提交
33
第三组,位置[23, 24, 25]数值分别是[0, 1, 0], 表示方向属性是侧面'side'。
34 35 36 37 38 39 40 41 42 43 44 45 46

其他组依次类推

### 数据标注

理解了上面`属性标注`格式的含义后,就可以进行数据标注的工作。其本质是:每张单人图建立一组26个长度的标注项,分别与26个位置的属性值对应。

举例:

对于一张原始图片,

1) 使用检测框,标注图片中每一个人的位置。

Z
zhiboniu 已提交
47
2) 每一个检测框(对应每一个人),包含一组26位的属性值数组,数组的每一位以0或1表示。对应上述26个属性。例如,如果图片是'Female',则数组第22位为0,如果满足'Age18-60',则位置[19, 20, 21]对应的数值是[0, 1, 0], 或者满足'AgeOver60',则相应数值为[1, 0, 0].
48 49 50

标注完成后利用检测框将每一个人截取成单人图,其图片与26位属性标注建立对应关系。也可先截成单人图再进行标注,效果相同。

Z
zhiboniu 已提交
51 52

## 模型训练
W
wangguanzhong 已提交
53

54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
数据标注完成后,就可以拿来做模型的训练,完成自定义模型的优化工作。

其主要有两步工作需要完成:1)将数据与标注数据整理成训练格式。2)修改配置文件开始训练。

### 训练数据格式

训练数据包括训练使用的图片和一个训练列表train.txt,其具体位置在训练配置中指定,其放置方式示例如下:
```
Attribute/
|-- data           训练图片文件夹
|   |-- 00001.jpg
|   |-- 00002.jpg
|   `-- 0000x.jpg
`-- train.txt      训练数据列表

```

train.txt文件内为所有训练图片名称(相对于根路径的文件路径)+ 26个标注值

其每一行表示一个人的图片和标注结果。其格式为:

```
00001.jpg    0,0,1,0,....
```

Z
zhiboniu 已提交
79
注意:1)图片与标注值之间是以Tab[\t]符号隔开, 2)标注值之间是以逗号[,]隔开。该格式不能错,否则解析失败。
80 81 82

### 修改配置开始训练

Z
zhiboniu 已提交
83
首先执行以下命令下载训练代码(更多环境问题请参考[Install_PaddleClas](https://github.com/PaddlePaddle/PaddleClas/blob/release/2.4/docs/en/installation/install_paddleclas_en.md)):
84

Z
zhiboniu 已提交
85 86 87 88 89
```shell
git clone https://github.com/PaddlePaddle/PaddleClas
```

需要在配置文件`PaddleClas/blob/develop/ppcls/configs/PULC/person_attribute/PPLCNet_x1_0.yaml`中,修改的配置项如下:
90 91

```
Z
zhiboniu 已提交
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
DataLoader:
  Train:
    dataset:
      name: MultiLabelDataset
      image_root: "dataset/pa100k/"                     #指定训练图片所在根路径
      cls_label_path: "dataset/pa100k/train_list.txt"   #指定训练列表文件位置
      label_ratio: True
      transform_ops:

  Eval:
    dataset:
      name: MultiLabelDataset
      image_root: "dataset/pa100k/"                     #指定评估图片所在根路径
      cls_label_path: "dataset/pa100k/val_list.txt"     #指定评估列表文件位置
      label_ratio: True
      transform_ops:
108 109
```
注意:
Z
zhiboniu 已提交
110 111
1. 这里image_root路径+train.txt中图片相对路径,对应图片的完整路径位置。
2. 如果有修改属性数量,则还需修改内容配置项中属性种类数量:
112
```
Z
zhiboniu 已提交
113 114 115 116 117 118
# model architecture
Arch:
  name: "PPLCNet_x1_0"
  pretrained: True
  use_ssld: True
  class_num: 26           #属性种类数量
119 120 121 122 123
```

然后运行以下命令开始训练。

```
Z
zhiboniu 已提交
124
#多卡训练
125 126 127 128 129
export CUDA_VISIBLE_DEVICES=0,1,2,3
python3 -m paddle.distributed.launch \
    --gpus="0,1,2,3" \
    tools/train.py \
        -c ./ppcls/configs/PULC/person_attribute/PPLCNet_x1_0.yaml
Z
zhiboniu 已提交
130 131 132 133

#单卡训练
python3 tools/train.py \
        -c ./ppcls/configs/PULC/person_attribute/PPLCNet_x1_0.yaml
134 135
```

Z
zhiboniu 已提交
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
训练完成后可以执行以下命令进行性能评估:
```
#多卡评估
export CUDA_VISIBLE_DEVICES=0,1,2,3
python3 -m paddle.distributed.launch \
    --gpus="0,1,2,3" \
    tools/eval.py \
        -c ./ppcls/configs/PULC/person_attribute/PPLCNet_x1_0.yaml \
        -o Global.pretrained_model=./output/PPLCNet_x1_0/best_model

#单卡评估
python3 tools/eval.py \
        -c ./ppcls/configs/PULC/person_attribute/PPLCNet_x1_0.yaml \
        -o Global.pretrained_model=./output/PPLCNet_x1_0/best_model
```

152 153 154 155 156 157 158
### 模型导出

使用下述命令将训练好的模型导出为预测部署模型。

```
python3 tools/export_model.py \
    -c ./ppcls/configs/PULC/person_attribute/PPLCNet_x1_0.yaml \
C
cuicheng01 已提交
159
    -o Global.pretrained_model=output/PPLCNet_x1_0/best_model \
160 161 162
    -o Global.save_inference_dir=deploy/models/PPLCNet_x1_0_person_attribute_infer
```

Z
zhiboniu 已提交
163
导出模型后,需要下载[infer_cfg.yml](https://bj.bcebos.com/v1/paddledet/models/pipeline/infer_cfg.yml)文件,并放置到导出的模型文件夹`PPLCNet_x1_0_person_attribute_infer`中。
164

Z
zhiboniu 已提交
165
使用时在PP-Human中的配置文件`./deploy/pipeline/config/infer_cfg_pphuman.yml`中修改新的模型路径`model_dir`项,并开启功能`enable: True`
166 167
```
ATTR:
Z
zhiboniu 已提交
168 169
  model_dir: [YOUR_DEPLOY_MODEL_DIR]/PPLCNet_x1_0_person_attribute_infer/   #新导出的模型路径位置
  enable: True                                                              #开启功能
170
```
Z
zhiboniu 已提交
171
然后可以使用-->至此即完成新增属性类别识别任务。
172 173 174 175 176

## 属性增减

上述是以26个属性为例的标注、训练过程。

Z
zhiboniu 已提交
177
如果需要增加、减少属性数量,则需要:
Z
zhiboniu 已提交
178

Z
zhiboniu 已提交
179
1)标注时需增加新属性类别信息或删减属性类别信息;
Z
zhiboniu 已提交
180

Z
zhiboniu 已提交
181
2)对应修改训练中train.txt所使用的属性数量和名称;
Z
zhiboniu 已提交
182

Z
zhiboniu 已提交
183 184 185
3)修改训练配置,例如``PaddleClas/blob/develop/ppcls/configs/PULC/person_attribute/PPLCNet_x1_0.yaml``文件中的属性数量,详细见上述`修改配置开始训练`部分。

增加属性示例:
186

Z
zhiboniu 已提交
187 188
1. 在标注数据时在26位后继续增加新的属性标注数值;
2. 在train.txt文件的标注数值中也增加新的属性数值。
Z
zhiboniu 已提交
189
3. 注意属性类型在train.txt中属性数值列表中的位置的对应关系需要时固定的,例如第[19, 20, 21]位表示年龄,所有图片都要使用[19, 20, 21]位置表示年龄,不再赘述。
190

Z
zhiboniu 已提交
191 192 193
<div width="500" align="center">
  <img src="../../images/add_attribute.png"/>
</div>
194

Z
zhiboniu 已提交
195
删减属性同理。
Z
zhiboniu 已提交
196
例如,如果不需要年龄属性,则位置[19, 20, 21]的数值可以去掉。只需在train.txt中标注的26个数字中全部删除第19-21位数值即可,同时标注数据时也不再需要标注这3位属性值。
Z
zhiboniu 已提交
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295

## 修改后处理代码

修改了属性定义后,pipeline后处理部分也需要做相应修改,主要影响结果可视化时的显示结果。

相应代码在路径`deploy/pipeline/pphuman/attr_infer.py`文件中`postprocess`函数。

其函数实现说明如下:

```
# 函数入口
    def postprocess(self, inputs, result):
        # postprocess output of predictor
        im_results = result['output']

# 1) 定义各组属性实际意义,其数量及位置与输出结果中占用位数一一对应。
        labels = self.pred_config.labels
        age_list = ['AgeLess18', 'Age18-60', 'AgeOver60']
        direct_list = ['Front', 'Side', 'Back']
        bag_list = ['HandBag', 'ShoulderBag', 'Backpack']
        upper_list = ['UpperStride', 'UpperLogo', 'UpperPlaid', 'UpperSplice']
        lower_list = [
            'LowerStripe', 'LowerPattern', 'LongCoat', 'Trousers', 'Shorts',
            'Skirt&Dress'
        ]
# 2) 部分属性所用阈值与通用值有明显区别,单独设置
        glasses_threshold = 0.3
        hold_threshold = 0.6

        batch_res = []
        for res in im_results:
            res = res.tolist()
            label_res = []
            # gender
# 3) 单个位置属性类别,判断该位置是否大于阈值,来分配二分类结果
            gender = 'Female' if res[22] > self.threshold else 'Male'
            label_res.append(gender)
            # age
# 4)多个位置属性类别,N选一形式,选择得分最高的属性
            age = age_list[np.argmax(res[19:22])]
            label_res.append(age)
            # direction
            direction = direct_list[np.argmax(res[23:])]
            label_res.append(direction)
            # glasses
            glasses = 'Glasses: '
            if res[1] > glasses_threshold:
                glasses += 'True'
            else:
                glasses += 'False'
            label_res.append(glasses)
            # hat
            hat = 'Hat: '
            if res[0] > self.threshold:
                hat += 'True'
            else:
                hat += 'False'
            label_res.append(hat)
            # hold obj
            hold_obj = 'HoldObjectsInFront: '
            if res[18] > hold_threshold:
                hold_obj += 'True'
            else:
                hold_obj += 'False'
            label_res.append(hold_obj)
            # bag
            bag = bag_list[np.argmax(res[15:18])]
            bag_score = res[15 + np.argmax(res[15:18])]
            bag_label = bag if bag_score > self.threshold else 'No bag'
            label_res.append(bag_label)
            # upper
# 5)同一类属性,分为两组(这里是款式和花色),每小组内单独选择,相当于两组不同属性。
            upper_label = 'Upper:'
            sleeve = 'LongSleeve' if res[3] > res[2] else 'ShortSleeve'
            upper_label += ' {}'.format(sleeve)
            upper_res = res[4:8]
            if np.max(upper_res) > self.threshold:
                upper_label += ' {}'.format(upper_list[np.argmax(upper_res)])
            label_res.append(upper_label)
            # lower
            lower_res = res[8:14]
            lower_label = 'Lower: '
            has_lower = False
            for i, l in enumerate(lower_res):
                if l > self.threshold:
                    lower_label += ' {}'.format(lower_list[i])
                    has_lower = True
            if not has_lower:
                lower_label += ' {}'.format(lower_list[np.argmax(lower_res)])

            label_res.append(lower_label)
            # shoe
            shoe = 'Boots' if res[14] > self.threshold else 'No boots'
            label_res.append(shoe)

            batch_res.append(label_res)
        result = {'output': batch_res}
        return result
```