OpenCV for Java 《二:采集人脸,训练模型,识别人脸》

OpenCV Java 采集人脸并训练模型,网上没有 OpenCV Java 最新训练模型的教程,经过自己看文档和一次次尝试,总结的教程。

环境

1
2
3
OS: macOS 10.13.3
OpenCV: 3.4.0_1
IDE: IntelliJ IDEA 2017.2.6

步骤

第一步:准备数据

下载数据库

官网教程里表示有三个数据库可以下载,我们这里用第一种

  • AT&T Facedatabase(有时也称为ORL面孔数据库)包含40个不同的人,其中每个人10种不同图像。图像是在不同的时间拍摄的,改变了照明,面部表情(开放/闭眼,微笑/不微笑)和面部细节(眼镜/没有眼镜)。所有图像都是在黑暗的均匀背景下拍摄的,拍摄对象处于直立的正面位置(对某些侧面运动具有宽容度)。

下载下来我放到了桌面 /Users/limbang/Desktop/orl_faces/ 该文件夹下有 s1 到 s40 每个文件夹代表一个人。

读取图片人脸收据数据,结合前面的,就可以用摄像头来创建数据

和前面一样先检测出人脸,代码重复的我就省略了,主要就是把图片缩放成和下载的数据一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 创建分类器
CascadeClassifier ...
省略...
// 检测人脸
face_cascade.detectMultiScale(...

int i = 0;
// 循环所有找到的人脸
for (Rect rect : faces.toArray()) {
// 将识别后的灰度图人脸,重新创建 MAT
Mat faceROI = new Mat(grayImg, rect);

if (faceROI.cols() > 100) {
// 图片缩放
Imgproc.resize(faceROI, faceROI, new Size(92, 112));
// 写出图片
Imgcodecs.imwrite("/Users/limbang/Desktop/orl_faces/s42/" + i + ".pgm", faceROI);
i++;
}
}

用上面那张图片试试 opencv-face

第二步:训练模型

FaceRecognizertrain方法来训练模型,有三种方式LBPHFaceRecognizer EigenFaceRecognizer FisherFaceRecognizer,这里我们使用 LBPH算法来训练模型。
可以看到 train(List src, Mat labels) ,可以看到标签也是 Mat,这里就是难找的网上没有教程,还是用 C++生成的一份 xml 对比才知道有问题,下面的代码只用6张图片来演示

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

List<Mat> images = new ArrayList<>();
Mat labels = new Mat(6,1,CvType.CV_32SC1); //有多少张图片就等于多少,这里的 rows = 6
int[] labelsArray={0, 1}; //这里代表2个人

//下面是手动添加标签为0的人3张图片
images.add(Imgcodecs.imread("/Users/limbang/Desktop/orl_faces/s1/1.pgm", Imgcodecs.CV_LOAD_IMAGE_GRAYSCALE));
labels.put(0,0,labelsArray[0]);
images.add(Imgcodecs.imread("/Users/limbang/Desktop/orl_faces/s1/2.pgm", Imgcodecs.CV_LOAD_IMAGE_GRAYSCALE));
labels.put(1,0,labelsArray[0]);
images.add(Imgcodecs.imread("/Users/limbang/Desktop/orl_faces/s1/3.pgm", Imgcodecs.CV_LOAD_IMAGE_GRAYSCALE));
labels.put(2,0,labelsArray[0]);
//下面是手动添加标签为1的人3张图片
images.add(Imgcodecs.imread("/Users/limbang/Desktop/orl_faces/s2/1.pgm", Imgcodecs.CV_LOAD_IMAGE_GRAYSCALE));
labels.put(3,0,labelsArray[1]);
images.add(Imgcodecs.imread("/Users/limbang/Desktop/orl_faces/s2/2.pgm", Imgcodecs.CV_LOAD_IMAGE_GRAYSCALE));
labels.put(4,0,labelsArray[1]);
images.add(Imgcodecs.imread("/Users/limbang/Desktop/orl_faces/s2/3.pgm", Imgcodecs.CV_LOAD_IMAGE_GRAYSCALE));
labels.put(5,0,labelsArray[1]);


FaceRecognizer faceRecognizer = LBPHFaceRecognizer.create();
// 训练模型
faceRecognizer.train(images,labels);
// 存储训练的模型
faceRecognizer.write("/Users/limbang/Desktop/face.xml");

打开 XML 文件可以看到,之前是按照这个问题里面创建的模型,也和他遇到的问题一样,标签会是一个疯狂大的数值或是负数(不是预想的0和1),最后看到这篇文章里的“神经网络java+opencv2.X ” 才知道标签应该这样创建。

1
2
3
4
5
6
<labels type_id="opencv-matrix">
<rows>6</rows>
<cols>1</cols>
<dt>i</dt>
<data>
0 0 0 1 1 1</data></labels>

第三步:识别人脸

就是把前面的几部分代码结合,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 创建分类器
CascadeClassifier ...
省略...
// 检测人脸
face_cascade.detectMultiScale(...

FaceRecognizer faceRecognizer = LBPHFaceRecognizer.create();
// 加载人脸识别模型
faceRecognizer.read("/Users/limbang/Desktop/face.xml");
// 循环所有找到的人脸
for (Rect rect : faces.toArray()) {
// 将识别后的灰度图人脸,重新创建 MAT
Mat faceROI = new Mat(grayImg, rect);
if (faceROI.cols() > 100) {
// 图片缩放
Imgproc.resize(faceROI, faceROI, new Size(92, 112));

int[] predictedLabel = {-1};
double[] confidence = {0.0};

faceRecognizer.predict(faceROI, predictedLabel, confidence);
System.out.println(String.format("预测的标签 = %s / 可信度 = %s.", Arrays.toString(predictedLabel), Arrays.toString(confidence)));
}
}

后记

遍历文件夹读取图片

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
 /**
* 遍历目录找出图片
*
* @param path 路径
* @return
* @throws IOException
*/
private static Map<String, List<File>> traverseFile(Path path) throws IOException {

final Map<String, List<File>> images = new HashMap<>();

SimpleFileVisitor<Path> finder = new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
String name = file.toString();
//将字符串全部转成小写
name = name.toLowerCase();
//测试字符串是否以指定后缀结尾
if (name.endsWith(".jpg") || name.endsWith(".pgm") || name.endsWith(".png")) {
//父文件夹名称
String paremtName = file.getParent().getFileName().toString();
//判断是否存在指定 KEY ,不存在就创建
if (images.get(paremtName) == null) {
images.put(paremtName, new ArrayList<>());
}
//添加进去
images.get(paremtName).add(file.toFile());
}
return super.visitFile(file, attrs);
}
};
//遍历目录
Files.walkFileTree(path, finder);

return images;
}
---------------- The End ----------------
分享到: