OpenCV的使用(二) - 人脸的识别与训练

前言

上篇文章我们讲到了OpenCV的安装和简单使用,OpenCV简介及使用(一)

这篇文章我们来看下如何使用OpenCV进行图像的识别与训练。

正文

由于OpenCV自带人脸识别及检测功能,我们来看下如何使用OpenCV来分辨人脸。

如果要进行人脸识别及训练,需要用到人脸灰度图,且有一定的数量,图片宽高需要保持一致。

上篇文章已经说到如何寻找并裁剪人脸,然后我们将它置灰即可,需要调用org.bytedeco.opencv.global.opencv_imgproc.cvtColor方法,如代码所示cvtColor(image,image,COLOR_BGR2GRAY)

其中第一个image是原Mat图,第二个image是生成的Mat图,两个设置成一样则生成的灰度图会覆盖原图,COLOR_BGR2GRAY表示生成的图片颜色。

则生成灰度图代码大致如下:

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
public static Mat detectFace(String sourceImage, String targetImage,int width,int height){
CascadeClassifier faceDetector = new CascadeClassifier(CASCADE_FACE_FILENAME);
if(faceDetector.empty()){
throw new RuntimeException("处理文件时发生异常!");
}
Mat image = imread(sourceImage);
RectVector rectVector = new RectVector();
// 进行人脸检测
faceDetector.detectMultiScale(image, rectVector);

Rect[] rects = rectVector.get();
if(rects.length <=0 ){
log.error("原图片上未检测到人脸:{}",sourceImage);
throw new RuntimeException("原图片上未检测到人脸!!");
}
if(rects.length >1){
log.error("原图片上检测到多个人脸:{}",sourceImage);
throw new RuntimeException("原图片上检测到多个人脸!!");
}
Rect rect = rects[0];
//人脸裁剪
image = new Mat(image,rect);
//将图片置灰
cvtColor(image,image,COLOR_BGR2GRAY);
//调整图片大小(如果传入宽高为0,就取rect图片宽高)
if(width==0||height==0){
width = rect.width();
height = rect.height();
}
resize(image,image,new Size(width,height));
//图片落地(灰色图也可以不落地)
if(StringUtils.isNotBlank(targetImage)){
imwrite(targetImage, image);
}
//返会Mat图
return image;
}

上述方法里主要使用了OpenCV中org.bytedeco.opencv.global.opencv_imgcodecsorg.bytedeco.opencv.global.opencv_imgproc两个包中的方法。

然后我们开始进行人脸识别训练。

人脸识别器(FaceRecognizer)这个类目前包含三种人脸识别方法:基于PCA变换的人脸识别(EigenFaceRecognizer)、基于Fisher变换的人脸识别(FisherFaceRecognizer)、基于局部二值模式的人脸识别(LBPHFaceRecognizer)。

这儿我们以基于PCA变换的人脸识别(EigenFaceRecognizer)来进行举例。

我们想识别两张不同的人脸,需要首先准备若干样本,这儿我准备了胡歌和刘亦菲的照片各10张来作为训练样本(想要更好的训练效果,训练图片至少要在数百张左右),如下图:

upload successful

其中胡歌的图片以0-开头,刘亦菲的图片以1-开头,对测试图片进行测试时,识别器的label(识别标签)返回0认为属于胡歌的照片,1认为属于刘亦菲的图片,-1认为不属于他们的照片。

训练时需要使用灰度图,我们使用上面的方法处理下生成宽高相等的灰度图。

其相关代码如下:

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public static void faceRecognize(String baseImagePath,String testImage){
//存放样本的地址
File dirFile = new File(baseImagePath);
if(!dirFile.isDirectory()){
throw new RuntimeException("请指定样本文件目录!!");
}
//过滤指定图片文件
FilenameFilter imgFilter = (dir,name)->{
name = name.toLowerCase();
return name.endsWith(".jpg") || name.endsWith(".pgm") || name.endsWith(".png");
};
File[] files = dirFile.listFiles(imgFilter);
if(files == null || files.length==0){
throw new RuntimeException("目录下没有符合要求的图片文件!!");
}
//生成的训练样本(灰色图)临时文件存放在train目录下
String trainFileBasePath = baseImagePath + "/train";
File trainFileBase = new File(trainFileBasePath);
if(!trainFileBase.exists()){
trainFileBase.mkdir();
}
//开始训练逻辑
//样本灰色图Mat
MatVector matVector = new MatVector(files.length);
//样本标签
Mat labels = new Mat(files.length, 1, CV_32SC1);
IntBuffer intBuffer = labels.createBuffer();
int baseWidth = 0;
int baseHeight = 0;
for (int i = 0; i < files.length; i++) {
//灰色图地址
String tempImage = trainFileBasePath+"/"+files[i].getName();
Mat mat = detectFace(files[i].getPath(),tempImage,baseWidth,baseHeight);
baseWidth = mat.rows();
baseHeight= mat.cols();
matVector.put(i,mat);
int label = Integer.parseInt(files[i].getName().split("-")[0]);
intBuffer.put(i,label);
}
//人脸识别训练(训练要求训练图片和测试图片必须为灰度图,且大小一致)
// num_components 主成分分析中保留的成分数(即特征面) 0为全部保留
// threshold 置信度阈值,待识别图片如果大于这个阈值的话label就会返回-1
// 可以不设置,会返回最低的置信度图片对于的label
FaceRecognizer faceRecognizer = EigenFaceRecognizer.create();
faceRecognizer.train(matVector,labels);
//保存训练xml
faceRecognizer.save(baseImagePath+"/face.xml");

//处理待识别的图片
String testTempImage = baseImagePath + "/test";
File file = new File(testTempImage);
if(!file.exists()){
file.mkdir();
}
Mat tempMat = detectFace(testImage,testTempImage+"/temp.jpg",baseWidth,baseHeight);

//识别后结果标签,如果没有识别到,会返回-1
IntPointer label = new IntPointer(1);
//识别置信度,越低越好,图片完全一样为0.0
DoublePointer confidence = new DoublePointer(1);
faceRecognizer.predict(tempMat, label, confidence);
int predictedLabel = label.get();
System.out.println("Predicted label: " + predictedLabel);
System.out.println(confidence.get());
}

其关键部分代码为faceRecognizer.train(matVector,labels),matVector接受生成的灰度图,labels为图片所属的标签,我们可以通过faceRecognizer.save(baseImagePath+"/face.xml")保存训练后的xml。

在训练之前,通过灰度图处理(detectFace方法),如果训练图片选择落地的话,会在文件夹下生成train文件夹,里面就是存放宽高相等的待训练的样本。如下图:

upload successful

我们取一张测试照片test.jpg,来进行测试,如下:

upload successful

其测试的主要代码就是上面代码的这部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//处理待识别的图片
String testTempImage = baseImagePath + "/test";
File file = new File(testTempImage);
if(!file.exists()){
file.mkdir();
}
Mat tempMat = detectFace(testImage,testTempImage+"/temp.jpg",baseWidth,baseHeight);

//识别后结果标签,如果没有识别到,会返回-1
IntPointer label = new IntPointer(1);
//识别置信度,越低越好,图片完全一样为0.0
DoublePointer confidence = new DoublePointer(1);
faceRecognizer.predict(tempMat, label, confidence);
int predictedLabel = label.get();
System.out.println("Predicted label: " + predictedLabel);
System.out.println(confidence.get());

识别时也是需要使用灰度图进行识别,调用faceRecognizer.predict(tempMat, label, confidence)来对测试图片进行测试,同时也会返回置信度。

我们构建Main运行代码:

1
2
3
4
public static void main(String[] args) throws Exception {
//人脸识别训练
faceRecognize("C:\\Users\\DELL-3020\\Desktop\\face\\base","C:\\Users\\DELL-3020\\Desktop\\face\\test.jpg");
}

运行后可以看到输出结果:

upload successful

可以看到符合我们的预期。

这儿只使用了少量训练样本来展示图像训练的方法,其实在进行识别训练时,样本越大精确度越高。

我们之前用到的haarcascade_frontalface_alt.xml,就是使用大量人脸样本进行训练生成的。

总结

今天我们使用了OpenCV自带的人脸识别器(FaceRecognizer)来进行了人脸的识别与训练,后续我们会在了解下OpenCV在图像应用方面的一些其它功能。




-------------文章结束啦 ~\(≧▽≦)/~ 感谢您的阅读-------------

您的支持就是我创作的动力!

欢迎关注我的其它发布渠道