Face ID 的興起帶動了一波面部識别技術熱潮。本文将介紹如何使用 OpenCV、Python 和深度學習在圖像和視訊中實作面部識别,以基于深度識别的面部嵌入,實時執行且達到高準确度。
以下内容由 CSDN 翻譯:
想知道怎樣用OpenCV、Python和深度學習進行面部識别嗎?
這篇文章首先将簡單介紹下基于深度學習的面部識别的工作原理,以及“深度度量學習”(deep metric learning)的概念。接下來我會幫你安裝好面部識别需要的庫。最後我們會發現,這個面部識别的實作能夠實時運作。
▌了解深度學習面部識别嵌入
那麼,基于深度學習的面部識别是怎樣工作的呢?秘密就是一種叫做“深度度量學習”的技術。
如果你有深度學習的經驗,你應該知道,通常情況下訓練好的網絡會接受一個輸入圖像,并且給輸入的圖像生成一個分類或标簽。
而在這裡,網絡輸出的并不是單一的标簽(也不是圖像中的坐标或邊界盒),而是輸出一個表示特征向量的實數。
對于dlib面部識别網絡來說,輸出的特征向量為128維(即一個由128個實數組成的清單),用來判斷面部。網絡的訓練是通過三元組進行的:
圖1:利用深度度量學習進行面部識别需要“三元組訓練”。三元組包括三張不同的面部圖像,其中兩張屬于同一個人。神經網絡為每張面部圖像生成一個128維向量。對于同一個人的兩張面部圖像,我們調整神經網絡使得輸出向量的距離度量盡可能接近。圖檔來源:Adam Geitgey的“Machine Learning is Fun”部落格(https://medium.com/@ageitgey/machine-learning-is-fun-part-4-modern-face-recognition-with-deep-learning-c3cffc121d78)
這裡我們需要給網絡提供三張圖檔:
其中兩張圖檔是同一個人的面部;第三張圖檔是從資料集中取得的随機面部圖檔,并且保證與另外兩張圖檔不是同一個人。以圖1為例,這裡我們用了三張圖檔,一張是Chad Smith,兩張是Will Ferrell。
網絡會測試這些面部圖檔,并為每張圖檔生成128維嵌入(embedding,即qualification)。
接下來,基本思路就是調整神經網絡的權重,使得兩張Will Ferrell的測量結果盡量接近,而Chad Smith的測量結果遠離。
我們的面部識别網絡的架構基于He等人在《Deep Residual Learning for Image Recognition》(https://arxiv.org/abs/1512.03385)中提出的ResNet-34,但層數較少,而且過濾器的數量減少了一半。
網絡本身由Davis King(https://www.pyimagesearch.com/2017/03/13/an-interview-with-davis-king-creator-of-the-dlib-toolkit/)在大約300萬張圖檔上訓練。在Labeled Faces in the Wild(LFW)(http://vis-www.cs.umass.edu/lfw/)資料集上與其他方法相比,該網絡的準确度達到了99.38%。
Davis King(dlib的作者)和Adam Geitgey(https://adamgeitgey.com/, 我們即将用到的face_recognition子產品的作者)都有文章介紹了基于深度學習的面部識别的工作原理:
High Quality Face Recognition with Deep Metric Learning(Davis,http://blog.dlib.net/2017/02/high-quality-face-recognition-with-deep.html)
Modern Face Recognition with Deep Learning( Adam,https://medium.com/@ageitgey/machine-learning-is-fun-part-4-modern-face-recognition-with-deep-learning-c3cffc121d78)
強烈建議閱讀以上文章,以深入了解深度學習面部嵌入的工作原理。
▌安裝面部識别庫
為了用Python和OpenCV吸納面部識别,我們需要安裝一些庫:
dlib(http://dlib.net/);
face_recognition(https://github.com/ageitgey/face_recognition)。
由Davis King維護的dlib庫包含了“深度度量學習”的實作,用來在實際的識别過程中建構面部嵌入。
Adam Geitgey建立的face_recognition庫則封裝了dlib的面部識别功能,使之更易用。
我假設你的系統上已經裝好了OpenCV。如果沒有也不用擔心,可以看看我的OpenCV安裝指南一文(https://www.pyimagesearch.com/opencv-tutorials-resources-guides/),選擇适合你的系統的指南即可。
這裡我們來安裝dlib和face_recognition庫。
注意:下面的安裝過程需要在Python虛拟環境中進行。我強烈推薦使用虛拟環境來隔離項目,這是使用Python的好習慣。如果你看了我的OpenCV安裝指南,并且安裝了virtualenv和virtualenvwrapper,那麼隻要在安裝dlib和face_recognition之前執行workon指令即可。
安裝沒有GPU支援的dlib
如果你沒有GPU,可以用pip安裝dlib(參考這篇指南:https://www.pyimagesearch.com/2018/01/22/install-dlib-easy-complete-guide/)。
$ workon # optional$ pip install dlib
或者從源代碼進行編譯:
$ workon # optional$ git clone https://github.com/davisking/dlib.git$ cd dlib$ mkdir build$ cd build$ cmake .. -DUSE_AVX_INSTRUCTIONS=1$ cmake --build .$ cd ..$ python setup.py install --yes USE_AVX_INSTRUCTIONS
安裝有GPU支援的dlib(可選)
如果你有相容CUDA的GPU,那麼可以安裝有GPU支援的dlib,這樣面部識别能更快、更精确。
我建議從源代碼安裝dlib,這樣可以更精細地控制安裝過程:
$ workon # optional$ git clone https://github.com/davisking/dlib.git$ cd dlib$ mkdir build$ cd build$ cmake .. -DDLIB_USE_CUDA=1 -DUSE_AVX_INSTRUCTIONS=1$ cmake --build .$ cd ..$ python setup.py install --yes USE_AVX_INSTRUCTIONS --yes DLIB_USE_CUDA
安裝face_recognition包
face_recognition子產品隻需簡單地使用pip指令即可安裝:
$ workon # optional$ pip install face_recognition
安裝imutlis
我們還需要imutils包提供一些周遊的函數。在Python虛拟環境中使用pip即可:
$ workon # optional$ pip install imutils
▌面部識别資料集
圖2:利用Python和Bing圖像搜尋API自動建立的面部識别資料集,圖中顯示的是電影侏羅紀公園的六個角色。
1993年的《侏羅紀公園》是我最喜歡的電影,為了紀念最新上映的《侏羅紀世界:失落王國》,我們将使用電影中的一些角色進行面部識别:
Alan Grant,古生物學家(22張圖像)
Clair Dearing,公園管理人(53張圖像)
Ellie Sattler,古生物學家(31張圖像)
Ian Malcolm,數學家(41張圖像)
John Hammond,商人,侏羅紀公園所有者(36張圖像)
Owen Grady,恐龍研究學者(35張圖像)
這個資料集隻需要30分鐘就可以建好,參見我的文章《怎樣(快速)建立深度學習圖像資料集》(https://www.pyimagesearch.com/2018/04/09/how-to-quickly-build-a-deep-learning-image-dataset/)。
有了這個資料集,我們可以:
為資料集中的每張圖像建立128維嵌入;
利用這些嵌入,從圖像和視訊中識别每個角色的面部。
▌面部識别項目結構
項目結構可以參考下面的tree指令的輸出結果:
$ tree --filelimit 10 --dirsfirst.├── dataset│ ├── alan_grant [22 entries]│ ├── claire_dearing [53 entries]│ ├── ellie_sattler [31 entries]│ ├── ian_malcolm [41 entries]│ ├── john_hammond [36 entries]│ └── owen_grady [35 entries]├── examples│ ├── example_01.png│ ├── example_02.png│ └── example_03.png├── output│ └── lunch_scene_output.avi├── videos│ └── lunch_scene.mp4├── search_bing_api.py├── encode_faces.py├── recognize_faces_image.py├── recognize_faces_video.py├── recognize_faces_video_file.py└── encodings.pickle10 directories, 11 files
該項目有4個頂層目錄:
dataset/:包含六個角色的面部圖像,用角色名組織到各個子目錄中;
examples/:包含三個不屬于該資料集的測試圖像;
output/:存儲經過面部識别處理後的視訊,上面有我生成的一個視訊,來自于原版《侏羅紀公園》電影的午飯場景;
videos/:輸入視訊存放于該檔案夾中,該檔案夾也包含了尚未經過面部識别的“午飯場景”的視訊。
根目錄下還有6個檔案:
search_bing_api.py:第一步就是建立資料集(我已經幫你做好了)。關于利用Bing API建立資料集的具體方法請參考我這篇文章:https://www.pyimagesearch.com/2018/04/09/how-to-quickly-build-a-deep-learning-image-dataset/;
encode_faces.py:該腳本用來進行面部編碼(128維向量);
recognize_faces_image.py:基于資料集生成的編碼,對單張圖檔進行面部識别;
recognize_faces_video.py:對來自攝像頭的實時視訊流進行面部識别并輸出視訊檔案;
recognize_faces_video_file.py:對硬碟上儲存的視訊檔案進行面部識别,并輸出處理後的視訊檔案。本文不再讨論該腳本,因為它的基本結構與上面識别視訊流的腳本相同;
encodings.pickle:該腳本将encode_faces.py生成的面部識别編碼序列化并儲存到硬碟上。
用search_bing_api.py建立好圖像資料集之後,就可以運作encode_faces.py來建立嵌入了。
接下來我們将運作識别腳本來進行面部識别。
▌用OpenCV和深度學習對面部進行編碼
圖3:利用深度學習和Python進行面部識别。對每一個面部圖像,用face_recognition子產品的方法生成一個128維實數特征向量。
在識别圖像和視訊中的面部之前,我們首先需要在訓練集中識别面部。要注意的是,我們并不是在訓練網絡——該網絡已經在300萬圖像的訓練集上訓練過了。
當然我們可以從頭開始訓練網絡,或者微調已有模型的權重,但那就超出了這個項目的範圍。再說,你需要巨量的圖像才能從頭開始訓練網絡。
相反,使用預先訓練好的網絡來給訓練集中的218張面部圖像建立128維嵌入更容易些。
然後,在分類過程中,隻需利用簡單的k-NN模型,加上投票,即可确定最終的面部分類,也可以使用其他經典機器學習模型。
現在打開本文“下載下傳”連結中的encode_faces.py檔案,看看是如何建構面部嵌入的:
1# import the necessary packages2from imutils import paths3import face_recognition4import argparse5import pickle6import cv27import os
首先需要導入必需的包。這個腳本需要事先安裝imutils、face_recognition和OpenCV。請翻到前面“安裝面部識别庫”一節確定你已經安裝了必須的庫。
首先用argparse處理運作時傳遞的指令行參數:
1# construct the argument parser and parse the arguments2ap = argparse.ArgumentParser()3ap.add_argument("-i", "--dataset", required=True,4 help="path to input directory of faces + images")5ap.add_argument("-e", "--encodings", required=True,6 help="path to serialized db of facial encodings")7ap.add_argument("-d", "--detection-method", type=str, default="cnn",8 help="face detection model to use: either `hog` or `cnn`")9args = vars(ap.parse_args())
如果你之前沒有用過PyImageSearch,你可以多讀讀我的部落格文章,就明白上面這段代碼了。首先利用argparse分析指令行參數,在指令行上執行Python程式時,可以在終端中給腳本提供格外的資訊。第2-9行不需要做任何改動,它們隻是為了分析終端上的輸入。如果不熟悉這些代碼,可以讀讀我這篇文章:https://www.pyimagesearch.com/2018/03/12/python-argparse-command-line-arguments/。
下面逐一列出參數:
--dataset:資料集的路徑(利用search_bing_api.py建立的資料集);
--encodings:面部編碼将被寫到該參數所指的檔案中;
--detection-method:首先需要檢測到圖像中的面部,才能對其進行編碼。兩種面部檢測方法為hog或cnn,是以該參數隻接受這兩個值。
現在參數已經定義好了,我們可以獲得資料集檔案的路徑了(同時進行兩個初始化):
1# grab the paths to the input images in our dataset2print("[INFO] quantifying faces...")3imagePaths = list(paths.list_images(args["dataset"]))45# initialize the list of known encodings and known names6knownEncodings = []7knownNames = []
行3用輸入資料集的路徑,建立了一個清單imagePaths。
我們還需要在循環開始之前初始化兩個清單,分别是knownEncodings和knownNames。這兩個清單分别包含面部編碼資料和資料集中相應人物的名字(行6和行7)。
現在可以依次循環侏羅紀公園中的每個角色了!
1# loop over the image paths 2for (i, imagePath) in enumerate(imagePaths): 3 # extract the person name from the image path 4 print("[INFO] processing image {}/{}".format(i + 1, 5 len(imagePaths))) 6 name = imagePath.split(os.path.sep)[-2] 7 8 # load the input image and convert it from BGR (OpenCV ordering) 9 # to dlib ordering (RGB)10 image = cv2.imread(imagePath)11 rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
這段代碼會循環218次,處理資料集中的218張面部圖像。行2在所有圖像路徑中進行循環。
接下來,行6要從imagePath中提取人物的名字(因為子目錄名就是人物名)。
然後将imagePath傳遞給cv2.imread(行10),讀取圖像儲存到image中。
OpenCV中的顔色通道排列順序為BGR,但dlib要求的順序為RGB。由于face_recognition子產品使用了dlib,是以在繼續下一步之前,行11轉換了顔色空間,并将轉換後的新圖像儲存在rgb中。
接下來定位面部位置并計算編碼:
1 # detect the (x, y)-coordinates of the bounding boxes 2 # corresponding to each face in the input image 3 boxes = face_recognition.face_locations(rgb, 4 model=args["detection_method"]) 5 6 # compute the facial embedding for the face 7 encodings = face_recognition.face_encodings(rgb, boxes) 8 9 # loop over the encodings10 for encoding in encodings:11 # add each encoding + name to our set of known names and12 # encodings13 knownEncodings.append(encoding)14 knownNames.append(name)
這段代碼是最有意思的部分!
每次循環都會檢測一個面部圖像(或者一張圖像中有多個面部,我們假設這些面部都屬于同一個人,但如果你使用自己的圖像的話,這個假設有可能不成立,是以一定要注意)。
比如,假設rgb裡的圖像是Ellie Sattler的臉。
行3和4查找面部位置,傳回一個包含了許多方框的清單。我們給face_recognition.face_locations方法傳遞了兩個參數:
rgb:RGB圖像;
model:cnn或hog(該值包含在指令行參數字典中,賦給了detection_method鍵)。CNN方法比較準确,但速度較慢;HOG比較快,但不太準确。
然後,在行7,我們要将Ellie Sattler的面部的邊界盒boxes轉換成128個數字。這個步驟就是将面部編碼成向量,可以通過face_recognition.face_encodings方法實作。
接下來秩序将Ellie Sattler的encoding和name添加到恰當的清單中(knownEncodings或knownNames)。
然後對資料集中所有218張圖像進行這一步驟。
提取這些編碼encodings的目的就是要在另一個腳本中利用它們進行面部識别。現在來看看怎麼做:
1# dump the facial encodings + names to disk2print("[INFO] serializing encodings...")3data = {"encodings": knownEncodings, "names": knownNames}4f = open(args["encodings"], "wb")5f.write(pickle.dumps(data))6f.close()
行3建構了一個字典,它包含encodings和names兩個鍵。
行4-6将名字和編碼儲存到硬碟中,供以後使用。
怎樣才能在終端上運作encode_faces.py腳本?
要建立面部嵌入,可以從終端執行以下指令:
1$ python encode_faces.py --dataset dataset --encodings encodings.pickle 2[INFO] quantifying faces... 3[INFO] processing image 1/218 4[INFO] processing image 2/218 5[INFO] processing image 3/218 6... 7[INFO] processing image 216/218 8[INFO] processing image 217/218 9[INFO] processing image 218/21810[INFO] serializing encodings...11$ ls -lh encodings*12-rw-r--r--@ 1 adrian staff 234K May 29 13:03 encodings.pickle
從輸出中課件,它生成了個名為encodings.pickle的檔案,該檔案包含了資料集中每個面部圖像的128維面部嵌入。
在我的Titan X GPU上,處理整個資料集花費了一分鐘多一點,但如果隻使用CPU,就要做好等待很久的心理準備。
在我的Macbook Pro上(沒有GPU),編碼218張圖像需要21分20秒。
如果你有GPU并且編譯dlib時選擇了支援GPU,那麼速度應該會快得多。
▌識别圖像中的面部
圖4:John Hammond的面部識别,使用了Adam Geitgey的深度學習Python子產品face_recognition。
現在已經給資料集中的每張圖像建好了128維面部嵌入,我們可以用OpenCV、Python和深度學習進行面部識别了。
打開recognize_faces_image.py,插入以下代碼(或者從本文的”下載下傳“部分下載下傳代碼和相關的圖像):
1# import the necessary packages 2import face_recognition 3import argparse 4import pickle 5import cv2 6 7# construct the argument parser and parse the arguments 8ap = argparse.ArgumentParser() 9ap.add_argument("-e", "--encodings", required=True,10 help="path to serialized db of facial encodings")11ap.add_argument("-i", "--image", required=True,12 help="path to input image")13ap.add_argument("-d", "--detection-method", type=str, default="cnn",14 help="face detection model to use: either `hog` or `cnn`")15args = vars(ap.parse_args())
這段代碼首先導入了必需的包(行2-5)。face_recognition子產品完成主要工作,OpenCV負責加載圖像、轉換圖像,并顯示處理之後的圖像。
行8-15負責分析三個指令行參數:
--encodings:包含面部編碼的pickle檔案的路徑;
--image:需要進行面部識别的圖像;
--detection-method:這個選項應該很熟悉了。可以根據系統的能力,選擇hog或cnn之一。追求速度的話就選擇hog,追求準确度就選擇cnn。
注意:在樹莓派上必須選擇hog,因為記憶體容量不足以運作CNN方法。
接下來要加載計算好的編碼和面部名稱,然後為輸入圖像建構128維面部編碼:
1# load the known faces and embeddings 2print("[INFO] loading encodings...") 3data = pickle.loads(open(args["encodings"], "rb").read()) 4 5# load the input image and convert it from BGR to RGB 6image = cv2.imread(args["image"]) 7rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) 8 9# detect the (x, y)-coordinates of the bounding boxes corresponding10# to each face in the input image, then compute the facial embeddings11# for each face12print("[INFO] recognizing faces...")13boxes = face_recognition.face_locations(rgb,14 model=args["detection_method"])15encodings = face_recognition.face_encodings(rgb, boxes)1617# initialize the list of names for each face detected18names = []
行3從硬碟加載pickle過的編碼和名字資料。稍後在實際的面部識别步驟中會用到這些資料。
然後,行6和行7加載輸入圖像image,并轉換其顔色通道順序(同encode_faces.py腳本一樣),儲存到rgb中。
接下來,行13-15繼續檢測輸入圖像中的所有面部,并計算它們的128維encodings(這些代碼也應該很熟悉了)。
現在應該初始化一個清單names,用來儲存每個檢測的面部。該清單将在下一步填充。
現在周遊面部編碼encodings清單:
1# loop over the facial embeddings2for encoding in encodings:3 # attempt to match each face in the input image to our known4 # encodings5 matches = face_recognition.compare_faces(data["encodings"],6 encoding)7 name = "Unknown"
行2開始周遊根據輸入圖像計算出的面部編碼。
接下來見證面部識别的奇迹吧!
在行5和行6,我們嘗試利用face_recognition.compare_faces将輸入圖像中的每個面部(encoding)對應到已知的編碼資料集(儲存在data["encodings"]中)上。
該函數會傳回一個True/False值的清單,每個值對應于資料集中的一張圖像。對于我們的侏羅紀公園的例子,資料集中有218張圖像,是以傳回的清單将包含218個布爾值。
compare_faces函數内部會計算待判别圖像的嵌入和資料集中所有面部的嵌入之間的歐幾裡得距離。
如果距離位于容許範圍内(容許範圍越小,面部識别系統就越嚴格),則傳回True,表明面部吻合。否則,如果距離大于容許範圍,則傳回False表示面部不吻合。
本質上我們用了個更”炫酷“的k-NN模型進行分類。具體的實作細節可以參考compare_faces的實作(https://github.com/ageitgey/face_recognition/blob/master/face_recognition/api.py#L213)。
最終,name變量會的值就是人的名字。如果沒有任何”投票“,則保持"Unknown"不變(行7)。
根據matches清單,可以計算每個名字的”投票“數目(與每個名字關聯的True值的數目),計票之後選擇最适合的人的名字:
1 # check to see if we have found a match 2 if True in matches: 3 # find the indexes of all matched faces then initialize a 4 # dictionary to count the total number of times each face 5 # was matched 6 matchedIdxs = [i for (i, b) in enumerate(matches) if b] 7 counts = {} 8 9 # loop over the matched indexes and maintain a count for10 # each recognized face face11 for i in matchedIdxs:12 name = data["names"][i]13 counts[name] = counts.get(name, 0) + 11415 # determine the recognized face with the largest number of16 # votes (note: in the event of an unlikely tie Python will17 # select first entry in the dictionary)18 name = max(counts, key=counts.get)1920 # update the list of names21 names.append(name)
如果matches中包含任何True的投票(行2),則需要确定True值在matches中的索引位置。這一步在行6中通過建立一個簡單的matchedIdxs清單實作。對于example_01.png來說,它大概是這個樣子:
1(Pdb) matchedIdxs2[35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 71, 72, 73, 74, 75]
然後初始化一個名為counts的字典,其鍵為角色的名字,值是投票的數量。
接下來周遊matchedIdxs,統計每個相關的名字,并在counts增加相應的計數值。counts字典可能是這個樣子(Ian Malcolm高票的情況):
1(Pdb) counts2{'ian_malcolm': 40}
回憶一下我們的資料集中隻有41張圖檔,是以40分并且沒有任何其他投票可以認為非常高了。
取出counts中投票最高的名字,本例中為'ian_malcolm'。
循環的第二次疊代(由于圖像中有兩個人臉)會取出下面的counts:
1(Pdb) counts2{'alan_grant': 5}
盡管這個投票分值較低,但由于這是字典中唯一的人名,是以很可能我們找到了Alan Grant。
注意:這裡使用了Python調試器PDB來檢查counts字典的值。PDB的用法超出了本文的範圍,你可以在Python的文檔頁面(https://docs.python.org/3/library/pdb.html)找到其用法。
如下面的圖5所示,我們正确識别了Ian Malcolm和Alan Grant,是以這一段代碼工作得還不錯。
我們來繼續循環每個人的邊界盒和名字,然後将名字畫在輸出圖像上以供展示之用:
1# loop over the recognized faces 2for ((top, right, bottom, left), name) in zip(boxes, names): 3 # draw the predicted face name on the image 4 cv2.rectangle(image, (left, top), (right, bottom), (0, 255, 0), 2) 5 y = top - 15 if top - 15 > 15 else top + 15 6 cv2.putText(image, name, (left, y), cv2.FONT_HERSHEY_SIMPLEX, 7 0.75, (0, 255, 0), 2) 8 9# show the output image10cv2.imshow("Image", image)11cv2.waitKey(0)
行2開始循環檢測到的面部邊界盒boxes和預測的names。我們調用了zip(boxes, names)以建立一個容易進行循環的對象,每次疊代将得到一個二進制組,從中可以提取邊界盒坐标和名字。
行4利用邊界盒坐标畫一個綠色方框。
我們還利用坐标計算了人名文本的顯示位置(行5),并将人名的文本畫在圖像上(行6和行7)。如果邊界盒位于圖像頂端,則将文本移到邊界盒下方(行5),否則文本就被截掉了。
然後顯示圖像,直到按下任意鍵為止(行10和11)。
怎樣運作面部識别的Python腳本?
在終端中,首先用workon指令保證位于正确的Python虛拟環境中(如果你用了虛拟環境的話)。
然後運作該腳本,同時至少提供兩個指令行參數。如果選擇HoG方式,别忘了傳遞--detection-method hog(否則預設會使用深度學習檢測方式)。
趕快試試吧!
打開終端并執行腳本,用OpenCV和Python進行面部識别:
1$ python recognize_faces_image.py --encodings encodings.pickle \2 --image examples/example_01.png3[INFO] loading encodings...4[INFO] recognizing faces...
圖5:Python + OpenCV + 深度學習方法識别出了Alan Grant和Ian Malcom的面部。
另一個面部識别的例子:
1$ python recognize_faces_image.py --encodings encodings.pickle \2 --image examples/example_02.png3[INFO] loading encodings...4[INFO] recognizing faces...
圖6:用OpenCV和Python進行面部識别。
▌在視訊中進行面部識别
圖7:用Python、OpenCV和深度學習在視訊中進行面部識别。
我們已經完成了圖像中的面部識别,現在來試試在視訊中進行(實時)面部識别。
關于性能的重要提示:CNN面部識别器隻能在有GPU的情況下實時運作(CPU也可以運作,但視訊會非常卡,實際的幀速率不到0.5FPS)。如果你隻有CPU,應當考慮使用HoG方式(或者甚至采用OpenCV的Haar層疊方式,以後會撰文說明),以獲得較好的速度。
下面的腳本從前面的recognize_faces_image.py腳本中借用了許多代碼。是以我将略過之前介紹過的部分,隻說明下視訊部分,以便于了解。
下載下傳好代碼之後,打開recognize_faces_video.py:
1# import the necessary packages 2from imutils.video import VideoStream 3import face_recognition 4import argparse 5import imutils 6import pickle 7import time 8import cv2 910# construct the argument parser and parse the arguments11ap = argparse.ArgumentParser()12ap.add_argument("-e", "--encodings", required=True,13 help="path to serialized db of facial encodings")14ap.add_argument("-o", "--output", type=str,15 help="path to output video")16ap.add_argument("-y", "--display", type=int, default=1,17 help="whether or not to display output frame to screen")18ap.add_argument("-d", "--detection-method", type=str, default="cnn",19 help="face detection model to use: either `hog` or `cnn`")20args = vars(ap.parse_args())
行2-8導入包,然後行11-20解析指令行參數。
這裡有四個指令行參數,其中兩個是介紹過的(--encodings和--detection-method)。另外兩個參數是:
--output:視訊輸出路徑;
--display:訓示是否将視訊幀輸出到螢幕的标志。1表示顯示到螢幕,0表示不顯示。
然後加載編碼并啟動VideoStream:
1# load the known faces and embeddings 2print("[INFO] loading encodings...") 3data = pickle.loads(open(args["encodings"], "rb").read()) 4 5# initialize the video stream and pointer to output video file, then 6# allow the camera sensor to warm up 7print("[INFO] starting video stream...") 8vs = VideoStream(src=0).start() 9writer = None10time.sleep(2.0)
我們利用imutils中的VideoStream類來通路攝像頭。行8啟動視訊流。如果系統中有多個攝像頭(如内置攝像頭和外置USB攝像頭),可以将src=0改成src=1等。
稍後會将處理過的視訊寫到硬碟中,是以這裡将writer初始化成None(行9)。sleep兩秒讓攝像頭預熱。
接下來啟動一個while循環,開始抓取并處理視訊幀:
1# loop over frames from the video file stream 2while True: 3 # grab the frame from the threaded video stream 4 frame = vs.read() 5 6 # convert the input frame from BGR to RGB then resize it to have 7 # a width of 750px (to speedup processing) 8 rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) 9 rgb = imutils.resize(frame, width=750)10 r = frame.shape[1] / float(rgb.shape[1])1112 # detect the (x, y)-coordinates of the bounding boxes13 # corresponding to each face in the input frame, then compute14 # the facial embeddings for each face15 boxes = face_recognition.face_locations(rgb,16 model=args["detection_method"])17 encodings = face_recognition.face_encodings(rgb, boxes)18 names = []
循環從行2開始,第一步就是從視訊流中抓取一個frame(行4)。
上述代碼中剩下的行8-18基本上與前一個腳本相同,隻不過這裡處理的是視訊幀,而不是靜态圖像。基本上就是讀取frame,預處理,檢測到面部邊界盒boxes,然後給每個邊界盒計算encodings。
接下來周遊每個找到的面部的encodings:
1 # loop over the facial embeddings 2 for encoding in encodings: 3 # attempt to match each face in the input image to our known 4 # encodings 5 matches = face_recognition.compare_faces(data["encodings"], 6 encoding) 7 name = "Unknown" 8 9 # check to see if we have found a match10 if True in matches:11 # find the indexes of all matched faces then initialize a12 # dictionary to count the total number of times each face13 # was matched14 matchedIdxs = [i for (i, b) in enumerate(matches) if b]15 counts = {}1617 # loop over the matched indexes and maintain a count for18 # each recognized face face19 for i in matchedIdxs:20 name = data["names"][i]21 counts[name] = counts.get(name, 0) + 12223 # determine the recognized face with the largest number24 # of votes (note: in the event of an unlikely tie Python25 # will select first entry in the dictionary)26 name = max(counts, key=counts.get)2728 # update the list of names29 names.append(name)
在這段代碼中依次循環每個encodings,并嘗試比對到已知的面部資料上。如果找到比對,則計算資料集中每個名字獲得的票數。然後取出得票最高的名字,就是該面部對應的名字。這些代碼與前面的代碼完全相同。
下一段代碼循環找到的面部并在周圍畫出邊界盒,并顯示人的名字:
1 # loop over the recognized faces 2 for ((top, right, bottom, left), name) in zip(boxes, names): 3 # rescale the face coordinates 4 top = int(top * r) 5 right = int(right * r) 6 bottom = int(bottom * r) 7 left = int(left * r) 8 9 # draw the predicted face name on the image10 cv2.rectangle(frame, (left, top), (right, bottom),11 (0, 255, 0), 2)12 y = top - 15 if top - 15 > 15 else top + 1513 cv2.putText(frame, name, (left, y), cv2.FONT_HERSHEY_SIMPLEX,14 0.75, (0, 255, 0), 2)
這些代碼也完全相同,是以我們隻關注與食品有關的代碼。
我們還可以将視訊幀寫到硬碟中,是以來看看是怎樣使用OpenCV将視訊寫到硬碟中的(https://www.pyimagesearch.com/2016/02/22/writing-to-video-with-opencv/):
1 # if the video writer is None *AND* we are supposed to write 2 # the output video to disk initialize the writer 3 if writer is None and args["output"] is not None: 4 fourcc = cv2.VideoWriter_fourcc(*"MJPG") 5 writer = cv2.VideoWriter(args["output"], fourcc, 20, 6 (frame.shape[1], frame.shape[0]), True) 7 8 # if the writer is not None, write the frame with recognized 9 # faces t odisk10 if writer is not None:11 writer.write(frame)
如果指令行參數提供了輸出檔案路徑(可選),而我們還沒有初始化視訊的writer(行3),就要先初始化之。
行4初始化了VideoWriter_fourcc。FourCC是一種四字元編碼,在這裡就是MJPG 四字元編碼。
接下來将對象、輸出路徑、每秒幀數的目标值和幀尺寸傳遞給VideoWriter(行5和6)。
最後,如果writer存在,就繼續将幀寫到磁盤中。
下面是是否将面部識别視訊幀輸出到螢幕的處理:
1 # check to see if we are supposed to display the output frame to2 # the screen3 if args["display"] > 0:4 cv2.imshow("Frame", frame)5 key = cv2.waitKey(1) & 0xFF67 # if the `q` key was pressed, break from the loop8 if key == ord("q"):9 break
如果設定了display指令行參數,就顯示視訊幀(行4)并檢查退出鍵("q")是否被按下(行5-8),如果被按下,則break掉循環(行9)。
最後是一些清理工作:
1# do a bit of cleanup2cv2.destroyAllWindows()3vs.stop()45# check to see if the video writer point needs to be released6if writer is not None:7 writer.release()
行2-7清理并釋放螢幕、視訊流和視訊writer。
準備好運作真正的腳本了嗎?
為了示範OpenCV和Python的實時面部識别,打開終端然後執行下面的指令:
1$ python recognize_faces_video.py --encodings encodings.pickle \2 --output output/webcam_face_recognition_output.avi --display 13[INFO] loading encodings...4[INFO] starting video stream...
下面是我錄制的示範視訊,用來示範面部識别系統:
視訊檔案中的面部識别
之前在“面部識别項目結構”一節中說過,下載下傳的代碼中還有個名為recognize_faces_video_file.py的腳本。
這個腳本實際上和剛才識别攝像頭的腳本相同,隻不過它接收視訊檔案作為輸入,然後生成輸出視訊檔案。
我對原版侏羅紀公園電影中經典的“午飯場景”做了面部識别,在該場景中,演員們圍在桌子旁邊讨論他們對于公園的想法:
1$ python recognize_faces_video_file.py --encodings encodings.pickle \2 --input videos/lunch_scene.mp4 --output output/lunch_scene_output.avi \3 --display 0
下面是結果:
注意:别忘了我們的模型是根據原版電影中的四個角色進行訓練的:Alan Grant、Ellie Sattler、Ian Malcolm和John Hammond。模型并沒有針對Donald Gennaro(律師)進行訓練,是以他的面部被标記為“Unknown”。這個行為是特意的(不是意外),以示範我們的視訊識别系統在識别訓練過的面部的同時,會把不認識的面部标記為“Unknown”。
下面的視訊中我從《侏羅紀公園》和《侏羅紀世界》的預告片中截取的剪輯:
可見,面部識别和OpenCV代碼的效果很不錯!
▌面部識别代碼能運作在樹莓派上嗎?
從某種意義上,可以。不過有一些限制:
樹莓派記憶體太小,沒辦法運作更準确的基于CNN的面部檢測器;
是以隻能用HOG方式;
即使如此,HOG方式在樹莓派上也太慢,沒辦法用于實時面部檢測;
是以隻能用OpenCV的Haar層疊方式。
即使這樣能運作起來,實際的速率也隻有1~2FPS,而且就算是這種速率也需要許多技巧。
▌總結
在這篇指南中,我們學習了如何利用OpenCV、Python和深度學習來進行面部識别。此外,我們還利用了Davis King的dlib庫和Adam Geitgey的face_recognition子產品,後者對dlib的深度度量學習進行了封裝,使得面部識别更容易完成。
我們發現,我們的面部識别實作同時具有以下兩個特點:準确,并且能在GPU上實時運作。
最後,希望你喜歡今天的面部識别的文章!