1. 程式人生 > 程式設計 >Kears+Opencv實現簡單人臉識別

Kears+Opencv實現簡單人臉識別

寫在前面:這篇文章也是借鑑了一些前輩的程式碼和思路寫的,程式碼有些也是複用了別人的。

先說下思路:

1.首先利用Opencv檢測出人臉的區域

2.在成功的檢測出人臉區域後,將識別出的人臉區域擷取成圖片,儲存起來,用作後續的訓練資料。

3.在獲取到了足夠多的資料後,搭建CNN網路進行訓練。

5.訓練完成後,將模型儲存起來。 6.在利用Opencv實時讀取視訊流,將檢測出人臉的區域變成圖片放入模型中進行預測。

以上就是這個專案的一個基本的思路了。

1.利用Opencv檢測人臉的程式碼如下,這個程式碼在Opencv的官方文件中也有,最重要的是載入的xml檔案,因為這個xml檔案中是儲存這已經訓練好的人臉檢測模型。

import cv2
 
def identify_face(window_name,camera_idx):
  cv2.namedWindow(window_name)
 
  # 視訊來源,可以來自一段已存好的視訊,也可以直接來自USB攝像頭
  cap = cv2.VideoCapture(camera_idx)
 
  # 告訴OpenCV使用人臉識別分類器
  classfier = cv2.CascadeClassifier("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和4分別為圖片縮放比例和需要檢測的有效點數
    faceRects = classfier.detectMultiScale(grey,scaleFactor=1.2,minNeighbors=4,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__':
  identify_face("identify face",0)

2.在檢測出人臉區域後,將人臉區域擷取成圖片儲存下來

import cv2
from threading import Thread
 
 
 
def identify_face_and_store_face_image(window_name,0)
 
 
  # 儲存圖片的索引
  num = 0
  while cap.isOpened(): # 是否初始化攝像頭裝置
    ok,cv2.COLOR_BGR2GRAY)
 
 
    # 人臉檢測,1.2和2分別為圖片縮放比例和需要檢測的有效點數
    faceRects = classfier.detectMultiScale(grey,h = faceRect  # 獲得人臉的左上角對應座標,及寬度和高度
 
        cv2.rectangle(frame,2)
 
        # store_face_image(frame,h,num,x,y)
        # 開啟一個執行緒去儲存人臉圖片
        t = Thread(target=store_face_image,args=(frame,))
        t.start()
 
 
        # 顯示捕捉了多少張人臉照片
        font = cv2.FONT_HERSHEY_SIMPLEX # 字型
        cv2.putText(frame,('num %d' % num),(x + 30,y + 30),font,1,(255,255),2)
 
        num += 1
        if num <= 1000: # 儲存1000張圖片後就退出
          break
 
 
    if num >= 1000:
      break
 
    # 顯示影象
    cv2.imshow(window_name,frame)
    c = cv2.waitKey(10)
    if c & 0xFF == ord('q'):
      break
 
  # 釋放攝像頭並銷燬所有視窗
  cap.release()
  cv2.destroyAllWindows()
 
 
def store_face_image(frame,y):
  # 將當前幀儲存為圖片
  img_name = '%s/%d.jpg' % (r'face_image',num)
  image = frame[y - 10: y + h + 10,x - 10: x + w + 10]
  cv2.imwrite(img_name,image)
 
if __name__ == '__main__':
  identify_face_and_store_face_image("identify face",0)

3.將儲存的資料進行處理,比如打上標籤,進行歸一化等等,下列程式碼是load_datasets.py檔案中的

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)
 
  # 獲取影象尺寸
  h,_ = 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]
 
  # 給影象增加邊界,是圖片長、寬等長,cv2.BORDER_CONSTANT指定邊界顏色由value指定
  constant = cv2.copyMakeBorder(image,top,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)
  # 共587張圖片,IMAGE_SIZE為64,故對我來說尺寸為587 * 64 * 64 * 3
  # 圖片為64 * 64畫素,一個畫素3個顏色值(RGB)
  images = np.array(images)
  print(images.shape)
 
  # 標註資料,'my_face_image'資料夾下都是我的臉部影象,全部指定為0,你可以在獲取他人的臉部圖片比如說同學的,指定為1,labels = np.array([0 if label.endswith('my_face_image') 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("face_image")

4.搭建模型,進行訓練

import random
 
import numpy as np
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_datasets 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,test_size=0.5,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_cols)
      test_images = test_images.reshape(test_images.shape[0],img_cols)
      self.input_shape = (img_channels,img_cols)
    else:
      train_images = train_images.reshape(train_images.shape[0],img_cols,img_channels)
      valid_images = valid_images.reshape(valid_images.shape[0],img_channels)
      test_images = test_images.reshape(test_images.shape[0],img_channels)
      self.input_shape = (img_rows,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,border_mode='same',input_shape=dataset.input_shape)) # 1 2維卷積層
    self.model.add(Activation('relu')) # 2 啟用函式層
 
    self.model.add(Convolution2D(32,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,border_mode='same')) # 7 2維卷積層
    self.model.add(Activation('relu')) # 8 啟用函式層
 
    self.model.add(Convolution2D(64,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,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,batch_size=batch_size),samples_per_epoch=dataset.train_images.shape[0],dataset.valid_labels))
 
  MODEL_PATH = 'zhuhaipeng.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,IMAGE_SIZE):
      image = resize_image(image) # 尺寸必須與訓練集一致都應該是IMAGE_SIZE x IMAGE_SIZE
      image = image.reshape((1,IMAGE_SIZE)) # 與模型訓練不同,這次只是針對1張圖片進行預測
    elif K.image_dim_ordering() == 'tf' and image.shape != (1,3):
      image = resize_image(image)
      image = image.reshape((1,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('face_image')
  dataset.load()
 
  model = Model()
  model.build_model(dataset)
 
  # 測試訓練函式的程式碼
  model.train(dataset)
 
if __name__ == '__main__': # 訓練並儲存模型
  dataset = Dataset('face_image')
  dataset.load()
 
  model = Model()
  model.build_model(dataset)
  model.train(dataset)
  model.save_model(file_path='model/zhuhaipeng.face.model.h5')
 
if __name__ == '__main__':  # 讀取模型進行評估
  dataset = Dataset('face_image')
  dataset.load()
 
  # 評估模型
  model = Model()
  model.load_model(file_path='model/zhuhaipeng.face.model.h5')
  model.evaluate(dataset)

5.訓練好模型後,使用Opencv實時讀取視訊流檢測人臉位置,再將人臉放入模型中進行預測

# -*- coding: utf-8 -*-
 
import cv2
import sys
from face_train 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='model/zhuhaipeng.face.model.h5')
 
  # 框住人臉的矩形邊框顏色
  color = (0,0)
 
  # 捕獲指定攝像頭的實時視訊流
  cap = cv2.VideoCapture(0)
 
  # 人臉識別分類器本地儲存路徑
  cascade_path = "haarcascade_frontalface_alt2.xml"
 
  # 迴圈檢測識別人臉
  while True:
    ret,frame = cap.read() # 讀取一幀視訊
 
    if ret is True:
 
      # 影象灰化,降低計算複雜度
      frame_gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
    else:
      continue
    # 使用人臉識別分類器,讀入分類器
    cascade = cv2.CascadeClassifier(cascade_path)
 
    # 利用分類器識別出哪個區域為人臉
    faceRects = cascade.detectMultiScale(frame_gray,minNeighbors=3,32))
    if len(faceRects) > 0:
      for faceRect in faceRects:
        x,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,thickness=2)
 
          # 文字提示是誰
          cv2.putText(frame,'zhuhaipeng',# 座標
                cv2.FONT_HERSHEY_SIMPLEX,# 字型
                1,# 字號
                (255,# 顏色
                2) # 字的線寬
        else: # 如果不是我
          # 文字提示未知
          cv2.putText(frame,'Unknown people ',# 顏色
                2) # 字的線寬
 
    cv2.imshow("identify me",frame)
 
    # 等待10毫秒看是否有按鍵輸入
    k = cv2.waitKey(10)
    # 如果輸入q則退出迴圈
    if k & 0xFF == ord('q'):
      break
 
  # 釋放攝像頭並銷燬所有視窗
  cap.release()
  cv2.destroyAllWindows()

到此一個簡單的人臉識別小專案就完成了,在這個專案中,只是簡單的二分類,可以在此基礎上進行擴充套件為多分類,如果識別的準確率低的話,可以嘗試更改網路架構,或者對資料進行預處理等等,如果大家有興趣改進的話,可以嘗試一下。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。