daliprf commited on
Commit
ad1e0a0
1 Parent(s): 1a8030f
.gitattributes CHANGED
@@ -29,3 +29,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
29
  *.zip filter=lfs diff=lfs merge=lfs -text
30
  *.zstandard filter=lfs diff=lfs merge=lfs -text
31
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
29
  *.zip filter=lfs diff=lfs merge=lfs -text
30
  *.zstandard filter=lfs diff=lfs merge=lfs -text
31
  *tfevents* filter=lfs diff=lfs merge=lfs -text
32
+ *.png filter=lfs diff=lfs merge=lfs -text
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Ali Pourramezan Fard
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md CHANGED
@@ -1,3 +1,77 @@
1
- ---
2
- license: mit
3
- ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # [ACR-Loss](https://scholar.google.com/citations?view_op=view_citation&hl=en&user=96lS6HIAAAAJ&citation_for_view=96lS6HIAAAAJ:eQOLeE2rZwMC)
2
+
3
+ ### Accepted in ICPR 2022
4
+ ACR Loss: Adaptive Coordinate-based Regression Loss for Face Alignment
5
+
6
+ #### Link to the paper:
7
+ https://arxiv.org/pdf/2203.15835.pdf
8
+
9
+
10
+ ```diff
11
+ @@plaese STAR the repo if you like it.@@
12
+ ```
13
+
14
+ ```
15
+ Please cite this work as:
16
+
17
+ @article{fard2022acr,
18
+ title={ACR Loss: Adaptive Coordinate-based Regression Loss for Face Alignment},
19
+ author={Fard, Ali Pourramezan and Mahoor, Mohammah H},
20
+ journal={arXiv preprint arXiv:2203.15835},
21
+ year={2022}
22
+ }
23
+ ```
24
+
25
+ ![Samples](https://github.com/aliprf/ACR-Loss/blob/main/img/ACR_300w_samples.png?raw=true)
26
+
27
+ ## Introduction
28
+
29
+ Although deep neural networks have achieved reasonable accuracy in solving face alignment, it is still a challenging task, specifically when we deal with facial images, under occlusion, or extreme head poses. Heatmap-based Regression (HBR) and Coordinate-based Regression (CBR) are among the two mainly used methods for face alignment. CBR methods require less computer memory, though their performance is less than HBR methods. In this paper, we propose an Adaptive Coordinatebased Regression (ACR) loss to improve the accuracy of CBR for face alignment. Inspired by the Active Shape Model (ASM), we generate Smooth-Face objects, a set of facial landmark points with less variations compared to the ground truth landmark points. We then introduce a method to estimate the level of difficulty in predicting each landmark point for the network by comparing the distribution of the ground truth landmark points
30
+ and the corresponding Smooth-Face objects. Our proposed ACR Loss can adaptively modify its curvature and the influence of the loss based on the difficulty level of predicting each landmark point in a face. Accordingly, the ACR Loss guides the network toward challenging points than easier points, which improves the accuracy of the face alignment task. Our extensive evaluation shows the capabilities of the proposed ACR Loss in predicting facial landmark points in various facial images.
31
+
32
+ We evaluated our ACR Loss using MobileNetV2, EfficientNetB0, and EfficientNet-B3 on widely used 300W, and COFW datasets and showed that the performance of face alignment using the ACR Loss is much better than the widely-used L2 loss. Moreover, on the COFW dataset, we achieved state-of-theart accuracy. In addition, on 300W the ACR Loss performance is comparable to the state-of-the-art methods. We also compared the performance of MobileNetV2 trained using the ACR Loss with the lightweight state-of-the-art methods, and we achieved the best accuracy, highlighting the effectiveness of our ACR Loss for face alignment specifically for the lightweight models.
33
+
34
+
35
+ ----------------------------------------------------------------------------------------------------------------------------------
36
+ ## Installing the requirements
37
+ In order to run the code you need to install python >= 3.5.
38
+ The requirements and the libraries needed to run the code can be installed using the following command:
39
+
40
+ ```
41
+ pip install -r requirements.txt
42
+ ```
43
+
44
+
45
+ ## Using the pre-trained models
46
+ You can test and use the preetrained models using the following codes:
47
+ ```
48
+ tester = Test()
49
+ tester.test_model(ds_name=DatasetName.w300,
50
+ pretrained_model_path='./pre_trained_models/ACRLoss/300w/EF_3/300w_EF3_ACRLoss.h5')
51
+
52
+ ```
53
+
54
+
55
+ ## Training Network from scratch
56
+
57
+
58
+ ### Preparing Data
59
+ Data needs to be normalized and saved in npy format.
60
+
61
+ ### PCA creation
62
+ you can you the pca_utility.py class to create the eigenvalues, eigenvectors, and the meanvector:
63
+ ```
64
+ pca_calc = PCAUtility()
65
+ pca_calc.create_pca_from_npy(dataset_name=DatasetName.w300,
66
+ labels_npy_path='./data/w300/normalized_labels/',
67
+ pca_percentages=90)
68
+
69
+ ```
70
+ ### Training
71
+ The training implementation is located in train.py class. You can use the following code to start the training:
72
+
73
+ ```
74
+ trainer = Train(arch=ModelArch.MNV2,
75
+ dataset_name=DatasetName.w300,
76
+ save_path='./')
77
+ ```
acr_loss.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from config import DatasetName
2
+ import tensorflow as tf
3
+
4
+
5
+ class ACRLoss:
6
+ def acr_loss(self, x_pr, x_gt, phi, lambda_weight, ds_name):
7
+ low_map = tf.cast(tf.abs(x_pr - x_gt) <= 1.0, dtype=tf.float32)
8
+ high_map = tf.cast(tf.abs(x_pr - x_gt) > 1.0, dtype=tf.float32)
9
+
10
+ '''Big errors'''
11
+ ln_2 = tf.ones_like(x_pr, dtype=tf.float32) * tf.math.log(2.0)
12
+ C = tf.cast(tf.cast(phi, dtype=tf.double) * tf.cast(ln_2, dtype=tf.double) - 1.0, dtype=tf.float32)
13
+ loss_high = 100 * tf.reduce_mean(tf.math.multiply(high_map, (tf.square(x_pr - x_gt) + C)))
14
+
15
+ '''Small errors'''
16
+ power = tf.cast(2.0 - phi, tf.dtypes.float32)
17
+ ll = tf.pow(tf.abs(x_pr - x_gt), power)
18
+ loss_low = 100 * tf.reduce_mean(tf.math.multiply(low_map, (lambda_weight * tf.math.log(1.0 + ll))))
19
+
20
+ loss_total = loss_low + loss_high
21
+
22
+ return loss_total, loss_low, loss_high
cnn.py ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from config import DatasetName, DatasetType, W300Conf, InputDataSize, LearningConfig
2
+ import tensorflow as tf
3
+ from tensorflow import keras
4
+ from skimage.transform import resize
5
+ from keras.regularizers import l2, l1
6
+ from keras.models import Model
7
+ from keras.applications import mobilenet_v2, mobilenet, densenet
8
+ from keras.layers import Dense, MaxPooling2D, Conv2D, Flatten, \
9
+ BatchNormalization, Activation, GlobalAveragePooling2D, DepthwiseConv2D, Dropout, ReLU, Concatenate, Input, \
10
+ GlobalMaxPool2D
11
+ import efficientnet.tfkeras as efn
12
+
13
+
14
+ class CNNModel:
15
+ def get_model(self, arch, output_len):
16
+
17
+ if arch == 'mobileNetV2':
18
+ model = self.create_MobileNet(inp_shape=[224, 224, 3], output_len=output_len)
19
+
20
+ elif arch == 'efNb0':
21
+ model = self.create_efficientNet_b0(inp_shape=[224, 224, 3], input_tensor=None, output_len=output_len)
22
+
23
+ elif arch == 'efNb3':
24
+ model = self.create_efficientNet_b3(inp_shape=[224, 224, 3], input_tensor=None, output_len=output_len)
25
+
26
+ return
27
+
28
+ def create_MobileNet(self, inp_shape, output_len):
29
+ mobilenet_model = mobilenet_v2.MobileNetV2(input_shape=inp_shape,
30
+ alpha=1.0,
31
+ include_top=True,
32
+ weights=None,
33
+ input_tensor=None,
34
+ pooling=None)
35
+ mobilenet_model.layers.pop()
36
+
37
+ x = mobilenet_model.get_layer('global_average_pooling2d').output # 1280
38
+ x = Dropout(0.1)(x)
39
+ out_landmarks = Dense(output_len, activation=keras.activations.linear, kernel_initializer=initializer,
40
+ use_bias=True, name='O_L')(x)
41
+ inp = mobilenet_model.input
42
+
43
+ revised_model = Model(inp, [out_landmarks])
44
+
45
+ revised_model.summary()
46
+ model_json = revised_model.to_json()
47
+
48
+ with open("mobileNet_v2.json", "w") as json_file:
49
+ json_file.write(model_json)
50
+
51
+ return revised_model
52
+
53
+ def create_efficientNet_b0(self, inp_shape, input_tensor, output_len):
54
+ initializer = tf.keras.initializers.he_uniform()
55
+
56
+ eff_net = efn.EfficientNetB0(include_top=True,
57
+ weights=None,
58
+ input_tensor=input_tensor,
59
+ input_shape=inp_shape,
60
+ pooling=None)
61
+ eff_net.layers.pop()
62
+ inp = eff_net.input
63
+
64
+ x = eff_net.get_layer('top_activation').output
65
+ x = GlobalAveragePooling2D()(x)
66
+ x = keras.layers.Dropout(rate=0.5)(x)
67
+ output = Dense(output_len, activation='linear', use_bias=True, name='out',
68
+ kernel_initializer=initializer)(x)
69
+
70
+ eff_net = Model(inp, output)
71
+ eff_net.summary()
72
+
73
+ model_json = eff_net.to_json()
74
+ with open("eff_net_b0.json", "w") as json_file:
75
+ json_file.write(model_json)
76
+
77
+ return eff_net
78
+
79
+ def create_efficientNet_b3(self, inp_shape, input_tensor, output_len):
80
+ initializer = tf.keras.initializers.he_uniform()
81
+
82
+ eff_net = efn.EfficientNetB3(include_top=True,
83
+ weights=None,
84
+ input_tensor=input_tensor,
85
+ input_shape=inp_shape,
86
+ pooling=None)
87
+ eff_net.layers.pop()
88
+ inp = eff_net.input
89
+
90
+ x = eff_net.get_layer('top_activation').output
91
+ x = GlobalAveragePooling2D()(x)
92
+ x = keras.layers.Dropout(rate=0.5)(x)
93
+
94
+ output = Dense(output_len, activation='linear', use_bias=True, name='out',
95
+ kernel_initializer=initializer)(x)
96
+
97
+ eff_net = Model(inp, output)
98
+ eff_net.summary()
99
+
100
+ model_json = eff_net.to_json()
101
+ with open("eff_net_b3.json", "w") as json_file:
102
+ json_file.write(model_json)
103
+
104
+ return eff_net
config.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class DatasetName:
2
+ w300 = '300W'
3
+ cofw = 'COFW'
4
+
5
+
6
+ class ModelArch:
7
+ MNV2 = 'mobileNetV2'
8
+ EFNB0 = 'EfficientNet-B0'
9
+ EFNB3 = 'EfficientNet-B3'
10
+
11
+
12
+ class DatasetType:
13
+ data_type_train = 0
14
+ data_type_test = 1
15
+
16
+
17
+ class LearningConfig:
18
+ batch_size = 3
19
+ epochs = 150
20
+
21
+
22
+ class InputDataSize:
23
+ image_input_size = 224
24
+
25
+
26
+ class W300Conf:
27
+ W300W_prefix_path = './300W/'
28
+
29
+ train_annotation = W300W_prefix_path + 'train_set/annotations/'
30
+ train_image = W300W_prefix_path + 'train_set/images/'
31
+
32
+ test_annotation_path = W300W_prefix_path + 'test_set/annotations/'
33
+ test_image_path = W300W_prefix_path + 'test_set/images/'
34
+ num_of_landmarks = 68
35
+
36
+ class CofwConf:
37
+ cofw_prefix_path = './cofw/'
38
+
39
+ train_annotation = cofw_prefix_path + 'train_set/annotations/'
40
+ train_image = cofw_prefix_path + 'train_set/images/'
41
+
42
+ test_annotation_path = cofw_prefix_path + 'test_set/annotations/'
43
+ test_image_path = cofw_prefix_path + 'test_set/images/'
44
+ num_of_landmarks = 29
data_util.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pca_utilities import PCAUtility
2
+ from config import DatasetName, DatasetType, W300Conf, InputDataSize, LearningConfig, CofwConf
3
+ import tensorflow as tf
4
+ import numpy as np
5
+ import os
6
+ from skimage.transform import resize
7
+ import csv
8
+ import sys
9
+ from PIL import Image
10
+ from pathlib import Path
11
+ import sqlite3
12
+ import cv2
13
+ import os.path
14
+ from keras import backend as K
15
+
16
+ from scipy import misc
17
+ from scipy.ndimage import gaussian_filter, maximum_filter
18
+ from numpy import save, load, asarray
19
+ from tqdm import tqdm
20
+ import pickle
21
+ import PIL.ImageDraw as ImageDraw
22
+ import math
23
+
24
+
25
+ class DataUtil:
26
+
27
+ def __init__(self, number_of_landmark):
28
+ self.number_of_landmark = number_of_landmark
29
+
30
+ def get_asm(self, input, dataset_name, accuracy, alpha=1.0):
31
+ pca_utils = PCAUtility()
32
+
33
+ eigenvalues = load('pca_obj/' + dataset_name + pca_utils.eigenvalues_prefix + str(accuracy) + ".npy")
34
+ eigenvectors = load('pca_obj/' + dataset_name + pca_utils.eigenvectors_prefix + str(accuracy) + ".npy")
35
+ meanvector = load('pca_obj/' + dataset_name + pca_utils.meanvector_prefix + str(accuracy) + ".npy")
36
+
37
+ b_vector_p = pca_utils.calculate_b_vector(input, True, eigenvalues, eigenvectors, meanvector)
38
+ out = alpha * meanvector + np.dot(eigenvectors, b_vector_p)
39
+ return out
40
+
41
+ def create_image_and_labels_name(self, img_path, annotation_path):
42
+ img_filenames = []
43
+ lbls_filenames = []
44
+
45
+ for file in os.listdir(img_path):
46
+ if file.endswith(".jpg") or file.endswith(".png"):
47
+ lbl_file = str(file)[:-3] + "npy" # just name
48
+ if os.path.exists(annotation_path + lbl_file):
49
+ img_filenames.append(str(file))
50
+ lbls_filenames.append(lbl_file)
51
+
52
+ return np.array(img_filenames), np.array(lbls_filenames)
img/ACR_300w_samples.png ADDED

Git LFS Details

  • SHA256: 8a37b8cc7de6ae544b63690c1062edbb2ebb91fee5daf8a9135d5a1f1f3b50f2
  • Pointer size: 132 Bytes
  • Size of remote file: 8.18 MB
main.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from train import Train
2
+ from test import Test
3
+ from config import DatasetName, ModelArch
4
+ from pca_utilities import PCAUtility
5
+
6
+ if __name__ == '__main__':
7
+ '''use the pretrained model'''
8
+ tester = Test()
9
+ tester.test_model(ds_name=DatasetName.w300,
10
+ pretrained_model_path='./pre_trained_models/ACRLoss/mnv2.h5')
11
+
12
+ '''training model from scratch'''
13
+
14
+ # pretrain prerequisites
15
+ # 1- PCA calculation:
16
+ pca_calc = PCAUtility()
17
+ pca_calc.create_pca_from_npy(dataset_name=DatasetName.w300,
18
+ labels_npy_path='./data/w300/normalized_labels/',
19
+ pca_percentages=90)
20
+
21
+ # Train:
22
+ trainer = Train(arch=ModelArch.MNV2,
23
+ dataset_name=DatasetName.w300,
24
+ save_path='./')
pca_utilities.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sklearn.decomposition import PCA, IncrementalPCA
2
+ from sklearn.decomposition import TruncatedSVD
3
+ import numpy as np
4
+ import pickle
5
+ import os
6
+ from tqdm import tqdm
7
+ from numpy import save, load
8
+ import math
9
+ from PIL import Image
10
+ from numpy import save, load
11
+
12
+
13
+ class PCAUtility:
14
+ eigenvalues_prefix = "_eigenvalues_"
15
+ eigenvectors_prefix = "_eigenvectors_"
16
+ meanvector_prefix = "_meanvector_"
17
+
18
+ def create_pca_from_npy(self, dataset_name, labels_npy_path, pca_percentages):
19
+ """
20
+ generate and save eigenvalues, eigenvectors, meanvector
21
+ :param labels_npy_path: the path to the normalized labels that are save in npy format.
22
+ :param pca_percentages: % of eigenvalues that will be used
23
+ :return: generate
24
+ """
25
+ path = labels_npy_path
26
+ print('PCA calculation started: loading labels')
27
+
28
+ lbl_arr = []
29
+ for file in tqdm(os.listdir(path)):
30
+ if file.endswith(".npy"):
31
+ npy_file = os.path.join(path, file)
32
+ lbl_arr.append(load(npy_file))
33
+
34
+ lbl_arr = np.array(lbl_arr)
35
+
36
+ reduced_lbl_arr, eigenvalues, eigenvectors = self._func_PCA(lbl_arr, pca_percentages)
37
+ mean_lbl_arr = np.mean(lbl_arr, axis=0)
38
+ eigenvectors = eigenvectors.T
39
+
40
+ save('./pca_obj/' + dataset_name + self.eigenvalues_prefix + str(pca_percentages), eigenvalues)
41
+ save('./pca_obj/' + dataset_name + self.eigenvectors_prefix + str(pca_percentages), eigenvectors)
42
+ save('./pca_obj/' + dataset_name + self.meanvector_prefix + str(pca_percentages), mean_lbl_arr)
43
+
44
+ def load_pca_obj(self, dataset_name, pca_percentages):
45
+ eigenvalues = np.load('./pca_obj/' + dataset_name + self.eigenvalues_prefix + str(pca_percentages))
46
+ eigenvectors = np.load('./pca_obj/' + dataset_name + self.eigenvectors_prefix + str(pca_percentages))
47
+ meanvector = np.load('./pca_obj/' + dataset_name + self.meanvector_prefix + str(pca_percentages))
48
+ return eigenvalues, eigenvectors, meanvector
49
+
50
+ def calculate_b_vector(self, predicted_vector, correction, eigenvalues, eigenvectors, meanvector):
51
+ tmp1 = predicted_vector - meanvector
52
+ b_vector = np.dot(eigenvectors.T, tmp1)
53
+
54
+ # put b in -3lambda =>
55
+ if correction:
56
+ i = 0
57
+ for b_item in b_vector:
58
+ lambda_i_sqr = 3 * math.sqrt(eigenvalues[i])
59
+
60
+ if b_item > 0:
61
+ b_item = min(b_item, lambda_i_sqr)
62
+ else:
63
+ b_item = max(b_item, -1 * lambda_i_sqr)
64
+ b_vector[i] = b_item
65
+ i += 1
66
+
67
+ return b_vector
68
+
69
+ def _func_PCA(self, input_data, pca_postfix):
70
+ input_data = np.array(input_data)
71
+ pca = PCA(n_components=pca_postfix / 100)
72
+ pca.fit(input_data)
73
+ pca_input_data = pca.transform(input_data)
74
+ eigenvalues = pca.explained_variance_
75
+ eigenvectors = pca.components_
76
+ return pca_input_data, eigenvalues, eigenvectors
pre_trained_models/300w/EF_0/300w_EF0_ACRLoss.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:937fc62cb7517a427905dc5e0c39711615269f481b080eb04b6bc30e2c22d7dd
3
+ size 17518832
pre_trained_models/300w/EF_0/300w_EF0_base.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:55a7836679d92933220c96e0b0563466aa4d24a4866b6ec3e0b5f44714a85e9a
3
+ size 17519192
pre_trained_models/300w/EF_3/300w_EF3_ACRLoss.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:abd7b1bc7e43cd4489fbde4f83a4dfb3722596f0dcd3e909256d4720af2b1920
3
+ size 44972528
pre_trained_models/300w/EF_3/300w_EF3_base.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f465c57e83bf4a8ec901baa0995b3d29a83fdf49af13432cdb7ccd99dccc2a53
3
+ size 44972528
pre_trained_models/300w/mnv2/300w_mnv2_ACRLoss.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:97eabc914cf24ffe6fd32442748c7c35d12297950feab8c20204b0a2b872d304
3
+ size 10197672
pre_trained_models/300w/mnv2/300w_mnv2_base.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e1ac8c5431a516e1d5218e7da3a86ebde77b71c6054d2654d76445d2e5f44ac0
3
+ size 10195168
pre_trained_models/cofw/EF_0/cofw_EF0_base.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:90283e68ac497f2baaec7c02c605be3bb8b9eb283fcd452e0c93c3c174c7a476
3
+ size 17119832
pre_trained_models/cofw/EF_0/cofw_EF0_xxxLoss.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3c1ff7991fa3b0434e3b75ea32127d77a3bd4c0e3ba241aa54276eb3dff67460
3
+ size 17119832
pre_trained_models/cofw/EF_3/cofw_EF3_base.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:36f7006d1215031b737a8072b4c67685308203eea59d431f5073d7687903ea4f
3
+ size 44491224
pre_trained_models/cofw/EF_3/cofw_EF3_xxxLoss.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e495279c4c60de5ad07e1aa29db1a361c51029f260f0cf0ea0a0b73fb260c59e
3
+ size 44491224
pre_trained_models/cofw/mnv2/cofw_mnv2_base.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5002926568613fe55a3e005987e4f9e41ef9859f9dfae9c2a0e2b19bdbc300f6
3
+ size 9799400
pre_trained_models/cofw/mnv2/cofw_mnv2_xxxLoss.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b4a2623fc19644604e401fc22c2104f1293645672464927fee72e37ad8609724
3
+ size 9800464
requirements.txt ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ numpy
2
+
3
+ # Mac M1:
4
+ tensorflow-macos
5
+ #linux
6
+ # tensorflow
7
+
8
+ keras
9
+ matplotlib
10
+ opencv-python
11
+ opencv-contrib-python
12
+ scipy
13
+ scikit-learn
14
+ scikit-image
15
+ Pillow
16
+ tqdm
17
+ efficientnet
18
+ tensorboard
test.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from config import DatasetName, W300Conf, DatasetType, LearningConfig, InputDataSize, CofwConf
2
+ import tensorflow as tf
3
+
4
+ import cv2
5
+ import os.path
6
+ import scipy.io as sio
7
+ from cnn import CNNModel
8
+ from tqdm import tqdm
9
+ import numpy as np
10
+ from os import listdir
11
+ from os.path import isfile, join
12
+ from scipy.integrate import simps
13
+ from scipy.integrate import trapz
14
+ import matplotlib.pyplot as plt
15
+ from skimage.io import imread
16
+
17
+
18
+ class Test:
19
+ def test_model(self, pretrained_model_path, ds_name):
20
+ if ds_name == DatasetName.w300:
21
+ test_annotation_path = W300Conf.test_annotation_path
22
+ test_image_path = W300Conf.test_image_path
23
+ elif ds_name == DatasetName.cofw:
24
+ test_annotation_path = CofwConf.test_annotation_path
25
+ test_image_path = CofwConf.test_image_path
26
+
27
+ model = tf.keras.models.load_model(pretrained_model_path)
28
+
29
+ for i, file in tqdm(enumerate(os.listdir(test_image_path))):
30
+ # load image and then normalize it
31
+ img = imread(test_image_path + file) / 255.0
32
+
33
+ # prediction
34
+ prediction = model.predict(np.expand_dims(img, axis=0))
35
+
36
+ # the first dimension is landmark point
37
+ landmark_predicted = prediction
train.py ADDED
@@ -0,0 +1,218 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from config import DatasetName, W300Conf, DatasetType, LearningConfig, InputDataSize, CofwConf
2
+ from cnn import CNNModel
3
+ import tensorflow as tf
4
+ from tensorflow import keras
5
+ import numpy as np
6
+ import matplotlib.pyplot as plt
7
+ import math
8
+ from datetime import datetime
9
+ from sklearn.utils import shuffle
10
+ from sklearn.model_selection import train_test_split
11
+ from numpy import save, load, asarray
12
+ import csv
13
+ from skimage.io import imread
14
+ import pickle
15
+ from tqdm import tqdm
16
+ import os
17
+ from data_util import DataUtil
18
+ from acr_loss import ACRLoss
19
+
20
+
21
+ class Train:
22
+ def __init__(self, arch, dataset_name, save_path, lambda_weight):
23
+
24
+ self.lambda_weight = lambda_weight
25
+ self.dataset_name = dataset_name
26
+ self.save_path = save_path
27
+ self.arch = arch
28
+ self.base_lr = 1e-3
29
+ self.max_lr = 5e-3
30
+ if dataset_name == DatasetName.w300:
31
+ self.num_landmark = W300Conf.num_of_landmarks * 2
32
+ self.img_path = W300Conf.train_image
33
+ self.annotation_path = W300Conf.train_annotation
34
+ '''evaluation path:'''
35
+ self.eval_img_path = W300Conf.test_image_path + 'challenging/'
36
+ self.eval_annotation_path = W300Conf.test_annotation_path + 'challenging/'
37
+
38
+ if dataset_name == DatasetName.cofw:
39
+ self.num_landmark = CofwConf.num_of_landmarks * 2
40
+ self.img_path = CofwConf.train_image
41
+ self.annotation_path = CofwConf.train_annotation
42
+ '''evaluation path:'''
43
+ self.eval_img_path = CofwConf.test_image_path
44
+ self.eval_annotation_path = CofwConf.test_annotation_path
45
+
46
+ def train(self, weight_path):
47
+ """
48
+ :param weight_path:
49
+ :return:
50
+ """
51
+ '''create loss'''
52
+ c_loss = ACRLoss()
53
+
54
+ '''create summary writer'''
55
+ summary_writer = tf.summary.create_file_writer(
56
+ "./train_logs/fit/" + datetime.now().strftime("%Y%m%d-%H%M%S"))
57
+
58
+ '''create sample generator'''
59
+ x_train_filenames, y_train_filenames = self._create_generators()
60
+
61
+ '''making models'''
62
+ model = self.make_model(arch=self.arch, w_path=weight_path)
63
+
64
+ '''create train configuration'''
65
+ step_per_epoch = len(x_train_filenames) // LearningConfig.batch_size
66
+
67
+ lr = 1e-3
68
+ for epoch in range(LearningConfig.epochs):
69
+ '''calculate Learning rate'''
70
+ optimizer = self._get_optimizer(lr=lr)
71
+ ''''''
72
+ x_train_filenames, y_train_filenames = self._shuffle_data(x_train_filenames, y_train_filenames)
73
+ for batch_index in range(step_per_epoch):
74
+ '''load annotation and images'''
75
+ images, annotation_gr = self._get_batch_sample(
76
+ batch_index=batch_index, x_train_filenames=x_train_filenames,
77
+ y_train_filenames=y_train_filenames)
78
+
79
+ phi = self.calculate_adoptive_weight(epoch=epoch, batch_index=batch_index,
80
+ y_train_filenames=y_train_filenames,
81
+ weight_path=weight_path)
82
+
83
+ '''convert to tensor'''
84
+ images = tf.cast(images, tf.float32)
85
+ annotation_gr = tf.cast(annotation_gr, tf.float32)
86
+ '''train step'''
87
+ loss_total, loss_low, loss_high = self.train_step(
88
+ epoch=epoch, step=batch_index,
89
+ total_steps=step_per_epoch,
90
+ images=images,
91
+ model=model,
92
+ annotation_gr=annotation_gr,
93
+ phi=phi,
94
+ lambda_weight=self.lambda_weight,
95
+ optimizer=optimizer,
96
+ summary_writer=summary_writer, c_loss=c_loss)
97
+
98
+ with summary_writer.as_default():
99
+ tf.summary.scalar('loss_total', loss_total, step=epoch)
100
+ tf.summary.scalar('loss_low', loss_low, step=epoch)
101
+ tf.summary.scalar('loss_high', loss_high, step=epoch)
102
+ '''save weights'''
103
+ model.save(self.save_path + str(epoch) + '_' + self.dataset_name + '.h5')
104
+
105
+ # @tf.function
106
+ def train_step(self, epoch, step, total_steps, images, model, annotation_gr, phi,
107
+ optimizer, summary_writer, c_loss, lambda_weight):
108
+ with tf.GradientTape() as tape:
109
+ '''create annotation_predicted'''
110
+ annotation_predicted = model(images, training=True)
111
+ '''calculate loss'''
112
+ loss_total, loss_low, loss_high = c_loss.acr_loss(x_pr=annotation_predicted,
113
+ x_gt=annotation_gr,
114
+ phi=phi,
115
+ lambda_weight=lambda_weight,
116
+ ds_name=self.dataset_name)
117
+ '''calculate gradient'''
118
+ gradients_of_model = tape.gradient(loss_total, model.trainable_variables)
119
+ '''apply Gradients:'''
120
+ optimizer.apply_gradients(zip(gradients_of_model, model.trainable_variables))
121
+ '''printing loss Values: '''
122
+ tf.print("->EPOCH: ", str(epoch), "->STEP: ", str(step) + '/' + str(total_steps),
123
+ ' -> : LOSS: ', loss_total,
124
+ ' -> : loss_low: ', loss_low,
125
+ ' -> : loss_high: ', loss_high
126
+ )
127
+ # print('==--==--==--==--==--==--==--==--==--')
128
+ with summary_writer.as_default():
129
+ tf.summary.scalar('loss_total', loss_total, step=epoch)
130
+ tf.summary.scalar('loss_low', loss_low, step=epoch)
131
+ tf.summary.scalar('loss_high', loss_high, step=epoch)
132
+ return loss_total, loss_low, loss_high
133
+
134
+ def calculate_adoptive_weight(self, epoch, batch_index, y_train_filenames, weight_path):
135
+
136
+ dt_utils = DataUtil(self.num_landmark)
137
+ batch_y = y_train_filenames[
138
+ batch_index * LearningConfig.batch_size:(batch_index + 1) * LearningConfig.batch_size]
139
+ asm_acc = None
140
+ if 0 <= epoch <= 15:
141
+ asm_acc = 80
142
+ elif 15 < epoch <= 30:
143
+ asm_acc = 85
144
+ elif 30 < epoch <= 70:
145
+ asm_acc = 90
146
+ elif 70 < epoch <= 100:
147
+ asm_acc = 95
148
+
149
+ pn_batch = np.array([self._load_and_normalize(self.annotation_path + file_name) for file_name in batch_y])
150
+ pn_batch_asm = np.array([dt_utils.get_asm(input=self._load_and_normalize(self.annotation_path + file_name),
151
+ dataset_name=self.dataset_name, accuracy=asm_acc)
152
+ for file_name in batch_y])
153
+
154
+ delta = np.array(abs(pn_batch - pn_batch_asm))
155
+
156
+ phi = np.array([delta[i] / np.max(delta[i]) for i in range(len(pn_batch))]) # bs * num_lnd
157
+ return phi
158
+
159
+ def _get_optimizer(self, lr=1e-1, beta_1=0.9, beta_2=0.999, decay=1e-5):
160
+ return tf.keras.optimizers.Adam(lr=lr, beta_1=beta_1, beta_2=beta_2, decay=decay)
161
+
162
+ def make_model(self, arch, w_path):
163
+ cnn = CNNModel()
164
+ model = cnn.get_model(arch=arch, output_len=self.num_landmark)
165
+ if w_path is not None:
166
+ model.load_weights(w_path)
167
+ return model
168
+
169
+ def _shuffle_data(self, filenames, labels):
170
+ filenames_shuffled, y_labels_shuffled = shuffle(filenames, labels)
171
+ return filenames_shuffled, y_labels_shuffled
172
+
173
+ def _create_generators(self, img_path=None, annotation_path=None):
174
+ tf_utils = DataUtil(number_of_landmark=self.num_landmark)
175
+ if img_path is None:
176
+ filenames, labels = tf_utils.create_image_and_labels_name(img_path=self.img_path,
177
+ annotation_path=self.annotation_path)
178
+ else:
179
+ filenames, labels = tf_utils.create_image_and_labels_name(img_path=img_path,
180
+ annotation_path=annotation_path)
181
+ filenames_shuffled, y_labels_shuffled = shuffle(filenames, labels)
182
+ return filenames_shuffled, y_labels_shuffled
183
+
184
+ def _get_batch_sample(self, batch_index, x_train_filenames, y_train_filenames, is_eval=False, batch_size=None):
185
+ if is_eval:
186
+ batch_x = x_train_filenames[
187
+ batch_index * batch_size:(batch_index + 1) * batch_size]
188
+ batch_y = y_train_filenames[
189
+ batch_index * batch_size:(batch_index + 1) * batch_size]
190
+
191
+ img_batch = np.array([imread(self.eval_img_path + file_name) for file_name in batch_x]) / 255.0
192
+ pn_batch = np.array([load(self.eval_annotation_path + file_name) for file_name in
193
+ batch_y])
194
+ else:
195
+ img_path = self.img_path
196
+ pn_tr_path = self.annotation_path
197
+ '''create batch data and normalize images'''
198
+ batch_x = x_train_filenames[
199
+ batch_index * LearningConfig.batch_size:(batch_index + 1) * LearningConfig.batch_size]
200
+ batch_y = y_train_filenames[
201
+ batch_index * LearningConfig.batch_size:(batch_index + 1) * LearningConfig.batch_size]
202
+ '''create img and annotations'''
203
+ img_batch = np.array([imread(img_path + file_name) for file_name in batch_x]) / 255.0
204
+ pn_batch = np.array([self._load_and_normalize(pn_tr_path + file_name) for file_name in batch_y])
205
+
206
+ return img_batch, pn_batch
207
+
208
+ def _load_and_normalize(self, point_path):
209
+ annotation = load(point_path)
210
+ width = InputDataSize.image_input_size
211
+ height = InputDataSize.image_input_size
212
+ x_center = width / 2
213
+ y_center = height / 2
214
+ annotation_norm = []
215
+ for p in range(0, len(annotation), 2):
216
+ annotation_norm.append((x_center - annotation[p]) / width)
217
+ annotation_norm.append((y_center - annotation[p + 1]) / height)
218
+ return annotation_norm