1. 概述

OpenCV 是一个由C++开发的计算器视觉库,提供了Python、Java等语言接口。本教程我们将学习,如何在Java中安装和使用OpenCV,并将其应用于实时人脸检测。

2.安装

要在我们的项目中使用OpenCV库,我们需要引入 OpenCV 的 Maven 依赖:

<dependency>
    <groupId>org.openpnp</groupId>
    <artifactId>opencv</artifactId>
    <version>3.4.2-0</version>
</dependency>

对于Gradle用户,我们需要将依赖项添加到我们的 build.gradle 文件中:

compile group: 'org.openpnp', name: 'opencv', version: '3.4.2-0'

然后我们就可以使用OpenCV提供的功能

3. 使用库

开始使用前,我们需要初始化OpenCV库, 这可以在我们的main方法中完成:

OpenCV.loadShared();

OpenCV类包含与加载各平台和架构的 OpenCV 库所需的navtive包相关的方法。

值得注意的是,一些文档 中采用了稍微不同的方法:

System.loadLibrary(Core.NATIVE_LIBRARY_NAME)

这两种方法调用实际上都会加载所需的navive库。

不同之处在于,后者需要提前安装navive库。 但是,如果给定机器上没有库,前者可以将库安装到临时文件夹中。由于这种差异,loadShared 方法通常是最佳选择。

现在我们已经初始化了库,让我们看看我们可以用它做什么。

4. 加载图片

首先,让我们 使用OpenCV从硬盘加载示例图像

public static Mat loadImage(String imagePath) {
    Imgcodecs imageCodecs = new Imgcodecs();
    return imageCodecs.imread(imagePath);
}

该方法将图片作为 Mat 对象加载进来,表示矩阵。

要保存加载的图片到硬盘上,使用Imgcodecs类的imwrite()方法:

public static void saveImage(Mat imageMatrix, String targetPath) {
    Imgcodecs imgcodecs = new Imgcodecs();
    imgcodecs.imwrite(targetPath, imageMatrix);
}

5. Haar级联分类器

在深入了解人脸识别前,我们先了解什么是Haar级联分类器。

分类器是一种程序,旨在根据过去的经验将新观察结果放入一个组中。级联分类器通过多个分类器的串联来实现这一目标。每个后续分类器使用前一个分类器的输出作为附加信息,大大提高了分类效果。

5.1. Haar 特征

OpenCV中的人脸检测是通过基于Haar特征的级联分类器完成的。

Haar 特征 是用于检测图像边缘和线条的filter。 filter被视为黑白相间的方块:

Haar Features

这些filter被多次应用于图像,逐像素处理,结果收集为单个值。该值是黑色方块下的像素和与白色方块下的像素和之间的差异。

6. 人脸检测

通常,级联分类器需要预先训练才能用于检测。

由于训练过程很长并且需要大量数据集,我们将使用OpenCV提供的预训练模型。我们将这个XML文件放在我们的 resources 文件夹中以便于访问。

下面我们逐步完成人脸检测:

Face To Detect

检测到人脸后,使用红色方框,把人脸标记出来。

第一步,我们需要加载图片为Mat格式:

Mat loadedImage = loadImage(sourceImagePath);

然后,我们将声明一个 MatOfRect 对象来保存我们找到的人脸:

MatOfRect facesDetected = new MatOfRect();

接下来,我们需要初始化 CascadeClassifier 进行识别:

CascadeClassifier cascadeClassifier = new CascadeClassifier(); 
int minFaceSize = Math.round(loadedImage.rows() * 0.1f); 
cascadeClassifier.load("./src/main/resources/haarcascades/haarcascade_frontalface_alt.xml"); 
cascadeClassifier.detectMultiScale(loadedImage, 
  facesDetected, 
  1.1, 
  3, 
  Objdetect.CASCADE_SCALE_IMAGE, 
  new Size(minFaceSize, minFaceSize), 
  new Size() 
);

上面,参数 1.1 表示我们要使用的缩放因子,指定每个图像缩放的图像大小减少多少。下一个参数 3 是minNeighbors,这是一个候选矩形应保留的邻居数量。

最后,遍历检测到的人脸,绘制红框并保存结果:

Rect[] facesArray = facesDetected.toArray(); 
for(Rect face : facesArray) { 
    Imgproc.rectangle(loadedImage, face.tl(), face.br(), new Scalar(0, 0, 255), 3); 
} 
saveImage(loadedImage, targetImagePath);

结果如下:

Face Detected

7. OpenCV 访问摄像头

目前为止,我们学会了如何对单张图片进行人脸检测。接下来我们学习如何采集摄像头图像,实现实时人脸检测。

为了实时显示从摄像头拍摄的图片,这里涉及到一些Java图像界面编程技术 —— JavaFX。

需要使用ImageView组件显示图片, OpenCV的Mat转换为JavaFX的Image的方法:

public Image mat2Img(Mat mat) {
    MatOfByte bytes = new MatOfByte();
    Imgcodecs.imencode("img", mat, bytes);
    InputStream inputStream = new ByteArrayInputStream(bytes.toArray());
    return new Image(inputStream);
}

在这里,我们将Mat转换为字节,再将字节转换为Image对象。

现在使用 loadShared 方法初始化OpenCV:

OpenCV.loadShared();

接下来,我们将使用VideoCapture和ImageView 创建Stage 来显示图像:

VideoCapture capture = new VideoCapture(0); 
ImageView imageView = new ImageView(); 
HBox hbox = new HBox(imageView); 
Scene scene = new Scene(hbox);
stage.setScene(scene); 
stage.show();

数字0是我们要使用的相机的 ID。我们还需要 创建AnimationTimer 来处理图像的设置:

new AnimationTimer() { 
    @Override public void handle(long l) { 
        imageView.setImage(getCapture()); 
    } 
}.start();

最后,我们的 getCapture 方法负责将 Mat 转换为 Image :

public Image getCapture() { 
    Mat mat = new Mat(); 
    capture.read(mat); 
    return mat2Img(mat); 
}

现在运行我们的程序会创建一个窗口,摄像头采集的画面会实时的显示到imageView窗口上。

8. 实时人脸检测

上一节我们学习了如何采集摄像头的图像并显示到我们的窗口上。现在我们将前面的知识全部串起来,使用 CascadeClassifier 类对采集的图像进行人脸检测,然后将其显示在屏幕上。

修改getCapture方法,抓拍的同时进行人脸检测:

public Image getCaptureWithFaceDetection() {
    Mat mat = new Mat();
    capture.read(mat);
    Mat haarClassifiedImg = detectFace(mat);
    return mat2Img(haarClassifiedImg);
}

现在重新运行我们的程序,人脸会被红色方框标记。

测试时我们会发现,如果我们将脸部转动的过快,红框就会消失。 这是因为我们使用了一个专门训练用于检测正脸的分类器。

9. 总结

在本教程中,我们学习了如何在 Java 中使用 OpenCV。

我们使用预先训练的级联分类器来检测图像上的脸部。借助 JavaFX,我们设法让分类器使用来自相机的图像实时检测脸部。

最后完整代码可在GitHub上找到。