AI時代的到來,手機上的APP開始應用人臉識別去完成事情,如iphoneX的人臉解鎖,百度自動販賣機的人臉識別系統進行支付,支付寶的人臉識別登錄等,提高了使用軟件的易用性,但也因為其便利性,在某些市面上的應用已經發生了非本人(活人)通過圖片可通過人臉識別,想想這樣自己在應用上存儲的信息和錢財不能得到保障,是一件很可怕的事情。因為這樣,更應該去了解人臉識別的理論及實現,從而更好的去防范這種行為的發生。話不多說,把實現方法和在實現過程中遇到的問題和大家討論討論~
一、環境准備
MacOs+PyCharm+Python+OpenCV
1.Mac下PyCharm下載、安裝、激活
參考:http://blog.csdn.net/qq_35246620/article/details/78254527?utm_source=gold_browser_extension
2.Python安裝
Mac系統下本身已自帶python2.7,所以這一步可以省去。要查看版本的話,終端使用如下命令查看:
which python
python
3.OpenCV安裝
在實現人臉識別的代碼中,我們需要用到的依賴庫有:
Pillow 5.0.0
Pillow-PIL 0.1dev
numpy 1.14.1
opencv-python 3.3.0.10
在本機環境安裝並配置以上庫步驟偏復雜,所以直接在PyCharm中該工程中設置虛擬環境,然后再在虛擬環境中下載安裝。虛擬環境與本機進行隔離,在不同的項目中使用到的第三方庫版本可切換。下面是虛擬環境的創建和安裝依賴庫步驟:
因為OpenCV是安裝到虛擬環境中,而我們后面需要的xml文件是在OpenCV/data/haarcascades/下,所以需要到OpenCV官網下載對應安裝包
https://opencv.org/opencv-3-3.html ,解壓縮后,我們就看到了xml文件。
二、代碼實現
1 # !/usr/bin/env python 2 # coding=utf-8 3 import os 4 import numpy 5 from PIL import Image, ImageDraw 6 import cv2 7 #created by chenfenyu 2018.3.20 8 9 10 cap = cv2.VideoCapture(0) 11 #獲取外接攝像頭 12 eye = cv2.imread("/Users/funny/Downloads/img/eye.png") 13 #讀取眼睛區域替換的圖片 14 mouth = cv2.imread("/Users/funny/Downloads/img/mouth.png") 15 #讀取嘴巴區域替換的圖片 16 size = (int(cap.get(cv2.CAP_PROP_FRAME_WIDTH) + 0.5), int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT) + 0.5)) 17 #獲取攝像頭返回的寬和高 18 fourcc = cv2.VideoWriter_fourcc(*'mp4v') 19 #確定保存視頻的格式 20 video = cv2.VideoWriter("/Users/funny/Downloads/a.avi", fourcc, 5, size) 21 ''' cv.VideoWriter參數(視頻存放路徑,視頻存放格式,fps幀率,視頻寬高) 22 注意點1:OpenCV只支持avi的格式,而且生成的視頻文件不能大於2GB,而且不能添加音頻 23 注意點2:若填寫的文件名已存在,則該視頻不會錄制成功,但可正常使用 24 ''' 25 print cap.isOpened() 26 #檢測是否攝像頭正常打開:成功打開時,isOpened返回ture 27 classifier_face = cv2.CascadeClassifier("/Users/funny/Documents/haarcascade_frontalface_alt.xml") 28 #定義分類器(人臉識別) 29 classifier_eye = cv2.CascadeClassifier("/Users/funny/Documents/haarcascade_eye.xml") 30 #定義分類器(人眼識別) 31 classifier_mouth=cv2.CascadeClassifier("/Users/funny/Documents/haarcascade_mcs_mouth.xml") 32 #定義分類器(嘴巴識別) 33 while (True): 34 #取得cap.isOpened()返回狀態為True,即檢測到人臉 35 #img = cv2.imread("/Users/funny/Downloads/img/pp.png") 36 ret, img = cap.read() 37 '''第一個參數ret的值為True或False,代表有沒有讀到圖片 38 第二個參數是frame,是當前截取一幀的圖片 39 ''' 40 faceRects_face = classifier_face.detectMultiScale(img, 1.2, 2, cv2.CASCADE_SCALE_IMAGE, (20, 20)) 41 #檢測器:detectMultiScale參數(圖像,每次縮小圖像的比例,匹配成功所需要的周圍矩形框的數目,檢測的類型,匹配物體的大小范圍) 42 key = cv2.waitKey(1) 43 #鍵盤等待 44 if len(faceRects_face) > 0: 45 #檢測到人臉 46 for faceRect_face in faceRects_face: 47 x, y, w, h = faceRect_face 48 #獲取圖像x起點,y起點,寬,高 49 h1=int(float(h/1.5)) 50 #截取人臉區域高度的一半位置,以精確識別眼睛的位置 51 intx=int(x) 52 inty=int(y) 53 intw=int(w) 54 inth=int(h) 55 #轉換類型為int,方便之后圖像截取 56 my = int(float(y + 0.7 * h)) 57 #截取人臉區域下半部分左上角的y起點,以精確識別嘴巴的位置 58 mh = int(0.4 * h) 59 #截取人臉區域下半部分高度,以精確識別嘴巴的位置 60 img_facehalf = img[inty:(inty+h1), intx:intx+intw] 61 img_facehalf_bottom = img[my:(my + mh), intx:intx + intw] 62 '''img獲取坐標為,【y,y+h之間(豎):x,x+w之間(橫)范圍內的數組】 63 img_facehalf是截取人臉識別到區域上半部分 64 img_facehalf_bottom是截取人臉識別到區域下半部分 65 ''' 66 cv2.rectangle(img, (int(x), my), (int(x) + int(w), my + mh), (0, 255, 0), 2, 0) 67 '''矩形畫出區域 rectangle參數(圖像,左頂點坐標(x,y),右下頂點坐標(x+w,y+h),線條顏色,線條粗細) 68 畫出人臉識別下部分區域,方便定位 69 ''' 70 faceRects_mouth = classifier_mouth.detectMultiScale(img_facehalf_bottom, 1.1, 1, cv2.CASCADE_SCALE_IMAGE, (5, 20)) 71 #嘴巴檢測器 72 if len(faceRects_mouth) > 0: 73 for faceRect_mouth in faceRects_mouth: 74 xm1, ym1, wm1, hm2 = faceRect_mouth 75 cv2.rectangle(img_facehalf_bottom, (int(xm1), int(ym1)), (int(xm1) + int(wm1), int(ym1) + int(hm2)), (0,0, 255), 2, 0) 76 img_mx = cv2.resize(mouth, (wm1, hm2), interpolation=cv2.INTER_CUBIC) 77 #調整覆蓋圖片大小 resize參數(圖像,檢測到的(寬,高),縮放類型) 78 if key == ord('z'): 79 #檢測當鍵盤輸入z時,開始替換圖片 80 img[my+ym1:(my+ym1+hm2), intx+xm1:(intx + xm1+wm1)] = img_mx 81 #將調整大小后的圖片賦值給img 82 cv2.rectangle(img, (int(x), int(y)), (int(x) + int(w), int(y) + int(h1)), (0, 255, 0), 2, 0) 83 # 畫出人臉識別上部分區域,方便定位 84 faceRects_eye = classifier_eye.detectMultiScale(img_facehalf, 1.2, 2, cv2.CASCADE_SCALE_IMAGE, (20, 20)) 85 #檢測器識別眼睛 86 if len(faceRects_eye) > 0: 87 #檢測到眼睛后循環 88 eye_tag = [] 89 #定義一個列表存放兩只眼睛坐標 90 for faceRect_eye in faceRects_eye: 91 x1, y1, w1, h2 = faceRect_eye 92 cv2.rectangle(img_facehalf, (int(x1), int(y1)), (int(x1) + int(w1), int(y1) + int(h2)), (0, 255, 0), 2, 0) 93 #畫出眼睛區域 94 a = ((inty+y1),(inty+y1 + h2), (intx+x1),(intx+x1 + w1)) 95 #定義a變量獲取眼睛坐標,現在img頂點位置已經改變,需要加上intx和inty的值才可以 96 eye_tag.append(a) 97 #通過append存入數組a中 98 n_eyetag = numpy.array(eye_tag) 99 #存放為ndarray數組類型,輸入內容為[[x1 y1 x1+w y1+h][x1 y1 x1+w y1+h]...],后面會獲取多維數組的下標來替換數值 100 101 if len(faceRects_eye)==2: 102 #眼睛識別到兩個時,同時替換圖片 103 img_ex=cv2.resize(eye,(n_eyetag[0,1]-n_eyetag[0,0], n_eyetag[0,3]-n_eyetag[0,2]),interpolation=cv2.INTER_CUBIC) 104 img_ex1 = cv2.resize(eye, (n_eyetag[1, 1] - n_eyetag[1, 0], n_eyetag[1, 3] - n_eyetag[1, 2]), interpolation=cv2.INTER_CUBIC) 105 if key == ord('p'): 106 #檢測到鍵盤輸入p時,進行替換 107 img[n_eyetag[0,0]:n_eyetag[0,1],n_eyetag[0,2]:n_eyetag[0,3]]=img_ex 108 img[n_eyetag[1, 0]:n_eyetag[1, 1], n_eyetag[1, 2]:n_eyetag[1, 3]] = img_ex1 109 if len(faceRects_eye)==1: 110 # 眼睛識別到一個時,替換圖片 111 img_ex = cv2.resize(eye, (n_eyetag[0, 1] - n_eyetag[0, 0], n_eyetag[0, 3] - n_eyetag[0, 2]), interpolation=cv2.INTER_CUBIC) 112 if key == ord('p'): 113 img[n_eyetag[0, 0]:n_eyetag[0, 1], n_eyetag[0, 2]:n_eyetag[0, 3]] = img_ex 114 115 video.write(img) 116 cv2.imshow('video', img) 117 #顯示圖片,標題名字為video 118 cv2.resizeWindow('video',1280,720) 119 #調整窗口大小video為1280*720 120 if key == ord('q'): 121 #檢測到鍵盤輸入q,退出循環 122 break 123 124 video.release() 125 #不再錄制視頻 126 cap.release() 127 #釋放攝像頭 128 cv2.destroyAllWindows() 129 #關閉所有窗口顯示
代碼中都有注釋,基本看懂第一個循環輸出人臉區域檢測的內容就足夠了,后面只是在原來的基礎上進行多個區域的循環和判斷。接下來我來講一下這個代碼的實現思路,畫成圖解釋一下:
1.打開攝像頭的時候,開始檢測人臉的區域。
2.將獲取到的區域上、下半部分圖像存儲下來。
3.檢測上半部分圖像區域中雙眼的位置。
4.檢測下半部分圖像區域中嘴巴的位置。
5.模擬人臉識別所進行的操作,如眨眼,搖頭,慢慢張開嘴巴,這里我們直接使用鍵盤‘p’來替換眼睛的位置,‘z’替換嘴巴的位置。
6.關閉攝像頭。
說到這里,大家可能有點疑問為什么要划出上下部分區域來識別眼睛和鼻子,原因是識別器的准確度不是很高,若檢測眼睛,鼻子區域也會識別成眼睛,所以才需要划分范圍,請看下圖:
下面我來跟大家講一下IMG取某區域內圖像的坐標以及cv2.rectangle的定位坐標。
1)cv2.rectangle,我們從上面的注釋中知道,獲取的是左上角和右下角的頂點坐標,用畫圖的方式表達如下:
2)IMG區域取值范圍則和1)不同,IMG[左上頂點y:左下頂點y,左上頂點x:右上頂點x],用圖來表示S◇acef區域如下:
所以,之后截取后記得以最原始的IMG坐標為頂點,再來表示要截取的區域,不然就會報錯。
三、Q&A
1、Q:2018-03-20 11:17:10.309 python[79368:108479406] mMovieWriter.status: 3. Error: Cannot Save
A:存放信息目錄下已有同名文件,只需要刪除文件即可,不影響程序正常使用
2、Q:網絡上下載的opencv 識別器xml文件,運行工程時提示
OpenCV Error: Unknown error code -49 (Input file is empty) in cvOpenFileStorage, file /Users/travis/build/skvark/opencv-python/opencv/
modules/core/src/persistence.cpp, line 4484 Traceback (most recent call last): File "/Users/funny/PycharmProjects/untitled1/open-cv.py", line 19, in <module> classifier_mouth=cv2.CascadeClassifier("/Users/funny/Documents/haarcascade_mcs_mouth1.xml") cv2.error: /Users/travis/build/skvark/opencv-python/opencv/modules/core/src/persistence.cpp:4484: error: (-49) Input file is empty in
function cvOpenFileStorage
A:一般xml文件不會超過1m,我下載的這個文件超過了6m,如何檢測是否是可用文件?在存放xml文件的目錄下,單擊后在預覽區能顯示內容則是正確的,請看下圖:
不能顯示內容則是錯誤不可使用的文件:
3、Q:報錯信息:無法覆蓋圖像
File "/Users/funny/PycharmProjects/untitled1/open-cv.py", line 52, in <module> img[ym1:(my+ym1+hm2), intx+xm1:(intx + xm1+wm1)] = img_mx ValueError: could not broadcast input array from shape (86,143,3) into shape (662,143,3)
A:計算圖像的位置不正確,已經超出了范圍,建議參考二代碼中講解圖像IMG顯示的區域坐標。
四、程序已知的問題
1、暫時定位嘴巴和眼睛位置是以人臉分開兩個區域來縮小搜索檢測范圍,所以抬起頭或多人時,仍會檢測到別的地方,這是由於OpenCV內的Haar特征分類器訓練模型還是不夠,所以檢測不夠精准。程序實現的是實時檢測攝像頭內圖像,換成識別靜態圖片則准確多了。
2、只要檢測到人臉,就會一直循環檢測,所以當人物晃動的時候,能檢測到多個眼睛和嘴巴,而且檢測框不斷變換大小。
3、程序暫時只實現了單眼、雙眼同時替換,嘴巴替換。當攝像頭檢測到多個眼睛,只能同時替換至多兩個眼睛。
4、替換的圖片在肉眼看來很假,瞳孔顏色,眼睛大小都還不夠精准,只是用替換圖替換。
五、參考資料
1、OpenCV教程
2、Python入門教程
3、人工智能書籍