###### tags: `OpenCV`,`臉部偵測`
# OpenCV 實作篇-臉部偵測
目標偵測與識別
影像分類是將㇐張圖片識別為既定類別的某㇐種,但如果要知道分類的物體在影像中的位置,就是目標偵測要解決的問題
目標偵測即檢測出圖片中主體所在的位置Bounding Box(簡稱BBox),矩形框的左上角座標與右下角座標或是左上角座標與矩形框⾧寬
分成3類:
* 傳統的目標檢測算法:Cascade + Harr / SVM + HOG /DPM 以及上述的諸多改進、優化
* 候選窗+深度學習分類:通過提取候選區域,並對相應區域進行以深度學習方法為主的分類的方案,如:RCNN /Fast-RCNN / Faster-RCNN / SPP-net / R-FCN 等系列方法
* 基於深度學習的回歸方法:YOLO / SSD / DenseBox等方法;以及最近出現的結合RNN算法的RRC detection;結合DPM的Deformable CNN等
認識Haar特徵分類器
**Haar features-哈爾特徵**
Haar特徵值反映了圖像的灰階變化情況,以人臉為例,將任意㇐個矩形放到人臉區域上,然後,將白色區域的畫素和減去黑色區域的畫素和,得到的值我們暫且稱之為人臉特徵值,如果你把這個矩形放到㇐個非人臉區域,那麼計算出的特徵值應該和人臉特徵值是不㇐樣的,而且越不㇐樣越好,所以這些方塊的目的就是把人臉特徵量化,以區分人臉和非人臉。

使用Haar特徵分類器模型時,系統會在圖片左上角產生㇐個檢測矩形,檢查此矩形是否符合Haar特徵分類模型特徵,接著將此矩形向右移動檢測,到最右方後移到左側下方檢測,直到圖片右下角為止。
示意圖如下~

:star:當我們提到Haar cascades,指的就是用Haar features做成的cascade classifiers
Haar-based face detector的理論概念
1.你可以去注意任何一張臉,眼睛的部分一定會比額頭和臉頰的部分暗。
2.嘴巴四周一定也會比臉頰部分暗。
Boost的想法
我們不需要絕對無敵的特徵點或是巨大複雜的模型,我們只需要很多表現高於平均的辨識器(weak learner),把它們串聯起來,就可以變超強。

導入模型-就用別人訓練好的
Rerference:
<https://github.com/opencv/opencv/tree/master/data/haarcascades>
```python=
import cv2
import numpy as np
#導入模型
haarCascadeFace = cv2.CascadeClassifier("haarcascade_frontalface_default.xml")
```
detectMultiScale語法
detectedObjects = cv2.CascadeClassifier.detectMultiScale(image,[scaleFactor,minNeighbors,flags,minSize,maxSize])
output:
detectedObjects-找出來的物件
input的部分:
1.image-目標照片-大家都說轉灰階照片比較好,但是彩色照片好像也沒差!
2.scaleFactor-每一次搜尋的時候,縮小的比例。這樣才可以避免尺寸的問題,做大大小小的偵測。這個數字越大,代表縮小比例越大,所以很快就看不到臉了!
3.minNeighbors-這是一個很妙的參數,簡單的說就是"人臉聚集的地方必定有人臉",所以區塊附近的搜尋區域應該也要發現有人臉的訊號。
4.minSize,maxSize-偵測區域尺寸的上限和下限

```python=
import cv2
#依照圖片格式,把圖片讀入
image = cv2.imread('child_resize.jpg')
#gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
haarCascadeFace = cv2.CascadeClassifier("haarcascade_frontalface_default.xml")
detectedObjects = haarCascadeFace.detectMultiScale(image, 1.2 ,2)
for face in detectedObjects:
x, y, w, h = face
cv2.rectangle(image, (x, y), (x+w, y+h), (0, 0, 255), 2)
cv2.imshow('Find Face image haarcascade', image)
cv2.waitKey(0)
cv2.destroyAllWindows()
```
哈哈 結果不太好 = =

```python=
#測試scaleFactor的差異
import cv2
haarCascadeFace = cv2.CascadeClassifier("haarcascade_frontalface_default.xml")
image = cv2.imread('child_resize.jpg')
# gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
detectedObjects = haarCascadeFace.detectMultiScale(image, 1.1,3) #detectedObjects這是一個list資料結構
for face in detectedObjects:
x, y, w, h = face
cv2.rectangle(image, (x, y), (x+w, y+h), (0, 0, 255), 2)
cv2.imshow(f'scaleFactor 1.1', image)
image1 = cv2.imread('child_resize.jpg')
gray_image1 = cv2.cvtColor(image1, cv2.COLOR_BGR2GRAY)
detectedObjects1 = haarCascadeFace.detectMultiScale(gray_image1, 1.5,3)
for face in detectedObjects1:
x, y, w, h = face
cv2.rectangle(image1, (x, y), (x+w, y+h), (0, 255, 0), 2)
cv2.imshow(f'scaleFactor 1.5', image1)
image2 = cv2.imread('child_resize.jpg')
gray_image2 = cv2.cvtColor(image2, cv2.COLOR_BGR2GRAY)
detectedObjects2 = haarCascadeFace.detectMultiScale(gray_image1, 2,3)
for face in detectedObjects2:
x, y, w, h = face
cv2.rectangle(image2, (x, y), (x+w, y+h), (255, 0, 0), 2)
cv2.imshow(f'scaleFactor 2', image2)
cv2.waitKey(0)
cv2.destroyAllWindows()
```

來試試其他deep learning face detector models
深度學習的架構有很多種...Caffe, TensorFlow, Torch, and Darknet,
接下來我們使用Caffe, TensorFlow兩種範例
**Caffe版**
需要有兩個檔案
res10_300x300_ssd_iter_140000_fp16.caffemodel :這就是權重系數檔案
deploy.prototxt :這是模型結構說明檔
**cv2.dnn.blobFromImage()**
cv2.dnn.blobFromImage(image[, scalefactor[, size[, mean[, swapRB[, crop[, ddepth]]]]]])
image:輸入圖像(1、3或者4通道)
scalefactor:圖像各通道數值的縮放比例
size:輸出圖像的空間尺寸,如size=(200,300)表示寬w=200,高h=300
mean:用於各通道減去的值,以降低亮度的影響(e.g. image為 BGR 3通道的圖像,mean=[104.0, 177.0, 123.0],表示b通道的值-104,g-177,r-123)
swapRB:交換RB通道,默認為False.(cv2.imread讀取的是彩圖是BGR通道)
crop:圖像裁剪,默認為False.當值為True時,先按比例縮放,然後從中心裁剪成size尺寸
ddepth:輸出的圖像深度,可選CV_32F 或者 CV_8U.
```python=
import cv2
import numpy as np
from matplotlib import pyplot as plt
#模型結構 跟 參數
net = cv2.dnn.readNetFromCaffe("deploy.prototxt", "res10_300x300_ssd_iter_140000_fp16.caffemodel")
#依照圖片格式,把圖片讀入
image = cv2.imread('child_resize.jpg')
(h, w) = image.shape[:2]
print(h,w) #605 454
```
```python=
#模型要求是BGR格式
#圖片尺寸用的是我們圖片的尺寸
#它要求每個通道的像數要減除一個常數
blob = cv2.dnn.blobFromImage(image, 1.0, (454, 605),[104., 117., 123.],False, False)
#blob轉換檔案格式,轉成模型可以使用的檔案格式
#image放入模型
net.setInput(blob)
#開始計算
detections = net.forward() #forward 一層處理完再處理下一層(deeplearning模式)
```
detections 是一個4d資料結構

```python=
detections[0][0][0] #4d結構
#array([0. , 1. , 0.99768186, 0.43984586, 0.35949108,0.52031076, 0.42666915], dtype=float32)
#[0,1,信心水準(偵測人臉的信心指數),start x比例,start y比例,end x比例,end y比例]
```
```python=
detected_faces = 0
# Iterate over all detections:
#detections.shape[2] #是偵測到的個數
for i in range(0, detections.shape[2]):
# Get the confidence (probability) of the current detection:
confidence = detections[0, 0, i, 2] #i每個都要跑過,2代表信心指數那個欄位
# Only consider detections if confidence is greater than a fixed minimum confidence:
if confidence > 0.3:
# Increment the number of detected faces:
detected_faces += 1
# 把算出來的定位比例還原成原本的尺寸,這樣我們才可以拿到座標
box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])
#座標沒有小數,所以要整理一下
(startX, startY, endX, endY) = box.astype("int")
# 把框框畫出來
cv2.rectangle(image, (startX, startY), (endX, endY), (255, 0, 0), 2)
#把信心指數寫一下
text = "{:.2f}%".format(confidence * 100)
#確定一下找到的框框上面還有沒有空間可以寫字,那就維持,如果沒有,就寫框框下面
y = startY - 10 if startY - 10 > 10 else startY + 10
#0.4字體大小
#2是字的粗細
cv2.putText(image, text, (startX, y), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 0, 255), 1)
```
```python=
cv2.imshow("image",image)
cv2.waitKey(0)
cv2.destroyAllWindows()
```

**Tensorflow**
需有兩個檔案
opencv_face_detector_uint8.pb
opencv_face_detector.pbtxt
```python=
import cv2
import numpy as np
#依照圖片格式,把圖片讀入
image = cv2.imread('child_resize.jpg')
(h, w) = image.shape[:2]
#模型要求是BGR格式
#要求300*300的尺寸
#它要求每個通道的像數要減除一個常數
blob = cv2.dnn.blobFromImage(image, 1.0, (584, 886), [104., 117., 123.],False, False)
net = cv2.dnn.readNetFromTensorflow("opencv_face_detector_uint8.pb","opencv_face_detector.pbtxt")
#image進入模型
net.setInput(blob)
#開始計算
detections = net.forward()
detected_faces = 0
# Iterate over all detections:
for i in range(0, detections.shape[2]):
# Get the confidence (probability) of the current detection:
confidence = detections[0, 0, i, 2]
# Only consider detections if confidence is greater than a fixed minimum confidence:
if confidence > 0.3:
# Increment the number of detected faces:
detected_faces += 1
# 把算出來的定位比例還原成原本的尺寸,這樣我們才可以拿到座標
box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])
#座標沒有小數,所以要整理一下
(startX, startY, endX, endY) = box.astype("int")
# 把框框畫出來
cv2.rectangle(image, (startX, startY), (endX, endY), (255, 0, 0), 2)
#把信心指數寫一下
text = "{:.2f}%".format(confidence * 100)
#確定一下找到的框框上面還有沒有空間可以寫字,那就維持,如果沒有,就寫框框下面
y = startY - 10 if startY - 10 > 10 else startY + 10
#0.4字體大小
#2是字的粗細
cv2.putText(image, text, (startX, y), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 0, 255), 1)
cv2.imshow("Tensorflow image",image)
cv2.waitKey(0)
cv2.destroyAllWindows()
```

:star: :star: :star:
結論呢~~ 深度學習的框架還是比較厲害!!
可以把哈爾偵測失敗的都偵測出來!