人臉辨識

      在〈人臉辨識〉中尚無留言

安裝套件

pip install dlib opencv-python

辨識流程

1. 首先在FaceBase目錄下, 存放多張人臉照片, 每張照片只能有一個人, 且影像愈清晰愈好, 然後使用此人的名字當作檔名, 副檔名為jpg or png皆可
2. 在主目錄下, 存放其他不同的照片, 檔名可以隨便命名, 比如img1.jpg, img2.jpg
3. 執行python程式, 並輸入要判別的照片檔名, 比如python face.py img1.jpg. 此時就會到FaceBase目錄下把所有照片載入, 再跟img1.jpg裏的人物進行比對, 找到最符合的照片, 然後把圖片檔名標示在img1.jpg裏

訓練模型

人臉外型68個特徵訓練模型
shape_predictor_68_face_landmarks.dat.bz2

ResNet人臉辨識訓練模型,ResNet用於影像辨識深度學習,非常簡潔的網路框架
dlib_face_recognition_resnet_model_v1.dat.bz2

將上面二個下載解開後, 置於跟程式碼同一目錄即可. 這二個訓練模型都是由dlib免費提供的

線性代數

在一個二維的平面上,一條線的方程式為 ax+by=c (一階方程式)。所以在三維的立体空間中,一條線的方程式為 ax+by+cz=d。x, y, z代表三個維度的軸。

那麼在n維度的空間中,一條線的方程式為 $(a_{1}x_{1}+a_{2}x_{2}+a_{3}x_{3}+….=k)$

四維

空間明明就只有 x(左右),  y(前後),  z(上下)三個方向,也就是所謂的三度(Dimension)空間,每一維度都是公尺這個單位來計算。那為什麼還要多一個莫名奇妙的四維空間呢 ? 那第四維的單位是什麼?? 如果單位也是公尺的話,那三軸不就可以表示出來了嗎,為什麼還要多一個煩人的軸??

但很多不懂數學或不懂物理的人,發明了四度空間這個名詞。所以在這邊修正一下,空間就只有 x, y, z三度空間,沒有四度空間。

那麼四維又是什麼?? 請再注意一下,是四維,不是四度空間。

四維是只加入另一項條件,比如加入時間這個條件,如下圖,當在第1秒時,位置位於(1,1,1), 而在第二秒時,位置位於(1, 1, 2),那麼二者的距離單位就不是公尺,而是速度(m/s)。

圖例–todo

因此當人類跨入四維時,就能感受到~~移動的速度(好快喔,好恐怖。或者是好慢喔,真無聊。或者是明天早上9:00am 時要去那麼遠的地方上班,好焦慮)

todo

歐幾里得直線距離

歐幾里得距離就是二點之間的直線距離, 源自於歐幾里得空間計算演算法。

二維空間的歐氏距離公式

$(\rho =\sqrt{{(x_{2}-x_{1})}^{2}+(y_{2}-y_{1})^{2}})$ , ρ為點(x2, y2)與點(x1, y1)之間的歐幾里得距離

三維空間的歐幾里得距離公式

$(\rho =\sqrt{{(x_{2}-x_{1})}^{2}+(y_{2}-y_{1})^{2}+(z_{2}-z_{1})^{2}})$

n維空間的歐幾里得距離公式

$(\rho =\sqrt{{(x_{1}-y_{1})}^{2}+(x_{2}-y_{2})^{2}+…+(x_{n}-y_{n})^{2}} = \sqrt{\sum_{i=1}^{n}(x_{i}-y_{i})^{2}})$

np.linalg.norm

numpy 的 lin 即為線性的意思,alg即為代數,norm則是求得 n 維空間的歐幾里得距離,又稱為範數。比如在四維中,有一點的座標為 [1,1,1,1] ,另一點的座標為 [1,2,3,4],則二點的直線距離則為

$(\sqrt{(1-1)^{2}+(2-1)^{2}+(3-1)^{2}+(4-1)^{2})}=\sqrt{1+4+9}=\sqrt{14}=3.7416573867739413)$

這麼複雜的算式,可以用 np.linalg.norm()來計算

import numpy as np
a=np.array([1, 1, 1, 1])
b=np.array([1, 2, 3, 4])
c=np.linalg.norm(b-a)
print(c)
結果 : 
3.7416573867739413

人臉辨識原理

Dlib將人臉分成 68 種特徵。然後再使用這 68 種特徵計算產生 128 個數字(維度),稱為描述子(description)。其實描述子就是將每個人的臉量化的意思。

現在開始要辨識路人甲了,那麼就先計算路人甲這個人的描述子,再與資料庫目錄預先計算的描述子比對,計算這128維度之間的歐幾里得距離,然後取得最小距離的那一張,就判定為同一個人。所以啦,資料庫裏的資料量愈多,就會愈準確。相對的,資料庫就那麼幾個人,判斷出來的就是那幾個鬼囉。

描述子的取得很耗時,需先取得臉部位置,再由此位置取得68組特徵,最後由這68組特徵計算出128組描述子。

請注意,128組描述子並不是陣列格式,所以要使用 np.array(描述子) 將之轉成陣列格式,才可以使用 np.linalg.norm計算歐幾里得距離。

人臉特徵

人臉特徵可以由 dlib.shape_predictor(shape_pattern) 取得偵測器 detector_shape,再把影像及臉部矩型框傳入偵測器後,取得68個特徵點(shape)。

shape.num_parts 變數為特徵數,shape.part(0).x 為第0個特徵點的x座標。所以可以由底下的代碼,將每個特徵列出來。

import cv2
import numpy as np
import dlib
path_face_shape_pattern = "shape_predictor_68_face_landmarks.dat"
detector_face = dlib.get_frontal_face_detector()
detector_shape = dlib.shape_predictor(path_face_shape_pattern)
path_img='./photodb/劉亦菲.jpg'

img=cv2.imdecode(np.fromfile(path_img, dtype=np.uint8), cv2.IMREAD_COLOR)
faces = detector_face(img, 1)
for face in faces:
    shape = detector_shape(img, face)
    print(f'特徵數 : {shape.num_parts}')
    for i in range(shape.num_parts):
        x=shape.part(i).x
        y=shape.part(i).y
        cv2.circle(img, (x,y), 5, (0,0,255), -1)
cv2.imshow('shape', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

描述子 – descriptor

描述子是將人臉量化、數位化的128組數字,為float32形態。

使用 dlib.face_recognition_model_v1傳入人臉辨識訓練模型,取得辨識偵測器 detector_recognition,然後將影像及特徵傳入偵測器,再用np.array()轉成陣列,即可取得這128組數字。

import cv2
import numpy as np
import dlib
path_face_shape_pattern = "shape_predictor_68_face_landmarks.dat"
path_face_recognition_pattern = "dlib_face_recognition_resnet_model_v1.dat"
detector_face = dlib.get_frontal_face_detector()
detector_shape = dlib.shape_predictor(path_face_shape_pattern)
detector_recognition= dlib.face_recognition_model_v1(path_face_recognition_pattern)
path_img='./photodb/劉亦菲.jpg'

img=cv2.imdecode(np.fromfile(path_img, dtype=np.uint8), cv2.IMREAD_COLOR)
faces = detector_face(img, 1)
for face in faces:
    shape = detector_shape(img, face)
    descriptor = detector_recognition.compute_face_descriptor(img, shape)
    print(np.asarray(descriptor))

結果 :
[-0.14054909 0.04426124 0.08866009 -0.0849032 -0.17231978 -0.03550968
-0.15141572 -0.16156833 0.12718649 -0.22248644 0.19372776 -0.10991256
-0.11338115 0.00483301 -0.10927599 0.26251966 -0.17240573 -0.15166888
-0.05355994 0.01177765 0.04092671 0.00246176 -0.02759925 0.05761251
-0.07394576 -0.30236602 -0.12749141 -0.00441253 -0.04730844 -0.10283802
-0.06325873 0.0747205 -0.18921919 0.0166836 0.00195603 0.11021935
0.01045647 -0.0985786 0.09239286 0.03786279 -0.27675414 0.10946561
0.07342842 0.18624522 0.19396684 -0.01562991 -0.0532582 -0.17993636
0.14952007 -0.19124591 0.0392213 0.10646417 0.06729751 0.02661212
0.05451194 -0.09769237 0.0426989 0.17178766 -0.13173173 0.0407474
0.1132625 -0.05298508 -0.010373 -0.15945846 0.16453081 0.22108312
-0.07539183 -0.24093667 0.11156385 -0.0860472 -0.09242575 0.07128195
-0.14813027 -0.18270497 -0.26679739 -0.01346047 0.381686 0.13438062
-0.18776414 0.02520162 0.00945836 -0.0469052 0.0667956 0.14827496
-0.00562041 -0.03608906 -0.03143981 0.01517139 0.22938004 -0.00084011
-0.00534477 0.21878409 -0.05320067 0.0191325 -0.03385412 -0.01841521
-0.11414495 0.03488658 -0.1423299 -0.09943943 -0.05250841 -0.01163368
-0.04529496 0.11440216 -0.13531663 0.14290756 -0.04970808 0.02698578
-0.01652338 0.0266864 -0.06138025 0.03710267 0.12207066 -0.16386668
0.2027695 0.11833911 0.11762075 0.07386067 0.1340881 0.09716622
-0.01273496 0.00316571 -0.18475032 0.00855643 0.10987271 -0.09012618
0.08601305 0.02782671]

人臉辨識專案完整程式碼

底下的代碼,先取得多張圖片的所有描述子(R)。再取得要測試圖片的描述子(A)。然後A與R的所有描述子計算歐幾里德距離,取得最小距離的那一組,就是相近的人臉。

請將已知姓名的照片,置於專案下 images 目錄,並將圖片名稱命名如下
許茹芸.jpg
許茹芸_1.jpg
許茹芸_2.jpg

#pip install opencv-python pillow matplotlib dlib --no-cache-dir
import os
import platform
import dlib
import numpy as np
import pylab as plt
from PIL import Image, ImageFont, ImageDraw
import cv2
def face_descriptor(img):
    img = img[:, :, ::-1].copy()
    faces = detector_face(img, 1)
    data = []
    for face in faces:
        data.append(
            np.asarray(
                detector_recognition.compute_face_descriptor(
                    img,
                    detector_shape(img, face)
                )
            )
        )
    return data
def read(file):
     return np.asarray(Image.open(file))[:,:,::-1]
def resize(img, width=400):
    h, w, _=img.shape
    r=w/width
    height=int(h/r)
    img=cv2.resize(
        img,
        (width, height),
        interpolation=cv2.INTER_LINEAR)
    return img
def text(img, text, xy, color=(0,0,0),size = 12):
    pil = Image.fromarray(img[:,:,::-1].copy())
    s=platform.system()
    if s == "Linux":
        font = ImageFont.truetype('/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc', size)
    elif s=="Darwin":
        font = ImageFont.truetype('....', size)
    else:
        font = ImageFont.truetype('simsun.ttc', size)
    ImageDraw.Draw(pil).text(xy, text, font=font, fill=color)
    return np.asarray(pil)[:,:,::-1].copy()
path_shape = "shape_predictor_68_face_landmarks.dat"
path_recognition = "dlib_face_recognition_resnet_model_v1.dat"
detector_face = dlib.get_frontal_face_detector()
detector_shape = dlib.shape_predictor(path_shape)
detector_recognition = dlib.face_recognition_model_v1(path_recognition)

#取得多張圖片的描述子
train_path="./images"
names=[]
descriptors=[]
for file in os.listdir(train_path):
    img=read(os.path.join(train_path, file))
    img=resize(img, width=600)
    name=os.path.splitext(file)[0]
    d=face_descriptor(img)[0]
    names.append(name)
    descriptors.append(d)

img=read("./test.jpg")
img=resize(img, width=600)
target=face_descriptor(img)[0]
distance=[np.linalg.norm(d-target) for d in descriptors]
min_index=np.argmin(distance)

name=names[min_index]
score=distance[min_index]
img=text(img, f"{name}:{score:.2f}", (0,0), (0,255,0), size=50)

plt.imshow(img[:,:,::-1].copy())
plt.show()

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *