1. 简介
在本教程中,我们将学习如何安装和使用OpenCV计算机视觉库并将其应用于实时人脸检测。
2. 安装
为了在我们的项目中使用OpenCV库,我们需要将opencv Maven依赖添加到pom.xml中:
<dependency>
<groupId>org.openpnp</groupId>
<artifactId>opencv</artifactId>
<version>4.9.0-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库所需的原生包相关的方法。
值得注意的是,文档中的说法略有不同:
System.loadLibrary(Core.NATIVE_LIBRARY_NAME)
这两种方法调用实际上都会加载所需的原生库。
此处的区别在于后者需要安装原生库,而前者则可以将库安装到临时文件夹中(如果给定计算机上没有库)。由于这种差异,loadShared方法通常是最佳选择。
或者,我们可以按照官方文档下载并编译该库,并在使用OpenCV相关类之前加载该库:
System.load("/opencv/build/lib/libopencv_java4100.dylib");
现在我们已经初始化了库,让我们看看可以用它做什么。
4. 加载图像
首先,让我们使用OpenCV从磁盘加载示例图像:
public static Mat loadImage(String imagePath) {
return new Imgcodecs(imagePath);
}
此方法将给定的图像加载为Mat对象,它是一个矩阵表示。
要保存之前加载的图像,我们可以使用Imgcodecs类的imwrite()方法:
public static void saveImage(Mat imageMatrix, String targetPath) {
Imgcodecs.imwrite(targetPath, imageMatrix);
}
5. Haar级联分类器
在深入研究面部识别之前,让我们先了解实现这一目标的核心概念。
简而言之,分类器是一种程序,它试图根据经验将新的观察结果归入一个组。级联分类器试图使用多个分类器的串联来实现这一点,每个后续分类器都使用前一个分类器的输出作为附加信息,从而显著改善分类效果。
5.1 Haar特征
在我们的例子中,我们将使用基于Haar特征的级联分类器在OpenCV中进行人脸检测。
Haar特征是用于检测图像上的边缘和线条的过滤器,过滤器看起来像是黑色和白色的方块:
这些过滤器会多次应用于图像,逐个像素地应用,并将结果收集为单个值。该值是黑色方块下像素总和与白色方块下像素总和之间的差值。
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);
String filename = FaceDetection.class.getClassLoader().getResource("haarcascades/haarcascade_frontalface_alt.xml").getFile();
cascadeClassifier.load(filename);
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), 10);
}
saveImage(loadedImage, targetImagePath);
当我们输入源图像时,我们现在应该收到所有面部都用红色矩形标记的输出图像。
这简要描述了我们的detectFace()方法的内容,让我们使用它来测试一切是否正常工作:
public static void main(String[] args) {
// Load the native library.
System.load("/opencv/build/lib/libopencv_java4100.dylib");
detectFace(Paths.get("portrait.jpg"),"./processed.jpg");
}
简而言之,我们将加载一张图片,并使用输入文件路径和输出文件路径调用detectFace()方法。结果,我们将得到一个名为processed.jpg的文件,其中包含一个围绕图片中人物脸部的红色矩形:
7. 使用OpenCV访问相机
到目前为止,我们已经了解了如何在加载的图像上执行人脸检测。但大多数时候,我们希望实时执行此操作,为此,我们需要访问相机。
但是,要显示来自相机的图像,除了显而易见的东西(相机)之外,我们还需要一些东西。为了显示图像,我们将使用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对象。
我们首先将摄像机视图传输到JavaFX Stage。
现在,让我们使用loadShared方法初始化库:
OpenCV.loadShared();
接下来,我们将创建带有VideoCapture和ImageView的舞台来显示图像:
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,我们让分类器使用来自相机的图像实时检测人脸。
Post Directory
