在计算机视觉领域,检测人脸等是一种很常见且非常重要的应用,我们可以先通过开放计算机视觉库OpenCV来熟悉这个人脸识别领域。另外OpenCV关于颜色的识别,可以查阅:OpenCV的HSV颜色空间在无人车中颜色识别的应用HSV颜色识别的跟踪实践https://blog.csdn.net/weixin_41896770/article/details/131746841
1、多尺度检测人脸
我们先直接对一张图片中的多个人脸进行检测,看下OpenCV自带的这个级联分类器HAAR对于人脸识别的效果怎么样:
import cv2
import numpy as np
img = cv2.imread('c.png') # (H,W,C)
imgGray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# 使用预训练模型创建 Cascade 分类器
getCascade = lambda model_name: cv2.CascadeClassifier(cv2.data.haarcascades + model_name)
# 人脸
Cascade = getCascade("haarcascade_profileface.xml")
#Cascade = getCascade("haarcascade_frontalface_alt2.xml")
# 多尺度识别人脸
faces = Cascade.detectMultiScale(imgGray,1.2,3)
# 矩形标注(左上角与右下角坐标)
for (x,y,w,h) in faces:
cv2.rectangle(img, (x,y), (x+w,y+h), (0,0,255) , 2)
cv2.imshow("face", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
如图:
虽然位置有偏差,往右移动了一些,边界框没有在中心位置,但是对目标的大概位置还是能够检测到,也就是对于脸部这个对象还是可以正确的被识别到。
其中这个haarcascade_profileface.xml文件是OpenCV自带的人脸分类器,在Windows中的位置如下(我这里是在虚拟环境pygpu中安装的OpenCV视觉库):
envs\pygpu\Lib\site-packages\cv2\data
我们将会在这个目录里面看到,还包含有其他很多的预训练模型,如图:
2、haarcascade分类器
我们知道OpenCV自带的haarcascade分类器还是挺多的,这里的cascade翻译为级联,什么意思呢?
我个人的理解是,这里的提取特征方法还是用到卷积,因为卷积可以检测到边缘,质地纹理等,而一张图里面有很多很多的特征,这个时候我们可以将它们各种尺度缩放来分别提取不同特征并分组,这样一层一层的过滤,当需要检测需要的对象时,只需将不符合的直接丢弃,减少计算,这样就可以加速得到特征。不清楚这种表达是否正确,欢迎指正。
这里的haarcascade分两部分理解,haar先提取特征,然后使用cascade来对特征进行分类。所以haarcascade_profileface.xml这个文件的意思就是提取特征之后,加载人脸分类的一个预训练模型。下划线后面跟随的profileface名称也可以知道,需要进行的分类是人脸。
接下来我们换一个对象,检测猫脸和猫的眼睛,只需要更换对应的模型即可:
2.1、猫脸
我更换为一张包含多只猫的图片,然后加载这个猫脸的预训练模型:
Cascade = getCascade("haarcascade_frontalcatface.xml")
如图:
从检测的图片中,我们可以看到第一只猫没有检测到,其余4只都很好的检测到并做了标注。
2.2、检测眼睛
除了检测脸部之外,还可以检测眼睛,同样的我们更换为眼睛分类模型:
Cascade = getCascade("haarcascade_eye.xml")
如图:
从检测图片中可以看到,除了中间的那只猫,其余的都很好的检测到了眼睛。
3、detectMultiScale
分类器创建好了之后,我们还可以做多尺度检测,先来认识下这个detectMultiScale函数:
help(detectMultiScale)
detectMultiScale(image[, scaleFactor[, minNeighbors[, flags[, minSize[, maxSize]]]]]) -> objects
参数说明:
image:CV_8U类型的矩阵,也就是8位无符号整数[0,255],其余还有16位、32位等有符号整数与浮点数,其中的字母S表示有符号整型,U表示无符号整型,F表示浮点型
scaleFactor:搜索窗口前后大小的比例系数,默认为1.1,也就是每次搜索窗口扩大10%
minNeighbors:指定每个候选矩形应该有多少个邻居的参数
minSize:检测的最小尺寸,小于该值的对象将被忽略
maxSize:检测的最大尺寸,大于该值的对象将被忽略。如果maxSize == minSize模型在单个尺度上进行评估。
对于这种多尺度的检测,还可以在一张图中检测出不同对象并标注,也就是说可以做嵌套:
faces1 = Cascade1.detectMultiScale(imgGray,1.3,2)
faces2 = Cascade2.detectMultiScale(imgGray,1.5,3)
for (x,y,w,h) in faces1:
cv2.rectangle(img, (x,y), (x+w,y+h), (0,0,255) , 2)
for (x,y,w,h) in faces2:
cv2.rectangle(img, (x,y), (x+w,y+h), (0,255,255) , 2)
如下图,就将猫脸和眼睛都检测出来了:
4、摄像头检测
既然对于图片能够识别其中想要检测的对象,那在视频中应该也是没有问题的,我们来看下摄像头检测的效果,由于本人电脑没有摄像头,还是使用无人车上的CSI摄像头来测试下:
测试环境:JupyterLab
from jetbotmini import Camera
from jetbotmini import bgr8_to_jpeg
import traitlets
import ipywidgets.widgets as widgets
from IPython.display import display
import cv2
camera = Camera.instance(width=720, height=720)
face_image = widgets.Image(format='jpeg', width=300, height=300)
face = widgets.Image(format='jpeg', width=300, height=300)
display(face_image)
display(face)
face_cascade = cv2.CascadeClassifier('haarcascade_profileface.xml')
初始化摄像头与图片显示组件之后,紧接着就是实时地将摄像头接收的数据反馈到Image组件,并检测人脸以及将人脸特写,给显示出来。
while 1:
frame = camera.value
frame = cv2.resize(frame, (300, 300))
frame_face =frame.copy()
gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray)
if len(faces)>0:
(face_x, face_y, face_w, face_h) = faces[0]
# 将检测到的人脸标记出来
cv2.rectangle(frame,(face_x,face_y),(face_x+face_h,face_y+face_w),(0,255,0),2)
#cv2.rectangle(frame,(face_x+10,face_y),(face_x+face_w-10,face_y+face_h+20),(0,255,0),2)
frame_face = frame_face[face_y:face_y+face_h,face_x:face_x+face_w]
frame_face = cv2.resize(frame_face,(300,300))
face.value = bgr8_to_jpeg(frame_face)
# 实时传回图像数据进行显示
face_image.value = bgr8_to_jpeg(frame)
如图:
这里还多出一个显示脸部特写的组件,这里没有截图了,比较简单,用法是一样的,将识别到的脸部显示出来即可。
5、错误处理
如果在前面不使用匿名函数:
getCascade = lambda model_name: cv2.CascadeClassifier(cv2.data.haarcascades + model_name)
Cascade = getCascade("haarcascade_profileface.xml")
处理的话,而使用类似后面摄像头中的写法:
cv2.CascadeClassifier('haarcascade_profileface.xml')
如果报下面的错误:
error: OpenCV(4.6.0) D:\a\opencv-python\opencv-python\opencv\modules\objdetect\src\cascadedetect.cpp:1689: error: (-215:Assertion failed) !empty() in function 'cv::CascadeClassifier::detectMultiScale'
就是缺少这个文件,需要将haarcascade_profileface.xml模型文件拷贝到当前目录即可。
6、小结
在做图片显示的时候,有两种方式,可以是OpenCV自带的imshow方法:
cv2.imshow("face", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
这种显示很简单直观,直接显示cv2.imread读取到的数据即可,另外需要注意的时,显示方法的后面需要waitkey,不然会出现程序不响应。
另外一种方法是在JupyterLab里面显示的情况,比如后面介绍的在摄像头里面的显示,这里需要注意图片的转换:
face_image = widgets.Image(format='jpeg', width=300, height=300)
display(face_image)
face_image.value = bytes(cv2.imencode('.jpg', img)[1])
这里的widgets.Image组件格式是jpeg格式,所以需要进行编码成jpeg格式之后,再转换成二进制的字节序列赋值给这个图片组件即可。
其中的字节函数bytes里面的取值范围是[0,255],比如
bytes([0,97,98,99,255]) # b'\x00abc\xff'
如果不在这个范围就会报错:
bytes([0,97,98,99,255,256])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: bytes must be in range(0, 256)