人工智能小例子(二)-基于keras+openCV的人脸识别


目前keras是对人工智能来说,入门比较好的一款中间件,屏蔽了很多参数配置和实现细节,直接一层层进行网络搭建就可以。最近一直在学习相关知识,但是一直也没有啥实用的功能出现,在网上有个例子,一下子就吸引住了我,使用keras迅速搭建一套人工智能系统,输入自己的照片和他人照片,训练其识别出自己,然后使用摄像头验证。

废话不多说,先上结果图:



是不是很6,其实实现起来还是依赖了很多网上的大牛,现在跟着我,咱们一步步实现这些:


前置知识:

人工神经网络

keras中文文档

tips:跟着一步步做也能实现,建议先看前置知识;


环境配置:python3.3 ;keras3;win10,OpenCV3,tensorflow

源码下载:https://github.com/xvshu/kera-demo-findme.git

1,识别人脸(OpenCV3)

import cv2

def CatchUsbVideo(window_name, camera_idx):
    cv2.namedWindow(window_name)

    #视频来源,可以来自一段已存好的视频,也可以直接来自USB摄像头
    cap = cv2.VideoCapture(camera_idx)

    #告诉OpenCV使用人脸识别分类器
    classfier = cv2.CascadeClassifier("D:/opencv/opencv/sources/data/haarcascades/haarcascade_frontalface_alt2.xml")

    #识别出人脸后要画的边框的颜色,RGB格式
    color = (0, 255, 0)

    while cap.isOpened():
        ok, frame = cap.read() #读取一帧数据
        if not ok:
            break

            #将当前帧转换成灰度图像
        grey = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

        #人脸检测,1.2和2分别为图片缩放比例和需要检测的有效点数
        faceRects = classfier.detectMultiScale(grey, scaleFactor = 1.2, minNeighbors = 3, minSize = (32, 32))
        if len(faceRects) > 0:            #大于0则检测到人脸
            for faceRect in faceRects:  #单独框出每一张人脸
                x, y, w, h = faceRect
                cv2.rectangle(frame, (x - 10, y - 10), (x + w + 10, y + h + 10), color, 2)

        #显示图像
        cv2.imshow(window_name, frame)
        c = cv2.waitKey(10)
        if c & 0xFF == ord('q'):
            break

            #释放摄像头并销毁所有窗口
    cap.release()
    cv2.destroyAllWindows()

if __name__ == '__main__':
    CatchUsbVideo("find face", 0)

CatchUsbVideo(window_name, camera_idx),中camera_idx是自己的电脑第几个摄像头,我没有扩展,一般就选0,如果你扩展了usb或者有其他摄像头,一类类推就好



2,准备训练数据

2.1准备自己的脸数据

这个好办,我们直接使用上边使用到的技术,自动截取图像就好,稍作添加:

#-*- coding: utf-8 -*-

import cv2

def CatchPICFromVideo(window_name, camera_idx, catch_pic_num, path_name):
    cv2.namedWindow(window_name)

    #视频来源,可以来自一段已存好的视频,也可以直接来自USB摄像头
    cap = cv2.VideoCapture(camera_idx)

    #告诉OpenCV使用人脸识别分类器
    classfier = cv2.CascadeClassifier("D:/opencv/opencv/sources/data/haarcascades/haarcascade_frontalface_alt2.xml")

    #识别出人脸后要画的边框的颜色,RGB格式
    color = (0, 255, 0)

    num = 0
    while cap.isOpened():
        ok, frame = cap.read() #读取一帧数据
        if not ok:
            break

        grey = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)  #将当前桢图像转换成灰度图像

        #人脸检测,1.2和2分别为图片缩放比例和需要检测的有效点数
        faceRects = classfier.detectMultiScale(grey, scaleFactor = 1.2, minNeighbors = 3, minSize = (32, 32))
        if len(faceRects) > 0:          #大于0则检测到人脸
            for faceRect in faceRects:  #单独框出每一张人脸
                x, y, w, h = faceRect

                #将当前帧保存为图片
                img_name = '%s/%d.jpg'%(path_name, num)
                image = frame[y - 10: y + h + 10, x - 10: x + w + 10]
                cv2.imwrite(img_name, image)

                num += 1
                if num > (catch_pic_num):   #如果超过指定最大保存数量退出循环
                    break

                #画出矩形框
                cv2.rectangle(frame, (x - 10, y - 10), (x + w + 10, y + h + 10), color, 2)

                #显示当前捕捉到了多少人脸图片了,这样站在那里被拍摄时心里有个数,不用两眼一抹黑傻等着
                font = cv2.FONT_HERSHEY_SIMPLEX
                cv2.putText(frame,'num:%d' % (num),(x + 30, y + 30), font, 1, (255,0,255),4)

                #超过指定最大保存数量结束程序
        if num > (catch_pic_num): break

        #显示图像
        cv2.imshow(window_name, frame)
        c = cv2.waitKey(10)
        if c & 0xFF == ord('q'):
            break

            #释放摄像头并销毁所有窗口
    cap.release()
    cv2.destroyAllWindows()

if __name__ == '__main__':
        CatchPICFromVideo("get face", 0, 150, "D:/test/face-xvshu")


2.2准备他人脸数据

这个数据可以继续使用2.1的方法,也可以网上下载,一般建议从网上下载不同人的不同脸数据,方便keras更好识别出自己脸特征,这里我使用自己同事的照片,出现loss动荡,使用人脸库的数据,就完美了。

我自己使用的训练人脸照片:许恕百度网盘

3,构建网络

3.1数据模型

新建face_dataset.py文件

# -*- coding: utf-8 -*-

import os
import sys
import numpy as np
import cv2

IMAGE_SIZE = 64

#按照指定图像大小调整尺寸
def resize_image(image, height = IMAGE_SIZE, width = IMAGE_SIZE):
    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))

#读取训练数据
images = []
labels = []
def read_path(path_name):
    for dir_item in os.listdir(path_name):
        #从初始路径开始叠加,合并成可识别的操作路径
        full_path = os.path.abspath(os.path.join(path_name, dir_item))

        if os.path.isdir(full_path):    #如果是文件夹,继续递归调用
            read_path(full_path)
        else:   #文件
            if dir_item.endswith('.jpg'):
                image = cv2.imread(full_path)
                image = resize_image(image, IMAGE_SIZE, IMAGE_SIZE)

                #放开这个代码,可以看到resize_image()函数的实际调用效果
                #cv2.imwrite('1.jpg', image)

                images.append(image)
                labels.append(path_name)

    return images,labels


#从指定路径读取训练数据
def load_dataset(path_name):
    images,labels = read_path(path_name)

    #将输入的所有图片转成四维数组,尺寸为(图片数量*IMAGE_SIZE*IMAGE_SIZE*3)
    #我和闺女两个人共1200张图片,IMAGE_SIZE为64,故对我来说尺寸为1200 * 64 * 64 * 3
    #图片为64 * 64像素,一个像素3个颜色值(RGB)
    images = np.array(images)
    print(images.shape)

    #标注数据,'me'文件夹下都是我的脸部图像,全部指定为0,另外一个文件夹下是闺女的,全部指定为1
    labels = np.array([0 if label.endswith('xvshu_me_meeee') else 1 for label in labels])

    return images, labels

if __name__ == '__main__':
    images, labels = load_dataset("./data")
    print("load over");


在相同路径下新建文件夹data,data下新建文件夹xvshu_me_meeee,这里存放的是自己的照片,新建other,存放的是其他人的照片(这里我放的是第三方数据集



3.2搭建神经网络

新建face_train.py

#-*- coding: utf-8 -*-
import random

import numpy as np
from sklearn.cross_validation import train_test_split
from keras.preprocessing.image import ImageDataGenerator
from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation, Flatten
from keras.layers import Convolution2D, MaxPooling2D
from keras.optimizers import SGD
from keras.utils import np_utils
from keras.models import load_model
from keras import backend as K

from face_dataset import load_dataset, resize_image, IMAGE_SIZE


class Dataset:
    def __init__(self, path_name):
        #训练集
        self.train_images = None
        self.train_labels = None

        #验证集
        self.valid_images = None
        self.valid_labels = None

        #测试集
        self.test_images  = None
        self.test_labels  = None

        #数据集加载路径
        self.path_name    = path_name

        #当前库采用的维度顺序
        self.input_shape = None

    #加载数据集并按照交叉验证的原则划分数据集并进行相关预处理工作
    def load(self, img_rows = IMAGE_SIZE, img_cols = IMAGE_SIZE,
             img_channels = 3, nb_classes = 2):
        #加载数据集到内存
        images, labels = load_dataset(self.path_name)

        train_images, valid_images, train_labels, valid_labels = train_test_split(images, labels, test_size = 0.3, random_state = random.randint(0, 100))
        _, test_images, _, test_labels = train_test_split(images, labels, test_size = 0.5, random_state = random.randint(0, 100))

        #当前的维度顺序如果为'th',则输入图片数据时的顺序为:channels,rows,cols,否则:rows,cols,channels
        #这部分代码就是根据keras库要求的维度顺序重组训练数据集
        if K.image_dim_ordering() == 'th':
            train_images = train_images.reshape(train_images.shape[0], img_channels, img_rows, img_cols)
            valid_images = valid_images.reshape(valid_images.shape[0], img_channels, img_rows, img_cols)
            test_images = test_images.reshape(test_images.shape[0], img_channels, img_rows, img_cols)
            self.input_shape = (img_channels, img_rows, img_cols)
        else:
            train_images = train_images.reshape(train_images.shape[0], img_rows, img_cols, img_channels)
            valid_images = valid_images.reshape(valid_images.shape[0], img_rows, img_cols, img_channels)
            test_images = test_images.reshape(test_images.shape[0], img_rows, img_cols, img_channels)
            self.input_shape = (img_rows, img_cols, img_channels)

            #输出训练集、验证集、测试集的数量
            print(train_images.shape[0], 'train samples')
            print(valid_images.shape[0], 'valid samples')
            print(test_images.shape[0], 'test samples')

            #我们的模型使用categorical_crossentropy作为损失函数,因此需要根据类别数量nb_classes将
            #类别标签进行one-hot编码使其向量化,在这里我们的类别只有两种,经过转化后标签数据变为二维
            train_labels = np_utils.to_categorical(train_labels, nb_classes)
            valid_labels = np_utils.to_categorical(valid_labels, nb_classes)
            test_labels = np_utils.to_categorical(test_labels, nb_classes)

            #像素数据浮点化以便归一化
            train_images = train_images.astype('float32')
            valid_images = valid_images.astype('float32')
            test_images = test_images.astype('float32')

            #将其归一化,图像的各像素值归一化到0~1区间
            train_images /= 255.0
            valid_images /= 255.0
            test_images /= 255.0

            self.train_images = train_images
            self.valid_images = valid_images
            self.test_images  = test_images
            self.train_labels = train_labels
            self.valid_labels = valid_labels
            self.test_labels  = test_labels

#CNN网络模型类
class Model:
    def __init__(self):
        self.model = None

    #建立模型
    def build_model(self, dataset, nb_classes = 2):
        #构建一个空的网络模型,它是一个线性堆叠模型,各神经网络层会被顺序添加,专业名称为序贯模型或线性堆叠模型
        self.model = Sequential()

        #以下代码将顺序添加CNN网络需要的各层,一个add就是一个网络层
        self.model.add(Convolution2D(32, 3, 3, border_mode='same',
                                     input_shape = dataset.input_shape))    #1 2维卷积层
        self.model.add(Activation('relu'))                                  #2 激活函数层

        self.model.add(Convolution2D(32, 3, 3))                             #3 2维卷积层
        self.model.add(Activation('relu'))                                  #4 激活函数层

        self.model.add(MaxPooling2D(pool_size=(2, 2)))                      #5 池化层
        self.model.add(Dropout(0.25))                                       #6 Dropout层

        self.model.add(Convolution2D(64, 3, 3, border_mode='same'))         #7  2维卷积层
        self.model.add(Activation('relu'))                                  #8  激活函数层

        self.model.add(Convolution2D(64, 3, 3))                             #9  2维卷积层
        self.model.add(Activation('relu'))                                  #10 激活函数层

        self.model.add(MaxPooling2D(pool_size=(2, 2)))                      #11 池化层
        self.model.add(Dropout(0.25))                                       #12 Dropout层

        self.model.add(Flatten())                                           #13 Flatten层
        self.model.add(Dense(512))                                          #14 Dense层,又被称作全连接层
        self.model.add(Activation('relu'))                                  #15 激活函数层
        self.model.add(Dropout(0.5))                                        #16 Dropout层
        self.model.add(Dense(nb_classes))                                   #17 Dense层
        self.model.add(Activation('softmax'))                               #18 分类层,输出最终结果

        #输出模型概况
        self.model.summary()

    #训练模型
    def train(self, dataset, batch_size = 20, nb_epoch = 100, data_augmentation = True):
        sgd = SGD(lr = 0.01, decay = 1e-6,
                  momentum = 0.9, nesterov = True) #采用SGD+momentum的优化器进行训练,首先生成一个优化器对象
        self.model.compile(loss='categorical_crossentropy',
                           optimizer=sgd,
                           metrics=['accuracy'])   #完成实际的模型配置工作

        #不使用数据提升,所谓的提升就是从我们提供的训练数据中利用旋转、翻转、加噪声等方法创造新的
        #训练数据,有意识的提升训练数据规模,增加模型训练量
        if not data_augmentation:
            self.model.fit(dataset.train_images,
                           dataset.train_labels,
                           batch_size = batch_size,
                           nb_epoch = nb_epoch,
                           validation_data = (dataset.valid_images, dataset.valid_labels),
                           shuffle = True)
        #使用实时数据提升
        else:
            #定义数据生成器用于数据提升,其返回一个生成器对象datagen,datagen每被调用一
            #次其生成一组数据(顺序生成),节省内存,其实就是python的数据生成器
            datagen = ImageDataGenerator(
                featurewise_center = False,             #是否使输入数据去中心化(均值为0),
                samplewise_center  = False,             #是否使输入数据的每个样本均值为0
                featurewise_std_normalization = False,  #是否数据标准化(输入数据除以数据集的标准差)
                samplewise_std_normalization  = False,  #是否将每个样本数据除以自身的标准差
                zca_whitening = False,                  #是否对输入数据施以ZCA白化
                rotation_range = 20,                    #数据提升时图片随机转动的角度(范围为0~180)
                width_shift_range  = 0.2,               #数据提升时图片水平偏移的幅度(单位为图片宽度的占比,0~1之间的浮点数)
                height_shift_range = 0.2,               #同上,只不过这里是垂直
                horizontal_flip = True,                 #是否进行随机水平翻转
                vertical_flip = False)                  #是否进行随机垂直翻转

            #计算整个训练样本集的数量以用于特征值归一化、ZCA白化等处理
            datagen.fit(dataset.train_images)

            #利用生成器开始训练模型
            self.model.fit_generator(datagen.flow(dataset.train_images, dataset.train_labels,
                                                  batch_size = batch_size),
                                     samples_per_epoch = dataset.train_images.shape[0],
                                     nb_epoch = nb_epoch,
                                     validation_data = (dataset.valid_images, dataset.valid_labels))

    MODEL_PATH = './me.face.model.h5'
    def save_model(self, file_path = MODEL_PATH):
        self.model.save(file_path)

    def load_model(self, file_path = MODEL_PATH):
        self.model = load_model(file_path)

    def evaluate(self, dataset):
        score = self.model.evaluate(dataset.test_images, dataset.test_labels, verbose = 1)
        print("%s: %.2f%%" % (self.model.metrics_names[1], score[1] * 100))

    #识别人脸
    def face_predict(self, image):
        #依然是根据后端系统确定维度顺序
        if K.image_dim_ordering() == 'th' and image.shape != (1, 3, IMAGE_SIZE, IMAGE_SIZE):
            image = resize_image(image)                             #尺寸必须与训练集一致都应该是IMAGE_SIZE x IMAGE_SIZE
            image = image.reshape((1, 3, IMAGE_SIZE, IMAGE_SIZE))   #与模型训练不同,这次只是针对1张图片进行预测
        elif K.image_dim_ordering() == 'tf' and image.shape != (1, IMAGE_SIZE, IMAGE_SIZE, 3):
            image = resize_image(image)
            image = image.reshape((1, IMAGE_SIZE, IMAGE_SIZE, 3))

            #浮点并归一化
        image = image.astype('float32')
        image /= 255.0

        #给出输入属于各个类别的概率,我们是二值类别,则该函数会给出输入图像属于0和1的概率各为多少
        result = self.model.predict_proba(image)
        print('result:', result)

        #给出类别预测:0或者1
        result = self.model.predict_classes(image)

        #返回类别预测结果
        return result[0]

if __name__ == '__main__':
    dataset = Dataset('./data/')
    dataset.load()

    #训练模型,这段代码不用,注释掉
    model = Model()
    model.build_model(dataset)
    #测试训练函数的代码
    model.train(dataset)
    model.save_model(file_path = './model/me.face.model.h5')


    #评估模型
    model = Model()
    model.load_model(file_path = './model/me.face.model.h5')
    model.evaluate(dataset)


Model是搭建keras神经网络

train是具体训练方法

具体含义,请参考keras中文文档

最终,我们的训练结果,也就是我的脸部特征,存放到了model/me.face.model.h5

运行并训练,最终得到特征集


4,测试

新建findme.py

#-*- coding: utf-8 -*-

import cv2
import sys
import gc
from face_train import Model

if __name__ == '__main__':
    #加载模型
    model = Model()
    model.load_model(file_path = './model/me.face.model.h5')

    #框住人脸的矩形边框颜色
    color = (0, 255, 0)

    #捕获指定摄像头的实时视频流
    cap = cv2.VideoCapture(0)

    #人脸识别分类器本地存储路径
    cascade_path = "D:/opencv/opencv/sources/data/haarcascades/haarcascade_frontalface_alt2.xml"

    #循环检测识别人脸
    while True:
        _, frame = cap.read()   #读取一帧视频

        #图像灰化,降低计算复杂度
        frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

        #使用人脸识别分类器,读入分类器
        cascade = cv2.CascadeClassifier(cascade_path)

        #利用分类器识别出哪个区域为人脸
        faceRects = cascade.detectMultiScale(frame_gray, scaleFactor = 1.2, minNeighbors = 3, minSize = (32, 32))
        if len(faceRects) > 0:
            for faceRect in faceRects:
                x, y, w, h = faceRect

                #截取脸部图像提交给模型识别这是谁
                image = frame[y - 10: y + h + 10, x - 10: x + w + 10]
                faceID = model.face_predict(image)

                #如果是“我”
                if faceID == 0:
                    cv2.rectangle(frame, (x - 10, y - 10), (x + w + 10, y + h + 10), color, thickness = 2)

                    #文字提示是谁
                    cv2.putText(frame,'xvshu',
                                (x + 30, y + 30),                      #坐标
                                cv2.FONT_HERSHEY_SIMPLEX,              #字体
                                1,                                     #字号
                                (255,0,255),                           #颜色
                                2)                                     #字的线宽
                else:
                    cv2.rectangle(frame, (x - 10, y - 10), (x + w + 10, y + h + 10), color, thickness = 2)

                    #文字提示是谁
                    cv2.putText(frame,'others',
                                (x + 30, y + 30),                      #坐标
                                cv2.FONT_HERSHEY_SIMPLEX,              #字体
                                1,                                     #字号
                                (255,0,255),                           #颜色
                                2)

        cv2.imshow("find me", frame)

        #等待10毫秒看是否有按键输入
        k = cv2.waitKey(10)
        #如果输入q则退出循环
        if k & 0xFF == ord('q'):
            break

    #释放摄像头并销毁所有窗口
    cap.release()
    cv2.destroyAllWindows()
最终我们得到了博客上图的结果,即便是照片也没放过:



总结

大家看这些过程非常的简单,大量的第三方库替我们做了很多,这里我只是进行神经网络的搭建,复杂的计算和搭建过程被keras屏蔽了,基于此,keras应该算是一个神经网络入门的好工具,建议搭建都学习学习!





猜你喜欢

转载自blog.csdn.net/xvshu/article/details/78863430
今日推荐