├── README.md
├── data
└── readme
├── faceAlign_demo.py
├── lena.jpg
├── predict.py
├── predicted.png
└── train.py
/README.md:
--------------------------------------------------------------------------------
1 | ## Description
2 | This is a implementation of Face Aligment with simple cnn in Keras, which is the second step of my **FaceID system**. You can find another two repositories as follows:
3 | 1. [Face-detection-with-mobilenet-ssd](https://github.com/bruceyang2012/Face-detection-with-mobilenet-ssd)
4 | 2. [Face-Alignment-with-simple-cnn](https://github.com/bruceyang2012/Face-Alignment-with-simple-cnn)
5 | 3. [Face-identification-with-cnn-triplet-loss](https://github.com/bruceyang2012/Face-identification-with-cnn-triplet-loss)
6 |
7 | ## Some Details
8 | Today there are lots of excellent face alignment algorithms, but they are somehow too complex to implement, and most of methods based on deep learning don't meet the requirement of real-time, here I introduce an efficient method based on simple convolution neural network, which can realize real-time face feature points detection.
9 |
10 | It costs me about only 10 minutes with cpu to train a model on a training set containing 7049 images. It's really fast, and the testing time is about 60ms per face. You can easily improve the accuray using different methods, such as make the convnet structure deeper or make a data augmentation and so on.
11 |
12 | ## testing results.
13 |

14 |
--------------------------------------------------------------------------------
/data/readme:
--------------------------------------------------------------------------------
1 | ## training set and test set
2 | you can get train.csv and test.csv from here.(https://www.kaggle.com/c/facial-keypoints-detection/data)
3 |
--------------------------------------------------------------------------------
/faceAlign_demo.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import numpy as np
3 | import pandas as pd
4 | from keypoints_kernel import load,load2d
5 | from keras.models import Sequential
6 | from keras.layers import Dense, Conv2D, Activation, Dropout, MaxPooling2D, Flatten, GlobalMaxPooling2D, BatchNormalization
7 | from keras.optimizers import SGD, Adam
8 | import matplotlib.pyplot as plt
9 | from keras.preprocessing import image
10 | import time
11 |
12 | def cnn():
13 | model = Sequential()
14 |
15 | model.add(BatchNormalization(input_shape=(96,96,1)))
16 |
17 | model.add(Conv2D(32, kernel_size=(3, 3),padding='same'))
18 | model.add(Activation('relu'))
19 | model.add(MaxPooling2D(pool_size=(2, 2)))
20 |
21 | model.add(Conv2D(64, (3, 3)))
22 | model.add(Activation('relu'))
23 | model.add(MaxPooling2D(pool_size=(2, 2)))
24 |
25 | model.add(Conv2D(128,kernel_size=(3, 3), padding='same'))
26 | model.add(Activation('relu'))
27 | model.add(MaxPooling2D(pool_size=(2, 2)))
28 |
29 | model.add(Conv2D(32, kernel_size=(3, 3)))
30 | model.add(Activation('relu'))
31 | model.add(MaxPooling2D(pool_size=(2, 2)))
32 |
33 | model.add(GlobalMaxPooling2D())
34 |
35 | model.add(Dense(512)) #512
36 | model.add(Activation('relu'))
37 |
38 | model.add(Dense(30))
39 | return model
40 |
41 | model = cnn()
42 | model.load_weights(filepath='weights.75-0.74206.hdf5')
43 |
44 | # load a image
45 | image_path='lena.jpg'
46 | img = image.load_img(image_path,grayscale=True, target_size=(96, 96))
47 |
48 | # preprocessing
49 | x = image.img_to_array(img)
50 | x /= 255
51 | x = np.expand_dims(x, axis=0)
52 |
53 | # time test
54 | t1 = time.time()
55 | pred = model.predict(x)
56 | t2 = time.time()
57 | t = t2 - t1
58 | print('{:0.5f}s'.format(t))
59 |
60 | points = pred[0]*48+48
61 | points = points.clip(0, 96)
62 | print(points)
63 |
64 | fig = plt.figure()
65 | axis = fig.add_subplot(111)
66 | # plot face
67 | axis.imshow(img, cmap='gray')
68 | axis.scatter(points[0::2],points[1::2],c = 'r',marker = 'o')
69 | plt.show()
70 |
71 |
--------------------------------------------------------------------------------
/lena.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bruceyang2012/Face-Alignment-with-simple-cnn/6f6615245339f8edf877966b5e633a6326ec5856/lena.jpg
--------------------------------------------------------------------------------
/predict.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import pandas as pd
3 | from train import load,load2d
4 | from keras.models import Sequential
5 | from keras.layers import Dense, Conv2D, Activation, Dropout, MaxPooling2D, Flatten, GlobalMaxPooling2D, BatchNormalization
6 | from keras.optimizers import SGD, Adam
7 | import matplotlib.pyplot as plt
8 |
9 | def plot_sample(x, y, axis):
10 | img = x.reshape(96, 96)
11 | axis.imshow(img, cmap='gray')
12 | axis.scatter(y[0::2]*48+48, y[1::2]*48+48, marker='x', s=10)
13 |
14 | model = Sequential()
15 |
16 | model.add(BatchNormalization(input_shape=(96,96,1)))
17 |
18 | model.add(Conv2D(32, kernel_size=(3, 3),padding='same'))
19 | model.add(Activation('relu'))
20 | model.add(MaxPooling2D(pool_size=(2, 2)))
21 |
22 | model.add(Conv2D(64, (3, 3)))
23 | model.add(Activation('relu'))
24 | model.add(MaxPooling2D(pool_size=(2, 2)))
25 |
26 | model.add(Conv2D(128,kernel_size=(3, 3), padding='same'))
27 | model.add(Activation('relu'))
28 | model.add(MaxPooling2D(pool_size=(2, 2)))
29 |
30 | model.add(Conv2D(32, kernel_size=(3, 3)))
31 | model.add(Activation('relu'))
32 | model.add(MaxPooling2D(pool_size=(2, 2)))
33 |
34 | model.add(GlobalMaxPooling2D())
35 |
36 | model.add(Dense(512)) #512
37 | model.add(Activation('relu'))
38 |
39 | model.add(Dense(30))
40 |
41 | model.load_weights(filepath='weights.75-0.74206.hdf5')
42 | #predict test images
43 | X_test, _ = load2d(test=True)
44 | y_test = model.predict(X_test)
45 |
46 | fig = plt.figure(figsize=(6,6))
47 | for i in range(25):
48 | axis = fig.add_subplot(5,5,i+1,xticks=[],yticks=[])
49 | plot_sample(X_test[i], y_test[i], axis)
50 | plt.show()
51 | fig.savefig('predicted.png')
52 |
--------------------------------------------------------------------------------
/predicted.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bruceyang2012/Face-Alignment-with-simple-cnn/6f6615245339f8edf877966b5e633a6326ec5856/predicted.png
--------------------------------------------------------------------------------
/train.py:
--------------------------------------------------------------------------------
1 | import os
2 | import numpy as np
3 | import pandas as pd
4 | from sklearn.utils import shuffle
5 | from sklearn.model_selection import train_test_split
6 | import matplotlib.pyplot as plt
7 |
8 | from keras.models import Sequential
9 | from keras.layers import Dense, Conv2D, Activation, Dropout, MaxPooling2D, Flatten, GlobalMaxPooling2D, BatchNormalization
10 | from keras.optimizers import SGD, Adam
11 | from keras.callbacks import ModelCheckpoint, EarlyStopping
12 | from keras.preprocessing.image import ImageDataGenerator
13 |
14 | FTRAIN = './data/training/training.csv'
15 | FTEST = './data/test/test.csv'
16 |
17 | def load(test=False, cols=None):
18 |
19 | fname = FTEST if test else FTRAIN
20 | df = pd.read_csv(os.path.expanduser(fname))
21 |
22 | df['Image'] = df['Image'].apply(lambda im: np.fromstring(im, sep=' '))
23 |
24 | if cols:
25 | df = df[list(cols)+['Image']]
26 |
27 | print (df.count())
28 | df = df.dropna()
29 |
30 | X = np.vstack(df['Image'].values)/255
31 | X = X.astype(np.float32)
32 |
33 | if not test:
34 | y = df[df.columns[:-1]].values
35 | y = (y-48)/48
36 | X, y = shuffle(X, y, random_state=42)
37 | y = y.astype(np.float32)
38 | else:
39 | y = None
40 |
41 | return X, y
42 |
43 | def load2d(test=False, cols=None):
44 |
45 | X, y = load(test, cols)
46 | X = X.reshape(-1,96,96,1)
47 |
48 | return X, y
49 |
50 | def plot_sample(x, y, axis):
51 |
52 | img = x.reshape(96, 96)
53 | axis.imshow(img, cmap='gray')
54 | axis.scatter(y[0::2]*48+48, y[1::2]*48+48, marker='x', s=10)
55 |
56 | if __name__ == "__main__":
57 |
58 | X, y = load2d(test=False)
59 |
60 | print ("X.shape", X.shape)
61 | print ("y.shape", y.shape)
62 |
63 | x_train, x_test, y_train, y_test = train_test_split(X,y,test_size=0.25,random_state=10)
64 | print ("x_train.shape", x_train.shape)
65 | print ("y_train.shape", y_train.shape)
66 | datagen = ImageDataGenerator() # you can do data augmentation from this function
67 |
68 | # my model(bn)
69 | model = Sequential()
70 |
71 | model.add(BatchNormalization(input_shape=(96,96,1)))
72 |
73 | model.add(Conv2D(32, kernel_size=(3, 3),padding='same'))
74 | model.add(Activation('relu'))
75 | model.add(MaxPooling2D(pool_size=(2, 2)))
76 |
77 | model.add(Conv2D(64, (3, 3)))
78 | model.add(Activation('relu'))
79 | model.add(MaxPooling2D(pool_size=(2, 2)))
80 |
81 | model.add(Conv2D(128,kernel_size=(3, 3), padding='same'))
82 | model.add(Activation('relu'))
83 | model.add(MaxPooling2D(pool_size=(2, 2)))
84 |
85 | model.add(Conv2D(32, kernel_size=(3, 3)))
86 | model.add(Activation('relu'))
87 | model.add(MaxPooling2D(pool_size=(2, 2)))
88 |
89 | model.add(GlobalMaxPooling2D())
90 |
91 | model.add(Dense(512)) #512
92 | model.add(Activation('relu'))
93 |
94 | model.add(Dense(30))
95 |
96 | adam = Adam(lr=0.0001, beta_1=0.9, beta_2=0.999, epsilon=1e-08, decay=0.0)
97 | model.compile(loss='mean_squared_error', optimizer=adam, metrics=['accuracy'])
98 |
99 | check = ModelCheckpoint("weights.{epoch:02d}-{val_acc:.5f}.hdf5", monitor='val_acc', verbose=1, save_best_only=True, save_weights_only=True, mode='auto')
100 | early = EarlyStopping(monitor='val_acc', min_delta=0, patience=20, verbose=1, mode='max')
101 | hist = model.fit_generator(datagen.flow(x_train, y_train, batch_size=32),steps_per_epoch=len(x_train)/32,epochs=100,callbacks=[check,early],validation_data=(x_test,y_test))
102 |
103 | #display loss
104 | f=plt.figure()
105 | plt.plot(hist.history['loss'], linewidth=3, label='train')
106 | plt.plot(hist.history['val_loss'], linewidth=3, label='valid')
107 | plt.grid()
108 | plt.legend()
109 | plt.xlabel('epoch')
110 | plt.ylabel('loss')
111 | plt.yscale('log')
112 | plt.show()
113 | f.savefig('loss.png')
114 |
115 |
--------------------------------------------------------------------------------