基于卷积神经网络的笑脸数据集(GENKI4K)训练
一、人脸图像特征提取的方法
1.HOG 特征
方向梯度直方图(Histogram of Oriented Gradient, HOG)特征是一种在计算机视觉和图
像处理中用来进行物体检测的特征描述子。它通过计算和统计图像局部区域的梯度方向
直方图来构成特征。Hog 特征结合 SVM 分类器已经被广泛应用于图像识别中,尤其在
行人检测中获得了极大的成功。需要提醒的是,HOG+SVM 进行行人检测的方法是法国
研究人员 Dalal 在 2005 的 CVPR 上提出的,而如今虽然有很多行人检测算法不断提出,
但基本都是以 HOG+SVM 的思路为主。
(1)主要思想:
在一副图像中,局部目标的表象和形状(appearance and shape)能够被梯度或边缘的
方向密度分布很好地描述。(本质:梯度的统计信息,而梯度主要存在于边缘的地方)。
(2)具体的实现方法是:
首先将图像分成小的连通区域,我们把它叫细胞单元。然后采集细胞单元中各像素点的
梯度的或边缘的方向直方图。最后把这些直方图组合起来就可以构成特征描述器。
(3)提高性能:
把这些局部直方图在图像的更大的范围内(我们把它叫区间或 block)进行对比度归一
化(contrast-normalized),所采用的方法是:先计算各直方图在这个区间(block)中
的密度,然后根据这个密度对区间中的各个细胞单元做归一化。通过这个归一化后,能
对光照变化和阴影获得更好的效果。
(4)优点:
与其他的特征描述方法相比,HOG 有很多优点。首先,由于 HOG 是在图像的局部方格
单元上操作,所以它对图像几何的和光学的形变都能保持很好的不变性,这两种形变只
会出现在更大的空间领域上。其次,在粗的空域抽样、精细的方向抽样以及较强的局部
光学归一化等条件下,只要行人大体上能够保持直立的姿势,可以容许行人有一些细微
的肢体动作,这些细微的动作可以被忽略而不影响检测效果。因此 HOG 特征是特别适
合于做图像中的人体检测的。
HOG 特征提取方法就是将一个 image(你要检测的目标或者扫描窗口):
1)灰度化(将图像看做一个 x,y,z(灰度)的三维图像);
2)采用 Gamma 校正法对输入图像进行颜色空间的标准化(归一化);目的是调节图
像的对比度,降低图像局部的阴影和光照变化所造成的影响,同时可以抑制噪音的干扰;
3)计算图像每个像素的梯度(包括大小和方向);主要是为了捕获轮廓信息,同时进
一步弱化光照的干扰。
4)将图像划分成小 cells(例如 66 像素/cell);
5)统计每个 cell 的梯度直方图(不同梯度的个数),即可形成每个 cell 的 descriptor;
6)将每几个 cell 组成一个 block(例如 33 个 cell/block),一个 block 内所有 cell 的
特征 descriptor 串联起来便得到该 block 的 HOG 特征 descriptor。
7)将图像 image 内的所有 block 的 HOG 特征 descriptor 串联起来就可以得到该 image
(你要检测的目标)的 HOG 特征 descriptor 了。这个就是最终的可供分类使用的特征
向量了。
2.Dlib
Dlib 是一个包含机器学习算法的 C++开源工具包。Dlib 可以帮助您创建很多复杂的机器
学习方面的软件来帮助解决实际问题。目前 Dlib 已经被广泛的用在行业和学术领域,包
括机器人,嵌入式设备,移动电话和大型高性能计算环境。
Dlib 的主要特点:
train_dir = os.path.join(base_dir, ‘train’)
os.mkdir(train_dir)
validation_dir = os.path.join(base_dir, ‘validation’)
os.mkdir(validation_dir)
test_dir = os.path.join(base_dir, ‘test’)
os.mkdir(test_dir)
#笑训练图片所在目录
train_smile_dir = os.path.join(train_dir, ‘smile’)
os.mkdir(train_smile_dir)
#不笑训练图片所在目录
train_unsmile_dir = os.path.join(train_dir, ‘unsmile’)
os.mkdir(train_unsmile_dir)
#笑验证图片所在目录
validation_smile_dir = os.path.join(validation_dir, ‘smile’)
os.mkdir(validation_smile_dir)
#不笑验证数据集所在目录
validation_unsmile_dir = os.path.join(validation_dir, ‘unsmile’)
os.mkdir(validation_unsmile_dir)
笑测试数据集所在目录
test_smile_dir = os.path.join(test_dir, ‘smile’)
os.mkdir(test_smile_dir)
不笑测试数据集所在目录
test_unsmile_dir = os.path.join(test_dir, ‘unsmile’)
os.mkdir(test_unsmile_dir)
#将前 1000 张笑图像复制到 train_smile_dir
fnames = [‘smile{}.jpg’.format(i) for i in range(1000)]
for fname in fnames:
src = os.path.join(original_dataset_dir1, fname)
dst = os.path.join(train_smile_dir, fname)
shutil.copyfile(src, dst)
将下 500 张笑图像复制到 validation_smile_dir
fnames = [‘smile{}.jpg’.format(i) for i in range(1000, 1500)]
for fname in fnames:
src = os.path.join(original_dataset_dir1, fname)
dst = os.path.join(validation_smile_dir, fname)
shutil.copyfile(src, dst)
#将下 500 张笑图像复制到 test_smile_dir
fnames = [‘smile{}.jpg’.format(i) for i in range(1000, 1500)]
for fname in fnames:
src = os.path.join(original_dataset_dir1, fname)
dst = os.path.join(test_smile_dir, fname)
shutil.copyfile(src, dst)
将前 1000 张不笑图像复制到 train_unsmile_dir
fnames = [‘unsmile{}.jpg’.format(i) for i in range(1000)]
for fname in fnames:
src = os.path.join(original_dataset_dir0, fname)
dst = os.path.join(train_unsmile_dir, fname)
shutil.copyfile(src, dst)
#将 500 张不笑图像复制到 validation_unsmile_dir
fnames = [‘unsmile{}.jpg’.format(i) for i in range(700, 1200)]
for fname in fnames:
src = os.path.join(original_dataset_dir0, fname)
dst = os.path.join(validation_unsmile_dir, fname)
shutil.copyfile(src, dst)
将 500 张不笑图像复制到 test_unsmile_dir
fnames = [‘unsmile{}.jpg’.format(i) for i in range(700, 1200)]
for fname in fnames:
src = os.path.join(original_dataset_dir0, fname)
dst = os.path.join(test_unsmile_dir, fname)
shutil.copyfile(src, dst)
作为健全性检查,让我们计算一下每个训练分组(训练/验证/测试)中有多少张图片:
print(‘total training cat images:’, len(os.listdir(train_smile_dir)))
print(‘total training dog images:’, len(os.listdir(train_unsmile_dir)))
print(‘total validation cat images:’, len(os.listdir(validation_smile_dir)))
print(‘total validation dog images:’, len(os.listdir(validation_unsmile_di
r)))
print(‘total test cat images:’, len(os.listdir(test_smile_dir)))
print(‘total test dog images:’, len(os.listdir(test_unsmile_dir)))
划分的图片数量与前面代码写的一致。
3.构建网络
from keras import layers
from keras import models
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation=‘relu’,
input_shape=(150, 150, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation=‘relu’))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation=‘relu’))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation=‘relu’))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Flatten())
model.add(layers.Dense(512, activation=‘relu’))
model.add(layers.Dense(1, activation=‘sigmoid’))
看一下特征贴图的尺寸如何随每个连续层变化:
model.summary()
4.数据预处理
from keras import optimizers
model.compile(loss=‘binary_crossentropy’,
optimizer=optimizers.RMSprop(lr=1e-4),
metrics=[‘acc’])
from keras.preprocessing.image import ImageDataGenerator
All images will be rescaled by 1./255
train_datagen = ImageDataGenerator(rescale=1./255)
test_datagen = ImageDataGenerator(rescale=1./255)
train_generator = train_datagen.flow_from_directory(
This is the target directory
train_dir,
All images will be resized to 150x150
target_size=(150, 150),
batch_size=20,
Since we use binary_crossentropy loss, we need binary labels
class_mode=‘binary’)
validation_generator = test_datagen.flow_from_directory(
validation_dir,
target_size=(150, 150),
batch_size=20,
class_mode=‘binary’)
for data_batch, labels_batch in train_generator:
print(‘data batch shape:’, data_batch.shape)
print(‘labels batch shape:’, labels_batch.shape)
break
5.训练模型
history = model.fit_generator(
train_generator,
steps_per_epoch=100,
epochs=30,
validation_data=validation_generator,
validation_steps=50)
取部分训练轮数:
保存模型:
model.save(‘smile_and_unsmile_small_1.h5’)
在训练和验证数据上绘制模型的损失和准确性:
import matplotlib.pyplot as plt
acc = history.history[‘acc’]
val_acc = history.history[‘val_acc’]
loss = history.history[‘loss’]
val_loss = history.history[‘val_loss’]
epochs = range(len(acc))
plt.plot(epochs, acc, ‘bo’, label=‘Training acc’)
plt.plot(epochs, val_acc, ‘b’, label=‘Validation acc’)
plt.title(‘Training and validation accuracy’)
plt.legend()
plt.figure()
plt.plot(epochs, loss, ‘bo’, label=‘Training loss’)
plt.plot(epochs, val_loss, ‘b’, label=‘Validation loss’)
plt.title(‘Training and validation loss’)
plt.legend()
plt.show()
6.使用数据增强
数据增强采用了通过现有的训练样本生成更多训练数据的方法,方法是通过许多随机变
换来“增加”样本,以产生看起来可信的图像。目的是模型在训练时不会两次查看完全相
同的图像。这让模型能够观察到数据的更多内容,从而具有更好的泛化能力。
在 Keras 中,这可以通过配置要对 ImageDataGenerator 实例读取的图像执行的许多随 机转换来完成。让我们开始一个例子:
datagen = ImageDataGenerator(
rotation_range=40,
width_shift_range=0.2,
height_shift_range=0.2,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True,
fill_mode=‘nearest’)
看一下我们的增强图像:
This is module with image preprocessing utilities
from keras.preprocessing import image
fnames = [os.path.join(train_smile_dir, fname) for fname in os.listdir(trai
n_smile_dir)]
We pick one image to “augment”
img_path = fnames[3]
Read the image and resize it
img = image.load_img(img_path, target_size=(150, 150))
Convert it to a Numpy array with shape (150, 150, 3)
x = image.img_to_array(img)
Reshape it to (1, 150, 150, 3)
x = x.reshape((1,) + x.shape)
The .flow() command below generates batches of randomly transformed image
s.
It will loop indefinitely, so we need to break
the loop at some point!
i = 0
for batch in datagen.flow(x, batch_size=1):
plt.figure(i)
imgplot = plt.imshow(image.array_to_img(batch[0]))
i += 1
if i % 4 == 0:
break
plt.show()
在紧密连接的分类器之前为模型添加一个 Dropout 层:
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation=‘relu’,
input_shape=(150, 150, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation=‘relu’))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activa