樹莓派4學習記錄(5)-opencv人臉檢測/識別(踩坑史....)

0. 安裝opencv

當然之前已經安裝好了opencv,詳情見我的上一篇文章:
樹莓派4學習記錄(4)-攝像頭

1. opencv人臉檢測

先上代碼:

# coding:utf-8

# 結論:直接使用opnecv可以實時監測人臉:
# imread: 0.0065610408782958984
# gray: 0.0018565654754638672
# classifier: 0.10116839408874512
# detect: 0.05451011657714844
# draw: 0.0002307891845703125
# write: 0.00553131103515625
# all: 0.1702742576599121

import cv2   
import time 

# 準備圖片,並使用cv2讀取
impath_1 = "new.jpg"
impath_2 = "new_2.jpg"
allstart = time.time()
image_1=cv2.imread(impath_1)
image_2=cv2.imread(impath_2)
print("imread:", time.time()- allstart)

# opencv進行人臉識別是基於灰度圖,所以需要先轉換爲灰度圖
start = time.time()
gray_1=cv2.cvtColor(image_1,cv2.COLOR_BGR2GRAY)
gray_2=cv2.cvtColor(image_2,cv2.COLOR_BGR2GRAY)
print("gray:", time.time()-start)

# 加載檢測器
start = time.time()
face_cade=cv2.CascadeClassifier(r'./haarcascade_frontalface_default.xml')
print("classifier:", time.time()-start)

# 開始檢測人臉
start = time.time()
fa_1=face_cade.detectMultiScale(gray_1,scaleFactor=1.15,minNeighbors=5)
fa_2=face_cade.detectMultiScale(gray_2,scaleFactor=1.15,minNeighbors=5)
print("detect:", time.time()-start)

# 在原始圖片中繪製人臉框
start = time.time()
for (x,y,w,h) in fa_1:
    cv2.rectangle(image_1,(x,y),(x+w,y+h),(0,255.0),2)
for (x,y,w,h) in fa_2:
    cv2.rectangle(image_2,(x,y),(x+w,y+h),(0,255.0),2)
print("draw:", time.time()-start)

# 回寫
start = time.time()
cv2.imwrite('cv_final.jpg',image_1)
cv2.imwrite('cv_final_2.jpg',image_2)
print("write:", time.time()-start)
print("all:", time.time()-allstart)

很明顯發現我這裏有很多測試,主要是用來檢測每個操作的耗時,驗證opencv是否能夠作爲實時人臉檢測的方法。
結論:可以實時監測。
注意
這裏我用到了一個opencv給出的人臉檢測器,所以需要去opencv的repo中下載下來,放到當前目錄中:
haarcascade_frontalface_default.xml
而這個檢測器是針對正臉的,所以如果有其他需求,實際上是可以換的。
效果圖
在這裏插入圖片描述

2. opencv+udp人臉實時檢測

在已經驗證使用opencv可以實時監測人臉後,我決定將其和UDP結合:

  1. 使用opencv獲取視頻流
  2. 使用opencv的人臉檢測得到人臉框
  3. 將人臉框回寫到數據流中
  4. 使用UDP傳輸到本地
  5. 本地顯示帶有人臉框的視頻流

先上代碼
server.py:

# coding: utf-8

import cv2
import numpy
import socket
import struct

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(("192.168.1.6", 6000))
print("UDP bound on port 6000...")

print('now starting to send frames...')
capture=cv2.VideoCapture(0)
data, addr = s.recvfrom(1024)
# 設置分辨率
capture.set(3, 256)
capture.set(4, 256)

face_cade=cv2.CascadeClassifier(r'./haarcascade_frontalface_default.xml')

while True:
	success,frame=capture.read()
	# print(success)
	while not success and frame is None:
		success,frame=capture.read() #獲取視頻幀
	# the type of variable "frame" is : <class 'numpy.ndarray'>
	
	# 檢測並繪製
	gray=cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
	fa=face_cade.detectMultiScale(gray,scaleFactor=1.15,minNeighbors=5)
	for (x,y,w,h) in fa:
		cv2.rectangle(frame,(x,y),(x+w,y+h),(0,255.0),2)
	
	result,imgencode=cv2.imencode('.jpg',frame,[cv2.IMWRITE_JPEG_QUALITY,50])
	s.sendto(struct.pack('i',imgencode.shape[0]), addr)
	s.sendto(imgencode, addr)
	# print('have sent one frame')

s.close()

client.py:

# coding: utf-8

import cv2
import numpy
import socket
import struct

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
addr = ("192.168.1.6", 6000)

# 建立連接
data = 'hello'
s.sendto(data.encode(), addr)

print('now waiting for frames...')
while True:
	data, addr = s.recvfrom(65535)
	if len(data)==1 and data[0]==1: #如果收到關閉消息則停止程序
		s.close()
		cv2.destroyAllWindows()
		exit()
	if len(data)!=4: #進行簡單的校驗,長度值是int類型,佔四個字節
		length=0
	else:
		length=struct.unpack('i',data)[0] #長度值
	data,address=s.recvfrom(65535)
	if length!=len(data): #進行簡單的校驗
		continue
	data=numpy.array(bytearray(data)) #格式轉換
	imgdecode=cv2.imdecode(data,1) #解碼
	# print('have received one frame')
	cv2.imshow('frames', imgdecode) #窗口顯示
	if cv2.waitKey(1)==27: #按下“ESC”退出
		break

s.close()
cv2.destroyAllWindows()

可以發現,相對於之前的UDP+opencv實時視頻流傳輸並沒有增加多少的代碼量,只是添加了一個讀取檢測器並識別的部分,相對來說還是很簡單的。

3. opencv人臉識別

3.1 正文

當然並不會滿足於只是人臉檢測了,所以直接開始搗鼓人臉識別。
依舊還是使用opencv內置的人臉識別方法,不過還是需要訓練的。
訓練比較簡單
可以參考這個
基於Opencv快速實現人臉識別(完整版)
基本的結構與作者的一致,不過訓練集和測試集還是修改了一下,改成了
[zhoujielun, chenyixun]
應該知道是誰吧 (笑
上代碼

# coding: utf-8 

import os
import numpy as np
import cv2

# 檢測人臉
def detect_face(img):
    #將測試圖像轉換爲灰度圖像,因爲opencv人臉檢測器需要灰度圖像
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
 
    #加載OpenCV人臉檢測分類器Haar
    face_cascade = cv2.CascadeClassifier('./haarcascade_frontalface_default.xml')
 
    #檢測多尺度圖像,返回值是一張臉部區域信息的列表(x,y,寬,高)
    faces = face_cascade.detectMultiScale(gray, scaleFactor=1.2, minNeighbors=5)
 
    # 如果未檢測到面部,則返回原始圖像
    if (len(faces) == 0):
        return None, None
 
    #目前假設只有一張臉,xy爲左上角座標,wh爲矩形的寬高
    (x, y, w, h) = faces[0]
 
    #返回圖像的正面部分
    return gray[y:y + w, x:x + h], faces[0]
 
 
# 該函數將讀取所有的訓練圖像,從每個圖像檢測人臉並將返回兩個相同大小的列表,分別爲臉部信息和標籤
def prepare_training_data(data_folder_path):
    # 獲取數據文件夾中的目錄(每個主題的一個目錄)
    dirs = os.listdir(data_folder_path)
 
    # 兩個列表分別保存所有的臉部和標籤
    faces = []
    labels = []
 
    # 瀏覽每個目錄並訪問其中的圖像
    for dir_name in dirs:
        # dir_name(str類型)即標籤
        label = int(dir_name)
        # 建立包含當前主題主題圖像的目錄路徑
        subject_dir_path = data_folder_path + "/" + dir_name
        # 獲取給定主題目錄內的圖像名稱
        subject_images_names = os.listdir(subject_dir_path)
 
        # 瀏覽每張圖片並檢測臉部,然後將臉部信息添加到臉部列表faces[]
        for image_name in subject_images_names:
            # 建立圖像路徑
            image_path = subject_dir_path + "/" + image_name
            # 讀取圖像
            image = cv2.imread(image_path)
            # 顯示圖像0.1s
            print("preparing the training images....")
            # cv2.imshow("Training on image...", image)
            # cv2.waitKey(100)
 
            # 檢測臉部
            face, rect = detect_face(image)
            # 我們忽略未檢測到的臉部
            if face is not None:
                #將臉添加到臉部列表並添加相應的標籤
                faces.append(face)
                labels.append(label)
 
    cv2.waitKey(1)
    cv2.destroyAllWindows()
    #最終返回值爲人臉和標籤列表
    return faces, labels
 
#調用prepare_training_data()函數
faces, labels = prepare_training_data("training_data")
 
#創建LBPH識別器並開始訓練,當然也可以選擇Eigen或者Fisher識別器
face_recognizer = cv2.face.createLBPHFaceRecognizer()
face_recognizer.train(faces, np.array(labels))
 
#根據給定的(x,y)座標和寬度高度在圖像上繪製矩形
def draw_rectangle(img, rect):
    (x, y, w, h) = rect
    cv2.rectangle(img, (x, y), (x + w, y + h), (128, 128, 0), 2)
# 根據給定的(x,y)座標標識出人名
def draw_text(img, text, x, y):
    cv2.putText(img, text, (x, y), cv2.FONT_HERSHEY_COMPLEX, 1, (128, 128, 0), 2)
 
#建立標籤與人名的映射列表(標籤只能爲整數)
subjects = ["zhoujielun", "chenyixun"]
 
# 此函數識別傳遞的圖像中的人物並在檢測到的臉部周圍繪製一個矩形及其名稱
def predict(test_img):
    #生成圖像的副本,這樣就能保留原始圖像
    img = test_img.copy()
    #檢測人臉
    face, rect = detect_face(img)
    #預測人臉
    label = face_recognizer.predict(face)
    # 獲取由人臉識別器返回的相應標籤的名稱
    label_text = subjects[label[0]]
 
    # 在檢測到的臉部周圍畫一個矩形
    draw_rectangle(img, rect)
    # 標出預測的名字
    draw_text(img, label_text, rect[0], rect[1] - 5)
    #返回預測的圖像
    return img
 
#加載測試圖像
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)
 
#顯示兩個圖像
cv2.imshow(subjects[0], predicted_img1)
cv2.imshow(subjects[1], predicted_img2)
cv2.waitKey(0)
cv2.destroyAllWindows()

具體的每一部分實現了什麼功能,都顯示在代碼註釋中。
效果
應
還可以吼…

3.2 踩坑(迴應題目)

3.2.1 關於API

可以很明顯發現,這裏人臉識別,先用到了人臉檢測,而之前的人臉檢測並沒有什麼大問題,所以很明顯問題出現在人臉識別過程中。
人臉識別最主要的兩個部分:

  1. 人臉數據集訓練
  2. 人臉預測

問題出現在了第一部分:
face_recognizer = cv2.face.createLBPHFaceRecognizer()
剛開始代碼使用的並不是這個API,而是:
LBPHFaceRecognizer_create()
肯定會理所當然的報錯了,因爲我使用的python環境爲3.7,所以要使用代碼中給出的API,因爲換名了…
(我就想說,換個蛇皮的名啊,代碼一致性啊,一致性啊,別換來換去的)

3.2.2 關於安裝依賴包

如果要運行起來還需要安裝一個依賴包:
opencv-contrib-python

pip3 install opencv-contrib-python

安裝好之後才能夠不出錯,如果出現其他錯誤怎麼辦?
這個我也不知道,因爲我之前已經將其依賴庫安裝的差不多了,所以最好安裝好所有的依賴庫,什麼apt什麼pip儘管安裝,準沒錯。
還有要注意版本一致性,我直接使用的默認版本,如果有特殊要求,最好指定版本。

3.2.3 解決出錯
ImportError: /home/pi/cv2/cv2.cpython-37m-arm-linux-gnueabihf.so: undefined symbol: __atomic_fetch_add_8

參考這個:
樹莓派上安裝Opencv遇到的小bug解決方法
其實就是在配置文件中手動添加一個庫。

/usr/lib/arm-linux-gnueabihf/libatomic.so.1

具體的解決辦法參考上面的文檔。

4. opencv+udp人臉實時識別

這一部分相當於是將人臉識別模塊和UDP傳輸模塊結合起來,原理很簡單,但是仍然有一部分需要注意,因爲要保證實時性,所以某些功能並不能大量重複。需要適當調整代碼結構。
server.py:

# coding: utf-8

import cv2
import numpy as np 
import socket
import struct
import os

#加載OpenCV人臉檢測分類器Haar
# 放在這兒減少延遲
# face_cascade = cv2.CascadeClassifier('./haarcascade_frontalface_default.xml')
face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_alt.xml')

# 檢測人臉
def detect_face(img):
    #將測試圖像轉換爲灰度圖像,因爲opencv人臉檢測器需要灰度圖像
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
 
    #檢測多尺度圖像,返回值是一張臉部區域信息的列表(x,y,寬,高)
    faces = face_cascade.detectMultiScale(gray, scaleFactor=1.2, minNeighbors=6)
 
    # 如果未檢測到面部,則返回原始圖像
    if (len(faces) == 0):
        return None, None
 
    #目前假設只有一張臉,xy爲左上角座標,wh爲矩形的寬高
    (x, y, w, h) = faces[0]
 
    #返回圖像的正面部分
    return gray[y:y + w, x:x + h], faces[0]

# 該函數將讀取所有的訓練圖像,從每個圖像檢測人臉並將返回兩個相同大小的列表,分別爲臉部信息和標籤
def prepare_training_data(data_folder_path):
    # 獲取數據文件夾中的目錄(每個主題的一個目錄)
    dirs = os.listdir(data_folder_path)
 
    # 兩個列表分別保存所有的臉部和標籤
    faces = []
    labels = []
 
    # 瀏覽每個目錄並訪問其中的圖像
    for dir_name in dirs:
        # dir_name(str類型)即標籤
        label = int(dir_name)
        # 建立包含當前主題主題圖像的目錄路徑
        subject_dir_path = data_folder_path + "/" + dir_name
        # 獲取給定主題目錄內的圖像名稱
        subject_images_names = os.listdir(subject_dir_path)
 
        # 瀏覽每張圖片並檢測臉部,然後將臉部信息添加到臉部列表faces[]
        for image_name in subject_images_names:
            # 建立圖像路徑
            image_path = subject_dir_path + "/" + image_name
            # 讀取圖像
            image = cv2.imread(image_path)
            # 顯示圖像0.1s
            # print("preparing the training images....")
            # cv2.imshow("Training on image...", image)
            # cv2.waitKey(100)
 
            # 檢測臉部
            face, rect = detect_face(image)
            # 我們忽略未檢測到的臉部
            if face is not None:
                #將臉添加到臉部列表並添加相應的標籤
                faces.append(face)
                labels.append(label)
 
    cv2.waitKey(1)
    cv2.destroyAllWindows()
    #最終返回值爲人臉和標籤列表
    return faces, labels

#根據給定的(x,y)座標和寬度高度在圖像上繪製矩形
def draw_rectangle(img, rect):
    (x, y, w, h) = rect
    cv2.rectangle(img, (x, y), (x + w, y + h), (128, 128, 0), 2)
# 根據給定的(x,y)座標標識出人名
def draw_text(img, text, x, y):
    cv2.putText(img, text, (x, y), cv2.FONT_HERSHEY_COMPLEX, 1, (128, 128, 0), 2)


print("preparing the face model")
#調用prepare_training_data()函數
faces, labels = prepare_training_data("training_data")

#創建LBPH識別器並開始訓練,當然也可以選擇Eigen或者Fisher識別器
face_recognizer = cv2.face.createLBPHFaceRecognizer()
face_recognizer.train(faces, np.array(labels))

#建立標籤與人名的映射列表(標籤只能爲整數)
subjects = ["mama", "yichen", "jiejie", "xiangyu"]

# 此函數識別傳遞的圖像中的人物並在檢測到的臉部周圍繪製一個矩形及其名稱
def predict(img):
    #生成圖像的副本,這樣就能保留原始圖像
    # img = test_img.copy()
    # print(img)
    #檢測人臉
    face, rect = detect_face(img)
    if face is None:
      return img
    #預測人臉
    label = face_recognizer.predict(face)
    print(label)
    # 獲取由人臉識別器返回的相應標籤的名稱
    label_text = subjects[label[0]]
 
    # 在檢測到的臉部周圍畫一個矩形
    draw_rectangle(img, rect)
    # 標出預測的名字
    draw_text(img, label_text, rect[0], rect[1] - 5)
    #返回預測的圖像
    return img

# opencv 創建視頻抓取對象
capture=cv2.VideoCapture(0)
# 設置分辨率
capture.set(3, 256)
capture.set(4, 256)

# 建立套接字
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(("192.168.1.6", 6000))

print("UDP bound on port 6000...")
print("waiting for connection....")

# 等待客戶端請求
data, addr = s.recvfrom(1024)

print('now starting to send frames...')
while True:
	# 以下嘗試抓取視頻幀
	success,frame=capture.read()
	while not success and frame is None:
		success,frame=capture.read() #獲取視頻幀
  
	# 人臉識別入口
	predicted_img = predict(frame)

	# 編碼併發送
	result,imgencode=cv2.imencode('.jpg', predicted_img, [cv2.IMWRITE_JPEG_QUALITY,50])
	s.sendto(struct.pack('i', imgencode.shape[0]), addr)
	s.sendto(imgencode, addr)

s.close()

有點長…不過問題不大,基本上所有功能都在這裏了(簡單的那種)。
效果還可以:
在這裏插入圖片描述
不過在實際運行的時候還是有下面的問題:

  1. 檢測框抖動,可能過於靈敏
  2. 基本完成了識別任務的功能,但是容易誤識別
  3. 無法設置識別閾值(我現在還沒找到)
  4. 識別模型可能過於簡單,容易出錯
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章