人工智能-機器學習-深度學習-深度學習框架TensorFlow之人臉識別

一、人臉檢測和人臉識別的概念
人臉檢測只是識別到有人臉,能獲取到一個人臉的大概位置,有幾個人臉,而人臉識別是要獲取到人臉特徵做對比,識別這個人臉。

二、人臉檢測
OpenCV(開源計算機視覺庫)完成人臉檢測的功能,提供了豐富的Api。下載地址:https://opencv.org/releases/


import cv2
import sys

#第一步使用opencv實現,人臉檢測
def CatchVideo(window_name, camera_idx):
    cv2.namedWindow(window_name)

    # 視頻來源,可以來自一段已存好的視頻,也可以直接來自筆記本自帶的攝像頭或外設USB攝像頭
    cap = cv2.VideoCapture(camera_idx)

    # 告訴OpenCV使用人臉識別分類器
    classfier = cv2.CascadeClassifier("haarcascade_frontalface_alt2.xml")
    classfier.load('D:\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__':
    if len(sys.argv) != 1:
        print("Usage:%s camera_id\r\n" % (sys.argv[0]))
    else:
        CatchVideo("Face detection", 0)

效果如下:
在這裏插入圖片描述

三、模型訓練
3-1、準備1000-2000張左右人臉數據


import cv2
import sys

#第二步,使用OpenCV從攝像頭中獲取截取人臉圖片
#參數說明:窗口名字,攝像頭系列號,捕捉照片數量,以及人臉圖片存儲路徑
def CatchFacePICFromVideo(window_name, camera_idx, catch_pic_num, path_name):
    cv2.namedWindow(window_name)

    # 視頻來源,可以來自一段已存好的視頻,也可以直接來自筆記本自帶的攝像頭或外設USB攝像頭
    cap = cv2.VideoCapture(camera_idx)

    # 告訴OpenCV使用人臉識別分類器
    classfier = cv2.CascadeClassifier("haarcascade_frontalface_alt2.xml")
    classfier.load('D:\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__':
    if len(sys.argv) != 1:
        print("Usage:%s camera_id face_num_max path_name\r\n" % (sys.argv[0]))
    else:
        CatchFacePICFromVideo("截取人臉", 0, 1000, 'd:\data\yangdecheng')

在這裏插入圖片描述
因爲需要根據人臉特徵對比識別出這張臉屬於誰,所以還需要準備其他人的人臉數據,我們可以在網上下載名叫"lfw.tgz"的公有人臉數據。
在這裏插入圖片描述
3-2 模型訓練
3-2-1. 標註數據,也就是給人臉數據加上分類標籤

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)
    # 圖片爲64 * 64像素,一個像素3個顏色值(RGB)
    images = np.array(images)
    print(images.shape)

    #數據分類:標註數據,'yangdecheng'文件夾下都是我的臉部圖像,全部指定爲0,其它全部指定爲1。在真實場景中應該有一個FaceId來進行標註數據
    labels = np.array([0 if label.endswith('yangdecheng') else 1 for label in labels])

    return images, labels


if __name__ == '__main__':
    if len(sys.argv) != 1:
        print("Usage:%s path_name\r\n" % (sys.argv[0]))
    else:
        images, labels = load_dataset("d:\data")

3-2-2. 模型訓練

import random

import numpy as np
#from sklearn.cross_validation import train_test_split
from sklearn.model_selection 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 load_data 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':
        if K.image_data_format() == 'channels_first':
            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
            valid_images /= 255
            test_images /= 255

            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=10, 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 = 'd:\model\yangdecheng.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':
        # if K.image_data_format() == 'channels_first':
        #https://blog.csdn.net/my_kingdom/article/details/84316000
        if K.image_data_format() == 'channels_first' 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_data_format() == 'channels_last' 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和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('d:\data')
    dataset.load()

    model = Model()
    model.build_model(dataset)

    # 先前添加的測試build_model()函數的代碼
    model.build_model(dataset)

    # 測試訓練函數的代碼
    model.train(dataset)

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

    model = Model()
    model.build_model(dataset)
    model.train(dataset)
    model.save_model(file_path='d:\model\yangdecheng.face.model.h5')

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

    # 評估模型
    model = Model()
    model.load_model(file_path='d:\model\yangdecheng.face.model.h5')
    model.evaluate(dataset)





經過對數據的驗證,準確率達到了99.50%。如下圖所示:
在這裏插入圖片描述

並且在指定路徑生成了我們想要的模型,如下圖所示:
在這裏插入圖片描述

3-3 人臉識別

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

import cv2
import sys
import gc
from face_train_upgrade import Model
#第四部,實現人臉識別
if __name__ == '__main__':
    if len(sys.argv) != 1:
        print("Usage:%s camera_id\r\n" % (sys.argv[0]))
        sys.exit(0)

    # 加載模型
    model = Model()
    model.load_model(file_path='d:\model\yangdecheng.face.model.h5')

    # 框住人臉的矩形邊框顏色
    color = (0, 255, 0)

    # 捕獲指定攝像頭的實時視頻流
    cap = cv2.VideoCapture(0)

    # 人臉識別分類器本地存儲路徑
    cascade_path = "D:\haarcascade_frontalface_alt2.xml"

    cascade = cv2.CascadeClassifier("haarcascade_frontalface_alt2.xml")
    cascade.load('D:\haarcascade_frontalface_alt2.xml')  # 一定要告訴編譯器文件所在的具體位置

    # 循環檢測識別人臉
    while True:
        ret, frame = cap.read()  # 讀取一幀視頻

        if ret is True:

            # 圖像灰化,降低計算複雜度
            frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        else:
            continue
        # 使用人臉識別分類器,讀入分類器
        # = 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, 'yangdecheng',
                                (x + 30, y + 30),  # 座標
                                cv2.FONT_HERSHEY_SIMPLEX,  # 字體
                                1,  # 字號
                                (255, 0, 255),  # 顏色
                                2)  # 字的線寬
                else:
                    pass

        cv2.imshow("識別朕", frame)

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

    # 釋放攝像頭並銷燬所有窗口
    cap.release()
    cv2.destroyAllWindows()

效果如下圖所示:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章