前言
上篇文章我们讲到了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); 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); } return image; }
|
上述方法里主要使用了OpenCV中org.bytedeco.opencv.global.opencv_imgcodecs
和org.bytedeco.opencv.global.opencv_imgproc
两个包中的方法。
然后我们开始进行人脸识别训练。
人脸识别器(FaceRecognizer)这个类目前包含三种人脸识别方法:基于PCA变换的人脸识别(EigenFaceRecognizer)、基于Fisher变换的人脸识别(FisherFaceRecognizer)、基于局部二值模式的人脸识别(LBPHFaceRecognizer)。
这儿我们以基于PCA变换的人脸识别(EigenFaceRecognizer)来进行举例。
我们想识别两张不同的人脸,需要首先准备若干样本,这儿我准备了胡歌和刘亦菲的照片各10张来作为训练样本(想要更好的训练效果,训练图片至少要在数百张左右),如下图:
其中胡歌的图片以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("目录下没有符合要求的图片文件!!"); } String trainFileBasePath = baseImagePath + "/train"; File trainFileBase = new File(trainFileBasePath); if(!trainFileBase.exists()){ trainFileBase.mkdir(); } 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); } FaceRecognizer faceRecognizer = EigenFaceRecognizer.create(); faceRecognizer.train(matVector,labels); 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);
IntPointer label = new IntPointer(1); 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文件夹,里面就是存放宽高相等的待训练的样本。如下图:
我们取一张测试照片test.jpg,来进行测试,如下:
其测试的主要代码就是上面代码的这部分:
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);
IntPointer label = new IntPointer(1);
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"); }
|
运行后可以看到输出结果:
可以看到符合我们的预期。
这儿只使用了少量训练样本来展示图像训练的方法,其实在进行识别训练时,样本越大精确度越高。
我们之前用到的haarcascade_frontalface_alt.xml
,就是使用大量人脸样本进行训练生成的。
总结
今天我们使用了OpenCV自带的人脸识别器(FaceRecognizer)来进行了人脸的识别与训练,后续我们会在了解下OpenCV在图像应用方面的一些其它功能。