杰瑞科技汇

opencv java 人脸识别

  1. 人脸检测:在图片或视频中找到人脸的位置(用矩形框标出),这是识别的基础。
  2. 人脸识别:在检测到的人脸基础上,判断这个人是谁。

我们将从最简单的人脸检测开始,然后逐步深入到人脸识别。

opencv java 人脸识别-图1
(图片来源网络,侵删)

准备工作:环境配置

在开始编码之前,你需要准备好 OpenCV Java 的开发环境。

下载 OpenCV

访问 OpenCV 官网下载页面:https://opencv.org/releases/ 下载适合你操作系统的版本(opencv-4.x.x-windows)。

配置 IDE (以 IntelliJ IDEA 为例)

这是最关键的一步,让你的 Java 项目能够找到并使用 OpenCV 的库文件(.dll.so)。

  1. 解压 OpenCV:将下载的 .zip 文件解压到一个固定路径,D:\dev\opencv
  2. 创建新项目:在 IntelliJ IDEA 中创建一个新的 Java 项目。
  3. 添加 OpenCV 库
    • 打开 File -> Project Structure...
    • 选择 Libraries -> 点击 号 -> 选择 Java
    • 浏览并选择你解压的 OpenCV 目录下的 build/java/opencv-4xx.jar 文件(xx 是版本号)。
    • 非常重要:在接下来的窗口中,确保勾选了 "Extract to the project"(或者 "Copy to the project"),这会将 JAR 文件复制到你的项目中。
    • 点击 OK
  4. 配置 Native Library Location
    • 再次打开 Project Structure... -> Libraries
    • 找到你刚刚添加的 opencv-4xx.jar 库,选中它。
    • 在右侧的 Native library locations 中,点击文件夹图标,然后选择 OpenCV 解压目录下的 build/java/x64 (如果你是 64 位系统) 或 build/java/x86 (32 位系统)。
    • 点击 OK

现在你的项目已经可以正确使用 OpenCV 了。

opencv java 人脸识别-图2
(图片来源网络,侵删)

第一部分:人脸检测

人脸检测的原理是使用一个预先训练好的“级联分类器”(Haar Cascade 或 LBP)模型,它在图像中滑动窗口,判断每个窗口是否包含人脸。

获取分类器文件

你需要一个 .xml 格式的分类器文件,OpenCV 安装包中已经包含了。

  • Haar Cascade 人脸检测器:效果较好,但对光照敏感,路径:<opencv_path>/data/haarcascades/haarcascade_frontalface_alt.xml
  • LBP (Local Binary Patterns) 人脸检测器:速度更快,对光照不敏感,但精度略低,路径:<opencv_path>/data/lbpcascades/lbpcascade_frontalface.xml

我们将使用 Haar Cascade 作为示例。

代码实现:从图片中检测人脸

import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
public class FaceDetectionImage {
    static {
        // 加载 OpenCV 的 native 库
        // 这行代码会去你在 Project Structure 中配置的 Native Library Location 路径下查找 .dll 文件
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
    }
    public static void main(String[] args) {
        // 1. 加载分类器
        // 请确保这里的路径是正确的,指向你本地的 haarcascade_frontalface_alt.xml 文件
        String classifierPath = "D:/dev/opencv/data/haarcascades/haarcascade_frontalface_alt.xml";
        CascadeClassifier faceDetector = new CascadeClassifier();
        if (!faceDetector.load(classifierPath)) {
            System.err.println("Error loading classifier file");
            return;
        }
        // 2. 读取图片
        String imagePath = "path/to/your/image.jpg"; // 替换成你的图片路径
        Mat image = Imgcodecs.imread(imagePath);
        if (image.empty()) {
            System.err.println("Error loading image");
            return;
        }
        // 3. 检测人脸
        // MatOfRect 用于存储检测到的人脸的矩形区域
        MatOfRect faceDetections = new MatOfRect();
        // detectMultiScale 方法执行检测
        // 参数:图像, 结果, 缩放因子, 邻近数, 标志, 最小尺寸
        faceDetector.detectMultiScale(image, faceDetections);
        System.out.println(String.format("Detected %s faces", faceDetections.toArray().length));
        // 4. 在检测到的人脸周围画矩形
        for (Rect rect : faceDetections.toArray()) {
            Imgproc.rectangle(
                image,                                  // 原始图像
                new Point(rect.x, rect.y),              // 矩形左上角坐标
                new Point(rect.x + rect.width, rect.y + rect.height), // 矩形右下角坐标
                new Scalar(0, 255, 0),                  // 颜色 (BGR格式, 绿色)
                3                                       // 线条粗细
            );
        }
        // 5. 显示结果
        // OpenCV 的 Mat 不能直接显示,需要转换为 Java 的 BufferedImage
        HighGui.imshow("Face Detection", matToBufferedImage(image));
        HighGui.waitKey(0); // 等待按键
        HighGui.destroyAllWindows();
    }
    // Mat 转 BufferedImage 的辅助方法
    public static BufferedImage matToBufferedImage(Mat mat) {
        int type = BufferedImage.TYPE_BYTE_GRAY;
        if (mat.channels() == 3) {
            type = BufferedImage.TYPE_3BYTE_BGR;
        }
        int bufferSize = mat.channels() * mat.cols() * mat.rows();
        byte[] b = new byte[bufferSize];
        mat.get(0, 0, b); // get all the pixels
        BufferedImage image = new BufferedImage(mat.cols(), mat.rows(), type);
        final byte[] targetPixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
        System.arraycopy(b, 0, targetPixels, 0, b.length);
        return image;
    }
}

代码实现:从摄像头实时检测人脸

import org.opencv.core.*;
import org.opencv.videoio.VideoCapture;
import org.opencv.objdetect.CascadeClassifier;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
public class FaceDetectionWebcam {
    static {
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
    }
    public static void main(String[] args) {
        // 1. 加载分类器
        String classifierPath = "D:/dev/opencv/data/haarcascades/haarcascade_frontalface_alt.xml";
        CascadeClassifier faceDetector = new CascadeClassifier(classifierPath);
        // 2. 打开摄像头
        VideoCapture camera = new VideoCapture(0); // 0 表示默认摄像头
        if (!camera.isOpened()) {
            System.err.println("Cannot open camera");
            return;
        }
        // 3. 创建显示窗口
        JFrame frame = new JFrame("Camera Face Detection");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JLabel label = new JLabel();
        frame.getContentPane().add(label);
        frame.pack();
        frame.setVisible(true);
        Mat frameMat = new Mat();
        while (true) {
            // 4. 读取摄像头一帧
            if (camera.read(frameMat)) {
                // 5. 检测人脸
                MatOfRect faceDetections = new MatOfRect();
                faceDetector.detectMultiScale(frameMat, faceDetections);
                // 6. 画框
                for (Rect rect : faceDetections.toArray()) {
                    Imgproc.rectangle(frameMat, new Point(rect.x, rect.y), new Point(rect.x + rect.width, rect.y + rect.height), new Scalar(0, 255, 0), 3);
                }
                // 7. 显示
                Icon icon = new ImageIcon(matToBufferedImage(frameMat));
                label.setIcon(icon);
                frame.pack();
            }
            // 按 ESC 键退出
            if (HighGui.waitKey(10) == 27) {
                break;
            }
        }
        // 8. 释放资源
        camera.release();
        frame.dispose();
    }
    // Mat 转 BufferedImage 的辅助方法 (同上)
    public static BufferedImage matToBufferedImage(Mat mat) {
        // ... (代码同上)
    }
}

第二部分:人脸识别

人脸识别的目标是识别出“这是谁”,这需要一个训练过程,将人脸数据转换成特征向量,然后通过比较特征向量来判断身份。

opencv java 人脸识别-图3
(图片来源网络,侵删)

我们将使用 OpenCV 自带的 LBPH (Local Binary Patterns Histograms) 人脸识别器,它相对简单且效果好。

流程:

  1. 收集数据:为每个要识别的人准备多张不同角度、表情的照片。
  2. 人脸检测与数据准备:从每张照片中检测人脸,并将其裁剪、缩放到统一大小(100x100)。
  3. 训练识别器:将所有人的脸数据喂给 LBPH 识别器进行训练。
  4. 识别:用训练好的识别器来识别新的人脸。

创建训练数据

你需要一个数据集,一个简单的目录结构如下:

dataset/
├── person_01/
│   ├── 1.jpg
│   ├── 2.jpg
│   └── ...
├── person_02/
│   ├── 1.jpg
│   ├── 2.jpg
│   └── ...
└── ...

person_01 是你要识别的人 A,person_02 是人 B,以此类推,每个文件夹里的图片都应包含该人的清晰正面人脸。

代码实现:训练识别器

这个脚本会读取 dataset 目录,为每个人创建一个标签,然后训练识别器,并将训练好的模型保存为 lbph_face_classifier.yml

import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;
import org.opencv.face.*;
import java.io.File;
import java.io.FilenameFilter;
import java.nio.IntBuffer;
public face.TrainRecognizer {
    static {
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
    }
    public static void main(String[] args) {
        // 1. 加载人脸检测器
        CascadeClassifier faceDetector = new CascadeClassifier("D:/dev/opencv/data/haarcascades/haarcascade_frontalface_alt.xml");
        // 2. 准备训练数据
        File root = new File("dataset");
        File[] personDirs = root.listFiles(File::isDirectory);
        if (personDirs == null || personDirs.length == 0) {
            System.err.println("No person directories found in 'dataset'");
            return;
        }
        // 存储所有人脸图像和对应的标签ID
        MatOfInt labels = new MatOfInt();
        MatOfFloat images = new MatOfFloat(); // 注意:LBPH可以直接使用Mat,这里为了清晰使用MatOfFloat
        // 更正:LBPH应该使用Mat来存储图像数据
        Mat trainingImages = new Mat();
        Mat labelsMat = new Mat();
        int labelId = 0;
        for (File personDir : personDirs) {
            File[] imageFiles = personDir.listFiles((dir, name) -> name.endsWith(".jpg") || name.endsWith(".png"));
            if (imageFiles == null) continue;
            System.out.println("Processing person: " + personDir.getName() + " with ID: " + labelId);
            for (File imageFile : imageFiles) {
                // 读取图片
                Mat image = Imgcodecs.imread(imageFile.getAbsolutePath());
                if (image.empty()) continue;
                // 转换为灰度图
                Mat grayImage = new Mat();
                Imgproc.cvtColor(image, grayImage, Imgproc.COLOR_BGR2GRAY);
                // 检测人脸
                MatOfRect faceDetections = new MatOfRect();
                faceDetector.detectMultiScale(grayImage, faceDetections);
                if (faceDetections.toArray().length > 0) {
                    // 假设每张图只有一张人脸,取第一个
                    Rect face = faceDetections.toArray()[0];
                    Mat faceROI = new Mat(grayImage, face);
                    // 调整大小到统一尺寸,100x100
                    Mat resizedFace = new Mat();
                    Size size = new Size(100, 100);
                    Imgproc.resize(faceROI, resizedFace, size);
                    // 将人脸图像添加到训练数据集中
                    trainingImages.push_back(resizedFace.reshape(1, 1).t());
                    labelsMat.put(labelId, 0, labelId);
                }
            }
            labelId++;
        }
        // 转换为正确的数据类型
        trainingImages.convertTo(trainingImages, CvType.CV_32F);
        labelsMat.convertTo(labelsMat, CvType.CV_32S);
        // 3. 创建并训练 LBPH 识别器
        FaceRecognizer lbphRecognizer = FaceRecognizer.createLBPHFaceRecognizer();
        // 注意:新版本的OpenCV中,FaceRecognizer的API有变化,使用createLBPHFaceRecognizer()
        // 并且训练方法也改了
        lbphRecognizer.train(trainingImages, labelsMat);
        // 4. 保存训练好的模型
        lbphRecognizer.save("lbph_face_classifier.yml");
        System.out.println("Training complete and model saved as lbph_face_classifier.yml");
    }
}

注意:OpenCV Java 的 API 在不同版本间有差异,上面的代码使用了较新的 API (FaceRecognizer.createLBPHFaceRecognizer()train(Mat, Mat)),如果你的版本较旧,可能需要使用 LBPHFaceRecognizer.create()train(MatOfInt, Mat)

代码实现:使用训练好的模型进行识别

现在我们创建一个新程序,加载 lbph_face_classifier.yml 模型,然后对摄像头捕获到的人脸进行识别。

import org.opencv.core.*;
import org.opencv.videoio.VideoCapture;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;
import org.opencv.face.*;
import javax.swing.*;
import java.awt.*;
import java.io.File;
public class FaceRecognizerApp {
    static {
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
    }
    public static void main(String[] args) {
        // 1. 加载人脸检测器和识别器模型
        CascadeClassifier faceDetector = new CascadeClassifier("D:/dev/opencv/data/haarcascades/haarcascade_frontalface_alt.xml");
        FaceRecognizer recognizer = FaceRecognizer.createLBPHFaceRecognizer();
        recognizer.read("lbph_face_classifier.yml");
        // 2. 创建标签到姓名的映射
        // 你需要根据你训练时的情况手动创建这个映射
        // 假设训练时 person_01 的 label 是 0, person_02 的是 1
        String[] names = {"Person A", "Person B", "Unknown"}; // Unknown 用于未识别出的人
        // 3. 打开摄像头
        VideoCapture camera = new VideoCapture(0);
        if (!camera.isOpened()) {
            System.err.println("Cannot open camera");
            return;
        }
        // 4. 创建显示窗口
        JFrame frame = new JFrame("Face Recognition");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JLabel label = new JLabel();
        frame.getContentPane().add(label);
        frame.pack();
        frame.setVisible(true);
        Mat frameMat = new Mat();
        while (true) {
            if (camera.read(frameMat)) {
                Mat grayFrame = new Mat();
                Imgproc.cvtColor(frameMat, grayFrame, Imgproc.COLOR_BGR2GRAY);
                MatOfRect faceDetections = new MatOfRect();
                faceDetector.detectMultiScale(grayFrame, faceDetections);
                for (Rect rect : faceDetections.toArray()) {
                    // 裁剪人脸
                    Mat faceROI = new Mat(grayFrame, rect);
                    Mat resizedFace = new Mat();
                    Imgproc.resize(faceROI, resizedFace, new Size(100, 100));
                    // 进行预测
                    int[] label = new int[1];
                    double[] confidence = new double[1];
                    recognizer.predict(resizedFace, label, confidence);
                    // 获取预测结果
                    String predictedName = names[label[0]];
                    System.out.println("Predicted: " + predictedName + ", Confidence: " + confidence[0]);
                    // 在人脸旁边显示姓名和置信度
                    Imgproc.putText(
                        frameMat,
                        predictedName + " (" + String.format("%.2f", confidence[0]) + ")",
                        new Point(rect.x, rect.y - 10),
                        Imgproc.FONT_HERSHEY_SIMPLEX,
                        0.8,
                        new Scalar(0, 255, 0),
                        2
                    );
                    Imgproc.rectangle(frameMat, new Point(rect.x, rect.y), new Point(rect.x + rect.width, rect.y + rect.height), new Scalar(0, 255, 0), 3);
                }
                // 显示结果
                Icon icon = new ImageIcon(matToBufferedImage(frameMat));
                label.setIcon(icon);
                frame.pack();
            }
            if (HighGui.waitKey(10) == 27) { // ESC to exit
                break;
            }
        }
        camera.release();
        frame.dispose();
    }
    // Mat 转 BufferedImage 的辅助方法 (同上)
    public static BufferedImage matToBufferedImage(Mat mat) {
        // ... (代码同上)
    }
}

总结与进阶

  • 人脸检测是基础:没有准确的人脸检测,后续的识别无从谈起。
  • 数据质量至关重要:人脸识别的准确率高度依赖于训练数据的质量(数量、多样性、清晰度)。
  • LBPH 是一个不错的起点:LBPH 对光照变化不敏感,训练速度快,是入门级人脸识别的绝佳选择。
  • 进阶模型:LBPH 的准确率不能满足你,可以尝试更先进的模型,如 EigenfacesFisherfaces,它们需要更多的训练数据,但效果通常更好,在 OpenCV 中,它们的使用方式与 LBPH 类似,只需在创建识别器时换掉即可(FaceRecognizer.createEigenFaceRecognizer())。
  • 深度学习:对于更高精度的需求,应该使用基于深度学习的模型,如 OpenCV 的 DNN 模块配合 CaffeTensorFlow 训练好的模型(如 Facenet),这会更复杂,但效果是目前最好的。

希望这个详细的教程能帮助你开始 OpenCV Java 人脸识别的旅程!

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