本文中简单介绍使用opencv的createsamples和traincascade生成自己的分类器以及借用python的opencv库实现人脸图像采集、人脸识别

使用opencv训练自己的级联分类器&python人脸识别

训练级联分类器

  1. 首先需要在自己的电脑或服务器上安装opencv,这里需要用到opencv_createsamplesopencv_traincascade,opencv版本我这里安装的是stable 3.4.14版本,据说是4之后的版本不会内置这两个工具,所以选择的V3。
  2. opencv_haartraining属于旧版本程序,仅支持Haar特征,而opencv_traincascade同时支持Haar和LBP特征,且LBP属于整数特征,速度更快。(当然了,默认的opencv_traincascade仅使用的是多线程来处理的,如果想进一步使用多进程来提高速度,可以使用TBB来重新编译traincascade)。
  3. 至于为什么是整数特征,这里只简单的介绍一下:此模式是通过找到图像的局部特征,通过每个像素与相邻像素比较来实现(他会用一个3*3的窗口在图片上进行移动,每次移动时将周边元素的像素值和中心元素的像素值进行比较,小于等于中心值的记为1,反之则为0,然后在这个窗口内进行顺时针读取,得到0101的二进制数,以此来代表并区分不同的特征)。

我这里使用的brew安装的

  • brew search opencv
  • brew install opencv@3
  • echo ’export PATH="/opt/homebrew/opt/opencv@3/bin:$PATH"’ » ~/.zshrc

至此完成opencv的安装,之后就可以继续后面的工作了。

准备样例

  1. 这里我们需要分别准备正向与反向样例(为了分类器的准确性,条件允许的话可以设置尽可能多的正向样例,以及多倍于正向的反向样例,以提高识别的准确性) 项目结构如下
.
├── neg.txt
├── pos.dat
├── pos.vec
├── to_gray.py
├── to_jpg.py
├── training-files
│   ├── neg
│   ├── neg_gray
│   ├── neg_jpg
│   ├── pos
│   └── pos_gray
└── xml
    ├── cascade.xml
    ├── params.xml
 ...
    └── stage9.xml
  1. neg和pos里分别为正向与反向的样例文件,这里全部转化为灰度图来使用,转灰度图可使用以下代码来实现
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# -*- coding:utf-8 -*-
import os
import cv2


def img2Gray():
    path = "./training-files/neg_jpg/"

    new_path = "./training-files/neg_gray/"
    i = 0
    for filename in os.listdir(path):
        img = cv2.imread(path + filename)
        img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        i += 1
        tmp = str.split(filename, '.')
        # new_name = tmp[0] + "_gray.jpg"
        new_name = str(i) + "_gray.jpg"

        cv2.imwrite(new_path + new_name, img)

    # cv2.waitKey(0) & 0xFF


if __name__ == '__main__':
    img2Gray()
  1. pos.dat为正向样例描述文件,内容如下,包括文件名,目标个数,以及目标的(x,y,w,h),以图片左上角为零点,目标起始位置的x与y的坐标,目标宽度和高度

  2. 反向样例的描述文件只需列出文件名(包含文件路径)即可

  3. usage

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
Usage: opencv_createsamples
  [-info <collection_file_name>]
  [-img <image_file_name>]
  [-vec <vec_file_name>]
  [-bg <background_file_name>]
  [-num <number_of_samples = 1000>]
  [-bgcolor <background_color = 0>]
  [-inv] [-randinv] [-bgthresh <background_color_threshold = 80>]
  [-maxidev <max_intensity_deviation = 40>]
  [-maxxangle <max_x_rotation_angle = 1.100000>]
  [-maxyangle <max_y_rotation_angle = 1.100000>]
  [-maxzangle <max_z_rotation_angle = 0.500000>]
  [-show [<scale = 4.000000>]]
  [-w <sample_width = 24>]
  [-h <sample_height = 24>]
  [-maxscale <max sample scale = -1.000000>]
  [-rngseed <rng seed = 12345>]
  1. 生成vec文件 opencv_createsamples -vec pos.vec -info pos.dat -num 17 -w 40 -h 40,这里的参数分别为输出的文件名、描述文件名、样例数量、输出宽以及高
  2. 接下来可以使用opencv_createsamples -vec pos.vec -w 40 -h 40查看输出的样例
  3. 最后使用以下命令生成分类器的xml文件
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
opencv_traincascade -data xml -vec pos.vec -bg neg.txt -numPos 17 -numNeg 53 -numStages 20 -w 40 -h 40 -featureType LBP -precalcValBufSize 4096 -precalcldxBufSize 4096 -maxWeakCount 200 -minHitRate 0.999 -maxFalseAlarmRate 0.2

-data 指定输出文件目录
-vec 指定正向样例描述文件
-bg 指定反向样例描述文件
-numPos 正向样例数量
-numNeg 反向样例数量
-numStages 训练的分类器的级数
-w -h 宽高
-featureType 特征类别 (LBP、HOG、Haar)推荐使用LBP 整数类型,速度更快,精度略差于Haar
-precalcValBufSize 缓存大小,用于存储预先计算的特征值(feature values),单位为MB,默认1024MB
-precalcldxBufSize 缓存大小,用于存储预先计算的特征索引(feature indices),单位为MB,默认1024MB。内存越大,训练时间越短
-maxWeakCount 每一级中的弱分类器的最大数目,默认100。增强的分类器(stage)将有许多弱树(<=maxWeakCount),这是实现给定-maxFalseAlarmRate所需要的。
-minHitRate 分类器的每一级希望得到的最小检测率,默认0.995。总的检测率大约为 min_hit_rate^number_of_stages
- maxFalseAlarmRate 分类器的每一级希望得到的最大误检率,默认0.5。总的误检率大约为 max_false_alarm_rate^number_of_stages 。在使用LBP特征参数训练时推荐设置成0.2甚至更小
  1. 至此,就可以生成自己的分类器了!

测试分类器

采集人脸数据

  • 这里需要用到cv2和numpy库, 可以使用pip安装
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 加载生成的xml文件
face_cascaded = cv2.CascadeClassifier('/Users/lhui/codes/python/haarcascade_frontalface_default.xml')
    # 打开摄像头
    camera = cv2.VideoCapture(0)
    cv2.namedWindow('Dynamic')
    count = 1

    while True:
        ret, frame = camera.read()
        if ret:
            # 转换为灰度图
            gray_img = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
            # 人脸检测
            face = face_cascaded.detectMultiScale(gray_img, 1.3, 5)
            for (x, y, w, h) in face:
                # 在原图上绘制矩形
                cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 0, 255), 2)
                # 调整图像大小
                new_frame = cv2.resize(frame[y:y + h, x:x + w], (92, 112))
                cv2.imwrite('%s/%s.png' % (path, str(count)), new_frame)
                count += 1
            cv2.imshow('Dynamic', frame)
            if cv2.waitKey(100) & 0xff == ord('q'):
                break
    camera.release()
    cv2.destroyAllWindows()

图像校验

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
 # 加载训练的数据
    X, y, names = load_images(data)
    print(X, y, names)
    print('x', X)
    model = cv2.face.EigenFaceRecognizer_create()
    model.train(X, y)

    camera = cv2.VideoCapture(0)
    cv2.namedWindow('Dynamic')

    # 创建级联分类器
    face_cascaded = cv2.CascadeClassifier('/Users/lhui/codes/python/haarcascade_frontalface_default.xml')

    while True:
        ret, frame = camera.read()
        # 判断图像是否读取成功
        if ret:
            # 转换为灰度图
            gray_img = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

            # 利用级联分类器鉴别人脸
            faces = face_cascaded.detectMultiScale(gray_img, 1.3, 5)

            for (x, y, w, h) in faces:
                frame = cv2.rectangle(frame, (x, y), (x + w, y + h), (255, 0, 0), 2)  # 蓝色
                roi_gray = gray_img[y:y + h, x:x + w]

                try:
                    roi_gray = cv2.resize(roi_gray, (92, 112), interpolation=cv2.INTER_LINEAR)
                    params = model.predict(roi_gray)
                    cv2.putText(frame, names[params[0]], (x, y - 20), cv2.FONT_HERSHEY_SIMPLEX, 1, 255, 2)
                except:
                    continue

            cv2.imshow('Dynamic', frame)

            # 按下q键退出
            if cv2.waitKey(100) & 0xff == ord('q'):
                break
    camera.release()
    cv2.destroyAllWindows()