安裝套件
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()