1. 介绍
在深度学习时代,人脸识别一般是利用卷积神经网络进行监督式学习,也就是通过让算法(神经网络)自己去发现规律的方式,创造出有用的卷积核,然后利用其进行寻找图片和视频中的人脸,而在这之前,人们需要的则是自己去设计算法,寻找人脸。不过后来人们发明了一种近似于深度学习思路的人脸寻找算法,那就是Haar 算法。
这个算法简单点来说,就是计算一个区域内不同像素之间的灰度差别,来判断是不是人脸,原理就是一种有规律的图像,比如一个物体,无论光线明亮与否,其不同区域之间的像素差总是有一定规律的。就是凭借这种特殊的思路,科学家们第一次创造出了一种及其有效的人脸识别算法。而且其运算性能要求真的很低。
OpenCV内部集成了人脸检测算法,并且 OpenCV 提供了训练好的人脸检测Haar模型,使用yml 保存。只需要简单的调用其中的类库就可以对照片或者祝频进行人脸检测。
2. 使用Haar 分类器进行面部及眼睛检测
以Haar 特征分类器为基础的对象检测技术是一种非常有效的对象检测技术。它是基于机器学习的,通过使用大量的正负样本图像训练得到一个 cascade_function,最后再用它来做对象检测。
现在我们来学习面部检测。开始时,算法需要大量的正样本图像(面部图像)和负样本图像(不含面部的图像)来训练分类器。需要从其中提取特征。下图中的Haar 特征会被使用。它们就像我们的卷积核。每一个特征是一个值,这个值等于黑色矩形中的像素值之后减去白色矩形中的像素值之和。
使用所有可能的核来计算足够多的特征。(想象一下这需要多少计算量?仅仅是一个24x24的窗口就有160000个特征)。对于每一个特征的计算我们都需要计算白色和黑色矩形内的像素和。为了解决这个问题,引入了积分图像,这可以大大的简化求和运算,对于任何一个区域的像素和只然要对积分图像上的四个像素操作即可。
但是在我们计算得到的所有的这些特征中,大多数是不相关的。如下图所示。上边一行显示了两个好的特征,第一个特征看上去是对眼部周围区域的描述,因为眼睛总是比鼻子黑一些。第二个特征是描述的是眼晴比鼻梁要黑一些。
但是如果把这两个窗口放到脸颊的话,就一点都不相关。那么我们怎样从超过160000+个特征中选出最好的特征呢?使用 Adaboost。
为了达到这个目的,我们将每一个特征应用于所有的训练图像。对于每一个特征,我们要找到它能够区分出正样本和负样本的最佳阈值。但是很明显,这会产生错误或者错误分类。我们要选取错误率最低的特征,这说明它们是检测面部和非面部图像最好的特征。(这个过程其实不像我们说的这么简单。在开始时每一张图像都具有相同的权重,每一次分类之后,被错分的图像的权重会增大。同样的过程会被再做一遍。然后我们又得到新的错误率和新的权重。重复执行这个过程知道到达要求的准确率或者错误率或者要求数目的特征找到)。
最终的分类器是这些弱分类器的加权和。之所以成为弱分类器是应为只是用这些分类器不足以对图像进行分类,但是与其他的分类器联合起来就是一个很强的分类器了。文章中说200个特征就能够提供 95% 的准确度了。他们最后使用6000个特征。
现在你有一幅图像,对每一个24x24的窗口使用这 6000个特征来做检查,看它是不是面部。这是不是很低效很耗时呢?的确如此,但作者有更好的解决方法。
在一副图像中大多数区域是非面部区域。所以虽好有一个简单的方法来证明这个窗口不是面部区域,如果不是就直接抛弃,不用对它再做处理。而不是集中在研究这个区域是不是面部。按照这种方法我们可以在可能是面部的区域多花点时间。
为了达到这个目的,提出了级联分类器的概念。不是在一开始就对窗口进行这 6000个特征测试,将这些特征分成不同组。在不同的分类阶段逐个使用。(通常前面很少的几个阶段使用较少的特征检测)。如果一个窗口第一阶段的检测都过不了就可以直接放弃后面的测试了,如果它通过了就进入第二阶段的检测。如果一个窗口经过了所有的测试,那么这个窗口就被认为是面部区域。
采用的模型将6000多个特征分为38个阶段,前五个阶段的特征数分别为1,10,25,25和50。(上图中的两个特征其实就是从 Adaboost 获得的最好特征)。
3. 源程序代码
OpenCV自带了训练器和检测器。如果你想自己训练一个分类器来检测汽车,飞机等的话,可以使用OpenCV构建。
现在我们来学习一下如何使用检测器。OpenCV 已经包含了很多已经训练好的分类器,其中包括:面部,眼睛,微笑等。下面我们将使用 OpenCV 创建一个面部和眼部检测器。
首先我们要加载需要的XML分类器。然后以灰度格式加载输入图像或者是视频。
然后,我们在图像中检测面部。如果检测到面部,它会返回面部所在的矩形区域Rect(x,y,w,h)。一旦我们获得这个位置,我们可以创建一个ROI 并在其中进行眼部检测。
python
# 导入必要的库
import numpy as np
import cv2
# 载入HAAR模型
face_cascade = cv2.CascadeClassifier('./images/haarcascade_frontalface_default.xml')
eye_cascade = cv2.CascadeClassifier('./images/haarcascade_eye.xml')
# 线程函数操作库
import threading # 线程
import ctypes
import inspect
# 线程结束代码
def _async_raise(tid, exctype):
tid = ctypes.c_long(tid)
if not inspect.isclass(exctype):
exctype = type(exctype)
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
if res == 0:
raise ValueError("invalid thread id")
elif res != 1:
ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
raise SystemError("PyThreadState_SetAsyncExc failed")
def stop_thread(thread):
_async_raise(thread.ident, SystemExit)
import libcamera
from picamera2 import Picamera2
picamera = Picamera2()
config = picamera.create_preview_configuration(main={"format": 'RGB888', "size": (640, 480)},
raw={"format": "SRGGB12", "size": (1920, 1080)})
config["transform"] = libcamera.Transform(hflip=0, vflip=1)
picamera.configure(config)
picamera.start()
# 创建显示控件
def bgr8_to_jpeg(value, quality=75):
return bytes(cv2.imencode('.jpg', value)[1])
import traitlets
import ipywidgets.widgets as widgets
from IPython.display import display
face_imge = widgets.Image(format='jpeg', width=480, height=320)
display(face_imge)
def Video_display():
while True:
frame = picamera.capture_array()
img = cv2.flip(frame,1)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray, 1.3, 5)
for (x,y,w,h) in faces:
cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)
roi_gray = gray[y:y+h, x:x+w]
roi_color = img[y:y+h, x:x+w]
print(int(x+w/2), int(y+h/2))
eyes = eye_cascade.detectMultiScale(roi_gray)
for (ex,ey,ew,eh) in eyes:
cv2.rectangle(roi_color,(ex,ey),(ex+ew,ey+eh),(0,255,0),2)
face_imge.value = bgr8_to_jpeg(img)
cap.release()
t = threading.Thread(target=Video_display)
t.setDaemon(True)
t.start()
# 结束线程
stop_thread(t)