Python實現笑臉檢測+人臉口罩檢測!確實挺神奇!

2020-08-13 16:43:07

一、人臉影象特徵提取方法

https://blog.csdn.net/weixin_45137708/article/details/107110857

二、對笑臉數據集genki4k進行訓練和測試(包括SVM、CNN),輸出模型訓練精度和測試精度(F1-score和ROC),實現檢測圖片笑臉和實時視訊笑臉檢測

(一)環境、數據集準備

本文操作在Jupyter notebook平臺進行,需要安裝tensorflow、Keras庫、Dlib庫、和opencv-python等。1、安裝tensorflow、Keras庫https://blog.csdn.net/weixin_45137708/article/details/1066748492、安裝設定Dlib庫和opencv-pythonhttps://blog.csdn.net/jajit/article/details/106630937?utm_source=app3、笑臉數據集下載笑臉數據集下載鏈接:https://pan.baidu.com/s/1ldA8CF6pH4q0Bheik_Tttg 提取碼:fui1

(二)訓練笑臉數據集genki4k

1、首先匯入Keras庫

import keras
keras.__version__
12

2、讀取笑臉數據集,然後將訓練的數據和測試數據放入對應的資料夾

import os, shutil
# The path to the directory where the original
# dataset was uncompressed
original_dataset_dir = 'C:\\Users\\asus\\Desktop\\test\\genki4k'

# The directory where we will
# store our smaller dataset
base_dir = 'C:\\Users\\asus\\Desktop\\test\\smile_and_nosmile'
os.mkdir(base_dir)

# Directories for our training,
# validation and test splits
train_dir = os.path.join(base_dir, 'train')
os.mkdir(train_dir)
validation_dir = os.path.join(base_dir, 'validation')
os.mkdir(validation_dir)
test_dir = os.path.join(base_dir, 'test')
os.mkdir(test_dir)

# Directory with our training smile pictures
train_smile_dir = os.path.join(train_dir, 'smile')
os.mkdir(train_smile_dir)

# Directory with our training nosmile pictures
train_nosmile_dir = os.path.join(train_dir, 'nosmile')
os.mkdir(train_nosmile_dir)

# Directory with our validation smile pictures
validation_smile_dir = os.path.join(validation_dir, 'smile')
os.mkdir(validation_smile_dir)

# Directory with our validation nosmile pictures
validation_nosmile_dir = os.path.join(validation_dir, 'nosmile')
os.mkdir(validation_nosmile_dir)

# Directory with our validation smile pictures
test_smile_dir = os.path.join(test_dir, 'smile')
os.mkdir(test_smile_dir)

# Directory with our validation nosmile pictures
test_nosmile_dir = os.path.join(test_dir, 'nosmile')
os.mkdir(test_nosmile_dir)
123456789101112131415161718192021222324252627282930313233343536373839404142

3、將笑臉圖片和非笑臉圖片放入對應資料夾在上面程式中生成了一個名爲smile_and_nosmile的資料夾,裏面有三個子檔案,分別存放訓練、測試、驗證數據,在這三個資料夾下還有smile和nosmile資料夾,我們需要將笑臉圖片放入smile資料夾,將非笑臉圖片放入nosmile資料夾。

3、列印每個數據集檔案中的笑臉和非笑臉圖片數

print('total training smile images:', len(os.listdir(train_smile_dir)))
print('total training nosmile images:', len(os.listdir(train_nosmile_dir)))
print('total validation smile images:', len(os.listdir(validation_smile_dir)))
print('total validation nosmile images:', len(os.listdir(validation_nosmile_dir)))
print('total test smile images:', len(os.listdir(test_smile_dir)))
print('total test nosmile images:', len(os.listdir(test_nosmile_dir)))
123456

4、構建小型折積網路我們已經爲MNIST構建了一個小型折積網,所以您應該熟悉它們。我們將重用相同的通用結構:我們的折積網將是一個交替的Conv2D(啓用relu)和MaxPooling2D層的堆疊。然而,由於我們處理的是更大的影象和更復雜的問題,因此我們將使我們的網路相應地更大:它將有一個更多的Conv2D + MaxPooling2D階段。這樣既可以擴大網路的容量,又可以進一步縮小特徵圖的大小,這樣當我們到達平坦層時,特徵圖就不會太大。在這裏,由於我們從大小爲150x150的輸入開始(有點隨意的選擇),我們在Flatten層之前得到大小爲7x7的feature map。

注意:feature map的深度在網路中逐漸增加(從32到128),而feature map的大小在減少(從148x148到7x7)。這是你會在幾乎所有convnets中看到的模式。由於我們解決的是一個二元分類問題,我們用一個單一單元(一個大小爲1的稠密層)和一個s型啓用來結束網路。這個單元將對網路正在檢視一個類或另一個類的概率進行編碼。

from keras import layers
from keras import models

model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu',
                        input_shape=(150, 150, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Flatten())
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
12345678910111213141516

讓我們來看看要素地圖的尺寸是如何隨每個連續圖層而變化的

model.summary()
1

讓我們來看看特徵地圖的尺寸是如何隨着每一個連續的層:爲我們編譯步驟,我們將一如既往地使用RMSprop優化器。由於我們用一個單一的乙狀結腸單元結束我們的網路,我們將使用二進制交叉熵作爲我們的損失

from keras import optimizers

model.compile(loss='binary_crossentropy',
              optimizer=optimizers.RMSprop(lr=1e-4),
              metrics=['acc'])
12345

5、數據預處理在將數據輸入到我們的網路之前,應該將數據格式化爲經過適當預處理的浮點張量。目前,我們的數據以JPEG檔案的形式儲存在硬碟上,因此將其匯入網路的步驟大致如下:

  • 讀取圖片檔案
  • 解碼JPEG內容到RBG畫素網格
  • 把它們轉換成浮點張量
  • 將畫素值(從0到255)縮放到[0,1]區間
from keras.preprocessing.image import ImageDataGenerator

# All images will be rescaled by 1./255
train_datagen = ImageDataGenerator(rescale=1./255)
test_datagen = ImageDataGenerator(rescale=1./255)

train_generator = train_datagen.flow_from_directory(
        # This is the target directory
        train_dir,
        # All images will be resized to 150x150
        target_size=(150, 150),
        batch_size=20,
        # Since we use binary_crossentropy loss, we need binary labels
        class_mode='binary')

validation_generator = test_datagen.flow_from_directory(
        validation_dir,
        target_size=(150, 150),
        batch_size=20,
        class_mode='binary')
1234567891011121314151617181920

讓我們看看其中一個生成器的輸出:它生成150×150 RGB影象的批次(Shape(20,150,150,3))和二進制標籤(Shape(20,))。20是每批樣品的數量(批次大小)。注意,生成器無限期地生成這些批:它只是無休止地回圈目標資料夾中的影象。因此,我們需要在某個點中斷迭代回圈。

for data_batch, labels_batch in train_generator:
    print('data batch shape:', data_batch.shape)
    print('labels batch shape:', labels_batch.shape)
    break
1234

使用生成器使我們的模型適合於數據

history = model.fit_generator(
      train_generator,
      steps_per_epoch=100,
      epochs=30,
      validation_data=validation_generator,
      validation_steps=50)
123456

這裏使用fit_generator方法來完成此操作,對於我們這樣的數據生成器,它相當於fit方法。它期望Python生成器作爲第一個參數,它將無限期地生成成批的輸入和目標,就像我們的範例一樣。因爲數據是不斷生成的,所以在宣告一個紀元結束之前,生成器需要知道範例從生成器中抽取多少樣本。這就是steps_per_epoch參數的作用:在從生成器中繪製完steps_per_epoch批次處理之後,即在執行完steps_per_epoch梯度下降步驟之後,擬合過程將轉到下一個epoch。在我們的例子中,批次是20個樣本大,所以在我們看到2000個樣本的目標之前將需要100個批次。

在使用fit_generator時,可以傳遞validation_data參數,就像fit方法一樣。重要的是,允許這個參數本身是一個數據生成器,但是它也可以是Numpy陣列的元組。如果您傳遞一個生成器作爲validation_data,那麼這個生成器將會不斷生成成批的驗證數據,因此您還應該指定validation_steps參數,它告訴流程從驗證生成器提取多少批來進行評估。

儲存模型

model.save('C:\\Users\\asus\\Desktop\\test\\smile_and_nosmile.h5')
1

在訓練和驗證數據上繪製模型的損失和準確性

import matplotlib.pyplot as plt

acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(len(acc))

plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()

plt.figure()

plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()
123456789101112131415161718192021

這些圖具有過擬合的特點。我們的訓練精度隨着時間線性增長,直到接近100%,而我們的驗證精度停留在70-72%。我們的驗證損失在5個epoch後達到最小,然後停止,而訓練損失繼續線性下降,直到接近0。6、數據增強過度擬合是由於可供學習的樣本太少,使我們無法訓練一個模型來泛化到新的數據。給定無限的數據,我們的模型將暴露於手頭數據分佈的每一個可能方面:我們永遠不會過度擬合。數據增強採用的方法是從現有的訓練樣本中生成更多的訓練數據,方法是通過一系列隨機變換來「增強」樣本,從而產生看上去可信的影象。我們的目標是在訓練時,我們的模型不會兩次看到完全相同的影象。這有助於將模型暴露於數據的更多方面,並更好地泛化。

datagen = ImageDataGenerator(
      rotation_range=40,
      width_shift_range=0.2,
      height_shift_range=0.2,
      shear_range=0.2,
      zoom_range=0.2,
      horizontal_flip=True,
      fill_mode='nearest')
12345678
  • rotation_range是一個角度值(0-180),在這個範圍內可以隨機旋轉圖片
  • width_shift和height_shift是範圍(作爲總寬度或高度的一部分),在其中可以隨機地垂直或水平地轉換圖片
  • shear_range用於隨機應用剪下轉換
  • zoom_range用於在圖片內部隨機縮放
  • horizontal_flip是用於水平隨機翻轉一半的影象——當沒有假設水平不對稱時(例如真實世界的圖片)
  • fill_mode是用於填充新建立畫素的策略,它可以在旋轉或寬度/高度移動之後出現。

檢視增強後的影象

# This is module with image preprocessing utilities
from keras.preprocessing import image

fnames = [os.path.join(train_smile_dir, fname) for fname in os.listdir(train_smile_dir)]

# We pick one image to "augment"
img_path = fnames[3]

# Read the image and resize it
img = image.load_img(img_path, target_size=(150, 150))

# Convert it to a Numpy array with shape (150, 150, 3)
x = image.img_to_array(img)

# Reshape it to (1, 150, 150, 3)
x = x.reshape((1,) + x.shape)

# The .flow() command below generates batches of randomly transformed images.
# It will loop indefinitely, so we need to `break` the loop at some point!
i = 0
for batch in datagen.flow(x, batch_size=1):
    plt.figure(i)
    imgplot = plt.imshow(image.array_to_img(batch[0]))
    i += 1
    if i % 4 == 0:
        break

plt.show()
12345678910111213141516171819202122232425262728

如果我們使用這種數據增加設定訓練一個新的網路,我們的網路將永遠不會看到兩次相同的輸入。然而,它看到的輸入仍然是高度相關的,因爲它們來自少量的原始影象——我們不能產生新的資訊,我們只能混合現有的資訊。因此,這可能還不足以完全消除過度擬合。

爲了進一步對抗過擬合,我們還將在我們的模型中增加一個Dropout層,就在密集連線分類器之前:

model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu',
                        input_shape=(150, 150, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Flatten())
model.add(layers.Dropout(0.5))
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))

model.compile(loss='binary_crossentropy',
              optimizer=optimizers.RMSprop(lr=1e-4),
              metrics=['acc'])
123456789101112131415161718

用數據增強和退出來訓練我們的網路:

train_datagen = ImageDataGenerator(
    rescale=1./255,
    rotation_range=40,
    width_shift_range=0.2,
    height_shift_range=0.2,
    shear_range=0.2,
    zoom_range=0.2,
    horizontal_flip=True,)

# Note that the validation data should not be augmented!
test_datagen = ImageDataGenerator(rescale=1./255)

train_generator = train_datagen.flow_from_directory(
        # This is the target directory
        train_dir,
        # All images will be resized to 150x150
        target_size=(150, 150),
        batch_size=32,
        # Since we use binary_crossentropy loss, we need binary labels
        class_mode='binary')

validation_generator = test_datagen.flow_from_directory(
        validation_dir,
        target_size=(150, 150),
        batch_size=32,
        class_mode='binary')

history = model.fit_generator(
      train_generator,
      steps_per_epoch=100,
      epochs=100,
      validation_data=validation_generator,
      validation_steps=50)
123456789101112131415161718192021222324252627282930313233

這裏程式會跑很久,我跑了幾個小時,用GPU跑會快很多很多。

儲存模型在convnet視覺化部分使用:

model.save('C:\\Users\\asus\\Desktop\\test\\smile_and_nosmile_1.h5')
1

再看一次結果

acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(len(acc))

plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()

plt.figure()

plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()
12345678910111213141516171819

由於數據的增加和遺漏,我們不再過度擬合:訓練曲線相當緊密地跟蹤驗證曲線。我們現在能夠達到82%的精度,相對於非正則化模型有15%的改進。通過進一步利用正則化技術和調整網路參數(比如每個折積層的濾波器數量,或者網路中的層數),我們可能能夠獲得更好的精度,可能達到86-87%。

7、優化提高笑臉影象分類模型精度構建折積網路

from keras import layers
from keras import models
from keras import optimizers
model = models.Sequential()
#輸入圖片大小是150*150 3表示圖片畫素用(R,G,B)表示
model.add(layers.Conv2D(32, (3,3), activation='relu', input_shape=(150 , 150, 3)))
model.add(layers.MaxPooling2D((2,2)))
model.add(layers.Conv2D(64, (3,3), activation='relu'))
model.add(layers.MaxPooling2D((2,2)))
model.add(layers.Conv2D(128, (3,3), activation='relu'))
model.add(layers.MaxPooling2D((2,2)))
model.add(layers.Conv2D(128, (3,3), activation='relu'))
model.add(layers.MaxPooling2D((2,2)))
model.add(layers.Flatten())
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer=optimizers.RMSprop(lr=1e-4),
             metrics=['acc'])
model.summary()
12345678910111213141516171819

(三)圖片笑臉檢測

# 單張圖片進行判斷  是笑臉還是非笑臉
import cv2
from keras.preprocessing import image
from keras.models import load_model
import numpy as np

model = load_model('smile_and_nosmile_1.h5')

img_path='C:\\Users\\asus\\Desktop\\test\\genki4k\\file2227.jpg'

img = image.load_img(img_path, target_size=(150, 150))
#img1 = cv2.imread(img_path,cv2.IMREAD_GRAYSCALE)
#cv2.imshow('wname',img1)
#cv2.waitKey(0)

#print(img.size)
img_tensor = image.img_to_array(img)/255.0
img_tensor = np.expand_dims(img_tensor, axis=0)

prediction =model.predict(img_tensor)  
print(prediction)
if prediction[0][0]>0.5:
    result='smile'
else:
    result='nosmile'
print(result)
1234567891011121314151617181920212223242526

結果正確,錯誤率在0.0883181左右,反覆 反復找圖片嘗試,結果都是正確的。

(四)實時視訊笑臉檢測

import cv2
from keras.preprocessing import image
from keras.models import load_model
import numpy as np
import dlib
from PIL import Image
model = load_model('smile_and_nosmile_1.h5')
detector = dlib.get_frontal_face_detector()
video=cv2.VideoCapture(0)
font = cv2.FONT_HERSHEY_SIMPLEX
def rec(img):
    gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    dets=detector(gray,1)
    if dets is not None:
        for face in dets:
            left=face.left()
            top=face.top()
            right=face.right()
            bottom=face.bottom()
            cv2.rectangle(img,(left,top),(right,bottom),(0,255,0),2)
            img1=cv2.resize(img[top:bottom,left:right],dsize=(150,150))
            img1=cv2.cvtColor(img1,cv2.COLOR_BGR2RGB)
            img1 = np.array(img1)/255.
            img_tensor = img1.reshape(-1,150,150,3)
            prediction =model.predict(img_tensor)    
            print(prediction)
            if prediction[0][0]<0.5:
                result='nosmile'
            else:
                result='smile'
            cv2.putText(img, result, (left,top), font, 2, (0, 255, 0), 2, cv2.LINE_AA)
        cv2.imshow('smile detector', img)
while video.isOpened():
    res, img_rd = video.read()
    if not res:
        break
    rec(img_rd)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
video.release()
cv2.destroyAllWindows()
1234567891011121314151617181920212223242526272829303132333435363738394041

視訊檢測正確,就是背景太黑了…

三、將笑臉數據集換成人臉口罩數據集,並對口罩數據集進行訓練,編寫程式實現人臉口罩檢測

(一)訓練人臉口罩數據集

人臉口罩數據集下載鏈接:https://pan.baidu.com/s/11PBCmDDx7Dtx_ckjwZR2uw提取碼:n2um訓練人臉口罩數據集和訓練笑臉數據集一樣,只需改一下和相應的變數名和數據集。

(二)程式設計實現人臉口罩檢測

import cv2
from keras.preprocessing import image
from keras.models import load_model
import numpy as np
import dlib
from PIL import Image
model = load_model('mask_and_nomask.h5')
detector = dlib.get_frontal_face_detector()
video=cv2.VideoCapture(0)
font = cv2.FONT_HERSHEY_SIMPLEX
def rec(img):
    gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    dets=detector(gray,1)
    if dets is not None:
        for face in dets:
            left=face.left()
            top=face.top()
            right=face.right()
            bottom=face.bottom()
            cv2.rectangle(img,(left,top),(right,bottom),(0,255,0),2)
            img1=cv2.resize(img[top:bottom,left:right],dsize=(150,150))
            img1=cv2.cvtColor(img1,cv2.COLOR_BGR2RGB)
            img1 = np.array(img1)/255.
            img_tensor = img1.reshape(-1,150,150,3)
            prediction =model.predict(img_tensor)    
            print(prediction)
            if prediction[0][0]>0.5:
                result='nomask'
            else:
                result='mask'
            cv2.putText(img, result, (left,top), font, 2, (0, 255, 0), 2, cv2.LINE_AA)
        cv2.imshow('mask detector', img)
while video.isOpened():
    res, img_rd = video.read()
    if not res:
        break
    rec(img_rd)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
video.release()
cv2.destroyAllWindows()
1234567891011121314151617181920212223242526272829303132333435363738394041

執行結果:不戴口罩

戴口罩

人臉口罩檢測正確!

雖然人臉口罩檢測正確,但是精度還是不高,因爲我的數據集裏面戴口罩得的影象太少了,朋友們可以多找一些戴口罩的圖片以提高精度,後續我也會不斷完善~