1. 程式人生 > >人工智慧小例子(二)-基於keras+openCV的人臉識別

人工智慧小例子(二)-基於keras+openCV的人臉識別

目前keras是對人工智慧來說,入門比較好的一款中介軟體,遮蔽了很多引數配置和實現細節,直接一層層進行網路搭建就可以。最近一直在學習相關知識,但是一直也沒有啥實用的功能出現,在網上有個例子,一下子就吸引住了我,使用keras迅速搭建一套人工智慧系統,輸入自己的照片和他人照片,訓練其識別出自己,然後使用攝像頭驗證。

廢話不多說,先上結果圖:


是不是很6,其實實現起來還是依賴了很多網上的大牛,現在跟著我,咱們一步步實現這些:

前置知識:

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是具體訓練方法

最終,我們的訓練結果,也就是我的臉部特徵,存放到了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應該算是一個神經網路入門的好工具,建議搭建都學習學習!