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被视为黑白相间的方块:
这些filter被多次应用于图像,逐像素处理,结果收集为单个值。该值是黑色方块下的像素和与白色方块下的像素和之间的差异。
6. 人脸检测
通常,级联分类器需要预先训练才能用于检测。
由于训练过程很长并且需要大量数据集,我们将使用OpenCV提供的预训练模型。我们将这个XML文件放在我们的 resources
文件夹中以便于访问。
下面我们逐步完成人脸检测:
检测到人脸后,使用红色方框,把人脸标记出来。
第一步,我们需要加载图片为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);
结果如下:
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上找到。