对目标人脸检测+识别(Python + OpenCV + Keras )

北慕城南 提交于 2020-02-27 10:51:49

实现目标:在茫茫人海中一眼相中(在数据库中找出目标人脸)
解决思路:Input > 人脸检测 > 人脸识别 > Output
Input:可上接 视频流 实现实时检测
Output:可下接 人脸检测框 可视化
在这里插入图片描述

所需工具:Python + OpenCV + Keras

Step1:人脸检测

现在有众多包含人脸的照片(数据来源于百度图片),我们要检测出图片中的人脸,并切出来保存。
在这里插入图片描述
包含目标人脸的照片
在这里插入图片描述
进行了两种人脸检测方案的测试,如下:

1·OpenCV_haarcascade特征分类器

优点:识别速度快、对于大图小脸的情况几乎都能够检测到
缺点:误识别情况较惨

Haar识别,输入_待检测图,返回_人脸图和人脸数量

import cv2

pho_add = 'F:/.jpg'
haar_path = 'F:/haarcascade_frontalface_default.xml'

def face_detector_haar(img):

    # 将测试图像转换为灰度图像,因为opencv人脸检测器需要灰度图像
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # 加载OpenCV人脸检测分类器Haar
    face_cascade = cv2.CascadeClassifier(haar_path)

    # 检测多尺度图像,返回值是一张脸部区域信息的列表(x,y,宽,高)
    faces = face_cascade.detectMultiScale(gray, scaleFactor=1.2, minNeighbors=5)

    # 如果未检测到面部
    if (len(faces) == 0):
        return None, None
    # 假设有好几张脸,xy为左上角坐标,wh为矩形的宽高
    else:
        face_nums = len(faces)
        imgs = []
        for (x, y, w, h) in faces:
            imgs.append(img[y:y + w, x:x + h])
        # 返回图像的正面部分
        return imgs, face_nums
        
img = cv2.imread(pho_add)
pho, nums = face_detector_haar(img)

print('There are', nums, 'faces')
# 显示两个图像
for show in pho:
    cv2.imshow('Face', show)
    cv2.waitKey(0)
cv2.destroyAllWindows()

详细原理传送门:Haar人脸检测详细原理
下载传送门:下载 haarcascade_frontalface_default.xml 文件
在这里OpenCV还提供面部、眼睛、等分类器

2·基于caffe预训练的Dnn模型

优点:更准、误识别情况少(DL果然腻害)

Dnn预训练模型,输入_待检测图,返回_原图和人脸图

import numpy as np
import cv2
from copy import deepcopy

modelPath = "F:/deploy.prototxt.txt"
weightPath = "F:/res10_300x300_ssd_iter_140000.caffemodel"
confidence = 0.3 # 置信度参数,高于此数认为是人脸
def face_detector_dnn(image):

    image_1 = deepcopy(image)
    net = cv2.dnn.readNetFromCaffe(modelPath, weightPath)

    # 输入图片并重置大小符合模型的输入要求
    (h, w) = image.shape[:2]  #获取图像的高和宽,用于画图
    blob = cv2.dnn.blobFromImage(cv2.resize(image, (300, 300)), 1.0,
        (300, 300), (104.0, 177.0, 123.0))

    net.setInput(blob)
    detections = net.forward()  # 预测结果
    face_img = []
    # 可视化:在原图加上标签和框
    for i in range(0, detections.shape[2]):
        # 获得置信度
        res_confidence = detections[0, 0, i, 2]

        # 过滤掉低置信度的像素
        if res_confidence > confidence :
            # 获得框的位置
            box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])
            (startX, startY, endX, endY) = box.astype("int")
            # 在图片上写上标签
            text = "{:.2f}%".format(res_confidence * 100)
            # 如果检测脸部在左上角,则把标签放在图片内,否则放在图片上面
            y = startY - 10 if startY - 10 > 10 else startY + 10
            cv2.rectangle(image, (startX, startY), (endX, endY),
                (0, 255, 0), 2)
            cv2.putText(image, text, (startX, y),
                cv2.FONT_HERSHEY_SIMPLEX, 0.45, (0, 0, 255), 2)

            if len(deepcopy(image_1)[startY:endY, startX:endX]) != 0:
                face_img.append(deepcopy(image_1)[startY:endY, startX:endX])

    if len(face_img) == 0:
        return None, None
    else:
        return image, face_img

使用OpenCV的DNN模块以及Caffe模型,必须要有
.prototxt 文件 它定义了模型的结构 点击这里下载
.caffemodel 文件 它保存了预训练后各层的权重数据 提取码:6axy

统一尺寸

检测出的人脸图片,现在统一尺寸(如:227X227像素),主要是保证所有数据都可以输入到后面的CNN进行训练。
统一尺寸时需要保证,图像不会被扭曲变形,在多余的空白处用纯黑色填充。

def resize_image(image, height=600, width=600):

    top, bottom, left, right = (0, 0, 0, 0)

    # 获取图像尺寸
    h, w, _ = image.shape
    # 对于长宽不相等的图片,找到最长的一边
    longest_edge = max(h, w)
    # 计算短边需要增加多上像素宽度使其与长边等长
    if h < longest_edge:
        dh = longest_edge - h
        top = dh // 2
        bottom = dh - top
    elif w < longest_edge:
        dw = longest_edge - w
        left = dw // 2
        right = dw - left
    else:
        pass
    # RGB颜色
    BLACK = [0, 0, 0]
    # 给图像增加边界,是图片长、宽等长,cv2.BORDER_CONSTANT指定边界颜色由value指定
    constant = cv2.copyMakeBorder(image, top, bottom, left, right, cv2.BORDER_CONSTANT, value=BLACK)

    # 调整图像大小并返回
    return cv2.resize(constant, (height, width))

Step2:人脸识别(AlexNet)

现在有了提取出的人脸数据,我们要进行识别,看看哪些是目标,哪些是Others
在这里插入图片描述
为了提高识别准确性,我们选用知名的CNN(卷积神经网络)AlexNet模型进行训练。

AlexNet是2012年ImageNet竞赛冠军获得者Hinton和他的学生Alex Krizhevsky设计的。

def AlexNet(num_classses=2):
    ##构建网络
    model = Sequential()
    model.add(ZeroPadding2D((2, 2), input_shape=(227, 227, 3)))  
    model.add(Lambda(lambda x: x / 255.0))  # 归一化
    model.add(Convolution2D(64, (11, 11), strides=(4, 4), activation='relu'))
    model.add(MaxPooling2D((3, 3), strides=(2, 2)))

    model.add(ZeroPadding2D((2, 2)))
    model.add(Convolution2D(192, (5, 5), activation='relu'))
    model.add(MaxPooling2D((3, 3), strides=(2, 2)))

    model.add(ZeroPadding2D((1, 1)))
    model.add(Convolution2D(384, (3, 3), activation='relu'))
    model.add(ZeroPadding2D((1, 1)))
    model.add(Convolution2D(256, (3, 3), activation='relu'))
    model.add(ZeroPadding2D((1, 1)))
    model.add(Convolution2D(256, (3, 3), activation='relu'))
    model.add(MaxPooling2D((3, 3), strides=(2, 2)))

    model.add(Flatten())
    model.add(Dense(4096, activation='relu'))
    model.add(Dropout(0.5))
    model.add(Dense(4096, activation='relu'))
    model.add(Dropout(0.5))
    model.add(Dense(num_classses, activation='softmax'))

    return model

数据集准备:

我按照这样放置训练数据,便于打标签。
在这里插入图片描述
对于目标文件作独热标签,目标脸[0.,1.],其他脸[1.,0.]

def load_data():
    data_x, data_y = [], []

    all_object_imgs = os.listdir(pho_object_add) 
    for img in all_object_imgs:
        image = cv2.imread(pho_object_add+'/'+img)
        data_x.append(image)
        data_y.append([1])  # [0.,1.]

    all_others_imgs = os.listdir(pho_others_add)
    for img in all_others_imgs:
        image = cv2.imread(pho_others_add+'/'+img)
        data_x.append(image)
        data_y.append([0])  # [1.,0.]

    data_y = np_utils.to_categorical(data_y, 2)  # One-Hot encoding
    return np.array(data_x), np.array(data_y)

神经网络需要数值进行计算,需要对字符型类别标签进行编码,最容易想到的就是把他们编码成1、2、3…这种,但是这样也就出现了强行给它们定义了大小的问题,因为如果一个类别是2,一个是4,他们之间就会有两倍的关系,但是实际上他们之间并没有直接的倍数关系,所以这里使用one-hot编码规则,做到所有标签的平等化。

设置训练:

奈何我的电脑显存堪忧,一次性吞吐不了太多的数据,只好用generator的方式,一点一点的从数据池里拿小批量数据喂给网络

def generator(samples_X, samples_Y, batch_size=32):
    num_samples = len(samples_X)
    while 1:  # Loop forever so the generator never terminates
        for offset in range(0, num_samples, batch_size):
            batch_samples_X = samples_X[offset:offset+batch_size]
            batch_samples_Y = samples_Y[offset:offset+batch_size]

            yield sklearn.utils.shuffle(batch_samples_X, batch_samples_Y)

这里用SGD优化器,SGD(随机梯度下降),对于大量的图片数据库,SGD在执行梯度下降时,只需要抽取batch_size的样本放入神经网络计算。所以避免出现程序报错,出现电脑显存不足的情况。
用mse(均方差)来计算loss,计划训练300各回合,如果持续30个回合稳定便自动保存网络。(很实用!)

def train():
    model = AlexNet()
    # Load data
    X, Y = load_data()
    # split train and test data
    X_train, X_test, Y_train, Y_test = train_test_split(
        X, Y, test_size=0.3)
    # compile and train the model using the generator function
    train_generator = generator(X_train, Y_train, batch_size=batch_size)
    validation_generator = generator(X_test, Y_test, batch_size=batch_size)
    # 训练计划
    sgd = keras.optimizers.SGD(lr=0.01, decay=1e-6, momentum=0.7, nesterov=True)
    model.compile(loss='mse', optimizer=sgd)

    callbacks_list = [
        keras.callbacks.EarlyStopping(monitor='val_loss', patience=30), 
        keras.callbacks.ModelCheckpoint(
            filepath=net_path + '/' + 'AlexNet_1.h5',
            monitor='val_loss', save_best_only=True)
    ]
    history = model.fit_generator(train_generator,
                                  steps_per_epoch=len(Y_train) / batch_size,
                                  validation_data=validation_generator,
                                  validation_steps=len(Y_test) / batch_size,
                                  epochs=300, verbose=2,
                                  callbacks=callbacks_list, shuffle=True)

    return history

起身离座,让显卡活动活动。

验证检测:

训练好后,我们的网络就保存成‘.h5’的文件,既包含网络的结构也包含训练好的weight值等等。

def one_hot_back(list):

    if list[0] > list[1]:
        return False   # [1., 0.]
    else:
        return True   # [0., 1.]
        
def display_error(imgs):
    for img in imgs:
        cv2.imshow('error', img)
        cv2.waitKey(0)
        
def validation():

    model = load_model(net_path + '/' + 'AlexNet_1.h5')
    # Load data
    X, Y = load_data()
    predict = model.predict(X)
    error = []
    for i in range(len(Y)):
        if one_hot_back(Y[i]) != one_hot_back(predict[i]):
            error.append(X[i])
    display_error(error)
    print('The total Face are {},There are {} face error!\n'
          'The Accuracy is {}'
          .format(len(Y), len(error), 1.-len(error)/len(Y)))

打印出错误的图像,并且计算准确率。
————————————————————————
实际测试中在训练了240个回合后,准确率能够达到98%,并且在数据集中加入新的照片也能正确识别。效果可期!
在这里插入图片描述
若有帮助,感谢点赞👍

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!