├── 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 | --------------------------------------------------------------------------------