Skip to content

Latest commit

 

History

History
395 lines (289 loc) · 12 KB

105.springboot_opencv_face_detect.md

File metadata and controls

395 lines (289 loc) · 12 KB

SpringBoot 集成 OpenCV 实现人脸检测功能

1 摘要

OpenCV 作为一个世界上最大的计算机视觉库,里边包含了非常丰富的函数,开箱即用。不过 OpenCV 都是以应用程序直接运行的方式在计算机上运行,要想通过各种编程语言来操作,就需要开发者自行适配。本文将介绍基于 Java 语言 SpringBoot 框架集成 OpenCV 实现图片人脸检测功能。

OpenCV 官网: https://opencv.org

市面上集成 OpenCV 的 java 库有不少,这里推荐两个用户比较多的:

JavaCV: https://github.com/bytedeco/javacv

JavaCV 是一个综合的 Java 库,提供统一的操作方法,除了集成了 OpenCV 之外还包括 FFmpeg 等众多出名的算法库。

OpenPnP: https://github.com/openpnp/opencv

OpenPnP-OpenCV 是由 OpenPnP 组织提供的 OpenCV 的 Java 适配版本,这个是只有 OpenCV 的接口,依赖体积小。

本文使用 OpenPnP 提供的 Java 库。

2 核心 Maven 依赖

demo-opencv/pom.xml
        <!--  openCV 函数库 -->
        <dependency>
            <groupId>org.openpnp</groupId>
            <artifactId>opencv</artifactId>
            <version>${openpnp-opencv.version}</version>
        </dependency>

其中 openpnp-opencv 的版本为:

<openpnp-opencv.version>4.9.0-0</openpnp-opencv.version>

这里 OpenPnP 的版本与 openCV 官方的版本保持了一致。

3 核心代码

3.1 人脸特征值匹配文件

人脸特征值匹配文件:

demo-opencv/src/main/resources/opencv/haarcascade_frontalface_alt.xml

这个文件比较大,这里就不展示代码了,需要的直接到 OpenCV 官方下载。

OpenCV 官方人脸特征值文件: https://github.com/opencv/opencv/blob/master/data/haarcascades/haarcascade_frontalface_alt.xml

3.2 人脸检测工具类

demo-opencv/src/main/java/com/ljq/demo/springboot/opencv/common/util/FaceDetectUtil.java
package com.ljq.demo.springboot.opencv.common.util;


import lombok.extern.slf4j.Slf4j;
import nu.pattern.OpenCV;
import org.opencv.core.Mat;
import org.opencv.core.MatOfByte;
import org.opencv.core.MatOfRect;
import org.opencv.core.Rect;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.objdetect.CascadeClassifier;
import org.springframework.util.ResourceUtils;

import java.io.File;
import java.util.Objects;

/**
 * @Description: 人脸检测工具
 * @Author: junqiang.lu
 * @Date: 2024/3/15
 */
@Slf4j
public class FaceDetectUtil {

    private static  CascadeClassifier faceDetector;


    /**
     * 初始化 opencv
     *
     * @param isDebug
     * @return
     */
    public static CascadeClassifier init(boolean isDebug) {
        if (Objects.isNull(faceDetector) || faceDetector.empty()) {
            synchronized (FaceDetectUtil.class) {
                if (Objects.isNull(faceDetector) || faceDetector.empty()) {
                    try {
                        // 加载 openCV 函数库
                        OpenCV.loadShared();
                        log.info("opencv 函数库加载成功");
                        // 初始化人脸探测器
                        faceDetector = new CascadeClassifier();
                        String xmlFilePath =  "opencv" + File.separator + "haarcascade_frontalface_alt.xml";
                        boolean detectorInitResult = false;
                        if (isDebug) {
                            File xmlFile = ResourceUtils.getFile("classpath:" + xmlFilePath);
                            detectorInitResult = faceDetector.load(xmlFile.getAbsolutePath());
                        } else {
                            detectorInitResult = faceDetector.load(System.getProperty("user.dir") + File.separator + xmlFilePath);
                        }
                        log.info("opencv 人脸检测工具加载结果: {}", detectorInitResult);
                    } catch (Exception e) {
                        log.error("opencv face check init error", e);
                    }
                }
            }
        }
        return faceDetector;
    }


    /**
     * 检测本地照片中是否包含人脸
     *
     * @param imgPath
     * @param isDebug
     * @return
     */
    public static boolean detectFace(String imgPath, boolean isDebug) {
        init(isDebug);
        // 读取图片
        Mat image = Imgcodecs.imread(imgPath);
        return detectFace(image, isDebug);
    }

    /**
     * 检测照片中是否包含人脸
     *
     * @param byteArray
     * @param isDebug
     * @return
     */
    public static boolean detectFace(byte[] byteArray, boolean isDebug) {
        init(isDebug);
        // 读取图片
        Mat image = Imgcodecs.imdecode(new MatOfByte(byteArray), Imgcodecs.IMREAD_UNCHANGED);
        return detectFace(image, isDebug);
    }

    /**
     * 检测照片中是否包含人脸
     *
     * @param image
     * @param isDebug
     * @return
     */
    public static boolean detectFace(Mat image, boolean isDebug) {
        // 读取图片
        if (image.empty()) {
            return false;
        }
        // 人脸特征值匹配
        MatOfRect face = new MatOfRect();
        init(isDebug).detectMultiScale(image, face);
        // 特征匹配
        Rect[] rects = face.toArray();
        if (rects.length > 0) {
            return true;
        }
        return false;
    }




}

这里提供了三种图片人脸检测的方式,分别是本地文件,图片字节数组,以及 opencv 的 org.opencv.core.Mat 对象。

3.3 人脸检测 Controller

根据真实业务场景,用户上传图片,然后进行人脸检测。

demo-opencv/src/main/java/com/ljq/demo/springboot/opencv/controller/FaceDetectController.java
package com.ljq.demo.springboot.opencv.controller;

import com.ljq.demo.springboot.opencv.common.config.OpencvConfig;
import com.ljq.demo.springboot.opencv.common.util.FaceDetectUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.IOException;

/**
 * @Description: 人脸检测控制层
 * @Author: junqiang.lu
 * @Date: 2024/3/18
 */
@Slf4j
@RequestMapping(value = "/api/opencv")
@RestController
public class FaceDetectController {

    @Resource
    private OpencvConfig opencvConfig;

    /**
     * 人脸检测
     *
     * @param file
     * @return
     */
    @PostMapping(value = "/face/detect", produces = {MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity<Object> detectFace(MultipartFile file){
        boolean flag = false;
        try {
            flag = FaceDetectUtil.detectFace(file.getBytes(), opencvConfig.getOpencvDebug());
            log.info("图片人脸检测结果: {}", flag);
        } catch (IOException e) {
            log.error("图片读取错误", e);
        }
        return ResponseEntity.ok(flag);
    }


}

3.4 其他相关代码

3.4.1 OpenCV 的配置类

demo-opencv/src/main/java/com/ljq/demo/springboot/opencv/common/config/OpencvConfig.java
package com.ljq.demo.springboot.opencv.common.config;

import lombok.Getter;
import lombok.ToString;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

/**
 * @Description: opencv 配置
 * @Author: junqiang.lu
 * @Date: 2024/3/18
 */
@ToString
@Getter
@Configuration
public class OpencvConfig {

    /**
     * 是否开启 opencv 调试模式
     * true: 从项目resource文件中加载人脸特征xml,实际部署,以 jar 启动无法获取到
     * false: 从本地文件加载人脸特征xml
     */
    @Value("${opencv.debug:false}")
    private Boolean opencvDebug;


}

3.4.2 application 配置文件

demo-opencv/src/main/resources/application.yml
# config
server:
  port: 9211


# spring
spring:
  application:
    name: demo-opencv
  servlet:
    multipart:
      max-file-size: 1000MB
      max-request-size: 1000MB


# opencv properties
opencv:
  debug: true

4 注意事项

4.1 人脸特征值匹配文件

OpenCV 函数库读取配置文件是必须按照文件的绝对路径来读取的,对于在 jar 包内的配置文件,是无法读取到的。因此在实际项目的部署中必须将人脸特征匹配文件外置。这也是上边专门设置一个 opencv 配置类的原因。

4.2 OpenCV 依赖的 GLIBC 版本问题

OpenCV 4.9 依赖的 GLIBC 版本为 2.27,CentOS 7 系统自带的 GLIBC 版本为 2.17,如果不手动安装高版本 GLIBC,程序将无法在 CentOS 7 上边运行。Windows 7 系统也无法运行,Windows 10 系统可以。

如果要在 CentOS 7 系统上直接运行,可以选择依赖的版本为 3.4.2-2。

作为 CentOS 系统的平行替代, Alma Linux 9.3 系统自带的 GLIBC 版本为 2.34,可以直接支持 OpenCV 4.9 运行。

Linux 系统查看 GLIBC 版本命令:

ldd --version

Linux 系统查看系统支持的 GLIBC 版本列表:

strings  /lib64/libc.so.6 | grep GLIBC_

低版本 GLIBC 运行程序时抛出的异常如下:

2024-05-08 11:05:09 | ERROR | http-nio-9211-exec-1 | org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/].[dispatcherServlet] 175 | Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.UnsatisfiedLinkError: /tmp/opencv_openpnp7898379110605450114/nu/pattern/opencv/linux/x86_64/libopencv_java490.so: /lib64/libm.so.6: version `GLIBC_2.27' not found (required by /tmp/opencv_openpnp7898379110605450114/nu/pattern/opencv/linux/x86_64/libopencv_java490.so)] with root cause
java.lang.UnsatisfiedLinkError: /tmp/opencv_openpnp7898379110605450114/nu/pattern/opencv/linux/x86_64/libopencv_java490.so: /lib64/libm.so.6: version `GLIBC_2.27' not found (required by /tmp/opencv_openpnp7898379110605450114/nu/pattern/opencv/linux/x86_64/libopencv_java490.so)
        at java.lang.ClassLoader$NativeLibrary.load(Native Method)
        at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1941)
        at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1857)
        at java.lang.Runtime.loadLibrary0(Runtime.java:870)
        at java.lang.System.loadLibrary(System.java:1122)
        at nu.pattern.OpenCV$SharedLoader.<init>(OpenCV.java:225)
        at nu.pattern.OpenCV$SharedLoader.<init>(OpenCV.java:189)
        at nu.pattern.OpenCV$SharedLoader$Holder.<clinit>(OpenCV.java:261)
        at nu.pattern.OpenCV$SharedLoader.getInstance(OpenCV.java:265)
        at nu.pattern.OpenCV.loadShared(OpenCV.java:183)
        at com.ljq.demo.springboot.opencv.common.util.FaceCheckUtil.init(FaceCheckUtil.java:40)
        at com.ljq.demo.springboot.opencv.common.util.FaceCheckUtil.checkFace(FaceCheckUtil.java:85)
        at com.ljq.demo.springboot.opencv.controller.FaceCheckController.checkFaceOpenpnp(FaceCheckController.java:39)

5 推荐参考资料

OpenPnP OpenCV Github

Intro to OpenCV with Java--Baeldung

Capturing Image From Webcam in Java -- Baeldung

6 本次代码提交记录

commit 057b58a7350b2e1f5ece9d6ffb5781ec70bd6fed (HEAD -> dev, origin/master, origin/dev, origin/HEAD, master)
Author: Flying9001 <flying9001@gmail.com>
Date:   Wed May 8 14:32:42 2024 +0800

    代码-新增 SpringBoot 集成 OpenCV 实现图片人脸检测功能

版本回退命令

git reset --soft 057b58a7350b2e1f5ece9d6ffb5781ec70bd6fed