opencv python dlib

基於python語言使用OpenCV搭配dlib實作人臉偵測與辨識

張鈞名 2018/10/15 09:00:00
68576

基於python語言使用OpenCV搭配dlib實作人臉偵測與辨識


簡介

OpenCV的全名是Open Source Computer Vision Library,是一個跨平台的影像函式庫,OpenCV可用於開發即時影像處理,本文章使用opencv並搭配dlib Machine learning函式庫,可實作出一個簡單的人臉偵測與辨識功能。

作者

張鈞名


什麼是OpenCV?

  OpenCV全名為Open Source Computer Vision Library,是一個跨平台的影像函式庫。OpenCV為BSD授權條款,可以免費用於商業和研究領域。
  OpenCV可處理領域很多,如擴增實境、人臉辨識、動作與物體辨識等等。
  OpenCV主要使用C++語言所編寫,但它有提供不同語言的API介面如Python、Java、C#,和Ruby,本文章使用Python語言來做進一步的實作介紹。

安裝OpenCV

  由於本文章環境於Mac執行,所以我們使用brew安裝套件來安裝OpenCV。
打開終端機執行以下指令:
# add opencv
brew tap homebrew/science

# install opencv
# 安装2.4
brew install opencv
# 安装opencv3
brew install opencv3
  在使用opencv前我們必須要安裝python的numpy和matplotlib函式庫。
pip install numpy
pip install matplotlib
  接下來我們要設定環境變數,我先進到Pyhton的所在目錄底下。
cd  /Library/Python/2.7/site-packages/
  然後我們必須設定軟連結,而何謂軟連結?在Linux/Unix 檔案系統中,軟連結(symbolic link)的意思是指產生一個特殊的檔案,而將這個檔案的內容位置指向到另一個檔案的位置。
sudo ln -s /usr/local/Cellar/opencv3/3.2.0/lib/python2.7/site-packages/cv2.so cv2.so
sudo ln -s /usr/local/Cellar/opencv3/3.2.0/lib/python2.7/site-packages/cv.py cv.py
  最後寫入環境變數。
vim ~/.bash_profile
#加入環境變數
export PYTHONPATH=$PYTHONPATH:/usr/local/lib/python2.7/site-packages 
#使其環境變數生效
source ~/.bash_profile
  最後打開終端機,鍵入python進入其環境,鍵入以下指令,若沒錯誤就代表有安裝成功。
import cv2

什麼是Dlib?

  Dlib是一套使用C++語言所編寫的函式庫,主要可應用在機器學習、影像處理,以及影像辨識等等,他開源而且免費,基於BSD授權條款。
Dlib官網還提供相當完整的文檔,每個類別與每個函示都有詳細的說明,也有提供大量範例程式碼,另外也有 提供Python API。

安裝Dlib

  安裝python依賴包
#使用pip進行安裝
pip install numpy
pip install scipy
pip install scikit-image
  安裝Dlib
pip install dlib
 
 接下來 驗證安裝是否正確鍵入Python進入其環境, 鍵入以下指令,若沒錯誤就代表有安裝成功。
import dlib

Python基本介紹

 Python是一種物件導向的高階程式語言,它是一種簡單優雅、容易上手的直譯式語言,它主要使用空格縮排劃分程式碼區塊,而不像C#使用大括號做區分, 而且Python擁有眾多的開源且免費的第三方函式庫使得Python異常的強大。
  Python中預設編碼格式為ASCII 格式,所以在讀取中文字的編碼時會出錯,所以必須要在文件一開頭加入以下編碼。
# -*- coding: utf-8 -*-
Hello World
>>> print("Hello, world!")
Hello, world!
程式碼範圍
if age > 18
print("我已經成年")
print("我在if裡面")
print("我在if外面")
基本數據類型
 
類型是不需要聲明的。
a = 1       # 整數
b = 1.2     # 浮點數
c = True    # 布林值
d = "False" # 字串
e = None    # NoneType
 
運算式
 
Python運算式跟C語言是差不多的
a = 2
b = 2.3         
c = 3
a + b           # 2 + 2.3 = 4.3
c  a           # 3 - 2 = 1
a / b           # 整數除以浮點數,運算以浮點數為準,2 / 2.3 = 0.8695652173913044
a / c           # 整數除法,向下取整 2 / 3 = 0
a ** c          # a的c次方,结果為8
a += 1          # Python中没有i++的用法,自增用+=
c -= 3          # c等於0
d = 'Hello'     
d + ' world!'   # 字串相加,結果為'Hello world!'
d += ' "world"!'# 相當於把字串接在當前字串尾,d變為'Hello "world"!'
e = r'\n\t\\'   
print(e)        # '\\n\\t\\\\'
  模塊導入
# 直接導入Python的內建數學函式庫
import math
print(math.cos(math.pi))
 
 for循環
a = ['This', 'is', 'a', 'list', '!']

# 依序輸出:'This', 'is', 'a', 'list', '!'
for x in a:
    print(x)
  if判斷式
  其中elif就是else if的意思
pets =['dog', 'cat', 'droid', 'fly']

for pet in pets:
    if pet == 'dog':        
        food = 'steak'      
    elif pet == 'cat':      
        food = 'milk'       
    elif pet == 'droid':   
        food = 'oil'        
    elif pet == 'fly':      
        food = 'sh*t'       
    else:
        pass
    print(food)
while循環
while True:     # 一直笑
    print("ha")
 
函式寫法
def say_hello():
    print('Hello!')

say_hello()                 		# Hello!

def create_a_list(x, y=2, z=3):	# default值必須放後面
    return [x, y, z]

b = create_a_list(1)        		# [1, 2, 3]
c = create_a_list(3, 3)     		# [3, 3, 3]
d = create_a_list(6, 7, 8)  	       # [6, 7, 8]

人臉偵測實作範例

  我們將實作人臉偵測如下圖所示影像
  本範例就是用OpenCV搭配Dlib這套Machine learning函式庫來實作人臉辨識,我們可以使用Dlib所提供的人臉辨識演算法來實作,如上圖左邊的數字為偵測到的分數,分數越高判斷為人臉的機率越大,而右邊括號內的數字為子偵測器的編號,也可以解釋為人臉的方向,0就為正面,其他數字則為不同方向的編號。
 另外dlib也提供訓練好的模型,可以辨識出人臉的68的特徵點,68 特徵點包括鼻子、眼睛、眉毛,以及嘴巴等等,如上圖紅點就是偵測出人臉的68個特徵點。
  下載完成後解壓縮到程式所在根目錄底下即可。

程式實作

# -*- coding: utf-8 -*-
import dlib
import cv2
import imutils

#選擇第一隻攝影機
cap = cv2.VideoCapture( 0)
#調整預設影像大小,預設值很大,很吃效能
cap.set(cv2. CAP_PROP_FRAME_WIDTH, 650)
cap.set(cv2. CAP_PROP_FRAME_HEIGHT, 500)

#取得預設的臉部偵測器
detector = dlib.get_frontal_face_detector()
#根據shape_predictor方法載入68個特徵點模型,此方法為人臉表情識別的偵測器
predictor = dlib.shape_predictor( 'shape_predictor_68_face_landmarks.dat')
  #當攝影機打開時,對每個frame進行偵測
  while(cap.isOpened()):
    #讀出frame資訊
    ret, frame = cap.read()

    #偵測人臉
    face_rects, scores, idx = detector.run(frame, 0)

    #取出偵測的結果
    for i, d in enumerate(face_rects):
      x1 = d.left()
      y1 = d.top()
      x2 = d.right()
      y2 = d.bottom()
      text = " %2.2f ( %d )" % (scores[i], idx[i])

      #繪製出偵測人臉的矩形範圍
      cv2.rectangle(frame, (x1, y1), (x2, y2), ( 0, 255, 0), 4, cv2. LINE_AA)

      #標上人臉偵測分數與人臉方向子偵測器編號
      cv2.putText(frame, text, (x1, y1), cv2. FONT_HERSHEY_DUPLEX,
      0.7, ( 255, 255, 255), 1, cv2. LINE_AA)
 
      #給68特徵點辨識取得一個轉換顏色的frame
      landmarks_frame = cv2.cvtColor(frame, cv2. COLOR_BGR2RGB)

      #找出特徵點位置
      shape = predictor(landmarks_frame, d)
 
      #繪製68個特徵點
      for i in range( 68):
        cv2.circle(frame,(shape.part(i).x,shape.part(i).y), 3,( 0, 0, 255), 2)
        cv2.putText(frame, str(i),(shape.part(i).x,shape.part(i).y),cv2. FONT_HERSHEY_COMPLEX, 0.5,( 255, 0, 0), 1)
    #輸出到畫面
    cv2.imshow( "Face Detection", frame)

    #如果按下ESC键,就退出
    if cv2.waitKey( 10) == 27:
       break
#釋放記憶體
cap.release()
#關閉所有視窗
cv2.destroyAllWindows()

人臉辨識

  接下來我們要來實作人臉辨識,一樣我們使用Dlib這套函式庫來實現,因為它有訓練好的特徵點模型與人臉辨識模型。
 
事前準備
 
我們必須準備人臉68特徵點訓練模型shape_predictor_68_face_landmarks.dat,這在前面人臉偵測已經有下載過了,再來是dlib_face_recognition_resnet_model_v1.dat,這是訓練好的ResNet人臉辨識模型,ResNet為用於影像辨識深度殘差學習,是一個可以用來訓練非常深的深度網絡又簡潔的框架。
  下載完成後解壓縮到程式所在根目錄底下即可。
  再來準備數張辨識的人臉照片,最好是正面的臉,並且是大張一點與解析度清楚的,在辨識上比較不會有錯誤。
我們的範例準備四張的人臉照片,並標示其人名為檔案名稱,以供識別, 並放在命名為rec的資料夾中。
  再準備幾張需要辨識的照片,可以選擇幾張正臉與側臉的照片來測試。

辨識流程

1. 先對比對的人臉進行檢測,取得特徵點,並產生描述子。
2. 對要辨識的人臉進行人臉檢測,取得特徵點,一樣產生描述子。
3. 最後比對辨識的人臉與這四張比對人臉描述子之間的歐氏距離,判斷距離最小的為同一個人臉。
  而歐式距離為一種計算距離的方法,源自於歐式空間計算兩點之間的演算法,我們從dlib訓練好的模型取得人臉的128維特徵向量,然後計算特徵向量之間的距離來得到人臉的相似度。

程式實作

# -*- coding: UTF-8 -*-
import sys,os,dlib,glob,numpy
from skimage import io
import cv2
import imutils

if len(sys.argv) != 2:
  print "缺少要辨識的圖片名稱"
  exit()


# 人臉68特徵點模型路徑
predictor_path = "shape_predictor_68_face_landmarks.dat"

# 人臉辨識模型路徑
face_rec_model_path = "dlib_face_recognition_resnet_model_v1.dat"

# 比對人臉圖片資料夾名稱
faces_folder_path = "./rec"

# 需要辨識的人臉圖片名稱
img_path = sys.argv[ 1]

# 載入人臉檢測器
detector = dlib.get_frontal_face_detector()

# 載入人臉特徵點檢測器
sp = dlib.shape_predictor(predictor_path)

# 載入人臉辨識檢測器
facerec = dlib.face_recognition_model_v1(face_rec_model_path)

# 比對人臉描述子列表
descriptors = []

# 比對人臉名稱列表
candidate = []

# 針對比對資料夾裡每張圖片做比對:
# 1.人臉偵測
# 2.特徵點偵測
# 3.取得描述子
for f in glob.glob(os.path.join(faces_folder_path, "*.jpg")):
  base = os.path.basename(f)
  # 依序取得圖片檔案人名
  candidate.append(os.path.splitext(base)[ 0])
  img = io.imread(f)

  # 1.人臉偵測
  dets = detector(img, 1)

  for k, d in enumerate(dets):
    # 2.特徵點偵測
    shape = sp(img, d)
 
    # 3.取得描述子,128維特徵向量
    face_descriptor = facerec.compute_face_descriptor(img, shape)

    # 轉換numpy array格式
    v = numpy.array(face_descriptor)
    descriptors.append(v)


# 針對需要辨識的人臉同樣進行處理
img = io.imread(img_path)
dets = detector(img, 1)

dist = []
for k, d in enumerate(dets):
  shape = sp(img, d)
  face_descriptor = facerec.compute_face_descriptor(img, shape)
  d_test = numpy.array(face_descriptor)

  x1 = d.left()
  y1 = d.top()
  x2 = d.right()
  y2 = d.bottom()
  # 以方框標示偵測的人臉
  cv2.rectangle(img, (x1, y1), (x2, y2), ( 0, 255, 0), 4, cv2. LINE_AA)
 
  # 計算歐式距離
  for i in descriptors:
    dist_ = numpy.linalg.norm(i -d_test)
    dist.append(dist_)

# 將比對人名和比對出來的歐式距離組成一個dict
c_d = dict( zip(candidate,dist))

# 根據歐式距離由小到大排序
cd_sorted = sorted(c_d.iteritems(), key = lambda d:d[ 1])
# 取得最短距離就為辨識出的人名
rec_name = cd_sorted[ 0][ 0]

# 將辨識出的人名印到圖片上面
cv2.putText(img, rec_name, (x1, y1), cv2. FONT_HERSHEY_SIMPLEX , 1, ( 255, 255, 255), 2, cv2. LINE_AA)

img = imutils.resize(img, width = 600)
img = cv2.cvtColor(img,cv2. COLOR_BGR2RGB)
cv2.imshow( "Face Recognition", img)
#隨意Key一鍵結束程式
cv2.waitKey( 0)
cv2.destroyAllWindows()
進入到程式所在路徑,在終端機鍵入以下指令
python rec.py 要辨識的圖片檔名.jpg
  接下來程式執行完畢就會跳出辨識出來結果的圖片,再隨意鍵入一鍵即可結束程式。

實作結果

  根據這幾張辨識人臉的照片,不管是正臉或是側臉,都能完整的辨識出來,目前測試的結果,辨識率可以說達到百分之百了。
張鈞名
1099317C960A41719558BFEE0F40C3F5
2020/03/16 22:09:12

想請問一下在img path=sys.arg[1]這邊是什麼意思

在實際運行的 print img path會出現'-f'

 

張鈞名
2020/03/17 15:01:20

您好,sys.arg[1]那段意思是,取得執行Python指令的第二個傳入參數的意思,以文章範例是指,你需要辨識圖片檔案的名稱。可以檢查看看執行的Python程式與要辨識圖片檔案是不是放在同一層目錄底下。

726B99D25DF0EBC196BAF4F8A8BEFB04
2020/03/27 23:55:21

想請問出現這樣的Error是甚麼問題?

Traceback (most recent call last):

  File "rec.py", line 90, in <module>

    cd_sorted = sorted(c_d.iteritems(), key = lambda d:d[ 1])

AttributeError: 'dict' object has no attribute 'iteritems'

53FCBC4DB5F4765DC611A916EA809E3D
2021/03/28 22:28:26

我也是同樣的問題,我把c_d.iteritems()改成c_d.items() 就成功執行。詳情參考https://stackoverflow.com/questions/30418481/error-dict-object-has-no-attribute-iteritems

FD1938A1A53B4D9943053D8CB36F6EE5
2020/05/27 23:18:28

請問這是甚麼問題呢?

Traceback (most recent call last):

  File "rec.py", line 90, in <module>

    cd_sorted = sorted(c_d.iteritems(), key = lambda d:d[ 1])

AttributeError: 'dict' object has no attribute 'iteritems'

53FCBC4DB5F4765DC611A916EA809E3D
2021/03/28 22:29:07

我也是同樣的問題,我把c_d.iteritems()改成c_d.items() 就成功執行。詳情參考https://stackoverflow.com/questions/30418481/error-dict-object-has-no-attribute-iteritems

8E7227C6008DB64A27C5D3CE2B3D74EA
2021/06/16 17:29:17

你好! 我在程式跑到face_rects, scores, idx = detector.run(frame, 0)的時候,會出現TypeError: run(): incompatible function arguments. The following argument types are supported:

    1. (self: _dlib_pybind11.fhog_object_detector, image: array, upsample_num_times: int=0, adjust_threshold: float=0.0) -> tuple

Invoked with: <_dlib_pybind11.fhog_object_detector object at 0x000001981067B030>, None, 0
這一串字,我查了很久,也修改了很多種可能,但都沒有成功,想請問一下,這個可能是甚麼原因造成的?謝謝!
D01F6498B6D54B11B89800BF0956E88C
2021/07/20 18:49:05

請問1.如何印出68個特徵點? 2.如何68個特徵點存到CSV檔? 謝謝