一、人臉圖像特征提取方法
http://www.zmynmublwnt.cn/article/213501.html
二、對笑臉數據集genki4k進行訓練和測試(包括SVM、CNN),輸出模型訓練精度和測試精度(F1-score和ROC),實現檢測圖片笑臉和實時視頻笑臉檢測
(一)環境、數據集準備
本文操作在Jupyter notebook平臺進行,需要安裝tensorflow、Keras庫、Dlib庫、和opencv-python等。
1、安裝tensorflow、Keras庫
https://blog.csdn.net/weixin_45137708/article/details/106674849
2、安裝配置Dlib庫和opencv-python
https://blog.csdn.net/jajit/article/details/106630937?utm_source=app
3、笑臉數據集下載
笑臉數據集下載鏈接:https://pan.baidu.com/s/1ldA8CF6pH4q0Bheik_Tttg
提取碼:fui1
(二)訓練笑臉數據集genki4k
1、首先導入Keras庫
1
2
|
import keras keras.__version__ |
2、讀取笑臉數據集,然后將訓練的數據和測試數據放入對應的文件夾
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
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) |
3、將笑臉圖片和非笑臉圖片放入對應文件夾
在上面程序中生成了一個名為smile_and_nosmile的文件夾,里面有三個子文件,分別存放訓練、測試、驗證數據,在這三個文件夾下還有smile和nosmile文件夾,我們需要將笑臉圖片放入smile文件夾,將非笑臉圖片放入nosmile文件夾。
3、打印每個數據集文件中的笑臉和非笑臉圖片數
1
2
3
4
5
6
|
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))) |
4、構建小型卷積網絡
我們已經為MNIST構建了一個小型卷積網,所以您應該熟悉它們。我們將重用相同的通用結構:我們的卷積網將是一個交替的Conv2D(激活relu)和MaxPooling2D層的堆棧。然而,由于我們處理的是更大的圖像和更復雜的問題,因此我們將使我們的網絡相應地更大:它將有一個更多的Conv2D + MaxPooling2D階段。這樣既可以擴大網絡的容量,又可以進一步縮小特征圖的大小,這樣當我們到達平坦層時,特征圖就不會太大。在這里,由于我們從大小為150x150的輸入開始(有點隨意的選擇),我們在Flatten層之前得到大小為7x7的feature map。
注意:feature map的深度在網絡中逐漸增加(從32到128),而feature map的大小在減少(從148x148到7x7)。這是你會在幾乎所有convnets中看到的模式。由于我們解決的是一個二元分類問題,我們用一個單一單元(一個大小為1的稠密層)和一個s型激活來結束網絡。這個單元將對網絡正在查看一個類或另一個類的概率進行編碼。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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' )) |
讓我們來看看要素地圖的尺寸是如何隨每個連續圖層而變化的
1
|
model.summary() |
讓我們來看看特征地圖的尺寸是如何隨著每一個連續的層:為我們編譯步驟,我們將一如既往地使用RMSprop優化器。由于我們用一個單一的乙狀結腸單元結束我們的網絡,我們將使用二進制交叉熵作為我們的損失
1
2
3
4
5
|
from keras import optimizers model. compile (loss = 'binary_crossentropy' , optimizer = optimizers.RMSprop(lr = 1e - 4 ), metrics = [ 'acc' ]) |
5、數據預處理
在將數據輸入到我們的網絡之前,應該將數據格式化為經過適當預處理的浮點張量。目前,我們的數據以JPEG文件的形式保存在硬盤上,因此將其導入網絡的步驟大致如下:
- 讀取圖片文件
- 解碼JPEG內容到RBG像素網格
- 把它們轉換成浮點張量
- 將像素值(從0到255)縮放到[0,1]區間
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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' ) |
讓我們看看其中一個生成器的輸出:它生成150×150 RGB圖像的批次(Shape(20,150,150,3))和二進制標簽(Shape(20,))。20是每批樣品的數量(批次大小)。注意,生成器無限期地生成這些批:它只是無休止地循環目標文件夾中的圖像。因此,我們需要在某個點中斷迭代循環。
1
2
3
4
|
for data_batch, labels_batch in train_generator: print ( 'data batch shape:' , data_batch.shape) print ( 'labels batch shape:' , labels_batch.shape) break |
使用生成器使我們的模型適合于數據
1
2
3
4
5
6
|
history = model.fit_generator( train_generator, steps_per_epoch = 100 , epochs = 30 , validation_data = validation_generator, validation_steps = 50 ) |
這里使用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參數,它告訴流程從驗證生成器提取多少批來進行評估。
保存模型
1
|
model.save( 'C:\\Users\\asus\\Desktop\\test\\smile_and_nosmile.h5' ) |
在訓練和驗證數據上繪制模型的損失和準確性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
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() |
這些圖具有過擬合的特點。我們的訓練精度隨著時間線性增長,直到接近100%,而我們的驗證精度停留在70-72%。我們的驗證損失在5個epoch后達到最小,然后停止,而訓練損失繼續線性下降,直到接近0。
6、數據增強
過度擬合是由于可供學習的樣本太少,使我們無法訓練一個模型來泛化到新的數據。給定無限的數據,我們的模型將暴露于手頭數據分布的每一個可能方面:我們永遠不會過度擬合。數據增強采用的方法是從現有的訓練樣本中生成更多的訓練數據,方法是通過一系列隨機變換來“增強”樣本,從而產生看上去可信的圖像。我們的目標是在訓練時,我們的模型不會兩次看到完全相同的圖像。這有助于將模型暴露于數據的更多方面,并更好地泛化。
1
2
3
4
5
6
7
8
|
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' ) |
- rotation_range是一個角度值(0-180),在這個范圍內可以隨機旋轉圖片
- width_shift和height_shift是范圍(作為總寬度或高度的一部分),在其中可以隨機地垂直或水平地轉換圖片
- shear_range用于隨機應用剪切轉換
- zoom_range用于在圖片內部隨機縮放
- horizontal_flip是用于水平隨機翻轉一半的圖像——當沒有假設水平不對稱時(例如真實世界的圖片)
- fill_mode是用于填充新創建像素的策略,它可以在旋轉或寬度/高度移動之后出現。
查看增強后的圖像
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
# 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() |
如果我們使用這種數據增加配置訓練一個新的網絡,我們的網絡將永遠不會看到兩次相同的輸入。然而,它看到的輸入仍然是高度相關的,因為它們來自少量的原始圖像——我們不能產生新的信息,我們只能混合現有的信息。因此,這可能還不足以完全消除過度擬合。
為了進一步對抗過擬合,我們還將在我們的模型中增加一個Dropout層,就在密集連接分類器之前:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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' ]) |
用數據增強和退出來訓練我們的網絡:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
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 ) |
這里程序會跑很久,我跑了幾個小時,用GPU跑會快很多很多。
保存模型在convnet可視化部分使用:
1
|
model.save( 'C:\\Users\\asus\\Desktop\\test\\smile_and_nosmile_1.h5' ) |
再看一次結果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
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() |
由于數據的增加和遺漏,我們不再過度擬合:訓練曲線相當緊密地跟蹤驗證曲線。我們現在能夠達到82%的精度,相對于非正則化模型有15%的改進。通過進一步利用正則化技術和調整網絡參數(比如每個卷積層的濾波器數量,或者網絡中的層數),我們可能能夠獲得更好的精度,可能達到86-87%。
7、優化提高笑臉圖像分類模型精度
構建卷積網絡
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
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() |
(三)圖片笑臉檢測
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
# 單張圖片進行判斷 是笑臉還是非笑臉 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) |
結果正確,錯誤率在0.0883181左右,反復找圖片嘗試,結果都是正確的。
(四)實時視頻笑臉檢測
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
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() |
視頻檢測正確,就是背景太黑了…
三、將笑臉數據集換成人臉口罩數據集,并對口罩數據集進行訓練,編寫程序實現人臉口罩檢測
(一)訓練人臉口罩數據集
人臉口罩數據集下載鏈接:
https://pan.baidu.com/s/11PBCmDDx7Dtx_ckjwZR2uw
提取碼:n2um
訓練人臉口罩數據集和訓練笑臉數據集一樣,只需改一下和相應的變量名和數據集。
(二)編程實現人臉口罩檢測
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
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() |
運行結果:
不戴口罩
戴口罩
人臉口罩檢測正確!
雖然人臉口罩檢測正確,但是精度還是不高,因為我的數據集里面戴口罩得的圖像太少了,朋友們可以多找一些戴口罩的圖片以提高精度,后續我也會不斷完善~
到此這篇關于Python實現笑臉檢測+人臉口罩檢測的文章就介紹到這了,更多相關Python人臉口罩檢測內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!
原文鏈接:https://blog.csdn.net/weixin_45137708/article/details/107142706