人臉檢測 (Face Detection) 通常是人臉辨識流程的前置處理。這裡我們利用 Haar 特徵 來進行實作。
在訓練過程中,該演算法使用 AdaBoost,即利用多個「弱分類器」級聯 (Cascade) 來判別。每一步都會提取一個特徵值來判斷是否為人臉:
- 如果判斷為「是」,則進入下一個層級的強分類器。
- 如果判斷為「否」,則直接排除該區域。
廣義來看,這就像是讓所有弱分類器進行投票,並根據各自的準確率加權集成。其組成的分類器架構稱為 Cascade,形式上類似於簡單的多層決策樹。
實際應用與調整
在實際使用中,Haar Cascade 的挑戰主要在於參數的調優,尤其是 scaleFactor 和 minNeighbors:
scaleFactor:控制影像縮放的比例。數值調大時,檢測的層數會變少,速度快但容易漏掉較小的目標。minNeighbors:決定一個目標區域被聲明為「人臉」前,周圍必須也被檢測到的人臉鄰居數量。
由於不同圖片的解析度與場景差異,往往需要手動調整參數才能達到最佳效果,這在自動化處理上較為困難。未來我會嘗試使用深度學習等更強健的方式。
參考來源:Face Recognition with Python
當時寫的程式如下,先偵測臉,再在每張臉的範圍裡偵測眼睛:
import cv2
import sys
# Create the haar cascade
faceCascade = cv2.CascadeClassifier("haarcascade_frontalface_default.xml")
eyecascade = cv2.CascadeClassifier('haarcascade_eye.xml')
# Read the image
image = cv2.imread('Img_GetImage.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Detect faces in the image
faces = faceCascade.detectMultiScale(
gray,
scaleFactor=1.3,
minNeighbors=5,
minSize=(30, 30),
flags = cv2.CASCADE_SCALE_IMAGE
)
# Draw a rectangle around the faces and eyes
for (x, y, w, h) in faces:
cv2.rectangle(image, (x, y), (x+w, y+h), (0, 0, 255), 2)
roi_gray = gray[y:y+h, x:x+w]
roi_color = image[y:y+h, x:x+w]
eyes = eyecascade.detectMultiScale(roi_gray,1.3,1)
for (ex,ey,ew,eh) in eyes:
cv2.rectangle(roi_color,(ex,ey),(ex+ew,ey+eh),(0,255,0),2)
cv2.imshow("Faces found", image)
cv2.waitKey(0)
cv2.destroyAllWindows()
兩個參數在數什麼
minNeighbors 這個名字看起來就是在數鄰居,鄰居到底指什麼,detectMultiScale 的另一個多載版本講得比較清楚。detectMultiScale2 除了回傳框,還會多回傳一組 numDetections,OpenCV 文件對它的說明是「An object’s number of detections is the number of neighboring positively classified rectangles that were joined together to form the object.」,被合併成這一個框的、位置相鄰而且判為正的矩形,總共有幾個。
所以一張臉在偵測過程中會被判為正很多次:稍微偏一點的位置、稍微大一點或小一點的尺寸,各自都會中,這一疊重疊的矩形最後合併成一個回傳出來,minNeighbors 就是這一疊要多厚才留得住,預設是 3。臉正對鏡頭、光線平均,這一疊就厚;壓在畫面邊角、比較糊的那張,疊出來就薄,門檻拉高一點就先被濾掉了。
scaleFactor 決定的是總共掃過幾種尺寸。分類器是拿一個固定大小的窗口在掃,訓練的時候正負樣本都被縮到同一個尺寸,例如 20x20,畫面裡的臉多大事先並不知道,只能換好幾種比例、掃好幾輪。scaleFactor 就是每一輪之間的比例,預設 1.1,一輪比一輪縮一成左右。
把它從 1.1 調到 1.3,輪數會少掉一截,跑起來快,相鄰兩輪之間的尺寸差距也跟著拉大,剛好落在中間的那個大小就沒被掃到。同一張臉被掃中的輪數變少,前面那一疊也跟著變薄,本來湊得出三個鄰居的臉,現在只湊得出一兩個,minNeighbors 還停在 3 就把它濾掉了。這兩個數字是連著動的,scaleFactor 調粗之後,門檻通常也要跟著往下調。
上面那份程式裡,臉的那次是 scaleFactor=1.3、minNeighbors=5、minSize=(30, 30);眼睛的那次是在每張臉切出來的 ROI 裡再跑一遍,eyecascade.detectMultiScale(roi_gray,1.3,1),一樣的比例,門檻降到 1。ROI 本來就小,能掃的輪數少,一隻眼睛疊得出來的框自然也少。
detectMultiScale2 的鄰居數
既然這個數量本來就是回傳值的一部分,就不必靠感覺猜。把 minNeighbors 先設低,改用 detectMultiScale2 跑一次,每個框旁邊都會附一個數字:
faces, num = face_cascade.detectMultiScale2(gray, scaleFactor=1.1, minNeighbors=1)
for (x, y, w, h), n in zip(faces, num):
print(x, y, w, h, n)
把框畫出來、數字印出來,哪幾個是臉、哪幾個是誤判,各自落在什麼區間,看一輪就有底了,門檻要設多少從這裡讀,不用一格一格往上試。minSize 就是上面給過的那個 (30, 30),比這個尺寸小的目標直接忽略,素材裡如果本來就沒有那麼小的臉,把下限拉起來能省掉一批雜訊。
現在的 OpenCV
這些 cascade 檔案目前都還在 OpenCV 裡,預訓練模型就放在安裝目錄的 data 資料夾,裝好就有,不用另外抓。訓練那一端則不一樣了,官方的訓練教學上有一行「Createsamples and traincascade are disabled since OpenCV 4.0.」,想自己訓一份新的,得回頭去用 3.4 分支。這批 XML 大致上就停在那裡,載得起來、跑得動,不會再多出新的。
檔名寫 frontalface 的那份就是正臉,側臉是同一個資料夾裡的另一個檔案 haarcascade_profileface.xml,眼睛又是另外一份。要框哪一種東西就換哪一份 XML。
4.5.4 起 OpenCV 內建了 FaceDetectorYN。它的回傳一列就是一張臉,框以外還帶著右眼、左眼、鼻尖和兩個嘴角的座標,上面要跑第二次 cascade 才拿得到的東西,在那邊是同一次的輸出。
它也有自己處理得動的範圍,能偵測的臉大約落在 10x10 到 300x300 像素之間,太小或太大一樣不在裡面。要用它得多帶一個 ONNX 模型檔進來。
所以現在還會把 Haar cascade 拿出來用的場合,大概是機器只有 CPU、不想為了框個臉多搬一個模型檔、畫面裡的臉又都大致正對鏡頭。剩下的成本是那兩個數字,得在自己的素材上先找一次。
(2026 年補的這幾段是照 OpenCV 官方文件目前的 4.x 版寫的,版本再往前走就不一定準了,有寫錯再跟我說。)


