OpenCV 和 Dlib 人臉識別基礎


00 環境配置

Anaconda 安裝

  • 2 安裝
  • 3 測試

在鍵盤按 Win + R, 輸入 cmd,回車,將會打開cmd窗口,輸入 activate base, 如下所示,表明anaconda環境系統變量無誤。

IDE PyCharm的安裝

自行百度搜索下載並破解。

http://idea.lanyus.com/

OpenCV安裝

C:\Users\Administrator>activate base

(base) C:\Users\Administrator>pip install opencv-python # [回車]

(base) C:\Users\Administrator>pip install opencv-contrib-python # [回車]

pip換源

Windows

  • Win + R
  • %HOMEPATH%
  • 新建pip文件夾,在文件夾內新建pip.ini文件,文件內容如下:
[global]
index-url = http://mirrors.aliyun.com/pypi/simple/
[install]
trusted-host = mirrors.aliyun.com

Ubuntu

home 目錄下,新建.pip文件夾,在 .pip目錄下,新建pip.conf文件,文件內容和Windows設置相同:

[global]
index-url = http://mirrors.aliyun.com/pypi/simple/
[install]
trusted-host = mirrors.aliyun.com

Dlib 安裝

.whl文件下載

https://pypi.org/simple/dlib/

推薦下載下面兩個之一:

自行搜索.whl文件的安裝,注意基於anaconda環境。

數據集下載准備

http://dlib.net/files/

下載下面三個數據集。

1 OpenCV 中的 Gui 特性

1.1 圖片

本節重點:(image)

  • 圖片的讀入(cv2.imread()

  • 圖片的顯示(cv2.imshow()

  • 圖片的保存(cv2.imwrite()

  • 拓展:matplotlib

1.1.1 圖片的讀入

  • cv2.imread()

查看該函數的幫助文檔:

>>> import cv2
>>> help(cv2.imread)

由幫助文檔,我們可以看出,imread() 使用的語法格式如下:

imread(filename[, flags])
  • filename:圖像路徑及名稱。
  • flags:告訴函數應該如何讀取這幅圖片。
    • cv2.IMREAD_COLOR:讀入一副彩色圖像。圖像的透明度會被忽略,默認參數。 --->傳遞參數:1
    • cv2.IMREAD_GRAYSCALE:以灰度模式讀入圖像 。--->傳遞參數:0
    • cv2.IMREAD_UNCHANGED:讀入一幅圖像,並且包括圖像的 alpha 通道
import numpy as np 
import cv2 

# 以灰度模式加載圖像
# img = cv2.imread('test.jpg', cv2.IMREAD_GRAYSCALE)
img = cv2.imread('test.jpg', 0)

此時,打印img,返回一組矩陣值。

注意:就算圖像的路徑是錯的, OpenCV 不會產生任何提醒,但是當print(img)時,得到的結果是None。

1.1.2 圖片的顯示

  • cv2.imshow(),其語法格式如下:
imshow(winname, mat)

顯示圖像時,圖像會自動調整為圖像大小。

  • winname:窗口名稱
  • mat:圖像(矩陣)
cv2.imshow('demo', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

其中,

  • cv2.waitKey()
    • 鍵盤綁定函數
      • 它的時間尺度是毫秒級。函數等待特定的幾毫秒,看是否有鍵盤輸入。特定的幾毫秒之內,如果按下任意鍵,這個函數會返回按鍵的 ASCII 碼值,程序將會繼續運行。如果沒有鍵盤輸入,返回值為 -1,如果我們設置這個函數的參數為 0,那它將會無限期的等待鍵盤輸入。它也可以被用來檢測特定鍵是否被按下,例如按鍵 a 是否被按下, 后續會討論。
  • cv2.destroyAllWindows()
    • 退出任何已建立的窗口。
    • 如果退出指定的窗口,可以使用cv2.destroyWindow([name]),[name]為我們欲退出的窗口名稱。

Notes:

一 種 特 殊 的 情 況 是, 你 也 可 以 先 創 建 一 個 窗 口, 之 后再 加 載 圖 像。 這 種 情 況 下, 你 可 以 決 定 窗 口 是 否 可 以 調 整大 小。 使 用 到 的 函 數 是 cv2.namedWindow()。 初 始 設 定 函 數標 簽 是 cv2.WINDOW_AUTOSIZE。 但 是 如 果 你 把 標 簽 改 成cv2.WINDOW_NORMAL,你就可以調整窗口大小了。當圖像維度太大,或者要添加軌跡條時,調整窗口大小將會很有用

Demo:

import numpy as np 
import cv2 
img = cv2.imread('demo.jpg')
cv2.namedWindow('demo', cv2.WINDOW_NORMAL)
cv2.imshow('demo', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

此時,顯示出來的圖片,我們就可以調整窗口了。

1.1.3 圖片的保存

  • cv2.imwrite(),其語法格式如下:
imwrite(filename, img[, params])
  • filename:圖像要保存的路徑及名稱。
  • img:圖像矩陣。
cv2.imwrite('./savetest.png', img)

1.1.4 鍵盤控制窗口

首先,看一個例子:

# import numpy as np 
import cv2 

img = cv2.imread('demo.jpg', 0)
cv2.imshow('demo', img)
k = cv2.waitKey(0)
if k == ord('q'):
	cv2.destroyAllWindows()
elif k == ord('s'):
	cv2.imwrite('savetest.jpg', img)
	cv2.destroyAllWindows()

注意:如果你用的是 64 位系統,你需要將 k = cv2.waitKey(0) 這行改成k = cv2.waitKey(0)&0xFF。

1.1.5 matplotlib的結合

Matplotib 是 python 的一個繪圖庫,有各種各樣的繪圖方法。之后會陸續了解到。現在,你可以學習怎樣用 Matplotib 顯示圖像。你可以放大圖像,保存它等等。

import numpy as np 
import cv2 
import matplotlib.pyplot as plt 

img = cv2.imread('demo.jpg', 0) # grayimage
plt.imshow(img, cmap='gray', interpolation='bicubic')
plt.axis('off')
plt.show()

延伸:

程序中,我們以灰度模式讀取圖像,嘗試其他模式讀取圖像,看一下最終顯示的圖像有什么不同。

彩色圖像使用 OpenCV 加載時是 BGR 模式。但是 Matplotib 是 RGB模式。所以彩色圖像如果已經被 OpenCV 讀取,那它將不會被 Matplotib 正確顯示。

import cv2
import numpy as np
import matplotlib.pyplot as plt

img = cv2.imread('demo.jpg')
b, g, r = cv2.split(img)
img2 = cv2.merge([r, g, b])
plt.subplot(121);plt.imshow(img) # expects distorted color
plt.subplot(122);plt.imshow(img2) # expect true color
plt.show()

cv2.imshow('bgr image',img) # expects true color
cv2.imshow('rgb image',img2) # expects distorted color
cv2.waitKey(0)
cv2.destroyAllWindows()
import cv2
import numpy as np
import matplotlib.pyplot as plt

img = cv2.imread('demo.jpg')
cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
cv2.imshow('RGB image', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

1.2 視頻

本節重點

  • 視頻文件讀取
  • 視頻文件顯示
  • 視頻文件保存
  • 攝像頭獲取並顯示視頻
  • 兩個函數cv2.VideoCapture()和cv2.VideoWriter()

1.2.1 用攝像頭捕獲視頻

OpenCV為攝像頭捕捉實時圖像提供了一個十分簡單的接口。

現在我們嘗試捕捉一段視頻,並將其轉換為灰度視頻並顯示出來。

cv2.VideoCapture():視頻獲取函數。

通過help()命令查看其使用:

help(cv2.VideoCapture)

為獲取視頻,需要創建一個 VideoCapture 對象。其參數可以是設備的索引號,或者是一個視頻文件。設備索引號就是指定要使用的攝像頭。一般的筆記本電腦都有內置攝像頭。所以參數就是 0。你可以通過設置成 1 或者其他的來選擇別的攝像頭。之后,你就可以一幀一幀的捕獲視頻了。但是最后,別忘了停止捕獲視頻。

Demo:

import numpy as np 
import cv2 

cap = cv2.VideoCapture(0)
if not cap.isOpened():
    print("Can't open camera")
    exit()
while True:
    # 一幀一幀的捕獲視頻
    ret, frame = cap.read()
    if not ret:
        print("Can't receive frame (stream end?). Exiting...")
        break
    # 對幁視頻進行操作
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    # 顯示結果
    cv2.imshow('frame', gray)
    if cv2.waitKey(1) & 0xff == ord('q'):
        break
# 釋放捕獲
cap.release()
cv2.destroyAllWindows()
  • cap.read() 返回一個布爾值。
    • 如果幁讀取正確,則返回True,否則,返回False。
    • 可以通過檢查其返回值來查看文件是否到了結尾。

cap可能不能成功的初始化攝像頭設備。此時,代碼會報錯。這個時候,可以使用cap.isOpened(),來檢查是否成功初始化了。如果返回值為True,說明初始化成功。否則,可以使用函數cap.open()。

你可以使用函數cap.get(propId) 來獲得視頻的一些參數信息。這里propId 可以是 0 到 18 之間的任何整數。每一個數代表視頻的一個屬性,見下表 cv::VideoCapture::get() ,其中的一些值可以使用 cap.set(propId,value) 來修改, value 就是你想要設置成的新值。

例如,我可以使用cap.get(cv.CAP_PROP_FRAME_WIDTH)cap.get(cv.CAP_PROP_FRAME_HEIGHT) (比如cpa.get(3)和cap.get(4) 來查看每一幀的寬和高。默認情況下得到的值是 640X480。但是我們可以使用 ret = cap.set(cv.CAP_PROP_FRAME_WIDTH,320) and ret = cap.set(cv.CAP_PROP_FRAME_HEIGHT,240)(比如ret=cap.set(3,320)和 ret=cap.set(4,240) )來把寬和高改成 320X240。

1.2.2 視頻保存

我們捕獲視頻,逐幀處理,我們希望保存該視頻。 對於圖像,保存非常簡單,只需使用cv.imwrite()。 對於視頻的保存,需要做更多的工作。

這次我們創建一個VideoWriter對象。 我們應該指定輸出文件名(例如:output.avi)。 然后我們應該指定FourCC編碼。 然后,應傳遞的每秒幀數(fps)和幀大小。 最后一個是isColor標志。 如果是True,則是彩色幀,否則,它適用於灰度幀。

FourCC是一個4字節編碼,用於指定視頻編解碼器。 可在fourcc.org中找到可用代碼列表。 它的平台是獨立的。 以下編解碼器對我來說很好。

  • In Fedora: DIVX, XVID, MJPG, X264, WMV1, WMV2. (XVID is more preferable. MJPG results in high size video. X264 gives very small size video)
  • In Windows: DIVX (More to be tested and added)
  • In OSX: MJPG (.mp4), DIVX (.avi), X264 (.mkv).

FourCC 碼以下面的格式傳給程序,以 MJPG 為例:
cv2.cv2.FOURCC('M','J','P','G') 或者 cv2.cv2.FOURCC(*'MJPG')
下面的代碼是從攝像頭中捕獲視頻,沿水平方向旋轉每一幀並保存它 。

import numpy as np 
import cv2

cap = cv2.VideoCapture(0)

# Define the codec and create Videowriter object
fourcc = cv2.VideoWriter_fourcc(*'XVID')
out = cv2.VideoWriter('output.avi', fourcc, 20.0, (640, 480))

while(cap.isOpened()):
    ret, frame = cap.read()
    if ret == True:
        frame = cv2.flip(frame, 0)

        # write the flipped frame
        out.write(frame)

        cv2.imshow('frame', frame)
        if cv2.waitKey(1) &0xFF == ord('q'):
            break 
    else:
        break

cap.release()
out.release()
cv2.destroyAllWindows()

1.2.3 從文件中播放視頻

與從攝像頭中捕獲相同,我們只需要把設備索引號改成視頻文件的名字。在播放每一幀時,使用cv2.waitKey()設置適當的持續時間。如果設置的太低,視頻會播放的很快。如果設置的太大,視頻就是播放的很慢(可用來控制視頻的播放速度,通常25ms即可)

import numpy as np 
import cv2

cap = cv2.VideoCapture('output.avi')

while cap.isOpened():
    ret, frame = cap.read()

    # if frame is read correctly ret is True
    if not ret:
        print("Can't receive frame (stream end?). Exiting...")
        break
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    cv2.imshow('frame', gray)
    if cv2.waitKey(1) == ord('q'):
        break 

cap.release()
cv2.destroyAllWindows()

1.3 OpenCV中的繪圖函數

目標:

  • 學會用OpenCV繪制不同的幾何形狀
  • 將要學習的函數:
    • cv2.line()
    • cv2.circle()
    • cv2.rectangle()
    • cv2.ellipse()
    • cv2.putText()
    • etc.

編碼約定

涉及的所有繪圖函數,設置如下:
img:你想要繪制圖形的圖像。
color:形狀的顏色。以 RGB 為例,需要傳入一個元組,例如:(255,0,0),代表藍色。對於灰度圖只需要傳入灰度值。
thickness:線條的粗細。如果給一個閉合圖形設置參數為 -1,那么這個圖形就會被填充。默認值是 1。
linetype:線條的類型, 8 聯通線型,抗鋸齒線型等。默認情況是 8 聯通線型。 cv2.LINE_AA為抗鋸齒線型,這樣看起來會非常平滑。

1.3.1 直線繪制

要繪制線條,需要傳遞線條的起點和終點坐標。

接下里的例子中,我們將創建一個黑色圖像,並在其中從左上角到右下角繪制一條藍線。

import numpy as np 
import cv2 
# Create a black image
img = np.zeros((512, 512, 3), np.uint8)

# Draw a diagonal blue line with thickness of 5 px
cv2.line(img, (0, 0), (511, 511), (255, 0, 0), 5)

cv2.imshow('bgr', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

matplotlib:

import numpy as np 
import cv2 
import matplotlib.pyplot as plt 
# Create a black image
img = np.zeros((512, 512, 3), np.uint8)

# Draw a diagonal blue line with thickness of 5 px
cv2.line(img, (0, 0), (511, 511), (0, 0, 255), 5)

plt.imshow(img, 'gray')
plt.show()

1.3.2 矩形繪制

要繪制矩形,您需要指定矩形的左上角和右下角。 這次我們將在圖像的右上角繪制一個綠色矩形。

# Draw a green rectangle at the top-right corner of image
cv2.rectangle(img, (384, 0), (510, 128), (0, 255, 0), 3)

1.3.3 圓形繪制

要繪制圓,需要其中心坐標和半徑。

我們將在上面繪制的矩形內繪制一個圓。

# Draw a circle inside the rectangle drawn above
cv2.circle(img, (447, 63), 63, (0, 0, 255), -1)

1.3.4 橢圓繪制

要繪制橢圓,我們需要傳遞幾個參數。 一個參數是中心位置(x,y)。 下一個參數是軸長度(長軸長度,短軸長度)。 角度是橢圓在逆時針方向上的旋轉角度。 startAngle和endAngle表示從主軸順時針方向測量的橢圓弧的起點和終點。 即給出值0和360給出完整的橢圓。 有關更多詳細信息,請查看cv.ellipse()的文檔。 下面的示例在圖像的中心繪制一個半橢圓。

# Draws a half ellipse at the center of the image
cv2.ellipse(img, (255, 256), (100, 50), 0, 0, 180, (0, 0, 255), -1)

1.3.5 多邊形繪制

要繪制多邊形,首先需要頂點坐標。 將這些點組成一個形狀為ROWSx1x2的數組,其中ROWS是頂點數,它應該是int32類型。 在這里,我們繪制一個帶有四個黃色頂點的小多邊形。

# raw a small polygon of with four vertices in yellow color
pts = np.array([[10, 5],
				[20, 30], 
				[70, 20], 
				[50, 10]],
				np.int32)
pts = pts.reshape((-1, 1, 2))
cv2.polylines(img, [pts], True, (0, 255, 255))

注意

  • 如果第三個參數為False,您將獲得連接所有點的折線,而不是閉合形狀。
  • cv2.polylines()可用於繪制多條線。 只需創建要繪制的所有行的列表,然后將其傳遞給函數。 所有線條都將單獨繪制。 繪制一組行比為每行調用cv2.line()要好得多,速度更快。

1.3.6 向圖像添加文本:

為了將文本放入圖像中,您需要指定以下內容:

  • 要寫入的文本數據
  • 放置位置坐標(即數據開始的左下角)。
  • 字體類型(使用cv2.putText()的幫助文檔可以查看OpenCV支持的字體)
  • 字體縮放(指定字體大小)
  • 常規的東西,如顏色,粗細,線型等。為了更好看,建議使用lineType = cv2.LINE_AA

我們將在圖像上書寫白色的"OpenCV"字樣。

font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(img, 'Hello, OpenCV', (10, 500), font, 2, (255, 255, 255), 2, cv2.LINE_AA)

最后,是時候看看我們繪圖的最終結果了。

正如之前文章中所研究的那樣,顯示圖像以查看它:

import numpy as np 
import cv2 
# Create a black image
img = np.zeros((512, 512, 3), np.uint8)

# Draw a diagonal blue line with thickness of 5 px
cv2.line(img, (0, 0), (511, 511), (255, 0, 0), 5)
cv2.rectangle(img, (384, 0), (510, 128), (0, 255, 0), 3)
cv2.circle(img, (447, 63), 63, (0, 0, 255), -1)
cv2.ellipse(img, (255, 256), (100, 50), 0, 0, 180, 255, -1)
pts = np.array([[10, 5],
				[20, 30], 
				[70, 20], 
				[50, 10]],
				np.int32)
pts = pts.reshape((-1, 1, 2))
cv2.polylines(img, [pts], True, (0, 255, 255))
font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(img, 'Hello, OpenCV', (10, 500), font, 2, (255, 255, 255), 2, cv2.LINE_AA)
cv2.imshow('bgr', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

其他資源

橢圓函數中使用的角度不是我們的圓角。 有關更多詳細信息,請訪問此討論

練習

嘗試使用OpenCV中提供的繪圖功能創建OpenCV的徽標。

# OpenCV徽標繪制
import numpy as np 
import cv2

# 創建白色背景畫布
img = np.ones((512, 512, 3), np.uint8) * 255

# 繪制3個圓
cv2.circle(img,(256,200),60,(0,0,255),-1)
cv2.circle(img,(256,200),25,(255,255,255),-1)
 
cv2.circle(img,(181,328),60,(0,255,0),-1)
cv2.circle(img,(181,328),25,(255,255,555),-1)
 
cv2.circle(img,(331,328),60,(255,0,0),-1)
cv2.circle(img,(331,328),25,(255,255,255),-1)

# 在圓環上疊加三角形,形成缺口圓環的效果
tri1=np.array([256,200,219,264,293,264],np.int32)
tri1=tri1.reshape((-1,1,2)) 
tri2=np.array([[181,328],[256,328],[218,264]],np.int32) 
tri3=np.array([[331,328],[368,264],[293,264]],np.int32)
 
cv2.fillPoly(img,[tri1,tri2,tri3],(255,255,255)) # 為方便看清,顏色可先設置

# 添加文字
font=cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(img, 'OpenCV', (125,450), font, 2, (0, 0, 0), 10) 

cv2.imshow('bgr', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

2 核心操作

在本節中,我們將學習圖像的基本操作,如像素編輯,幾何變換,代碼優化,一些數學工具等。

2.1 圖片的基本操作

目標:

  • 訪問並修改像素值
  • 訪問圖像屬性
  • 設置感興趣區域(ROI)
  • 拆分和合並圖像通道

本節中的幾乎所有操作主要與Numpy而不是OpenCV有關。 使用OpenCV編寫更好的優化代碼需要熟悉Numpy。
(示例將在Python終端中顯示,因為大多數只是單行代碼)

2.1.1 獲取並修改像素值

首先,我們加載一幅彩色圖像

>>> import numpy as np
>>> import cv2
>>> img = cv2.imread('demo.jpg')
>>>

我們可以通過像素值的行和列坐標來訪問像素值。 對於BGR圖像,它返回一個藍色,綠色,紅色值的數組。 對於灰度圖像,僅返回相應的強度。

>>> px = img[100, 100]
>>> px
array([57, 63, 68], dtype=uint8)

# accessing only blue pixel
>>> blue = img[100, 100, 0]
>>> blue
57
>>>

我們可以用同樣的方法來修改像素值

>>> img[100, 100] = [255, 255, 255]
>>> img[100, 100]
array([255, 255, 255], dtype=uint8)
>>>

警告

Numpy是經過優化了的進行快速矩陣運算的軟件包。所以我們不推薦逐個獲取像素值並修改,這樣會很慢,能有矩陣運算就不要用循環。

注意:

上面提到的方法被用來選取矩陣的一個區域,比如說前 5 行的后 3列。對於獲取每一個像素值,也許使用 Numpy 的 array.item() 和 array.itemset() 會更好。但是返回值是標量。如果你想獲得所有 B,G,R 的值,你需要使用 array.item() 分割他們。

獲取像素值及修改的更好方法。

# accessing RED value
>>> img.item(10, 10, 2)
50
# modifying RED value
>>> img.itemset((10,10,2),100)
>>> img.item(10,10,2)
100

2.1.2 獲取圖像屬性

圖像屬性包括行數,列數和通道數,圖像數據類型,像素數等。

  • img.shape
    • 可以獲取圖像的形狀。 他的返回值是一個包含行數,列數,通道數的元組。(如果圖像是彩色的):

注意

如果圖像是灰度圖像,則返回的元組僅包含行數和列數,因此,過檢查這個返回值就可以知道加載的是灰度圖還是彩色圖。

  • img.size
    • 返回圖像的像素總數
>>> img.size
378000
>>>
  • img.dtype
    • 返回圖像的數據類型
>>> img.dtype
dtype('uint8')
>>>

注意

img.dtype在調試時非常重要,因為OpenCV-Python代碼中的大量錯誤是由無效的數據類型引起的。

2.1.3 圖像的感興趣區域

有時,我們需要對一幅圖像的特定區域進行操作。例如我們要檢測一副圖像中眼睛的位置,我們首先應該在圖像中找到臉,再在臉的區域中找眼睛,而不是直接在一幅圖像中搜索。這樣會提高程序的准確性和性能。
ROI 也是使用 Numpy 索引來獲得的。現在我們選擇球的部分並把他拷貝到圖像的其他區域。

>>> ball = img[140:170, 165:195]
>>> img[136:166, 50:80] = ball

2.1.4 拆分和合並圖像通道

有時候我們需要分別處理圖像的B,G,R通道,這個時候我們需要將圖像拆分為單通道圖。還有一些情況,我們需要用單通道圖合成BGR圖像。

>>> b, g, r = cv2.split(img)
>>> img = cv2.merge((b, g, r))

或者

>>> b = img[:, :, 0]

假設要將所有紅色像素設置為零,則無需先拆分通道。使用 Numpy索引更快:

>>> img[:,:,2] = 0

b --- 0;

g --- 1;

r --- 2

注意:

cv.split() 是一個比較耗時的操作。 所以只有在你需要時才這樣做。 否則優先使用Numpy索引。

人臉標記

人臉識別原理

​ 在檢測到人臉並定位面部關鍵特征點之后,主要的人臉區域就可以被裁剪出來,經過預處理之后,饋入后端的識別算法。識別算法要完成人臉特征的提取,並與庫存的已知人臉進行比對,完成最終的分類。

​ 人臉識別(Facial Recognition),就是通過視頻采集設備獲取用戶的面部圖像,再利用核心的算法對其臉部的五官位置、臉型和角度進行計算分析,進而和自身數據庫里已有的范本進行比對,判斷出用戶的真實身份。

​ 作為人臉識別的第一步,人臉檢測所進行的工作是將人臉從圖像背景中檢測出來,由於受圖像背景、亮度變化以及人的頭部姿勢等因素影響使人臉檢測成為一項復雜研究內容。
檢測定位:檢測是判別一幅圖像中是否存在人臉,定位則是給出人臉在圖像中的位置。

​ 定位后得到的臉部圖像信息是測量空間的模式,要進行識別工作,首先要將測量空間中的數據映射到特征空間中。采用主分量分析方法,原理是將一高維向量,通過一個特殊的特征向量矩陣,投影到一個低維的向量空間中,表征為一個低維向量,並且僅僅損失一些次要信息。
人臉識別系統采用基於特征臉的主成分分析法(PCA),根據一組人臉訓練樣本構造主元子空間,檢測時,將測試圖像投影到 主元空間上,得到一組投影系數,再和各已知的人臉圖像模式比較,從而得到檢測結果。

人臉檢測的實現

在搜索“github opencv”,在 “opencv/data/haarcascades” 目錄下可以看到各種各樣的特征分類器(xml文件),從文件名上可以輕易區分出分類器的用途,如 “haarcascadesfrontalface_default.xml” 是臉部正面特征分類器,它是我們用python實現人臉檢測的關鍵。

圖片中人臉的檢測

import cv2
#加載圖片
img = cv2.imread("./img/img_remark.jpg")
#轉換成灰度,提高計算速度
gray=cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 加載Haar特征分類器
face_cascade = cv2.CascadeClassifier("./data/haarcascade_frontalface_default.xml")

#探檢測圖片中的人臉
faces = face_cascade.detectMultiScale(
	gray,                  # 要檢測的圖像
	scaleFactor=1.15,      # 圖像尺寸每次縮小的比例
	minNeighbors=3,        # 一個目標至少要被檢測到3次才會被標記為人驗
	minSize=(5, 5))        # 目標的最小尺寸


# 為每個人臉輸制矩形框
for (x, y, w, h) in faces:
	cv2.rectangle(img,(x, y),(x+w, y+h), (0, 255, 0), 2)

cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

​ 上圖是python實現人臉檢測的完整代碼,第1行引入 OpenCV 庫;第4行使用 imread() 函數加載待檢測的圖片,第7行將圖片轉換成灰度以降低圖像運算時間;第10行加載臉部正面特征分類器;第13行調用detectMultiScale( )函數對灰度圖像進行面部檢測,該函數中除了第一個之外其它參數都是非必需的,這些參數可以用來調節檢測的精度;第21行在所有檢測出來的所有人臉位置繪制矩形框。

捕獲攝像頭實時監測人臉:

import cv2
 
cap = cv2.VideoCapture(0) 
face_cascade = cv2.CascadeClassifier('./data/haarcascade_frontalface_default.xml') # 加載人臉特征庫
 
while True:
	ret, frame = cap.read() # 讀取一幀的圖像
	# gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 轉灰
	faces = face_cascade.detectMultiScale(
		frame, 
		scaleFactor=1.15, 
		minNeighbors=5, 
		minSize=(5, 5)) # 檢測人臉
	for(x, y, w, h) in faces:
		cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2) # 用矩形圈出人臉


	cv2.imshow('Face Recognition', frame)
	if cv2.waitKey(1) & 0xFF == ord('q'):
		break
cap.release() # 釋放攝像頭
cv2.destroyAllWindows()

存取視頻人臉檢測

#讀取並且顯示視屏,框出檢測到的人臉
import cv2

cascPath = "./data/haarcascade_frontalface_default.xml"
faceCascade = cv2.CascadeClassifier(cascPath)

# 打開視頻捕獲設備
cap = cv2.VideoCapture('./video/output.avi')

while True:
    if not cap.isOpened():
        print('Unable to load camera.')
        sleep(5)
        pass

        # 讀視頻幀
    ret, frame = cap.read()

    # 轉為灰度圖像
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    print(gray)
    # 調用分類器進行檢測
    faces = faceCascade.detectMultiScale(
        gray,
        scaleFactor=1.1,
        minNeighbors=5,
        minSize=(200, 200)
        # flags=cv2.CV_HAAR_SCALE_IMAGE
    )

    # 畫矩形框
    for (x, y, w, h) in faces:
        cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)


    # 顯示視頻
    cv2.resizeWindow('Video', 500, 500)
    cv2.imshow('Video', frame)

    if cv2.waitKey(60) & 0xFF == ord('q'):
        break
        # 關閉攝像頭設備
cap.release()

# 關閉所有窗口
cv2.destroyAllWindows()

人臉特征點檢測

1 Dlib安裝

pip install xxx.whl

2 基於dlib庫人臉特征提取

基於dlib庫對人臉特征進行提取,在視頻流中抓取人臉特征、並保存為64x64大小的圖片文件。
注意的是:因為我們后面會對人臉數據集進行訓練識別,因此,這一步非常重要。

  • 光線——曝光和黑暗圖片因手動剔除
  • 攝像頭的清晰度也比較重要——在哪台筆記本識別,就要在那台筆記本做數據集采集,我用了同學在其他筆記本采取的數據,因為電腦配置,在后面的訓練中出現不能識別或錯誤識別的情況,因此,盡量同一設備——采取數據集和做人臉識別。

3 Dlib人臉特征檢測原理

  • (1)提取特征點

  • 獲取特征數據集

根據數據集,模型----訓練----》68個特征數據

  • (3)計算特征數據集的歐氏距離作對比

4 人臉識別流程

人臉識別是由一系列的幾個相關問題組成的:

  • 首先找到一張圖片中的所有人臉。
  • 對於每一張臉來說,無論光線明暗或面朝別處,它依舊能夠識別出是同一個人的臉。
  • 能夠在每一張臉上找出可用於他人區分的獨特之處,比如眼睛多大,臉有多長等等。
  • 最后將這張臉的特點與已知所有人臉進行比較,以確定這個人是誰。

(1) 找出所有的面孔

很顯然在我們在人臉識別的流程中得首先找到圖片中的人臉。我們在使用手機或相機拍照時都會有人像模式,它能輕松的檢測出人臉的位置,幫助相機快速對焦。

我們得感謝 保羅·比奧拉(Paul Viola)和邁克爾·瓊斯(Michael Jones)在2000年發明了一種能夠快速在廉價相機上運行的人臉檢測方法,人臉檢測在相機上的應用才成為主流。然而現在我們有更可靠的解決方案HOG(Histogram of Oriented Gradients)方向梯度直方圖,一種能夠檢測物體輪廓的算法。

首先,我們把圖片灰度化,因為顏色信息對於人臉檢測而言沒什么用。

我們分析每個像素以及其周圍的像素,根據明暗度畫一個箭頭,箭頭的指向代表了像素逐漸變暗的方向,如果我們重復操作每一個像素,最終像素會被箭頭取代。這些箭頭被稱為梯度(gradients),它們能顯示出圖像從明亮到黑暗流動的過程。

分析每個像素對我們來說有點不划算,因為它太過細節化了,我們可能會迷失在像素的海洋里,我們應該從更高的角度觀察明暗的流動。

為此我們將圖像分割成16x16像素的小方塊。在每個小方塊中,計算出每個主方向有多少個剃度(有多少指向上,指向右上,指向右等)。然后用指向性最強的那個方向箭頭來代替原來那個小方塊。

最終結果,我們把原始圖像轉換成一個非常簡單的HOG表達形式,它可以很輕松的捕獲面部的基本結構。

為了在HOG圖像中找到臉部,我們需要做的是,與已知的一些HOG圖案中,看起來最相似的部分。這些HOG圖案都是重其他面部訓練數據中提取出來的。

(2) 臉部的不同姿勢

我們已經找出了圖片中的人臉,那么如何鑒別面朝不同方向的人臉呢?

對於電腦來說朝向不同的人臉是不同的東西,為此我們得適當的調整扭曲圖片中的人臉,使得眼睛和嘴總是與被檢測者重疊。

為了達到目的我們將使用一種面部特征點估計(face landmark
estimation)的算法。其實還有很多算法都可以做到,但我們這次使用的是由瓦希德·卡奇米(Vahid Kazemi)和約瑟菲娜·沙利文(Josephine
Sullivan)在 2014 年發明的方法。

這一算法的基本思路是找到68個人臉上普遍存在的點(稱為特征點, landmark)

  • **下巴輪廓17個點 [0-16] **
  • **左眉毛5個點 [17-21] **
  • **右眉毛5個點 [22-26] **
  • **鼻梁4個點 [27-30] **
  • **鼻尖5個點 [31-35] **
  • **左眼6個點 [36-41] **
    **右眼6個點 [42-47] **
  • **外嘴唇12個點 [48-59] **
  • **內嘴唇8個點 [60-67] **

有了這68個點,我們就可以輕松的知道眼睛和嘴巴在哪兒了,后續我們將圖片進行旋轉,縮放和錯切,使得眼睛和嘴巴盡可能的靠近中心。

現在人臉基本上對齊了,這使得下一步更加准確。

(3)給臉部編碼

1:1

1:N

我們還有個核心的問題沒有解決, 那就是如何區分不同的人臉。

最簡單的方法就是把我們第二步中發現的未知人臉與我們已知的人臉作對比。當我們發現未知的面孔與一個以前標注過的面孔看起來相似的時候,就可以認定他們是同一個人。

我們人類能通過眼睛大小,頭發顏色等等信息輕松的分辨不同的兩張人臉,可是電腦怎么分辨呢?沒錯,我們得量化它們,測量出他們的不同,那要怎么做呢?

實際上,對於人臉這些信息很容易分辨,可是對於計算機,這些值沒什么價值。實際上最准確的方法是讓計算機自己找出他要收集的測量值。深度學習比人類更懂得哪些面部測量值比較重要。

所以,解決方案是訓練一個深度卷積神經網絡,訓練讓它為臉部生成128個測量值。

為了確保旋轉不變性,會以關鍵點為中心,以關鍵點的方向建立坐標軸,不是單獨考察單一的這個關鍵點,而是需要一個鄰域。鄰域中每個小格的方向代表該像素的梯度方向,長度是梯度模大小,在每個4X4的小塊上計算8個方向的梯度方向直方圖,統計每個方向的累加值,形成一個種子點。David G.Lowe建議對每個關鍵點使用4X4=16個種子點進行描述,每個種子點包含8個方向信息,所以一個關鍵點就會產生16X8=128維的信息,形成128維的SIFT特征向量。

SIFT特征詳解

每次訓練要觀察三個不同的臉部圖像:

  • 加載一張已知的人的面部訓練圖像
  • 加載同一個人的另一張照片
  • 加載另外一個人的照片

然后,算法查看它自己為這三個圖片生成的測量值。再然后,稍微調整神經網絡,以確保第一張和第二張生成的測量值接近,而第二張和第三張生成的測量值略有不同。

我們要不斷的調整樣本,重復以上步驟百萬次,這確實是個巨大的挑戰,但是一旦訓練完成,它能攻輕松的找出人臉。

慶幸的是 OpenFace 上面的大神已經做完了這些,並且他們發布了幾個訓練過可以直接使用的網絡,我們可以不用部署復雜的機器學習,開箱即用,感謝開源精神。

這128個測量值是什么?

其實我們不用關心,這對我們也不重要。我們關心的是,當看到同一個人的兩張不同照片時,我們的網絡需要能得到幾乎相同的數值。

(4) 從編碼中找出人的名字

最后一步實際上是最簡單的一步,我們需要做的是找到數據庫中與我們的測試圖像的測量值最接近的那個人。

如何做呢,我們利用一些現成的數學公式,計算兩個128D數值的歐氏距離。

這樣我們得到一個歐式距離值,系統將給它一個認為是同一個人歐氏距離的閥值,即超過這個閥值我們就認定他們是 同 (失) 一 (散) 個 (兄) 人 (弟)。

人臉識別就這樣實現了,來來我們再回顧下流程

  • 使用HOG找出圖片中所有人臉的位置。
  • 計算出人臉的68個特征點並適當的調整人臉位置,對齊人臉。
  • 把上一步得到的面部圖像放入神經網絡,得到128個特征測量值,並保存它們。
  • 與我們以前保存過的測量值一並計算歐氏距離,得到歐氏距離值,比較數值大小,即可得到是否同一個人。

5 68關鍵點檢測

圖片

import dlib
import cv2
 
# 與人臉檢測相同,使用dlib自帶的frontal_face_detector作為人臉檢測器
detector = dlib.get_frontal_face_detector()
 
# 使用官方提供的模型構建特征提取器
predictor = dlib.shape_predictor('./dat/shape_predictor_68_face_landmarks.dat')
# cv2讀取圖片
img = cv2.imread("./img/demo.jpg")
 
# 與人臉檢測程序相同,使用detector進行人臉檢測 dets為返回的結果
dets = detector(img, 1)
# 使用enumerate 函數遍歷序列中的元素以及它們的下標
# 下標k即為人臉序號
# left:人臉左邊距離圖片左邊界的距離 ;right:人臉右邊距離圖片左邊界的距離
# top:人臉上邊距離圖片上邊界的距離 ;bottom:人臉下邊距離圖片上邊界的距離
for k, d in enumerate(dets):
    print("dets{}".format(d))
    print("Detection {}: Left: {} Top: {} Right: {} Bottom: {}".format(
        k, d.left(), d.top(), d.right(), d.bottom()))
 
    # 使用predictor進行人臉關鍵點識別 shape為返回的結果
    shape = predictor(img, d)
    # 獲取第一個和第二個點的坐標(相對於圖片而不是框出來的人臉)
    print("Part 0: {}, Part 1: {} ...".format(shape.part(0), shape.part(1)))
 
    # 繪制特征點
    for index, pt in enumerate(shape.parts()):
        print('Part {}: {}'.format(index, pt))
        pt_pos = (pt.x, pt.y)
        cv2.circle(img, pt_pos, 1, (255, 0, 0), 2)
        #利用cv2.putText輸出1-68
        font = cv2.FONT_HERSHEY_SIMPLEX
        cv2.putText(img, str(index+1),pt_pos,font, 0.3, (0, 0, 255), 1, cv2.LINE_AA)
 
cv2.imshow('img', img)
k = cv2.waitKey()
cv2.destroyAllWindows()

視頻

import cv2
import dlib
 
predictor_path = "./dat/shape_predictor_68_face_landmarks.dat"
 
#初始化
predictor = dlib.shape_predictor(predictor_path)
 
#初始化dlib人臉檢測器
detector = dlib.get_frontal_face_detector()
 
#初始化窗口
win = dlib.image_window()
 
# cap = cv2.VideoCapture('./vid/demo.mp4')
cap = cv2.VideoCapture(0)
while cap.isOpened():
    ok, cv_img = cap.read()
    if not ok:
        break
 
    img = cv2.cvtColor(cv_img, cv2.COLOR_RGB2BGR)#轉灰
 
    dets = detector(img, 0)
    shapes =[]
    for k, d in enumerate(dets):
        print("dets{}".format(d))
        print("Detection {}: Left: {} Top: {} Right: {} Bottom: {}".format(
            k, d.left(), d.top(), d.right(), d.bottom()))
 
 
        # 使用predictor進行人臉關鍵點識別 shape為返回的結果
        shape = predictor(img, d)
        #shapes.append(shape)
    #繪制特征點
        for index, pt in enumerate(shape.parts()):
            print('Part {}: {}'.format(index, pt))
            pt_pos = (pt.x, pt.y)
            cv2.circle(img, pt_pos, 1, (0,225, 0), 2)
        # 利用cv2.putText輸出1-68
            font = cv2.FONT_HERSHEY_SIMPLEX
            cv2.putText(img, str(index+1),pt_pos,font, 0.3, (0, 0, 255), 1, cv2.LINE_AA)
 
 
 
    win.clear_overlay()
    win.set_image(img)
    if len(shapes)!= 0 :
        for i in range(len(shapes)):
            win.add_overlay(shapes[i])
    # win.add_overlay(dets)
 
cap.release()

攝像頭捕獲

# cap = cv2.VideoCapture('./vid/demo.mp4')
cap = cv2.VideoCapture(0)

6 128特征點

為了確保旋轉不變性,會以關鍵點為中心,以關鍵點的方向建立坐標軸,不是單獨考察單一的這個關鍵點,而是需要一個鄰域。鄰域中每個小格的方向代表該像素的梯度方向,長度是梯度模大小,在每個4X4的小塊上計算8個方向的梯度方向直方圖,統計每個方向的累加值,形成一個種子點。David G.Lowe建議對每個關鍵點使用4X4=16個種子點進行描述,每個種子點包含8個方向信息,所以一個關鍵點就會產生16X8=128維的信息,形成128維的SIFT特征向量。
摘自:https://blog.csdn.net/vonzhoufz/article/details/45647053

本質上人臉識別就是把圖像通過映射函數映射到低維空間,並且這個空間里不同人臉是可以被很好的區分的。而這個映射函數現在大多是通過訓練深度網絡得到。128D特征向量就這映射后空間的維度.

圖片

import cv2 
import dlib
# face recognition model, the object maps human faces into 128D vectors
img = './img/timg.jpg'
facerec = dlib.face_recognition_model_v1("./dat/dlib_face_recognition_resnet_model_v1.dat")
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor('./dat/shape_predictor_68_face_landmarks.dat')
img = cv2.imread(img)
# dets = detector(img, 1)
dets = detector(img, 0)
shape = predictor(img, dets[0])
face_descriptor = facerec.compute_face_descriptor(img, shape)
print(face_descriptor)

視頻

https://www.cnblogs.com/AdaminXie/p/9010298.html

OpenCV 中人臉識別算法

Face Recognition with OpenCV

CVPR2019 論文解讀匯總(0611 更新中)

人臉識別的編碼步驟

  • 訓練數據集: 收集要識別的人的面部數據(本例中為面部圖像)
  • 識別器的訓練: 將人臉數據(以及每個人臉的相應名稱)輸入人臉識別器,使其能夠學習。
  • 識別: 輸入這些人的新面孔,看看你剛訓練過的人臉識別器是否識別他們

OpenCV帶有內置的人臉識別器,我們要做的就是給它輸入人臉數據。這很簡單,一旦我們完成了編碼,它就會看起來很簡單。

OpenCV面部識別器

  OpenCV有三個內置的人臉識別器,多虧了OpenCV干凈的編碼,你可以通過改變一行代碼來使用它們中的任何一個。下面是這些人臉識別器的名稱和它們的OpenCV調用

  1.EigenFaces人臉識別器 - cv2.face.EigenFaceRecognizer_create()

  2.FisherFaces人臉識別器 - cv2.face.FisherFaceRecognizer_create()

   3.局部二值模式直方圖(LBPH)人臉識別器 - cv2.face.LBPHFaceRecognizer_create()

  現在我們有三個人臉識別器,但是你知道該用哪一個嗎?什么時候用嗎?或者哪個更好?接下來我們將深入研究每一個識別器。

qyt538

EigenFaces面部識別器

  這個算法考慮的事實是,並不是臉的所有部分都同樣重要,或同樣有用。當你看一個人的時候,你會通過他獨特的特征認出他/她,比如眼睛、鼻子、臉頰、前額以及他們之間的差異。所以你實際上關注的是最大變化的區域(數學上說,這個變化是方差)。例如,從眼睛到鼻子有一個顯著的變化,從鼻子到嘴也是如此。當你看多張臉的時候你可以通過看臉的這些部分來比較它們因為這些部分是臉最有用和最重要的組成部分。重要的是,它們捕捉到人臉之間的最大變化,這種變化可以幫助你區分不同的人臉,這就是特征人臉識別系統的工作原理。

  EignFaces人臉識別器將所有人的訓練圖像作為一個整體,並試圖提取重要和有用的成分(捕捉最大方差/變化的成分),並丟棄其余的成分。這樣,它不僅從訓練數據中提取重要的組件,而且通過丟棄不重要的組件來節省內存。它提取的這些重要成分被稱為主成分

  我所用主成分,方差,高變化區域,可互換的有用特征等術語,它們的性質基本上是一樣的東西。
  以下是顯示從面部列表中提取的主要組件的圖像。

主成分(PCA)

你可以看到,主分量實際上表示面的,這些面被稱為特征面,也就是算法的名字。

  這就是特征面識別器自身的訓練方式(通過提取主成分),它還記錄了哪個主成分屬於哪個人。在上面的圖像中需要注意的一點是特征面算法也將光照作為一個重要的組成部分。

  在隨后的識別過程中,當你向算法輸入新圖像時,它也會在該圖像上重復同樣的過程。它從新映像中提取主組件,並將該組件與它在訓練期間存儲的組件列表進行比較,並找到匹配最好的組件,並返回與該最佳匹配組件關聯的person標簽。

  輕松+容易,對吧? 下一個比這個更容易。

FisherFaces人臉識別器

  該算法是改進后的FisherFaces人臉識別算法。FisherFaces人臉識別器同時查看所有人的訓練面,並從所有人的訓練面中找到主要的組成部分。通過從所有的人臉中捕獲主要的組成部分,你並沒有把注意力集中在區分一個人和另一個人的特征上,而是集中在代表整個訓練數據中所有人的所有面孔的特征上。

  這種方法有一個缺點。 例如,考慮下面的面光照變化。

你知道特征面人臉識別器也認為照明是一個重要的組成部分,對吧?想象一個場景,一個人所有的臉都有非常高的亮度變化(非常暗或者非常亮等等)。特征人臉識別者將會考慮這些光照變化非常有用的特征,並且可能會忽略其他人的面部特征,認為這些特征不太有用。現在所提取的特征特征面只代表一個人的面部特征,而不是所有人的面部特征。

  如何解決這個問題? 我們可以通過調整EigenFaces人臉識別器來解決這個問題,以便從每個人的臉部分別提取有用的特征,而不是提取所有臉部組合的有用特征。 這樣,即使一個人的光照變化很大,也不會影響其他人物特征提取過程。 這正是FisherFaces人臉識別器算法的功能。

  Fisherfaces算法不是提取表示所有人員所有面部的有用特征,而是提取可區分一個人和另一個人的有用特征。 通過這種方式,一個人的特征不會占據主導地位(被認為是更有用的特征)而其他人則具有區分一個人和另一個人的特征。

下面是使用Fisherfaces算法提取的特征的圖像。

  Fisher Faces

你可以看到提取的特征實際上代表了面孔,這些面被稱為Fisher faces,因此算法的名稱。

  這里需要注意的一點是,Fisherfaces人臉識別器只會阻止一個人的特征凌駕於另一個人的特征之上,但它仍然認為光照變化是有用的特征。我們知道光照變化不是一個有用的特征來提取,因為它不是真正的臉的一部分。那么,該怎么擺脫這個照明問題?這就是我們的下一個人臉識別器鎖解決的問題。

局部二值模式直方圖(LBPH)人臉識別器

  我們知道Eigenfaces和Fisherfaces都受光線影響,在現實生活中,我們無法保證完美的光照條件。 LBPH人臉識別器是克服這個缺點的一種改進。

  這種想法是不看整個圖像,而是查找圖像的局部特征。 LBPH算法試圖找出圖像的局部結構,並通過比較每個像素與其相鄰像素來實現。

  取一個3x3的窗口,每移動一個圖像(圖像的每個局部),將中心的像素與相鄰像素進行比較。強度值小於或等於中心像素的鄰域用1表示,其它鄰域用0表示。然后你以順時針的順序讀取3x3窗口下的0/1值,你會得到一個像11100011這樣的二進制模式,這個模式在圖像的特定區域是局部的。在整個圖像上這樣做,就會得到一個局部二進制模式的列表。

LBP標簽

現在你明白為什么這個算法的名字中有局部二進制模式? 因為你得到一個局部二進制模式列表。 現在你可能想知道,LBPH的直方圖部分呢? 在獲得局部二進制模式列表后,您可以使用二進制到十進制轉換將每個二進制模式轉換為十進制數(如上圖所示),然后對所有這些十進制值進行直方圖制作。 樣本直方圖是像下面這樣的。

  樣本直方圖

我猜這回答了直方圖部分的問題。所以最終你會得到訓練數據集中每個人臉圖像的一個直方圖,這意味着如果訓練數據集中有100個圖像,那么LBPH會在訓練后提取100個直方圖,並儲存起來以便以后識別。記住,算法也會跟蹤哪個直方圖屬於哪個人。

  在識別后期,當您將新圖像送入識別器進行識別時,它將生成新圖像的直方圖,將該直方圖與其已有的直方圖進行比較,找到最佳匹配直方圖並返回與該最佳匹配關聯的人員標簽 匹配直方圖。

  下面是一張臉和它們各自的局部二進制模式圖像的列表。您可以看到,LBP圖像不受光照條件變化的影響。

  局部人臉

理論部分已經結束,現在是編碼部分!准備好開始編寫代碼了嗎?那我們開始吧。

使用OpenCV編碼人臉識別

  本教程中的人臉識別過程分為三個步驟。

  1、准備訓練數據:在這一步中,我們將讀取每個人/主體的訓練圖像及其標簽,從每個圖像中檢測人臉並為每個檢測到的人臉分配其所屬人員的整數標簽。

  2、訓練人臉識別器:在這一步中,我們將訓練OpenCV的LBPH人臉識別器,為其提供我們在步驟1中准備的數據。

  3、測試:在這一步中,我們會將一些測試圖像傳遞給人臉識別器,並查看它是否能夠正確預測它們

編程工具:

  1. OpenCV 4.1.0.
  2. Python v3.6.
  3. NumPy

   注:Numpy使Python中的計算變得容易。 除此之外,它還包含一個強大的N維數組實現,我們將使用它來將數據作為OpenCV函數的輸入。

導入必需的模塊

  在開始實際編碼之前,我們需要導入所需的編碼模塊。 所以讓我們先導入它們。

  cv2:是Python的OpenCV模塊,我們將用它來進行人臉檢測和人臉識別。

  os:我們將使用這個Python模塊來讀取我們的培訓目錄和文件名。

  numpy:我們將使用此模塊將Python列表轉換為numpy數組,因為OpenCV人臉識別器接受numpy數組。

#導入OpenCV模塊
import cv2
#導入os模塊用於讀取訓練數據目錄和路徑
import os
# 導入numpy將python列表轉換為numpy數組,OpenCV面部識別器需要它
import numpy as np

測試數據文件夾包含我們將用於在成功培訓完成后測試人臉識別器的圖像

  由於OpenCV人臉識別器接受標簽為整數,因此我們需要定義整數標簽和人物實際名稱之間的映射,所以下面我定義了人員整數標簽及其各自名稱的映射。

  注意:由於我們尚未將標簽0分配給任何人,因此標簽0的映射為空。

#我們的訓練數據中沒有標簽0,因此索引/標簽0的主題名稱為空
subjects = ["", "Ramiz Raja", "Elvis Presley"]

准備訓練數據

  您可能想知道為什么要進行數據准備,對嗎? 那么,OpenCV人臉識別器接受特定格式的數據。 它接受兩個矢量,一個矢量是所有人的臉部,第二個矢量是每個臉部的整數標簽,因此在處理臉部時,臉部識別器會知道該臉部屬於哪個人。

  例如,如果我們有兩個人和兩個圖像為每個人。

 PERSON-1    PERSON-2   

  img1        img1         
  img2        img2

然后,准備數據步驟將生成以下面和標簽向量。

准備數據步驟可以進一步分為以下子步驟。

  1.文件夾中提供的所有主題/人員的文件夾名稱。 例如,在本教程中,我們有文件夾名稱:s1,s2。

  2.對於每個主題,提取標簽號碼。 文件夾名稱遵循格式sLabel,其中Label是一個整數,代表我們已分配給該主題的標簽。 因此,例如,文件夾名稱s1表示主題具有標簽1,s2表示主題標簽為2等。 將在此步驟中提取的標簽分配給在下一步中檢測到的每個面部。

  3.閱讀主題的所有圖像,從每張圖像中檢測臉部。

  4.將添加到標簽矢量中的具有相應主題標簽(在上述步驟中提取)的每個臉部添加到臉部矢量。

#使用OpenCV用來檢測臉部的函數
def detect_face(img):
    #將測試圖像轉換為灰度圖像,因為opencv人臉檢測器需要灰度圖像
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
    #加載OpenCV人臉檢測器,我正在使用的是快速的LBP
    #還有一個更准確但緩慢的Haar分類器
    face_cascade = cv2.CascadeClassifier('opencv-files/lbpcascade_frontalface.xml')

    #讓我們檢測多尺度(一些圖像可能比其他圖像更接近相機)圖像
    #結果是一張臉的列表
    faces = face_cascade.detectMultiScale(gray, scaleFactor=1.2, minNeighbors=5);
    
    #如果未檢測到面部,則返回原始圖像
    if (len(faces) == 0):
        return None, None
    
    #假設只有一張臉,
    #提取面部區域
    (x, y, w, h) = faces[0]
    
    #只返回圖像的正面部分
    return gray[y:y+w, x:x+h], faces[0]

我正在使用OpenCV的LBP人臉檢測器。 在第4行,我將圖像轉換為灰度,因為OpenCV中的大多數操作都是以灰度進行的,然后在第8行使用cv2.CascadeClassifier類加載LBP人臉檢測器。 在第12行之后,我使用cv2.CascadeClassifier類'detectMultiScale方法來檢測圖像中的所有面部。 在第20行中,從檢測到的臉部我只挑選第一張臉部,因為在一張圖像中只有一張臉部(假設只有一張醒目的臉部)。 由於detectMultiScale方法返回的面實際上是矩形(x,y,寬度,高度),而不是實際的面部圖像,所以我們必須從主圖像中提取面部圖像區域。 所以在第23行我從灰色圖像中提取人臉區域並返回人臉圖像區域和人臉矩形。

  現在您已經有了一個面部檢測器,您知道准備數據的4個步驟,那么您准備好編寫准備數據步驟了嗎?是嗎?讓我們來做它。

#該功能將讀取所有人的訓練圖像,從每個圖像檢測人臉
#並將返回兩個完全相同大小的列表,一個列表 
# 每張臉的臉部和另一列標簽
def prepare_training_data(data_folder_path):
    
    #------STEP-1--------
    #獲取數據文件夾中的目錄(每個主題的一個目錄)
    dirs = os.listdir(data_folder_path)
    
    #列表來保存所有主題的面孔
    faces = []
    #列表以保存所有主題的標簽
    labels = []
    
    #讓我們瀏覽每個目錄並閱讀其中的圖像
    for dir_name in dirs:
        
        #我們的主題目錄以字母's'開頭
        #如果有的話,忽略任何不相關的目錄
        if not dir_name.startswith("s"):
            continue;
            
        #------STEP-2--------
        #從dir_name中提取主題的標簽號
        #目錄名稱格式= slabel
        #,所以從dir_name中刪除字母''會給我們標簽
        label = int(dir_name.replace("s", ""))
        
        #建立包含當前主題主題圖像的目錄路徑
        #sample subject_dir_path = "training-data/s1"
        subject_dir_path = data_folder_path + "/" + dir_name
        
        #獲取給定主題目錄內的圖像名稱
        subject_images_names = os.listdir(subject_dir_path)
        
        #------STEP-3--------
        #瀏覽每個圖片的名稱,閱讀圖片,
        #檢測臉部並將臉部添加到臉部列表
        for image_name in subject_images_names:
            
            #忽略.DS_Store之類的系統文件
            if image_name.startswith("."):
                continue;
            
            #建立圖像路徑
            #sample image path = training-data/s1/1.pgm
            image_path = subject_dir_path + "/" + image_name

            #閱讀圖像
            image = cv2.imread(image_path)
            
            #顯示圖像窗口以顯示圖像
            cv2.imshow("Training on image...", image)
            cv2.waitKey(100)
            
            #偵測臉部
            face, rect = detect_face(image)
            
            #------STEP-4--------
            #為了本教程的目的
            #我們將忽略未檢測到的臉部
            if face is not None:
                #將臉添加到臉部列表
                faces.append(face)
                #為這張臉添加標簽
                labels.append(label)
            
    cv2.destroyAllWindows()
    cv2.waitKey(1)
    cv2.destroyAllWindows()
    
    return faces, labels

我已經定義了一個函數,它將存儲培訓主題文件夾的路徑作為參數。 該功能遵循上述的4個准備數據子步驟。

(step--1)在第8行,我使用os.listdir方法讀取存儲在傳遞給函數的路徑上的所有文件夾的名稱作為參數。 在第10-13行,我定義了標簽並面向矢量。

(step--2)之后,我遍歷所有主題的文件夾名稱以及第27行中每個主題的文件夾名稱,我將提取標簽信息。 由於文件夾名稱遵循sLabel命名約定,所以從文件夾名稱中刪除字母將給我們分配給該主題的標簽。

(step--3)在第34行,我讀取了當前被攝體的所有圖像名稱,並且在第39-66行中我逐一瀏覽了這些圖像。 在53-54行,我使用OpenCV的imshow(window_title,image)和OpenCV的waitKey(interval)方法來顯示當前正在傳播的圖像。 waitKey(interval)方法將代碼流暫停給定的時間間隔(毫秒),我以100ms的間隔使用它,以便我們可以查看100ms的圖像窗口。 在第57行,我從當前正在遍歷的圖像中檢測出臉部。

(step--4)在第62-66行,我將檢測到的面和標簽添加到它們各自的向量中。

  但是一個函數只能在需要准備的某些數據上調用它時才能做任何事情,對嗎? 別擔心,我有兩張臉的數據。 我相信你至少會認出其中的一個!

#讓我們先准備好我們的訓練數據
#數據將在兩個相同大小的列表中
#一個列表將包含所有的面孔
#數據將在兩個相同大小的列表中
print("Preparing data...")
faces, labels = prepare_training_data("training-data")
print("Data prepared")

#打印總面和標簽
print("Total faces: ", len(faces))
print("Total labels: ", len(labels))
Preparing data...
Data prepared
Total faces:  23
Total labels:  23

  這可能是最無聊的部分,對吧?別擔心,接下來會有有趣的事情發生。現在是時候訓練我們自己的人臉識別器了,這樣一旦訓練它就能識別它所訓練的人的新面孔了。讀嗎?那我們來訓練我們的人臉識別器。

訓練人臉識別器

我們知道,OpenCV配備了三個人臉識別器。

  • 1、EigenFaces人臉識別器 - cv2.face.createEigenFaceRecognizer()
  • 2、FisherFaces人臉識別器 - cv2.face.createFisherFaceRecognizer()
  • 3、局部二值模式直方圖(LBPH)人臉識別器 - cv2.face.createLBPHFaceRecognizer()

  我將使用LBPH人臉識別器,但您可以使用您選擇的任何人臉識別器。 無論您使用哪個OpenCV的臉部識別器,其代碼都將保持不變。 您只需更改一行,即下面給出的面部識別器初始化行。

#創建我們的LBPH人臉識別器
face_recognizer = cv2.face.createLBPHFaceRecognizer()

#或者使用EigenFaceRecognizer替換上面的行
#face_recognizer = cv2.face.createEigenFaceRecognizer()

#或者使用FisherFaceRecognizer替換上面的行
#face_recognizer = cv2.face.createFisherFaceRecognizer()

 現在我們已經初始化了我們的人臉識別器,也准備了我們的訓練數據,現在是時候訓練人臉識別器了。我們將通過調用人臉識別器的序列(面向量,標簽向量)方法來實現這一點。

#訓練我們的面部識別器
face_recognizer.train(faces, np.array(labels))

你有沒有注意到,不是直接將標簽矢量直接傳遞給人臉識別器,而是先把它轉換成numpy數組?這是因為OpenCV希望標簽向量是一個numpy數組。

  仍然不滿意? 想看到一些行動? 下一步是真正的行動,我保證!

預測

  現在是我最喜歡的部分,預測部分。 這是我們真正了解我們的算法是否確實能夠識別受過訓練的對象臉部的地方。 我們將拍攝兩張我們的景點的測試圖像,從他們每個人身上檢測臉部,然后將這些臉部傳遞給我們訓練有素的臉部識別器,看看它們是否識別它們。

  下面是一些實用功能,我們將用它來繪制圍繞臉部的邊界框(矩形)並將邊界名稱放在邊界框附近。

#函數在圖像上繪制矩形
#根據給定的(x,y)坐標和
#給定的寬度和高度
def draw_rectangle(img, rect):
    (x, y, w, h) = rect
    cv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 2)
    
#函數在從圖像開始繪制文本
#通過(x,y)坐標。
def draw_text(img, text, x, y):
    cv2.putText(img, text, (x, y), cv2.FONT_HERSHEY_PLAIN, 1.5, (0, 255, 0), 2)

第一個函數draw_rectangle根據傳入的矩形坐標在圖像上繪制一個矩形。 它使用OpenCV的內置函數cv2.rectangle(img,topLeftPoint,bottomRightPoint,rgbColor,lineWidth)繪制矩形。 我們將使用它在測試圖像中檢測到的臉部周圍畫一個矩形。

  第二個函數draw_text使用OpenCV的內置函數cv2.putText(img,text,startPoint,font,fontSize,rgbColor,lineWidth)在圖像上繪制文本。

  既然我們有繪圖功能,我們只需要調用人臉識別器預測(人臉)方法來測試我們的測試圖像上的人臉識別器。 以下功能為我們做了預測。

#this function recognizes the person in image passed
#and draws a rectangle around detected face with name of the 
#學科
def predict(test_img):
    #制作圖像的副本,因為我們不想更改原始圖像
    img = test_img.copy()
    #從圖像中檢測臉部
    face, rect = detect_face(img)

    #使用我們的臉部識別器預測圖像
    label= face_recognizer.predict(face)
    #獲取由人臉識別器返回的相應標簽的名稱
    label_text = subjects[label]
    
    #在檢測到的臉部周圍畫一個矩形
    draw_rectangle(img, rect)
    #畫預計人的名字
    draw_text(img, label_text, rect[0], rect[1]-5)
    
    return img

第6行讀取測試圖像
第7行從測試圖像中檢測臉部
第11行通過調用面部識別器的預測(面部)方法來識別面部。 該方法將返回一個標簽
第12行獲取與標簽關聯的名稱
第16行在檢測到的臉部周圍繪制矩形
第18行繪制預測主體在面部矩形上方的名稱

現在我們已經很好地定義了預測函數,下一步就是在我們的測試圖像上實際調用這個函數,並顯示這些測試圖像以查看我們的人臉識別器是否能正確識別它們。 所以讓我們來做。 這就是我們一直在等待的。

print("Predicting images...")

#加載測試圖像
test_img1 = cv2.imread("test-data/test1.jpg")
test_img2 = cv2.imread("test-data/test2.jpg")

#執行預測
predicted_img1 = predict(test_img1)
predicted_img2 = predict(test_img2)
print("Prediction complete")

#顯示兩個圖像
cv2.imshow(subjects[1], predicted_img1)
cv2.imshow(subjects[2], predicted_img2)
cv2.waitKey(0)
cv2.destroyAllWindows()

Predicting images...
Prediction complete

結語

  你可以從這個Github下載完整的代碼和相關文件 打開GitHub.

  人臉識別是一個非常有趣的想法,OpenCV使得它非常簡單,易於我們對其進行編碼。 只需幾行代碼即可完成全面工作的人臉識別應用程序,並且我們可以通過一行代碼更改在所有三個人臉識別器之間切換。 就這么簡單。

  盡管EigenFaces,FisherFaces和LBPH人臉識別器都不錯,但是使用面向梯度直方圖(HOG)和神經網絡進行人臉識別還有更好的方法。 因此,更先進的人臉識別算法現在是一個使用OpenCV和機器學習相結合。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM