Skip to content

Commit 63cd17b

Browse files
committed
Update readme.md
1 parent cdc31d6 commit 63cd17b

11 files changed

+152
-60
lines changed

.gitignore

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
.idea/
22
*.pyc
3+
__pycache__/
34
test.data/
4-
5+
training/snapshot/
6+
training/logs/
7+
train.configs/param_all_norm.pkl
8+
train.configs/param_all_norm_val.pkl
9+
train.configs/train_aug_120x120.list.*

benchmark.py

+9-5
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
from ddfa_utils import ToTensorGjz, NormalizeGjz, DDFATestDataset
1919
from params import *
20+
import argparse
2021

2122

2223
def _reconstruct_vertex(param, whitening=True, dense=False):
@@ -75,7 +76,7 @@ def extract_param(checkpoint_fp, root='', filelists=None, arch='mobilenet_1', nu
7576
outputs.append(param_prediction)
7677
outputs = np.array(outputs, dtype=np.float32)
7778

78-
print(f'{time.time() - end: .3f}s')
79+
print(f'Extracting params take {time.time() - end: .3f}s')
7980
return outputs
8081

8182

@@ -103,10 +104,8 @@ def benchmark_aflw2000_params(params):
103104
return _benchmark_aflw2000(outputs)
104105

105106

106-
def benchmark_pipeline():
107+
def benchmark_pipeline(arch, checkpoint_fp):
107108
device_ids = [0]
108-
checkpoint_fp = 'models/phase1_wpdc_vdc.pth.tar'
109-
arch = 'mobilenet_1'
110109

111110
def aflw():
112111
params = extract_param(
@@ -135,7 +134,12 @@ def aflw2000():
135134

136135

137136
def main():
138-
benchmark_pipeline()
137+
parser = argparse.ArgumentParser(description='3DDFA Benchmark')
138+
parser.add_argument('--arch', default='mobilenet_1', type=str)
139+
parser.add_argument('-c', '--checkpoint-fp', default='models/phase1_wpdc_vdc.pth.tar', type=str)
140+
args = parser.parse_args()
141+
142+
benchmark_pipeline(args.arch, args.checkpoint_fp)
139143

140144

141145
if __name__ == '__main__':

params.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@
55
import numpy as np
66
from io_utils import _load, _numpy_to_cuda, _numpy_to_tensor, _load_gpu
77

8-
d = 'train.configs'
8+
9+
def make_abs_path(d):
10+
return osp.join(osp.dirname(osp.realpath(__file__)), d)
11+
12+
13+
d = make_abs_path('train.configs')
914
keypoints = _load(osp.join(d, 'keypoints_sim.npy'))
1015
w_shp = _load(osp.join(d, 'w_shp_sim.npy'))
1116
w_exp = _load(osp.join(d, 'w_exp_sim.npy')) # simplified version

readme.md

+37-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,40 @@
1-
## Face Alignment in Full Pose Range: A 3D Total Solution
1+
# Face Alignment in Full Pose Range: A 3D Total Solution
22

3+
## Introduction
34
The pytorch implementation of paper [Face Alignment in Full Pose Range: A 3D Total Solution](https://arxiv.org/abs/1804.01005).
45

5-
This work is in progress.
6+
## Citation
7+
@article{zhu2017face,
8+
title={Face Alignment in Full Pose Range: A 3D Total Solution},
9+
author={Zhu, Xiangyu and Lei, Zhen and Li, Stan Z and others},
10+
journal={IEEE Transactions on Pattern Analysis and Machine Intelligence},
11+
year={2017},
12+
publisher={IEEE}
13+
}
14+
15+
16+
## Requirements
17+
- PyTorch >= 0.4.0
18+
- Python3.6
19+
20+
I strongly recommend using Python3.6 instead of older version for its better design.
21+
22+
## Evaluation
23+
First, you should download the cropped testset ALFW and ALFW-2000-3D in [test.data.zip](https://pan.baidu.com/s/1DTVGCG5k0jjjhOc8GcSLOw), then unzip it and put it in the root directory.
24+
Next, run the benchmark code by providing trained model path.
25+
I have already provided four pre-trained models in `models` directory. These models are trained using different loss in the first stage. The model size is about 13M due to the high efficiency of mobilenet-v1 structure.
26+
```
27+
python3 ./benchmark.py -c models/phase1_wpdc_vdc.pth.tar
28+
```
29+
30+
The performances of pre-trained models are shown below. In the first stage, the effectiveness of different loss is in order: WPDC > VDC > PDC. While the strategy using VDC to finetune WPDC achieves the best result.
31+
32+
| Model | AFLW (21 pts) | AFLW 2000-3D (68 pts) |
33+
|:-:|:-:|:-:|
34+
| phase1_pdc.pth.tar | 6.956±0.981 | 5.644±1.323 |
35+
| phase1_vdc.pth.tar | 6.717±0.924 | 5.030±1.044 |
36+
| phase1_wpdc.pth.tar | 6.348±0.929 | 4.759±0.996 |
37+
| phase1_wpdc_vdc.pth.tar | **5.401±0.754** | **4.252±0.976** |
38+
39+
## Training
40+
[todo]

train.py

100644100755
+6-8
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
#!/usr/bin/env python3
22
# coding: utf-8
33

4-
import sys
5-
import os
64
import os.path as osp
75
from pathlib import Path
86
import numpy as np
@@ -34,7 +32,7 @@ def parse_args():
3432
parser.add_argument('-j', '--workers', default=6, type=int)
3533
parser.add_argument('--epochs', default=40, type=int)
3634
parser.add_argument('--start-epoch', default=1, type=int)
37-
parser.add_argument('-b', '--batch-size', default=128)
35+
parser.add_argument('-b', '--batch-size', default=128, type=int)
3836
parser.add_argument('-vb', '--val-batch-size', default=32, type=int)
3937
parser.add_argument('--base-lr', '--learning-rate', default=0.001, type=float)
4038
parser.add_argument('--momentum', default=0.9, type=float, metavar='M',
@@ -58,12 +56,13 @@ def parse_args():
5856
parser.add_argument('--frozen', default='false', type=str2bool)
5957
parser.add_argument('--milestones', default='15,25,30', type=str)
6058
parser.add_argument('--task', default='all', type=str)
59+
parser.add_argument('--test_initial', default='false', type=str2bool)
6160
parser.add_argument('--warmup', default=-1, type=int)
6261
parser.add_argument('--param-fp-train',
63-
default='configs/param_all_norm.pkl',
62+
default='',
6463
type=str)
6564
parser.add_argument('--param-fp-val',
66-
default='configs/param_all_norm_val.pkl')
65+
default='')
6766
parser.add_argument('--opt-style', default='resample', type=str) # resample
6867
parser.add_argument('--resample-num', default=132, type=int)
6968
parser.add_argument('--loss', default='vdc', type=str)
@@ -168,8 +167,8 @@ def validate(val_loader, model, criterion, epoch):
168167
target = target.cuda(non_blocking=True)
169168
output = model(input)
170169

171-
loss = criterion(output, target)
172-
losses.append(loss)
170+
loss = criterion(output, target)
171+
losses.append(loss)
173172

174173
elapse = time.time() - end
175174
loss = np.mean(losses)
@@ -274,7 +273,6 @@ def main():
274273
filename
275274
)
276275

277-
# [todo:] add evaluation for AFLW and ALFW2000
278276
validate(val_loader, model, criterion, epoch)
279277

280278

training/train.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../train.py

training/train_pdc.sh

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#!/usr/bin/env bash
2+
3+
LOG_ALIAS=$1
4+
LOG_DIR="logs"
5+
mkdir -p ${LOG_DIR}
6+
7+
LOG_FILE="${LOG_DIR}/${LOG_ALIAS}_`date +'%Y-%m-%d_%H:%M.%S'`.log"
8+
#echo $LOG_FILE
9+
10+
./train.py --arch="mobilenet_1" \
11+
--start-epoch=1 \
12+
--loss=vdc \
13+
--snapshot="snapshot/phase1_pdc" \
14+
--param-fp-train='../train.configs/param_all_norm.pkl' \
15+
--param-fp-val='../train.configs/param_all_norm_val.pkl' \
16+
--warmup=5 \
17+
--opt-style=resample \
18+
--batch-size=256 \
19+
--base-lr=0.01 \
20+
--epochs=50 \
21+
--milestones=30,40 \
22+
--print-freq=50 \
23+
--devices-id=0,1,2,3 \
24+
--workers=8 \
25+
--filelists-train="../train.configs/augtrain_aug_120x120.list.train" \
26+
--filelists-val="../train.configs/train_aug_120x120.list.val" \
27+
--root="/mnt/ramdisk/augmentation_crop_120x120_v2" \
28+
--log-file="${LOG_FILE}"

training/train_vdc.sh

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/usr/bin/env bash
2+
3+
LOG_ALIAS=$1
4+
LOG_DIR="logs"
5+
mkdir -p ${LOG_DIR}
6+
7+
LOG_FILE="${LOG_DIR}/${LOG_ALIAS}_`date +'%Y-%m-%d_%H:%M.%S'`.log"
8+
#echo $LOG_FILE
9+
10+
./train.py --arch="mobilenet_1" \
11+
--start-epoch=1 \
12+
--loss=vdc \
13+
--snapshot="snapshot/phase1_vdc" \
14+
--param-fp-train='../train.configs/param_all_norm.pkl' \
15+
--param-fp-val='../train.configs/param_all_norm_val.pkl' \
16+
--warmup=-1 \
17+
--opt-style=resample \
18+
--resample-num=232 \
19+
--batch-size=512 \
20+
--base-lr=0.00001 \
21+
--epochs=50 \
22+
--milestones=30,40 \
23+
--print-freq=50 \
24+
--devices-id=0,1,2,3 \
25+
--workers=8 \
26+
--filelists-train="../train.configs/augtrain_aug_120x120.list.train" \
27+
--filelists-val="../train.configs/train_aug_120x120.list.val" \
28+
--root="/mnt/ramdisk/augmentation_crop_120x120_v2" \
29+
--log-file="${LOG_FILE}"

training/train_wpdc.sh

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/usr/bin/env bash
2+
3+
LOG_ALIAS=$1
4+
LOG_DIR="logs"
5+
mkdir -p ${LOG_DIR}
6+
7+
LOG_FILE="${LOG_DIR}/${LOG_ALIAS}_`date +'%Y-%m-%d_%H:%M.%S'`.log"
8+
#echo $LOG_FILE
9+
10+
./train.py --arch="mobilenet_1" \
11+
--start-epoch=1 \
12+
--loss=wpdc \
13+
--snapshot="snapshot/phase1_wpdc" \
14+
--param-fp-train='../train.configs/param_all_norm.pkl' \
15+
--param-fp-val='../train.configs/param_all_norm_val.pkl' \
16+
--warmup=5 \
17+
--opt-style=resample \
18+
--resample-num=232 \
19+
--batch-size=512 \
20+
--base-lr=0.02 \
21+
--epochs=50 \
22+
--milestones=30,40 \
23+
--print-freq=50 \
24+
--devices-id=0,1,2,3 \
25+
--workers=8 \
26+
--filelists-train="../train.configs/augtrain_aug_120x120.list.train" \
27+
--filelists-val="../train.configs/train_aug_120x120.list.val" \
28+
--root="/mnt/ramdisk/augmentation_crop_120x120_v2" \
29+
--log-file="${LOG_FILE}"

vdc_loss.py

+1-20
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,15 @@
11
#!/usr/bin/env python3
22
# coding: utf-8
33

4-
import sys
5-
import os.path as osp
6-
from pathlib import Path
7-
import numpy as np
84
import torch
95
import torch.nn as nn
10-
import torch.cuda as cuda
116
from io_utils import _load, _numpy_to_cuda, _numpy_to_tensor
12-
import time
13-
import random
147
from params import *
158

16-
# d = 'configs'
17-
# keypoints = _load(osp.join(d, 'keypoints_sim.npy'))
18-
# w_shp = _load(osp.join(d, 'w_shp_sim.npy'))
19-
# w_exp = _load(osp.join(d, 'w_exp_sim.npy')) # simplified version
20-
# meta = _load(osp.join(d, 'param_whitening.pkl'))
21-
#
22-
# param_mean = meta.get('param_mean')
23-
# param_std = meta.get('param_std')
24-
# u_shp = _load(osp.join(d, 'u_shp.npy'))
25-
# u_exp = _load(osp.join(d, 'u_exp.npy'))
26-
# u = u_shp + u_exp
27-
28-
# _to_tensor = _numpy_to_tensor # cpu
299
_to_tensor = _numpy_to_cuda # gpu
3010

3111

12+
3213
def _parse_param_batch(param):
3314
"""Work for both numpy and tensor"""
3415
N = param.shape[0]

wpdc_loss.py

-23
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,12 @@
11
#!/usr/bin/env python3
22
# coding: utf-8
33

4-
import sys
5-
import os.path as osp
6-
from pathlib import Path
7-
import numpy as np
84
import torch
95
import torch.nn as nn
10-
import torch.nn.functional as F
11-
import pickle
126
from math import sqrt
13-
import time
147
from io_utils import _load, _numpy_to_cuda, _numpy_to_tensor, _load_gpu
158
from params import *
169

17-
# d = 'configs'
18-
# keypoints = _load(osp.join(d, 'keypoints_sim.npy'))
19-
# w_shp = _load(osp.join(d, 'w_shp_sim.npy'))
20-
# w_exp = _load(osp.join(d, 'w_exp_sim.npy')) # simplified version
21-
#
22-
# meta = _load(osp.join(d, 'param_whitening.pkl'))
23-
# param_mean = meta.get('param_mean')
24-
# param_std = meta.get('param_std')
25-
# u_shp = _load(osp.join(d, 'u_shp.npy'))
26-
# u_exp = _load(osp.join(d, 'u_exp.npy'))
27-
# u = u_shp + u_exp
28-
# w = np.concatenate((w_shp, w_exp), axis=1)
29-
# w_base = w[keypoints]
30-
# w_norm = np.linalg.norm(w, axis=0)
31-
# w_base_norm = np.linalg.norm(w_base, axis=0)
32-
3310
_to_tensor = _numpy_to_cuda # gpu
3411

3512

0 commit comments

Comments
 (0)