# 基於 Yolov3-tiny 與 dlib 的人臉辨識
辨識原理是先用yolo框臉部的範圍,再利用python dlib進行特徵點的抓取,最後取左臉邊界(2)、右臉邊界(16)、眉心(21,22)、下巴邊界(8)以及鼻子(30)座標,計算頭部水平、垂直旋轉量。\
\
以下用區段的方式介紹程式碼,先附上完整程式碼以及結果圖\
程式碼:
```python=
import numpy as np
import cv2
import dlib
import time
import math
net = cv2.dnn.readNetFromDarknet("/home/bmeimage/Desktop/eBrain/face_detection/weight/face-yolov3-tiny.cfg", "/home/bmeimage/Desktop/eBrain/face_detection/weight/face-yolov3-tiny_41000.weights") #讀取模型
layer_names = net.getLayerNames()
output_layers = []
for i in net.getUnconnectedOutLayers():
output_layers.append(layer_names[i-1])
predictor = dlib.shape_predictor("/home/bmeimage/Desktop/eBrain/face_detection/weight//shape_predictor_68_face_landmarks.dat")
detector = dlib.get_frontal_face_detector()
start = 0
end = 0
cap=cv2.VideoCapture(0)
x = 0
y = 0
w = 0
h = 0
nose = None
left = None
right = None
up = None
down = None
xangle = 0
yangle =0
while(True):
state = False
start = time.time()
ret,img =cap.read()
img=cv2.flip(img,1)
img1=img.copy()
#cv2.imshow("RAW", img1)
height, width, channels = img.shape
blob = cv2.dnn.blobFromImage(img, 1/255.0, (600, 600), (0, 0, 0), True, crop = False)
net.setInput(blob)
outs = net.forward(output_layers)
confidences = []
boxes = []
for out in outs:
for detection in out :
tx, ty, tw, th, confidence = detection[0:5]
scores = detection[5:]
if confidence > 0.3:
state = True
center_x = int(tx*width)
center_y = int(ty*height)
w = int(tw*width)+10
h = int(th*height)+10
x = int(center_x - w/2)-5
y = int(center_y - h/2)-2
boxes.append([x, y, w, h])
confidences.append(float(confidence))
if state:
crop_img = img[y:y+h, x:x+w]
#cv2.imshow("crop image",crop_img)
dets = detector(crop_img,1)
for det in dets:
landmark=[]
for p in predictor(crop_img,det).parts():
landmark.append(np.matrix([p.x,p.y]))
for idx, point in enumerate(landmark):
pos = (point[0, 0]+x, point[0, 1]+y)
#cv2.circle(img, pos, 5, (0,0,255),-1)
if(idx == 30): #nose
nose = pos
cv2.circle(img, pos, 5, (0,0,255),-1)
elif(idx == 1):
left = pos
cv2.circle(img, pos, 5, (0,0,255),-1)
elif(idx == 15):
right = pos
cv2.circle(img, pos, 5, (0,0,255),-1)
elif(idx == 21):
up = pos
cv2.circle(img, pos, 2, (255,255,255),-1)
elif(idx == 22):
up = (int((pos[0]+up[0])/2),int((pos[1]+up[1])/2))
cv2.circle(img, pos, 2, (255,255,255),-1)
cv2.circle(img, up, 5, (0,0,255),-1)
elif(idx == 8):
down = pos
cv2.circle(img, pos, 5, (0,0,255),-1)
else:
cv2.circle(img, pos, 2, (255,255,255),-1)
end=time.time()
cv2.rectangle(img, (0,0), (190, 109), (50,50,50), -1)
cv2.putText(img, " Webcam frame: "+str(cap.get(cv2.CAP_PROP_FPS)), (0, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1, cv2.LINE_AA)
cv2.putText(img, " Real frame: "+str(1//(end-start)), (0, 43), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (50, 50, 255), 1, cv2.LINE_AA)
if(nose != None and left != None and right != None and state == True):
cv2.line(img, nose, left, (0, 0, 255), 1)
cv2.line(img, nose, right, (0, 0, 255), 1)
print(nose[0]-left[0],right[0]-nose[0],end=" ")
if((nose[0]-left[0])>(right[0]-nose[0])):
#print("Right")
angle = 90 - math.degrees(math.acos(1-((2*abs(right[0]-nose[0]))/(right[0]-left[0]))))
elif((nose[0]-left[0])<(right[0]-nose[0])):
#print("Left")
angle = -1 *(90 - math.degrees(math.acos(1-((2*abs(nose[0]-left[0]))/(right[0]-left[0])))))
else:
angle = 0
print(angle)
cv2.putText(img, " Angle(x): " + str(round(angle,2)), (0, 66), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1, cv2.LINE_AA)
else:
cv2.putText(img, " Angle(x): None", (0, 66), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1, cv2.LINE_AA)
if(nose != None and up != None and down != None and state == True):
cv2.line(img, nose, up, (0, 0, 255), 1)
cv2.line(img, nose, down, (0, 0, 255), 1)
print(nose[1]-up[1],down[1]-nose[1],end=" ")
if((nose[1]-up[1])>(down[1]-nose[1])):
#print("up")
angle = -1 * (90 - math.degrees(math.acos(1-((2*abs(down[1]-nose[1]))/(down[1]-up[1])))))
elif((nose[1]-up[1])<(down[1]-nose[1])):
#print("down")
angle = 90 - math.degrees(math.acos(1-((2*abs(nose[1]-up[1]))/(down[1]-up[1]))))
else:
angle = 0
print(angle)
cv2.putText(img, " Angle(y): " + str(round(angle,2)), (0, 89), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1, cv2.LINE_AA)
else:
cv2.putText(img, " Angle(y): None", (0, 89), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1, cv2.LINE_AA)
indexes = cv2.dnn.NMSBoxes(boxes, confidences, 0.3, 0.4)
for i in range(len(boxes)) :
if i in indexes :
x, y, w, h = boxes[i]
label = "face"+" "+str(int(confidences[i]*100))+"%"
color = (255,255,255)
cv2.rectangle(img, (x, y), (x+w, y+h), color, 1)
cv2.putText(img, label, (x, y-5), cv2.FONT_HERSHEY_PLAIN, 1, color, 2)
cv2.imshow("main", img)
key=cv2.waitKey(1)
if key==ord('q') or key==ord('Q') or key==27:
break
cap.release()
cv2.destroyAllWindows()
```
結果運行:

----
# 辨識人臉
**首先先引入所需要的函式庫**:\
包含讀取Yolo模型的cv2(opencv)和讀取特徵點的dlib
```python=
import numpy as np
import cv2
import dlib
import time
import math
...
```
\
**接著輸入模型檔、模型組態以及輸出規格**:\
`face-yolov3-tiny.cfg` 是 yolov3-tiny 的組態檔,與模型架構相關\
`face-yolov3-tiny_41000.weights` 是yolov3-tiny 的權重檔,與模型中神經元計算權重相關,檔名中 `41000` 代表該權重係數訓練41000次
```python=
...
net = cv2.dnn.readNetFromDarknet("/home/bmeimage/Desktop/eBrain/face_detection/weight/face-yolov3-tiny.cfg", "/home/bmeimage/Desktop/eBrain/face_detection/weight/face-yolov3-tiny_41000.weights") #讀取模型
layer_names = net.getLayerNames()
output_layers = []
for i in net.getUnconnectedOutLayers():
output_layers.append(layer_names[i-1]) #輸出規格
predictor = dlib.shape_predictor("/home/bmeimage/Desktop/eBrain/face_detection/weight//shape_predictor_68_face_landmarks.dat")
detector = dlib.get_frontal_face_detector()
...
```
\
**變數的初始化以及取得webcam物件**:\
`start` 和 `end` 是計算辨識效率相關的變數\
`cap`是webcam物件,參數 `0` 大多是內建鏡頭, `1` 數字以上皆是外接鏡頭\
`nose`、`left`、`right`、`up`、`down`、`xangle`、`yangle` 是計算頭部轉向相關的變數(可以先忽略)\
`x`、`y`、`w`、`h` 是臉部範圍變數
```python=
...
start = 0
end = 0
cap=cv2.VideoCapture(0)
x = 0
y = 0
w = 0
h = 0
nose = None
left = None
right = None
up = None
down = None
xangle = 0
yangle =0
...
```
\
**接著進入執行的迴圈內**:\
先辨識臉部範圍後,將圖片裁切留下臉部,接著辨識特徵點。\
由鏡頭物件讀入影像,對影像做預處裡後,輸入到模型中\
輸出得出辨識的臉部範圍:`x` `y`是框線左上的座標(x,y),`w` `h`分別是框選的長、寬\
`confidences` 則是辨識的信心指數(0.8表示80%信心框選目標在框選區域內)
```python=
...
ret,img =cap.read()
img=cv2.flip(img,1)
img1=img.copy()
#cv2.imshow("RAW", img1)
height, width, channels = img.shape
blob = cv2.dnn.blobFromImage(img, 1/255.0, (600, 600), (0, 0, 0), True, crop = False)
net.setInput(blob) #將影像輸入模型
outs = net.forward(output_layers)
confidences = []
boxes = []
for out in outs:
for detection in out :
tx, ty, tw, th, confidence = detection[0:5]
scores = detection[5:]
if confidence > 0.3:
state = True
center_x = int(tx*width)
center_y = int(ty*height)
w = int(tw*width)+10
h = int(th*height)+10
x = int(center_x - w/2)-5
y = int(center_y - h/2)-2
boxes.append([x, y, w, h])
confidences.append(float(confidence))
...
```
\
若以上人臉辨識成立,即接著辨識特徵點\
將上述辨識的框選範圍對影像裁切,減少對於特徵點辨識的計算量以及錯誤判別\
`detector` 取得正臉後,`predictor` 取得臉部的特徵點(landmark)\
再根據特徵點的編號分別取得左臉邊界(1)、右臉邊界(15)、眉心(21,22)、下巴邊界(8)以及鼻子(30)座標,像是圖中顯示:

```python=
...
if state:
crop_img = img[y:y+h, x:x+w]
#cv2.imshow("crop image",crop_img)
dets = detector(crop_img,1)
for det in dets:
landmark=[]
for p in predictor(crop_img,det).parts():
landmark.append(np.matrix([p.x,p.y]))
for idx, point in enumerate(landmark):
pos = (point[0, 0]+x, point[0, 1]+y)
#cv2.circle(img, pos, 5, (0,0,255),-1)
if(idx == 30): #nose
nose = pos
cv2.circle(img, pos, 5, (0,0,255),-1)
elif(idx == 1):
left = pos
cv2.circle(img, pos, 5, (0,0,255),-1)
elif(idx == 15):
right = pos
cv2.circle(img, pos, 5, (0,0,255),-1)
elif(idx == 21):
up = pos
cv2.circle(img, pos, 2, (255,255,255),-1)
elif(idx == 22):
up = (int((pos[0]+up[0])/2),int((pos[1]+up[1])/2))
cv2.circle(img, pos, 2, (255,255,255),-1)
cv2.circle(img, up, 5, (0,0,255),-1)
elif(idx == 8):
down = pos
cv2.circle(img, pos, 5, (0,0,255),-1)
else:
cv2.circle(img, pos, 2, (255,255,255),-1)
...
```
完整參考官方Sample Code:[face-landmark-detection](http://dlib.net/face_landmark_detection.py.html)\
\
**註:後面就是計算頭部轉動的角度,這邊就不贅述*\
\
\
最後就是脫離迴圈的條件\
預設是讓與使用者按下`q`、`Q`、`ESC`可以跳出迴圈
```python=
...
key=cv2.waitKey(1)
if key==ord('q') or key==ord('Q') or key==27:
break
...
```
\
**迴切結束後,程式終止**
將鏡頭物件釋出,並且關閉視窗(若未關閉會導致程式崩潰)
```python=
...
cap.release()
cv2.destroyAllWindows()
```