杰瑞科技汇

Java OpenCV人脸识别如何实现?

概念区分:人脸检测 vs. 人脸识别

在学习之前,必须先分清这两个概念:

Java OpenCV人脸识别如何实现?-图1
(图片来源网络,侵删)
  1. 人脸检测:在一张图片或视频帧中,找到人脸的位置,并用方框把它标记出来,它只回答“哪里有人脸?”这个问题。
  2. 人脸识别:在已经检测到人脸的基础上,判断这张脸是谁,它回答“这是谁?”这个问题。

人脸识别通常包括以下步骤:

  1. 人脸检测:从图像中裁剪出人脸区域。
  2. 特征提取:将裁剪出的人脸图像转换成一个独特的数学向量(也称为“嵌入”或 Embedding),这个向量代表了人脸的唯一特征。
  3. 比对:将新的人脸特征向量与数据库中已知人脸的特征向量进行比较,找出最相似的一个。

第一步:环境搭建

安装 Java

确保你已经安装了 JDK 8 或更高版本,并配置好了 JAVA_HOME 环境变量。

下载 OpenCV

你需要下载 OpenCV 的 Java 包,注意,你需要的是包含 Java 库的版本。

  • 访问 OpenCV 官方下载页面:https://opencv.org/releases/
  • 下载最新的版本(opencv-4.x.x)。
  • 解压下载的 ZIP 文件,在解压后的目录中,找到 build/java 文件夹。
    • opencv-4.x.x/build/java/opencv-490.jar:这是 Java 的 JAR 包。
    • opencv-4.x.x/build/java/x64/opencv_java490.dll (Windows) 或 libopencv_java490.so (Linux):这是本地库文件。

配置你的 Java 项目(以 IntelliJ IDEA 为例)

  1. 创建新项目:创建一个新的 Java Maven 或 Gradle 项目。
  2. 添加 JAR 包
    • 手动添加:将 opencv-490.jar 复制到你的 lib 目录下,然后在项目设置中将其添加为库。
    • Maven 方式(推荐):在你的 pom.xml 文件中添加以下依赖,Maven 会自动帮你下载和管理依赖。
      <dependency>
          <groupId>org.openpnp</groupId>
          <artifactId>opencv</artifactId>
          <version>4.9.0-0</version> <!-- 可以使用最新版本 -->
      </dependency>
  3. 加载本地库:这是最关键的一步,Java 代码需要加载 OpenCV 的本地库(.dll.so 文件)才能工作。
    • 方法一(推荐):将 opencv_java490.dll (Windows) 或 libopencv_java490.so (Linux) 文件复制到你的项目输出目录(通常是 target/classesout/production/你的项目名)。
    • 方法二(代码中指定路径):在代码中明确指定库文件的路径。

第二步:人脸检测

人脸识别的第一步是找到人脸,OpenCV 提供了基于 Haar 级联分类器和基于深度学习(如 DNN 模块)的两种方法,我们这里使用更常用和效果更好的 DNN 模块

Java OpenCV人脸识别如何实现?-图2
(图片来源网络,侵删)

下载预训练模型

你需要一个预训练好的模型来检测人脸,OpenCV 官方提供了一个基于 Caffe 的模型:

  • 模型文件deploy.prototxt
  • 权重文件res10_300x300_ssd_iter_140000.caffemodel

这两个文件可以在 OpenCV 的 GitHub 仓库中找到:https://github.com/opencv/opencv_3rdparty/tree/dnn_samples_face_detector_20250830

下载这两个文件,并放到你的项目资源文件夹(src/main/resources)中。

人脸检测代码示例

import org.opencv.core.*;
import org.opencv.dnn.Dnn;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import java.io.File;
public class FaceDetection {
    static {
        // 加载 OpenCV 的本地库
        // dll/so 文件在项目输出目录,可以直接用 System.loadLibrary
        // 否则,需要指定绝对路径
        // System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
        String opencvPath = "D:/path/to/your/opencv/build/java/x64/"; // 修改为你的 OpenCV 路径
        System.load(opencvPath + "opencv_java490.dll");
    }
    public static void main(String[] args) {
        // 1. 加载预训练的 DNN 模型
        String modelWeights = "src/main/resources/res10_300x300_ssd_iter_140000.caffemodel";
        String modelConfig = "src/main/resources/deploy.prototxt";
        Net net = Dnn.readNetFromCaffe(modelConfig, modelWeights);
        // 2. 读取图片
        String imagePath = "path/to/your/image.jpg"; // 替换为你的图片路径
        Mat image = Imgcodecs.imread(imagePath);
        if (image.empty()) {
            System.out.println("无法加载图片: " + imagePath);
            return;
        }
        // 3. 预处理图片
        // DNN 模型通常需要固定大小的输入
        Mat blob = Dnn.blobFromImage(image, 1.0, new Size(300, 300), new Scalar(104, 177, 123), false, false);
        net.setInput(blob);
        // 4. 进行人脸检测
        Mat detections = net.forward();
        // 5. 解析检测结果并绘制
        // detections 矩阵的维度是 [1, N, 7],N 是检测到的对象数量
        // 每个对象包含 [id, confidence, left, top, right, bottom, ...]
        for (int i = 0; i < detections.rows(); i++) {
            Mat row = detections.row(i);
            double confidence = row.get(0, 2)[0]; // 置信度
            // 设置一个置信度阈值,过滤掉低置信度的检测结果
            if (confidence > 0.7) {
                int x = (int) (row.get(0, 3)[0] * image.cols());
                int y = (int) (row.get(0, 4)[0] * image.rows());
                int width = (int) ((row.get(0, 5)[0] - row.get(0, 3)[0]) * image.cols());
                int height = (int) ((row.get(0, 6)[0] - row.get(0, 4)[0]) * image.rows());
                // 绘制矩形框
                Imgproc.rectangle(image, new Point(x, y), new Point(x + width, y + height), new Scalar(0, 255, 0), 2);
                // 显示置信度
                String label = String.format("Confidence: %.2f", confidence);
                Imgproc.putText(image, label, new Point(x, y - 10), Imgproc.FONT_HERSHEY_SIMPLEX, 0.5, new Scalar(0, 255, 0), 1);
            }
        }
        // 6. 保存结果图片
        Imgcodecs.imwrite "detected_faces.jpg", image);
        System.out.println("人脸检测完成,结果已保存为 detected_faces.jpg");
    }
}

运行这段代码后,你会在项目目录下得到一张标记了人脸方框的图片。

Java OpenCV人脸识别如何实现?-图3
(图片来源网络,侵删)

第三步:人脸识别

人脸识别的核心是提取特征向量,我们将使用 OpenCV 的 FaceRecognizer API,它支持几种经典的算法(如 Eigenfaces, Fisherfaces, LBPH),这里我们选择 LBPH (Local Binary Patterns Histograms),因为它对光照变化不敏感,且易于使用。

准备数据集

为了进行识别,你需要一个包含多个人脸的数据库,数据集的组织结构如下:

dataset/
├── person1/
│   ├── 1.jpg
│   ├── 2.jpg
│   └── ...
├── person2/
│   ├── 1.jpg
│   ├── 2.jpg
│   └── ...
└── ...
  • 每个文件夹代表一个不同的人。
  • 文件夹名就是人的标签("person1", "person2")。
  • 每个文件夹里存放这个人的多张不同角度、不同光照下的照片。

训练模型并识别

我们将编写一个程序,完成以下任务:

  1. dataset 目录加载所有的人脸图片和对应的标签。
  2. 使用这些数据训练一个 LBPH 人脸识别器。
  3. 加载一张待识别的图片。
  4. 在待识别图片中检测人脸。
  5. 将检测到的人脸输入到训练好的识别器中,得到识别结果。
import org.opencv.core.*;
import org.opencv.dnn.Dnn;
import org.opencv.face.Face;
import org.opencv.face.FaceRecognizer;
import org.opencv.face LBPHFaceRecognizer;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;
import java.io.File;
import java.io.FilenameFilter;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.List;
public class FaceRecognition {
    static {
        // 加载 OpenCV 的本地库
        String opencvPath = "D:/path/to/your/opencv/build/java/x64/"; // 修改为你的 OpenCV 路径
        System.load(opencvPath + "opencv_java490.dll");
    }
    public static void main(String[] args) {
        // 1. 准备训练数据
        String trainingDir = "path/to/your/dataset"; // 你的数据集路径
        File root = new File(trainingDir);
        // 用于存储人脸数据和标签
        List<Mat> images = new ArrayList<>();
        List<Integer> labels = new ArrayList<>();
        Integer label = 0;
        // Haar 级联分类器用于人脸检测(也可以用上面的 DNN 模型)
        String haarCascadePath = "D:/path/to/opencv/data/haarcascades/haarcascade_frontalface_alt.xml"; // OpenCV 安装路径下的分类器
        CascadeClassifier faceDetector = new CascadeClassifier(haarCascadePath);
        if (faceDetector.empty()) {
            System.out.println("无法加载 Haar 级联分类器");
            return;
        }
        // 遍历数据集文件夹
        File[] classDirs = root.listFiles(File::isDirectory);
        if (classDirs == null) return;
        for (File classDir : classDirs) {
            File[] imageFiles = classDir.listFiles((dir, name) -> name.endsWith(".jpg") || name.endsWith(".png"));
            if (imageFiles == null || imageFiles.length == 0) continue;
            for (File imageFile : imageFiles) {
                Mat image = Imgcodecs.imread(imageFile.getAbsolutePath(), Imgcodecs.IMREAD_GRAYSCALE); // 转为灰度图
                if (image.empty()) continue;
                // 检测人脸
                MatOfRect faceDetections = new MatOfRect();
                faceDetector.detectMultiScale(image, faceDetections);
                // 如果检测到人脸,则添加到训练数据中
                if (!faceDetections.empty()) {
                    // 通常取检测到的人脸中最大的一个
                    Rect largestFace = findLargestFace(faceDetections.toArray());
                    Mat faceROI = image.submat(largestFace);
                    images.add(faceROI);
                    labels.add(label);
                }
            }
            label++; // 每个文件夹处理完后,标签加1
        }
        if (images.isEmpty()) {
            System.out.println("未找到任何可用于训练的人脸图像");
            return;
        }
        // 2. 训练 LBPH 模型
        FaceRecognizer recognizer = LBPHFaceRecognizer.create();
        Mat labelsMat = new Mat(labels.size(), 1, CvType.CV_32SC1);
        for (int i = 0; i < labels.size(); i++) {
            labelsMat.put(i, 0, labels.get(i));
        }
        recognizer.train(new MatList(images), labelsMat);
        System.out.println("模型训练完成!");
        // 3. 识别新图片中的人脸
        String testImagePath = "path/to/your/test_image.jpg"; // 你的测试图片路径
        Mat testImage = Imgcodecs.imread(testImagePath);
        Mat testImageGray = new Mat();
        Imgproc.cvtColor(testImage, testImageGray, Imgproc.COLOR_BGR2GRAY);
        MatOfRect testFaceDetections = new MatOfRect();
        faceDetector.detectMultiScale(testImageGray, testFaceDetections);
        for (Rect rect : testFaceDetections.toArray()) {
            Mat testFaceROI = testImageGray.submat(rect);
            // 进行预测
            IntBuffer labelBuf = IntBuffer.allocate(1);
            DoubleBuffer confidenceBuf = DoubleBuffer.allocate(1);
            recognizer.predict(testFaceROI, labelBuf, confidenceBuf);
            int predictedLabel = labelBuf.get();
            double confidence = confidenceBuf.get();
            // 设置一个置信度阈值,低于此阈值则认为是未知人脸
            if (confidence < 70.0) { // 这个阈值需要根据你的模型和数据集调整
                String predictedName = classDirs[predictedLabel].getName();
                Imgproc.rectangle(testImage, new Point(rect.x, rect.y), new Point(rect.x + rect.width, rect.y + rect.height), new Scalar(0, 255, 0), 2);
                Imgproc.putText(testImage, predictedName, new Point(rect.x, rect.y - 10), Imgproc.FONT_HERSHEY_SIMPLEX, 1, new Scalar(0, 255, 0), 2);
            } else {
                Imgproc.rectangle(testImage, new Point(rect.x, rect.y), new Point(rect.x + rect.width, rect.y + rect.height), new Scalar(0, 0, 255), 2);
                Imgproc.putText(testImage, "Unknown", new Point(rect.x, rect.y - 10), Imgproc.FONT_HERSHEY_SIMPLEX, 1, new Scalar(0, 0, 255), 2);
            }
        }
        // 4. 保存识别结果
        Imgcodecs.imwrite("recognized_faces.jpg", testImage);
        System.out.println("人脸识别完成,结果已保存为 recognized_faces.jpg");
    }
    // 辅助函数:找到最大的人脸
    private static Rect findLargestFace(Rect[] faces) {
        if (faces == null || faces.length == 0) {
            return new Rect();
        }
        Rect largestFace = faces[0];
        int maxArea = largestFace.width * largestFace.height;
        for (Rect face : faces) {
            int area = face.width * face.height;
            if (area > maxArea) {
                maxArea = area;
                largestFace = face;
            }
        }
        return largestFace;
    }
}

总结与进阶

  • 核心流程回顾

    1. 环境配置:下载 OpenCV,配置 JAR 和本地库。
    2. 人脸检测:使用 DNN 或 Haar 级联分类器定位人脸。
    3. 数据准备:为识别任务整理好人脸数据集(每人一个文件夹)。
    4. 模型训练:使用 FaceRecognizer (如 LBPH) 训练一个识别模型。
    5. 人脸识别:用训练好的模型对检测到的人脸进行预测,得到身份和置信度。
  • 进阶方向

    1. 更先进的特征提取:LBPH 是传统方法,效果有限,现代人脸识别通常使用深度学习模型提取特征,如 FaceNetDeepFace 等,这些模型能生成更具区分度的特征向量,识别率更高。

      实现方式:可以使用 OpenCV 的 DNN 模块加载这些预训练模型(如 TensorFlow 或 Caffe 格式的模型),然后提取人脸图像的特征向量,再使用余弦相似度或欧氏距离来比对。

    2. 实时视频流处理:将上述逻辑应用到摄像头视频流中,使用 VideoCapture 类捕获每一帧,然后进行检测和识别。
    3. 人脸数据库:在实际应用中,你可能需要一个数据库(如 MySQL, SQLite)来存储用户信息和他们的人脸特征向量,而不是像示例中那样依赖文件系统。
    4. 多线程处理:为了提高视频处理的性能,可以将人脸检测和识别放在不同的线程中。

希望这份详细的指南能帮助你成功地在 Java 中使用 OpenCV 进行人脸识别!

分享:
扫描分享到社交APP
上一篇
下一篇