[ OpenCV ] 利用 OpenCV進行臉部辨識

之前寫了捕捉臉部的 Code後,就變得有點貪心了,想說試著看看能不能分類 Twice的成員。
先利用上一篇 "利用 OpenCV抓取臉部數據",捕捉要訓練用的臉部圖片。
很殘酷的是,用同樣的參數抓取大量格式不一的網路圖片時,應該會有很多圖片的臉部捕捉錯誤,可能需要手工移除錯誤的圖片,目前我也沒有有效的方法去解決這個問題。有招的大大拜託不要留招。

開始之前,我的資料夾結構如下
.
├── cascades
│   └── haarcascade_frontalface_default.xml
├── Chaeyoung
│   └── ...
├── Dahyun
│   └── ...
├── Jeongyeon
│   └── ...
├── Jihyo
│   └── ...
├── Mina
│   └── ...
├── Momo
│   └── ...
├── Nayeon
│   └── ...
├── Sana
│   └── ...
├── Tzuyu
│   └── ...
└── facial_recognition.py
裡面放著類似這樣的圖,大約每個成員都有40張左右的數據。
 
 
一個都不認識?沒關係,讓 OpenCV介紹給你認識。
import os
import cv2
import numpy as np
import random

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

x,y = [],[]
for idx,member in enumerate(names):
    for fileNames in os.walk("./%s" % member):
        for fileName in fileNames[-1]:
            if fileName == '.DS_Store':
                pass
            else:
                img = cv2.imread(str("./"+member+"/"+fileName), cv2.IMREAD_GRAYSCALE)
                x.append(img)
                y.append(idx)

data = list(zip(x, y))
random.shuffle(data)
data_np = np.array(data)
x = data_np[:,0].tolist()
y = data_np[:,1].tolist()
先把成員的名字建立成 list,再將每個成員的名字和檔案名稱加工成路徑,再利用 OpenCV讀取檔案加入 Training Data的 list,並同時建立 y作為 Label。
雖然不知道對 OpenCV的學習系統有沒有影響,但還是把資料的順序打亂一下好了。
(用 os.walk來找資料夾中的文件並不是很有效率,比較好的做法可以使用 glob。文中雖然不是寫得很聰明,但這是小弟學習的過程,所以就沒有更新代碼了,有需要的朋友試著自己動手改吧。)

OpenCV提供了三種辨識用的演算法,分別是
  • Eigenfaces
  • Fisherfaces
  • Local Binary Pattern Histogram ( LBPH )
有興趣的話可以查查這些算法的論文。

為了比較一下三種算法的差異,所以把每個模型都拿來訓練一下。
model_EigenFaces = cv2.face.createEigenFaceRecognizer()
model_EigenFaces.train(np.asarray(x), np.asarray(y))

model_FisherFaces = cv2.face.createFisherFaceRecognizer()
model_FisherFaces.train(np.asarray(x), np.asarray(y))

model_LBPH = cv2.face.createLBPHFaceRecognizer()
model_LBPH.train(np.asarray(x), np.asarray(y))
建立模型後,用這些模型來辨識,經 OpenCV辨識出的臉吧。
face_cascade = cv2.CascadeClassifier('./cascades/haarcascade_frontalface_default.xml')

def detect(filename):
    img = cv2.imread(filename)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    faces = face_cascade.detectMultiScale(gray, 1.3, 5, minSize=(100,100))
    for (x,y,w,h) in faces:
        img = cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)
        roi = gray[y:y+h, x:x+w]
        try:
            roi = cv2.resize(roi, (200,200), interpolation=cv2.INTER_LINEAR)
            params = model_EigenFaces.predict(roi)
            print("Label: %s, Confidence: %.2f" % (params[0], params[1]))
            cv2.putText(img, names[params[0]], (x,y-35), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255,0,0), 2)
        except:
            cv2.putText(img, 'Happy partner', (x,y-35), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255,0,0), 2)
        try:
            roi = cv2.resize(roi, (200,200), interpolation=cv2.INTER_LINEAR)
            params = model_FisherFaces.predict(roi)
            print("Label: %s, Confidence: %.2f" % (params[0], params[1]))
            cv2.putText(img, names[params[0]], (x,y-20), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0,255,0), 2)
        except:
            cv2.putText(img, 'Happy partner', (x,y-20), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0,255,0), 2)
        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]))
            cv2.putText(img, names[params[0]], (x,y-5), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0,0,255), 2)
        except:
            cv2.putText(img, 'Happy partner', (x,y-5), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0,0,255), 2)
    cv2.imwrite('./face_tzuyu.png', img)
    print('Working with %s' % filename)

detect('tzuyu.jpg')
接著把辨識出來的臉,先用 "try" 試看看有沒有辦法辨認是哪位成員,如果辨識成功就會在附近標注名字。若辨識失敗,就會標注 "Happy partner"。
利用 cv2.putText在圖片上進行標注,其參數依序如下
  • 要被標注的圖片
  • 標注的文字
  • 位置
  • 字型
  • 字體大小
  • 字體顏色
  • 字體粗細
其中我把 Eigenfaces、Fisherfaces、LBPH的辨識結果分別設為藍色、綠色和紅色,以利分辨。
遺憾的是,OpenCV似乎沒有辦法支援中文字型的輸出,如果各位大大有招,拜託教一下。

完成後,先試試看有沒有辦法辨識子瑜女神

看來 LBPH辨識錯誤了,再來試試 Twice和愉快伙伴們?
 
結果挺令人失望了,幾乎全部辨識錯誤,連愉快伙伴都被辨識為 Twice的成員。
會不會任誰的臉都會被辨識為 Twice的成員呢?
為了測試模型,所以做了以下實驗性的嘗試,Once和其他照片中的粉絲請不要生氣。
看來在這個分類器眼中,似乎沒有愉快伙伴,幾乎什麼人都可以是 Twice。
有可能是資料量太少,或是 Training Data解析度差異太大等原因。說實話,我也不曉得。
所以對不起摟,目前這個分類器沒有辦法介紹 Twice成員。之後有機會再來寫個用其他深度學習模塊的分類器好了。

雖然這三個模型都沒有辦法準確的辨識 Twice,但還是儲存一下好了。
儲存時,副檔名可以選擇 yml或是 xml。
model_EigenFaces.save('twice_model_EigenFaces.yml')
model_FisherFaces.save('twice_model_FisherFaces.yml')
model_LBPH.save('twice_model_LBPH.yml')
要在讀取已訓練的數據時,先建立一個相對應的模型,再讀取 yml檔或 xml檔。
model_EigenFaces_new = cv2.face.createEigenFaceRecognizer()
model_EigenFaces_new.load('twice_model_EigenFaces.yml')

model_FisherFaces_new = cv2.face.createFisherFaceRecognizer()
model_FisherFaces_new.load('twice_model_FisherFaces.yml')

model_LBPH_new = cv2.face.createLBPHFaceRecognizer()
model_LBPH_new.load('twice_model_LBPH.yml')

留言