我們將透過以下方法實現人臉檢測:
使用 OpenCV 的 Haar 級聯分類器
使用 Dlib 的方向梯度直方圖
使用 Dlib 的卷積神經網路
本文程式碼的 Github 庫(以及作者其他部落格的程式碼)連結:
https://github.com/maelfabien/Machine_Learning_Tutorials
我們將使用用於計算機視覺的開源庫 OpenCV,它用 C/C++編寫,有 C++、Python 和 Java 介面。同時支援 Windows、Linux、MacOS、iOS 和 Android 系統。同時我們還需要工具包 Dlib,它是一個包含機器學習演算法和建立複雜軟體的 C++工具包。
步驟
第一步是安裝 OpenCV 和 Dlib。執行以下命令:
pip install opencv-python
pip install dlib
檔案生成的路徑如下(版本不同,路徑會稍有差別):
/usr/local/lib/python3.7/site-packages/cv2
如果在使用 Dlib 時出現問題,請參見文章:https://www.pyimagesearch.com/2018/01/22/install-dlib-easy-complete-guide/
匯入工具包和模型路徑
建立一個新的 Jupyter notebook/Python 檔案,從以下程式碼開始:
import cv2
import matplotlib.pyplot as plt
import dlib
from imutils import face_utils
font = cv2.FONT_HERSHEY_SIMPLEX
級聯分類器
首先研究級聯分類器。
理論
級聯分類器,即使用類 Haar 特徵工作的級聯增強分類器,是整合學習的一種特殊情況,稱為 boost。它通常依賴於 Adaboost 分類器(以及其他模型,如 Real Adaboost、Gentle Adaboost 或 Logitboost)。
級聯分類器在包含檢測目標的幾百個樣本影像以及不包含檢測目標的其他影像上進行訓練。
我們如何檢測圖上是否有人臉呢?有一種名為 Viola-Jones 的目標檢測框架的演算法,包括了實時人臉檢測所需的所有步驟:
提取 Haar 特徵,特徵來自 Haar 小波
建立影像
Adaboost 訓練
級聯分類器
Haar 特徵選擇
人臉上最常見的一些共同特徵如下:
與臉頰相比,眼部顏色較深
與眼睛相比,鼻樑區域較為明亮
眼睛、嘴巴、鼻子的位置較為固定......
這些特徵稱為 Haar 特徵。特徵提取過程如下所示:
Haar 特徵
在上圖中,第一個特徵測量眼部和上臉頰之間的強度差異。特徵值計算的方法很簡單,對黑色區域中的畫素求和再減去白色區域中的畫素即可。
然後,將這個矩形作為卷積核作用到整個影像。為了不產生遺漏,我們需要用到每個卷積核的所有的維度和位置。簡單的 24 * 24 的影像可能會產生超過 160000 個特徵,每個特徵由畫素值的和/差組成。這樣在計算上無法實現實時人臉檢測。那麼,該如何加快這個過程呢?
一旦透過矩形框識別到有用區域,則在與之完全不同的區域上就無需再做計算了。這一點可以透過 Adaboost 實現。
使用積分影像原理計算矩形框特徵的方法更快。我們將在下一節介紹這一點。
原始論文中提到幾種可用於 Haar 特徵提取的矩形框:
雙矩形特徵計算的是兩個矩形區域內畫素和的差,主要用於檢測邊緣 (a,b)
三矩形特徵計算的是中心矩形和減去兩個外部矩形和的差,主要用於檢測線 (c,d)
四矩形特徵計算的是矩形對角線對之間的差 (e)
Haar 矩形
特徵提取完成後,使用 Adaboost 分類器將它們應用於訓練集,該分類器結合了一組弱分類器來建立準確的整合模型。只需 200 個特徵(最初是 16 萬個),實現了 95%的準確率。該論文的作者提取了 6000 個特徵。
積分影像
以卷積核的形式計算特徵需要花費很長時間。出於這個原因,作者 Viola 和 Jones 提出了影像的中間表示:積分影像。積分影像的作用是僅使用四個值簡單地計算矩形和。我們來看看它是如何工作的!
假設我們想要確定一個座標為 (x,y) 的給定畫素的矩形特徵。然後,畫素的積分影像是給定畫素的上方和左側的畫素之和。
其中 ii(x,y) 是積分影像,i(x,y) 是原始影像。
當計算整個積分影像時,有一種只需要遍歷一次原始影像的遞迴方法。實際上,我們可以定義以下一對遞迴形式:
其中 s(x,y) 是累積行和,而 s(x−1)=0, ii(−1,y)=0。
這是怎麼實現的呢?假設我們想要估算區域 D 的畫素總和。我們已經定義了 3 個其他區域:A,B 和 C。
點 1 處的積分影像的值是矩形 A 中的畫素的總和。
點 2 處的值為 A + B。
點 3 處的值為 A + C。
點 4 處的值是 A + B + C + D。
因此,區域 D 中的畫素之和可以簡單地計算為: 4+1−(2+3)。
這樣我們僅使用 4 個陣列值就計算出了矩形 D 的值。
人們應該知道矩形在實際中是非常簡單的特徵,但對於人臉檢測已經足夠了。當涉及複雜問題時,可調濾波器往往更靈活多變。
可調濾波器
使用 Adaboost 學習分類函式
給定一組帶標籤的訓練影像(正負樣本均有),Adaboost 用於:
提取一小部分特徵
訓練分類器
由於 16 萬個特徵中的大多數特徵與之極不相關,因此我們設計一個增強模型的弱學習演算法,用來提取單個矩形特徵,將最好的正負樣本區分開。
級聯分類器
雖然上述過程非常有效,但仍存在一個重大問題。在影像中,大部分影像為非面部區域。對影像的每個區域給予等同的注意力是沒有意義的,因為我們應該主要關注最有可能包含人臉的區域。Viola 和 Jone 使用級聯分類器在減少了計算時間的同時,實現了更高的檢測率。
關鍵思想是在識別人臉區域時排除不含人臉的子視窗。由於任務是正確識別人臉,我們希望假陰率最小,即包含人臉卻未被識別的子視窗最少。
每個子視窗都使用一系列分類器。這些分類器是簡單的決策樹:
如果第一個分類器檢測為正樣本,繼續用第二個
如果第二個分類器檢測是正樣本,繼續用第三個
以此類推
雖然有時可能包含人臉的圖被認成負樣本被子視窗漏檢。但初級分類器以較低的計算成本篩除了大多數負樣本,下圖的分類器可額外消除更多的負樣本,但需要更多的計算量。
使用 Adaboost 訓練分類器,並調整閾值使錯誤率降到最低。在訓練該模型時,變數如下:
每個階段分類器數量
每個階段的特徵數量
每個階段的閾值
幸運的是,在 OpenCV 中,整個模型已經經過預訓練,可直接用於人臉檢測。
如果想了解有關 Boosting 技術的更多資訊,歡迎檢視作者關於 Adaboost 的文章:
https://maelfabien.github.io/machinelearning/adaboost
輸入
下一步是找到預訓練的權重。我們將使用預設的預訓練模型來檢測人臉、眼睛和嘴巴。檔案應位於此路徑(python 版本不同,路徑略有不同):
/usr/local/lib/python3.7/site-packages/cv2/data
確定路徑後,以此方式宣告級聯分類器:
cascPath = "/usr/local/lib/python3.7/site-packages/cv2/data/haarcascade_frontalface_default.xml"
eyePath = "/usr/local/lib/python3.7/site-packages/cv2/data/haarcascade_eye.xml"
smilePath = "/usr/local/lib/python3.7/site-packages/cv2/data/haarcascade_smile.xml"
faceCascade = cv2.CascadeClassifier(cascPath)
eyeCascade = cv2.CascadeClassifier(eyePath)
smileCascade = cv2.CascadeClassifier(smilePath)
檢測影像中的人臉
在實現實時人臉檢測演算法之前,讓我們先嚐試在影像上簡單檢測一下。從載入測試影像開始:
# Load the image
gray = cv2.imread('face_detect_test.jpeg', 0)
plt.figure(figsize=(12,8))
plt.imshow(gray, cmap='gray')
plt.show()
測試影像
然後開始檢測人臉,並將檢測到的人臉框起來。
# Detect faces
faces = faceCascade.detectMultiScale(
gray,
scaleFactor=1.1,
minNeighbors=5,
flags=cv2.CASCADE_SCALE_IMAGE
)
# For each face
for (x, y, w, h) in faces:
# Draw rectangle around the face
cv2.rectangle(gray, (x, y), (x+w, y+h), (255, 255, 255), 3)
以下是 detectMultiScale 函式常見的引數列表:
scaleFactor:確定每個影像縮放比例大小。
minNeighbors:確定每個候選矩形應保留多少個相鄰框。
minSize:最小目標的大小。小於該值的目標將被忽略。
maxSize:最大目標的大小。大於該值的目標將被忽略。
最後,顯示結果:
plt.figure(figsize=(12,8))
plt.imshow(gray, cmap='gray')
plt.show()
在測試影像上成功檢測到人臉。現在開始實時檢測!
實時人臉檢測
下面繼續進行實時人臉檢測的 Python 實現。第一步是啟動攝像頭,並拍攝影片。然後,將影像轉換為灰度圖。這用於減小輸入影像的維數。實際上,我們應用了一個簡單的線性變換,而不是每個畫素用三個點來描述紅、綠、藍。
這在 OpenCV 中是預設實現的。
video_capture = cv2.VideoCapture(0)
while True:
# Capture frame-by-frame
ret, frame = video_capture.read()
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
現在我們使用上述定義的 faceCascade 變數,它包含一個預訓練演算法,現在將其用於灰度圖。
faces = faceCascade.detectMultiScale(
gray,
scaleFactor=1.1,
minNeighbors=5,
minSize=(30, 30),
flags=cv2.CASCADE_SCALE_IMAGE
)
對於檢測到的每個人臉,都加上一個矩形框:
for (x, y, w, h) in faces:
if w > 250 :
cv2.rectangle(frame, (x, y), (x+w, y+h), (255, 0, 0), 3)
roi_gray = gray[y:y+h, x:x+w]
roi_color = frame[y:y+h, x:x+w]
對於檢測到的每張嘴,都加上一個矩形框:
smile = smileCascade.detectMultiScale(
roi_gray,
scaleFactor= 1.16,
minNeighbors=35,
minSize=(25, 25),
flags=cv2.CASCADE_SCALE_IMAGE
)
for (sx, sy, sw, sh) in smile:
cv2.rectangle(roi_color, (sh, sy), (sx+sw, sy+sh), (255, 0, 0), 2)
cv2.putText(frame,'Smile',(x + sx,y + sy), 1, 1, (0, 255, 0), 1)
對於檢測到的每雙眼睛,都加上一個矩形框:
eyes = eyeCascade.detectMultiScale(roi_gray)
for (ex,ey,ew,eh) in eyes:
cv2.rectangle(roi_color,(ex,ey),(ex+ew,ey+eh),(0,255,0),2)
cv2.putText(frame,'Eye',(x + ex,y + ey), 1, 1, (0, 255, 0), 1)
然後計算人臉總數,顯示整體影像:
cv2.putText(frame,'Number of Faces : ' + str(len(faces)),(40, 40), font, 1,(255,0,0),2)
# Display the resulting frame
cv2.imshow('Video', frame)
當按下 q 鍵時,執行退出選項。
if cv2.waitKey(1) & 0xFF == ord('q'):
break
最後當所有操作完成後,關閉所有視窗。在 Mac 上關閉視窗存在一些問題,可能需要透過活動管理器退出 Python。
video_capture.release()
cv2.destroyAllWindows()
封裝
import cv2
cascPath = "/usr/local/lib/python3.7/site-packages/cv2/data/haarcascade_frontalface_default.xml"
eyePath = "/usr/local/lib/python3.7/site-packages/cv2/data/haarcascade_eye.xml"
smilePath = "/usr/local/lib/python3.7/site-packages/cv2/data/haarcascade_smile.xml"
faceCascade = cv2.CascadeClassifier(cascPath)
eyeCascade = cv2.CascadeClassifier(eyePath)
smileCascade = cv2.CascadeClassifier(smilePath)
font = cv2.FONT_HERSHEY_SIMPLEX
video_capture = cv2.VideoCapture(0)
while True:
# Capture frame-by-frame
ret, frame = video_capture.read()
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
faces = faceCascade.detectMultiScale(
gray,
scaleFactor=1.1,
minNeighbors=5,
minSize=(200, 200),
flags=cv2.CASCADE_SCALE_IMAGE
)
# Draw a rectangle around the faces
for (x, y, w, h) in faces:
cv2.rectangle(frame, (x, y), (x+w, y+h), (255, 0, 0), 3)
roi_gray = gray[y:y+h, x:x+w]
roi_color = frame[y:y+h, x:x+w]
cv2.putText(frame,'Face',(x, y), font, 2,(255,0,0),5)
smile = smileCascade.detectMultiScale(
roi_gray,
scaleFactor= 1.16,
minNeighbors=35,
minSize=(25, 25),
flags=cv2.CASCADE_SCALE_IMAGE
)
for (sx, sy, sw, sh) in smile:
cv2.rectangle(roi_color, (sh, sy), (sx+sw, sy+sh), (255, 0, 0), 2)
cv2.putText(frame,'Smile',(x + sx,y + sy), 1, 1, (0, 255, 0), 1)
eyes = eyeCascade.detectMultiScale(roi_gray)
for (ex,ey,ew,eh) in eyes:
cv2.rectangle(roi_color,(ex,ey),(ex+ew,ey+eh),(0,255,0),2)
cv2.putText(frame,'Eye',(x + ex,y + ey), 1, 1, (0, 255, 0), 1)
cv2.putText(frame,'Number of Faces : ' + str(len(faces)),(40, 40), font, 1,(255,0,0),2)
# Display the resulting frame
cv2.imshow('Video', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# When everything is done, release the capture
video_capture.release()
cv2.destroyAllWindows()
結果
我已經制作了人臉檢測演算法的 YouTube 影片演示:
Dlib 的方向梯度直方圖(HOG)
第二種常用的人臉檢測工具由 Dlib 提供,它使用了方向梯度直方圖(HOG)的概念。論文《Histograms of Oriented Gradients for Human Detection》實現這一方案。
理論
HOG 背後的想法是將特徵提取到一個向量中,並將其輸入到分類演算法中,例如支援向量機,它將評估人臉(或實際想識別的任何物件)是否存在於某個區域中。
提取的特徵是影像梯度(方向梯度)方向的分佈(直方圖)。梯度通常在邊緣和角落周圍較大,並允許我們檢測這些區域。
在原始論文中,該演算法用於人體檢測,檢測過程如下:
預處理
首先,輸入影像必須尺寸相同(可透過裁剪和縮放)。影像長寬比要求為 1:2,因此輸入影像的尺寸可能為 64x128 或 100x200。
計算梯度影像
第一步是透過以下卷積核計算影像的水平梯度和垂直梯度:
計算梯度的卷積核
影像的梯度通常會消除非必要資訊。
上面影像的梯度可以透過下面的 python 語句找到:
gray = cv2.imread('images/face_detect_test.jpeg', 0)
im = np.float32(gray) / 255.0
# Calculate gradient
gx = cv2.Sobel(im, cv2.CV_32F, 1, 0, ksize=1)
gy = cv2.Sobel(im, cv2.CV_32F, 0, 1, ksize=1)
mag, angle = cv2.cartToPolar(gx, gy, angleInDegrees=True)
繪製圖片:
plt.figure(figsize=(12,8))
plt.imshow(mag)
plt.show()
我們之前沒有預處理影像。
計算 HOG
首先將影像分成 8x8 個單元來提供緊湊表示,使 HOG 對噪聲更魯棒。然後,計算每個單元的 HOG。
為了估計區域內的梯度方向,我們只需在每個區域內的 64 個梯度方向值(8x8)及其大小(另外 64 個值)之間構建直方圖。直方圖的類別對應梯度的角度,從 0 到 180°。總共 9 類:0°,20°,40°...... 160°。
上面的程式碼給了我們 2 個資訊:
梯度方向
梯度大小
當我們構建 HOG 時,有 3 種情況:
角度小於 160°,且不介於兩類之間。在這種情況下,角度將新增到 HOG 的正確類中。
角度小於 160°,恰好在兩類之間。在這種情況下,畫素被均分到左右兩側類中。
角度大於 160°。在這種情況下,我們認為畫素與 160°和 0°成比例。
每個 8x8 單元的 HOG 如下所示:
HOG
模組歸一化
最後,可以用 16×16 的模組對影像進行歸一化,並使其對光照不變。這可以透過將大小為 8x8 的 HOG 的每個值除以包含它的 16x16 模組的 HOG 的 L2 範數來實現,這個模組實際上是長度為 9*4 = 36 的簡單向量。
模組歸一化
最後,將所有 36x1 向量連線成一個大向量。OK!現在有了特徵向量,我們可以在上面訓練一個軟 SVM 分類器(C=0.01)。
檢測影像上的人臉
實現非常簡單:
face_detect = dlib.get_frontal_face_detector()
rects = face_detect(gray, 1)
for (i, rect) in enumerate(rects):
(x, y, w, h) = face_utils.rect_to_bb(rect)
cv2.rectangle(gray, (x, y), (x + w, y + h), (255, 255, 255), 3)
plt.figure(figsize=(12,8))
plt.imshow(gray, cmap='gray')
plt.show()
實時人臉檢測
如前所述,該演算法非常容易實現。我們還實現了一個更輕量的版本,只用來識別人臉。Dlib 讓人臉關鍵點的檢測更加容易,但這是另一個話題。
video_capture = cv2.VideoCapture(0)
flag = 0
while True:
ret, frame = video_capture.read()
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
rects = face_detect(gray, 1)
for (i, rect) in enumerate(rects):
(x, y, w, h) = face_utils.rect_to_bb(rect)
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv2.imshow('Video', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
video_capture.release()
cv2.destroyAllWindows()
Dlib 中的卷積神經網路
最後一種方法基於卷積神經網路。為了增強結果,它還實現了最大邊緣目標檢測(MMOD)。
理論
卷積神經網路是主要用於計算機視覺的前饋神經網路。它們提供自動影像預處理以及密集的神經網路部分。CNN 還是用來處理帶有網格狀拓撲的資料的特殊神經網路。它的架構靈感來自動物視覺皮層。
以前的方法中,很大一部分工作是選擇濾波器來建立特徵,以便盡從影像中可能多地提取資訊。隨著深度學習和計算能力的提高,這項工作現在可以實現自動化。CNN 的名稱就來自我們用一組濾波器卷積初始影像輸入的事實。需要選擇的引數仍是需要應用的濾波器數量以及尺寸。濾波器的尺寸稱為步幅。一般步幅設定在 2 到 5 之間。
在這種特定情況下,CNN 的輸出是二分類,如果有人臉,則取值 1,否則取 0。
檢測影像上的人臉
一些元素在實現中會發生變化。
第一步是下載預訓練模型:https://github.com/davisking/dlib-models/blob/master/mmod_human_face_detector.dat.bz2
將下載後的權重放到資料夾中,並定義 dnnDaceDetector:
dnnFaceDetector = dlib.cnn_face_detection_model_v1("mmod_human_face_detector.dat")
然後,與之前做的相同:
rects = dnnFaceDetector(gray, 1)
for (i, rect) in enumerate(rects):
x1 = rect.rect.left()
y1 = rect.rect.top()
x2 = rect.rect.right()
y2 = rect.rect.bottom()
# Rectangle around the face
cv2.rectangle(gray, (x1, y1), (x2, y2), (255, 255, 255), 3)
plt.figure(figsize=(12,8))
plt.imshow(gray, cmap='gray')
plt.show()
實時人臉檢測
最後,實現實時 CNN 人臉檢測:
video_capture = cv2.VideoCapture(0)
flag = 0
while True:
# Capture frame-by-frame
ret, frame = video_capture.read()
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
rects = dnnFaceDetector(gray, 1)
for (i, rect) in enumerate(rects):
x1 = rect.rect.left()
y1 = rect.rect.top()
x2 = rect.rect.right()
y2 = rect.rect.bottom()
# Rectangle around the face
cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
# Display the video output
cv2.imshow('Video', frame)
# Quit video by typing Q
if cv2.waitKey(1) & 0xFF == ord('q'):
break
video_capture.release()
cv2.destroyAllWindows()
如何選擇模型
這是一個很難回答的問題,但我們只討論兩個重要指標:
計算時間
準確率
在速度方面,HOG 是最快的演算法,其次是 Haar 級聯分類器和 CNN。
但是,Dlib 中的 CNN 是準確率最高的演算法。HOG 表現也很好,但在識別較小的人臉時會有一些問題。Haar 級聯分類器的整體表現與 HOG 相似。
考慮到實時人臉檢測的速度,我在個人專案中使用了 HOG。
希望這個關於 OpenCV 和 Dlib 的人臉檢測的快速教程能對你有所幫助。
原文連結:https://towardsdatascience.com/a-guide-to-face-detection-in-python-3eab0f6b9fc1