树莓派4学习记录(5)
- 0. 安装opencv
- 1. opencv人脸检测
- 2. opencv+udp人脸实时检测
- 3. opencv人脸识别
- 3.1 正文
- 3.2 踩坑(回应题目)
- 3.2.1 关于API
- 3.2.2 关于安装依赖包
- 3.2.3 解决出错
- 4. opencv+udp人脸实时识别
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结合:
- 使用opencv获取视频流
- 使用opencv的人脸检测得到人脸框
- 将人脸框回写到数据流中
- 使用UDP传输到本地
- 本地显示带有人脸框的视频流
先上代码:
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
可以很明显发现,这里人脸识别,先用到了人脸检测,而之前的人脸检测并没有什么大问题,所以很明显问题出现在人脸识别过程中。
人脸识别最主要的两个部分:
- 人脸数据集训练
- 人脸预测
问题出现在了第一部分:
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()
有点长…不过问题不大,基本上所有功能都在这里了(简单的那种)。
效果还可以:
不过在实际运行的时候还是有下面的问题:
- 检测框抖动,可能过于灵敏
- 基本完成了识别任务的功能,但是容易误识别
- 无法设置识别阈值(我现在还没找到)
- 识别模型可能过于简单,容易出错