[ OpenCV ] 利用 OpenCV進行臉部辨識批量訓練

透過上一章 "利用 OpenCV抓取影片中的臉部數據",有了大量的圖片數據後,可能沒有辦法像之前一樣,全部一起訓練模型,必須採用批量訓練。
但模型更新受到一定的限制,官方說法是只有 Local Binary Pattern Histogram ( LBPH )可以對模型進行更新。所以本章只會使用 LBPH練習。
開始訓練前的資料夾結構如下
.
├── Chaeyoung
│   └── ...
├── Dahyun
│   └── ...
├── Jeongyeon
│   └── ...
├── Jihyo
│   └── ...
├── Mina
│   └── ...
├── Momo
│   └── ...
├── Nayeon
│   └── ...
├── Sana
│   └── ...
├── Tzuyu
│   └── ...
└── model_train.py
model_train.py代碼如下
import os
import cv2
import numpy as np
 
batch = 50
names = ["Nayeon", "Jeongyeon", "Momo", "Sana", "Jihyo", "Mina", "Dahyun", "Chaeyoung", "Tzuyu"]

model_LBPH = cv2.face.createLBPHFaceRecognizer()
if os.path.isfile("./twice_model_LBPH.xml"):
    model_LBPH.load("twice_model_LBPH.xml")

trained_data = 0
def model_training(x,y):
    if not os.path.isfile("./twice_model_LBPH.xml"):
        model_LBPH.train(np.asarray(x), np.asarray(y))
    else:
        model_LBPH.update(np.asarray(x), np.asarray(y))
    model_LBPH.save("twice_model_LBPH.xml")

    global trained_data
    trained_data = trained_data + len(y)
    print("Model are trained {0} images".format(trained_data))

def move_trained_image(image_paths, trained_folders):
    for image_path, trained_folder in zip(image_paths, trained_folders):
        if not os.path.isdir(trained_folder):
            os.system("mkdir {0}".format(trained_folder))
            print("Create {0}".format(trained_folder))
        os.system("mv {0} {1}".format(image_path, trained_folder))
    print("Moved {0} images".format(len(image_paths)))

x,y = [],[]
image_paths, trained_folders = [],[]
for label,member in enumerate(names):
    for fileNames in os.walk("./{0}".format(member)):
        if ("./{0}/trained".format(member)) == fileNames[0]:  # 避免 os.walk爬到第二層導致錯誤。
            continue
        for fileName in fileNames[-1]:
            if fileName.endswith('.png'):
                image_paths.append("./{0}/{1}".format(member, fileName))
                trained_folders.append("./{0}/trained".format(member))
                img = cv2.imread("./{0}/{1}".format(member, fileName), cv2.IMREAD_GRAYSCALE)
                x.append(img)
                y.append(label)
            if len(y)!=0 and len(y)%batch == 0:
                model_training(x,y)
                move_trained_image(image_paths,trained_folders)
                x,y = [],[]
                image_paths, trained_folders = [],[]
        if len(y)!=0:
            model_training(x,y)
            move_trained_image(image_paths,trained_folders)
            x,y = [],[]
            image_paths, trained_folders = [],[]

print("Done!!")
訓練數據的部分一樣透過 os.walk抓取,訓練後會將訓練過的照片移動到底下的 trained資料夾中。
os.walk抓取也會抓取資料夾中的資料夾內的資料,所以可能要避免讀取移動過的資料(代碼中有提到)。
每當 list中有 50筆資料時就訓練,訓練後移動,移動後就把所有的 list清空。
最後把未滿 50筆的資料也重複一樣的動作。

模型的部分,代碼開頭先建立 LBPH模型,檢查有沒有之前訓練儲存的 xml檔,有就讀取。
model_training function中,一樣先檢查 xml檔,沒有就訓練新的,有就更新。完成後存檔。

訓練數據共使用了九位成員的 TT前導宣傳短片做數據捕捉,大約抓了 5000多張相片來訓練這個模型。
對這幾部影片有興趣的話可以點擊這裡,可以在右邊的 list看到許多由 JYP娛樂提供的影片。
接著就拿它來進行辨識吧。
import cv2
import numpy as np
# import matplotlib.pyplot as plt

names = ["Nayeon", "Jeongyeon", "Momo", "Sana", "Jihyo", "Mina", "Dahyun", "Chaeyoung", "Tzuyu"]

model_LBPH = cv2.face.createLBPHFaceRecognizer()
model_LBPH.load("twice_model_LBPH.xml")

face_cascade = cv2.CascadeClassifier('./cascades/haarcascade_frontalface_default.xml')
eye_cascade = cv2.CascadeClassifier('./cascades/haarcascade_eye.xml')

def detect(img, idx):
    font_size = 1.2
    font_thickness = 3
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    faces = face_cascade.detectMultiScale(gray, 
                                          scaleFactor=1.1,
                                          minNeighbors=3,
                                          minSize=(130,130),)
    for (x,y,w,h) in faces:
        roi = gray[y:y+h, x:x+w]
        eyes = eye_cascade.detectMultiScale(roi,
                                            scaleFactor=1.06,
                                            minNeighbors=5,
                                            minSize=(30,30),)
        if len(eyes)>=1:
            try:
                roi = cv2.resize(roi, (200,200), interpolation=cv2.INTER_LINEAR)
                params = model_LBPH.predict(roi)
                print("Label: %s, Confidence: %.2f" % (params[0], params[1]))
                if params[1] < 80:
                    img = cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),3)
                    cv2.putText(img, names[params[0]], (x+5,y+h-5), cv2.FONT_HERSHEY_SIMPLEX, font_size, (255,0,0), font_thickness)
                else:
                    pass
                    # img = cv2.rectangle(img,(x,y),(x+w,y+h),(0,0,255),3)
                    # cv2.putText(img, 'Happy partner', (x+5,y+h-5), cv2.FONT_HERSHEY_SIMPLEX, font_size, (255,0,0), font_thickness)
            except:
                pass
                # img = cv2.rectangle(img,(x,y),(x+w,y+h),(0,0,255),3)
                # cv2.putText(img, 'Happy partner', (x+5,y+h-5), cv2.FONT_HERSHEY_SIMPLEX, font_size, (255,0,0), font_thickness)
    print('Working with %s frame' % idx)
    return img


input_file = 'twice_song.mp4'
output_file = 'face_twice_song.avi'
videoCapture = cv2.VideoCapture(input_file)
fps = videoCapture.get(cv2.CAP_PROP_FPS)
size = (int(videoCapture.get(cv2.CAP_PROP_FRAME_WIDTH)),
        int(videoCapture.get(cv2.CAP_PROP_FRAME_HEIGHT)))
videoWriter = cv2.VideoWriter(output_file, cv2.VideoWriter_fourcc('I','4','2','0'), fps, size)

success, frame = videoCapture.read()
frame_counter = 1
# plt.ion()
while success:
    frame = detect(frame, frame_counter)
    videoWriter.write(frame)
    # plt.imshow(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
    # plt.pause(.01)
    # plt.cla()  # Clear axis
    # plt.clf()  # Clear figure
    success, frame = videoCapture.read()
    frame_counter += 1

# plt.ioff()
print("Done!!")
內容與 "利用 OpenCV進行臉部辨識" 這張相當類似,參數的部分可以參考該篇。
比較值得注意的是第 33行的 "if params[1] < 80:",這裡可以對精度設定條件,再決定要不要加上標籤。
Eigenfaces、Fisherfaces和 LBPH顯示出的數字對精度的定義不太一樣,這裡也需要注意一下。
本篇是針對 LBPH設定,至於要設定多少,我也不好拿捏,不過數字越低,則表示預測的可靠度越高。
matplotlib.pyplot的部分被著解掉了,因為實在太消耗資源了,處理速度慢太多了。
接著我把臉部偵測的條件降低,眼睛至少偵測到一隻,就進行辨識,若辨識不出來或不精準精度高過 80就不顯示。
利用這個辨識模型製作了下面兩部影片。
(實際捕捉時的參數不一定和上面的代碼一樣,辨識不同的素材時,多少做了些修改,所以用上面的代碼可能是某次調適時所設定的,不一定會得到一樣的結果。)
第一部是使用了相同訓練素材製作的 MV,影片中的精度還算高,當然也因為有對精度進行篩選。

第二部也是使用了相同的模型對沒有訓練過的影片進行辨識,但可能因為造型變化大,且鏡頭移動快速,結果慘不人睹。


其實嘗試辨識了五六部影片的,但有些因為臉部捕捉率太低,或是版權問題無法分享,
由於對 OpenCV的辨識系統沒有很了解,但下意識覺得有可能是 Overfitting之類的問題,或是訓練數據仍不夠或品質不佳等原因。
有機會再來用 Face Alignment的方式提高數據品質,還有機會的話也來做個用深度學習的辨識系統。

留言

  1. 錯誤/修改:model_train.py代碼如下

    os.system("mv {0} {1}".format(image_path, trained_folder))
    中在在命令台中顯示
    >> 'mv' 不是內部或外部命令、可執行的程式或批次檔。
    這裡的'mv'須改為 'move'
    os.system("move {0} {1}".format(image_path, trained_folder))

    回覆刪除

張貼留言