关于人脸识别的心得(一)人脸采集
项目介绍
本次项目是采用keras深度学习框架,训练人脸识别模型,采用opencv实时检测与识别周围的人脸,本文主要讲解如何采集训练集,本次代码在人脸识别的代码上添加了将识别的照片依次写入指定文件夹
具体代码
# -*- coding:utf-8 -*-
import cv2
def CatchPicture(window_name, catch_pic_num, path_name):
cv2.namedWindow(window_name)
cap = cv2.VideoCapture(0)
cascade_path = "haarcascade_frontalface_default.xml"
num = 0
while True:
ok, frame = cap.read()
if not ok:
break
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
cascade = cv2.CascadeClassifier(cascade_path)
#识别出人脸数量
facerect = cascade.detectMultiScale(frame_gray, scaleFactor=1.2, minNeighbors=3, minSize=(12, 12))
if len(facerect) > 0:
print('face detected')
color = (255, 255, 255) # 白
for rect in facerect:
x, y, w, h = rect
img_name = '%s/%d.jpg' % (path_name, num)
image = frame[y - 10: y + h + 10, x - 10: x + w + 10]
cv2.imwrite(img_name, image) # 照片写入到文件
num += 1
if num > (catch_pic_num): # 如果超过指定最大保存数量退出循环
break
# 画出矩形框
cv2.rectangle(frame, (x - 10, y - 10), (x + w + 10, y + h + 10), color, 2)
# 显示当前捕捉到了多少人脸图片了
font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(frame, 'num:%d' % (num), (x + 30, y + 30), font, 1, (0, 255, 0), 2)
# 超过指定最大保存数量结束程序
if num > (catch_pic_num): break
# 显示图像
cv2.imshow(window_name, frame)
c = cv2.waitKey(15)
if c & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
if __name__ == '__main__':
CatchPicture("截取人脸",500,'./data/my')
#文件夹的名字可以改
其他人脸采集
运行完后,500张照片存到同路径的“/data/my”文件夹,然后再采集一组存到“data/其他文件夹”,同样采集500张作为训练数据,以提高模型的识别精度接下来的训练程序将以文件夹作为标签数据区分个人。。
#OpenCV的级联分类器类CascadeClassifier和Haar特征原理
对OpenCV中Haar特征CvHaarClassifierCascade等结构理解
首先说一下这个级联分类器,OpenCV中级联分类器是根据VJ 04年的那篇论文(Robust Real-Time Face Detection)编写的,查看那篇论文,知道构建分类器的步骤如下:
1、根据haar-like特征训练多个弱分类器
2、使用adaboost算法将多个弱分类器组合成一个强分类器
3、最终的分类器是由多个强分类器级联而成
下面这幅图是弱分类器组合成强分类器的示意图(图片来源于网络):
下面这张是多个强分类器级联的示意图(图片来源于网络):
在了解了级联分类器是怎么一回事后,我们来看一看OpenCV里面级联分类器的结构
在调用OpenCV中的级联分类器对目标进行分类时,都会将一个训练好的分类器(一个训练好的.xml文件)读入到一个CvHaarClassifierCascade结构中,如下:
1 CvHaarClassifierCascade* cascade = (CvHaarClassifierCascade*)cvLoad( “haarcascade_frontalface_alt.xml”, 0, 0, 0 );
那么这个CvHaarClassifierCascade结构体里面的内容都有哪些呢?
1 typedef struct CvHaarClassifierCascade
2 {
3 int flags; /* 标志位 */
4 int count; /* 分级分类器中强分类器的数量 */
5 CvSize orig_window_size; /* 训练中原始目标的大小 */
6
7 /* these two parameters are set by cvSetImagesForHaarClassifierCascade */
8 CvSize real_window_size; /* 待检测物体的大小 */
9 double scale; /* Haar块缩放的尺寸 */
10
11 CvHaarStageClassifier* stage_classifier; /* 定义强分类器数组 */
12 CvHidHaarClassifierCascade* hid_cascade;
13 }CvHaarClassifierCascade;
第一个flags,在debug模式下,flags=1112539136
第二个count,表示整个分级分类器中强分类器的数量,即最后参与级联的强分类器的个数
第三个orig_window_size,表示的是在训练时用的正样本的尺寸,OpenCV中的尺寸是20x20
第四个和第五个,注释中说了,这两个参数需要自己设置,具体每个参数看注释
第六个stage_classifier,是强分类器指针,指向一个强分类器数组,之前的count是多少,那么此处的强分类器就有多少
最后一个hid_cascade,还不是很清楚
下面来看上面第六个参数的强分类器结构体
1 typedef struct CvHaarStageClassifier
2 {
3 int count; /* number of classifiers in the battery 构成强分类器的弱分类器的数量*/
4 float threshold; /* threshold for the boosted classifier 叠加分类器的阈值*/
5 CvHaarClassifier* classifier; /* array of classifiers 定义分类器数组*/
6 /* these fields are used for organizing trees of stage classifiers,
7 rather than just stright cascades */
8 int next;
9 int child;
10 int parent;
11 }CvHaarStageClassifier;
第一个count,表示该强分类器中,弱分类器的数量,即该强分类器由多少个弱分类器组成
第二个threshold,叠加分类器的阈值
第三个classifier,是一个指针,指向的是一个弱分类器数组,之前的count是多少,此处的弱分类器就有多少
后面3个都不清楚。。。
下面是弱分类器的结构
1 typedef struct CvHaarClassifier
2 {
3 int count; /* number of nodes in the decision tree */
4 /* these are "parallel" arrays. Every index i corresponds to a node of the decision tree (root has 0-th index).
5 left[i] - index of the left child (or negated index if the left child is a leaf)
6 right[i] - index of the right child (or negated index if the right child is a leaf)
7 threshold[i] - branch threshold. if feature responce is <= threshold, left branch is chosen, otherwise right branch is chosed.
8 alpha[i] - output value correponding to the leaf. */
9
10 CvHaarFeature* haar_feature;
11 float* threshold;
12 int* left;
13 int* right;
14 float* alpha;
15 }CvHaarClassifier;
第一个count,在opencv里,发现始终都是1,自己想了想,因为这个结构体记录的是一个弱分类器,自然弱分类器的个数就是1了。
第二个haar_feature,也是一个指针,指向一个(因为count是1)特征结构体CvHaarFeature,这个结构体中记录的内容是弱分类器的类型(包括该haar-like特征的位置,大小,以及种类,这个结构体会在下面给出,再细说)
第三个threshold,就是那个判别函数:h(x,f,p,theta) = (pf(x) < ptheta ? 1 : 0),中的阈值theta
第四个left,第五个right不是很清楚,第六个alpha,就是这个弱分类器的权重(每一个强分类器都是由多个弱分类器按照各自的权重进行表决,而得到的)
特征的结构体如下
1 #define CV_HAAR_FEATURE_MAX 3
2 // 一个Haar特征由2~3个具有相应权重的矩形组成
3 typedef struct CvHaarFeature
4 {
5 int tilted; // 0 means up-right feature, 1 means 45-rotated feature
6 struct
7 {
8 CvRect r;
9 float weight;
10 } rect[CV_HAAR_FEATURE_MAX];
11 // 2-3 rectangles with weights of opposite signs and with absolute values inversely proportional to the areas of the rectangles. if rect[2].weight != 0, then the feature consists of 3 rectangles, otherwise it consists of 2.
12 }CvHaarFeature;
第一个参数titled,0表示该特征是标准的haar-like特征,1表示旋转45°后的特征
标准的haar-like特征如下:
而旋转45°后的特征如下:
第二个参数是个结构体数组,每个结构体中包括一个矩形和一个权重,这个数组的大小是CV_HAAR_FEATURE_MAX(3)(注释中说:此处可能有2~3个矩形,这里的矩形等看了下面的解释就知道了),这个矩形和权重有什么用呢?
在debug模式下,查看第一个弱分类器数组内的元素
第一个元素:
1 rect[0].r.x = 3
2 rect[0].r.y = 7
3 rect[0].r.width = 14
4 rect[0].r.height = 4
5 rect[0].weight = -1
第二个元素:
1 rect[1].r.x = 3
2 rect[1].r.y = 9
3 rect[1].r.width = 14
4 rect[1].r.height = 2
5 rect[1].weight = 2
第三个元素则全都是0
这么看这些坐标,并不是很清楚,我们可以画个图:
由图可见,第一个矩形表示的是A+B区域,第二个矩形表示的是B区域。
此时再看一看每个元素的权重weight,结合积分图的概念,发现第一个矩形的积分图乘以其权重加上第二个积分图乘以其权重,恰好得到下述结果:
(A+B)(-1)+B2=B-A
看到这个公式,这正是VJ论文中给出的众多haar-like模板中的其中一个模板的计算方法
我们继续考察第二个弱分类器的特征部分,其特征参数如下:
1 rect[0].r.x = 1, rect[0].r.y = 2
2 rect[0].r.width = 18, rect[0].r.height = 4
3 rect[0].weight = -1
4
5 rect[1].r.x = 7, rect[1].r.y = 2
6 rect[1].r.width = 6, rect[1].r.height = 4
7 rect[1].weight = 3
数组的第三个元素依然都是是0,对其绘图:
第一块矩形区域是A+B+C,第二块矩形区域是B,积分图乘以权重,再相加,可得:
(A+B+C)(-1)+B3 = 2*B-A-C
也是haar-like特征模板之一(此处不知如何表达,大家将就,看懂就行)
刚刚找了好久,找到一个第三个元素权重不为0的,该数组三个元素如下:
1 rect[0].r.x = 0, rect[0].r.y = 2
2 rect[0].r.width = 20, rect[0].r.height = 6;
3 rect[0].weight = -1
4
5 rect[1].r.x = 10, rect[1].r.y = 2
6 rect[1].r.width = 10, rect[1].r.height = 3;
7 rect[1].weight = 2
8
9 rect[2].r.x = 0, rect[2].r.y = 5
10 rect[2].r.width = 10, rect[2].r.height = 3;
11 rect[2].weight = 2
绘图可得:
将每个矩形乘以相应的权重,相加可得:
(A+B+C+D)(-1)+2B + 2*C = B+C-(A+D)
也是模板之一
(佩服设计这个结构体的程序员)