一、为什么需要人脸图片质量检测?

"人脸图片质量检测"功能基于深度神经网络模型训练文件,可智能评估图像中人脸的可识别性,为生物识别、证件核验等场景提供质量管控。通过分析模糊度、光照均匀性、面部遮挡、姿态角度及图像分辨率等核心指标,系统自动生成质量评分并标识具体缺陷。该功能有效解决因低质量图像导致的识别失败问题,可集成于身份认证系统、智能安防平台或影像采集设备,通过前置质量筛查显著提升人脸识别准确率,同时为用户提供实时的拍摄优化建议,降低重复采集成本。

二、如何实现人脸图片质量检测(需求分析)?

2.1 人脸图片质量检测需求分析

人脸图片质量检测本质是对采集过来的图片进行校验,包括:

  • 图片尺寸大小
  • 图片清晰度
  • 图片亮度
  • 是否包含人脸
  • 人脸是否被遮挡
  • 人脸尺寸大小

2.2 检测处理流程

在这里插入图片描述

三、关键技术-javacv

3.1 javacv概述

JavaCV是基于OpenCV、FFmpeg等开源库的Java跨平台接口,提供高效的计算机视觉与多媒体处理能力。它封装了底层C/C++库的复杂操作,支持图像处理(如人脸识别、目标检测)、音视频编解码、流媒体传输等功能,适用于实时视频分析、AI模型集成及多媒体应用开发。通过简化JNI调用,JavaCV让开发者能直接在Java/Kotlin中调用高性能原生算法,广泛应用于安防监控、直播推流、医学影像分析等领域,是Java生态中处理音视频与计算机视觉任务的核心工具之一。
其中javacv提供DNN模块,可以加载深度学习预训练好的模型文件。通过加载预训练的人脸检测模型,很好的实现了人脸检测功能,准确率能到99%以上。

四、实现逻辑

4.1 图片基本信息校验

 public FaceCheckVO checkFacePic(InputStream imageInputStream) throws IOException {
        Mat gayMat = null;
        InputStream standardPic = null;
        try {
            standardPic = convertStandardPic(imageInputStream);
            Mat image = convertMat(ImageIO.read(standardPic));
            if (image == null){
                return new FaceCheckVO(false,"获取图片失败,请重试!");
            }
            // 基本校验
            if(image.width() < minFacePicSize || image.height() < minFacePicSize){
                return new FaceCheckVO(false,"图片高、宽不能小于300");
            }
            //gayMat = cvtColor(picMat);
            FaceCheckVO faceCheckVo = new FaceCheckVO();
            // 人脸检测
            List<Mat> facePicList;
            try {
                facePicList = doCheckFacePic(image);
            }catch (ValidateException e){
                return faceCheckVo.toResult(false,e.getMessage());
            }
            if (facePicList.size() == 0){
                return faceCheckVo.toResult(false,"未检测到人脸");
            }
            if (facePicList.size() > 1){
                return faceCheckVo.toResult(false,"检测到多张人脸,请重新拍摄");
            }

            Mat faceMat = facePicList.get(0);
            // 图片灰度化处理
            gayMat = cvtColor(faceMat);
            //校验亮度
            BrightnessBO brightnessBo = brightnessCheck(gayMat);
            faceCheckVo.setBrightness(brightnessBo.getCast());
            //检测清晰度
            Double definition = DefinitionCheck(gayMat);
            faceCheckVo.setDefinition(definition);

            if (brightnessBo.getResultCode() == 1){
                return faceCheckVo.toResult(false,"人脸区域过暗");
            }
            if (faceCheckVo.getDefinition().doubleValue() < minDefinition){
                return faceCheckVo.toResult(false,"人脸区域清晰度过低");
            }

            return faceCheckVo.toResult(true,"检测通过");
        }finally {
            if (gayMat != null){
                gayMat.release();
            }
            if (standardPic != null){
                standardPic.close();
            }
        }

    }

4.2 加载深度神经网络预训练文件

public void init() throws IOException {
        // opencv本地库加载
        OpencvJavaLocalLoadUtil.loadOpenCvNative();

         // 文件在项目resources包下
        MatOfByte deployProtoMat = getMatOfByteFromLocalFile("D:\\code source\\rider\\face-check\\src\\main\\resources\\dnn\\deploy.prototxt");
         // 文件在项目resources包下
        InputStream ins = new FileInputStream("D:\\code source\\rider\\face-check\\src\\main\\resources\\dnn\\res10_300x300_ssd_iter_140000.caffemodel");
        ByteArrayOutputStream ous = new ByteArrayOutputStream();
        FileUtil.inputStreamToOutputStream(ins,ous);
        MatOfByte caffeModelMat = new MatOfByte(ous.toByteArray());
        ins.close();
        net = Dnn.readNetFromCaffe(deployProtoMat, caffeModelMat);
    }

加载opencv:

/**
 * @Description:
 * @Create: Date:2025年02月28日
 */
public class OpencvJavaLocalLoadUtil {
    private static final Logger log = LoggerFactory.getLogger(OpencvJavaLocalLoadUtil.class);
    private static boolean isLoad = false;

    public static void loadOpenCvNative(){
        if (isLoad){
            return;
        }
        isLoad = true;
        try {
            System.setProperty("org.bytedeco.javacpp.maxphysicalbytes", "0");
            System.setProperty("org.bytedeco.javacpp.maxbytes", "0");
            Loader.load(opencv_java.class);
        }catch (Exception e){
            log.warn("加载opencn_java异常:",e);
        }
    }
}

4.3 人脸图片检测并裁剪(用于下一步分析)

 public List<Mat> faceCheck(Mat image) throws IOException {
        List<Mat> faceMatList = new ArrayList<>();
        //为了获得最佳精度,必须分别对蓝色、绿色和红色通道执行 `(104, 177, 123)` 通道均值减法
        Mat inputBlob = Dnn.blobFromImage(image, 1.0f,
                new Size(200, 200),
                new Scalar(104, 117, 123), false, false);
        net.setInput(inputBlob);
        Mat res = net.forward();
        Mat faces = res.reshape(1, res.size(2));
        for (int i=0; i<faces.rows(); i++) {
            float [] data = new float[7];
            faces.get(i, 0, data);
            float confidence = data[2];
            if (confidence > minConfidence) {
                int left   = (int)(data[3] * image.cols());
                int top    = (int)(data[4] * image.rows());
                int right  = (int)(data[5] * image.cols());
                int bottom = (int)(data[6] * image.rows());
                if (left < 0 || top < 0){
                    throw new ValidateException("人脸区域部分被遮挡");
                }
                //Imgproc.rectangle(image, new Point(left,top), new Point(right,bottom), new Scalar(0,200,0), 3);
                //截取的区域:参数,坐标X,坐标Y,截图宽度,截图长度,bottom-top
                log.info("image width:{},image height:{}",image.width(),image.height());
                log.info("left:{},top:{},right:{},bottom:{}",left,top,right,bottom);
                log.info("face width:{},face height:{}",right-left,bottom-top);
                int faceWidth = right-left;
                int faceHeight = bottom-top;
                if (faceWidth > image.width() || faceHeight > image.height()){
                    throw new ValidateException("人脸区域部分被遮挡");
                }
                if ((top+faceHeight) > image.height()){
                    throw new ValidateException("人脸区域部分被遮挡");
                }
                if ((left+faceWidth) > image.width()){
                    throw new ValidateException("人脸区域部分被遮挡");
                }
                Rect rect = new Rect (left,top,faceWidth, faceHeight);
                Mat sub = image.submat(rect);
                MatOfByte mob=new MatOfByte();
                Imgcodecs.imencode(".jpg", sub, mob);
                ByteArrayInputStream in = new ByteArrayInputStream(mob.toArray());
                Mat faceMat = convertMat(ImageIO.read(in));
                faceMatList.add(faceMat);
            }
        }
        return faceMatList;
    }

    /**
     * bufferedImage convert mat
     * @param im
     * @return
     */
    private static Mat convertMat(BufferedImage im) {
        // Convert INT to BYTE
        //im = toBufferedImageOfType(im, BufferedImage.TYPE_3BYTE_BGR);
        byte[] pixels = ((DataBufferByte) im.getRaster().getDataBuffer())
                .getData();
        // Create a Matrix the same size of image
        Mat image = new Mat(im.getHeight(), im.getWidth(), 16);
        // Fill Matrix with image values
        image.put(0, 0, pixels);
        return image;
    }

4.4 人脸图片区域校验

 Mat faceMat = facePicList.get(0);
            // 图片灰度化处理
            gayMat = cvtColor(faceMat);
            //校验亮度
            BrightnessBO brightnessBo = brightnessCheck(gayMat);
            faceCheckVo.setBrightness(brightnessBo.getCast());
            //检测清晰度
            Double definition = DefinitionCheck(gayMat);
            faceCheckVo.setDefinition(definition);

            if (brightnessBo.getResultCode() == 1){
                return faceCheckVo.toResult(false,"人脸区域过暗");
            }
            if (faceCheckVo.getDefinition().doubleValue() < minDefinition){
                return faceCheckVo.toResult(false,"人脸区域清晰度过低");
            }

            return faceCheckVo.toResult(true,"检测通过");

4.5 运行效果

检测样例(网图)

在这里插入图片描述
检测结果:
在这里插入图片描述

五、结语

以上实现了人脸图片质量检测的从分析到技术实现完整流程,并且在生产环境得到了很好的效果,在人脸图片采集的质量上得到很大的提升。有兴趣学习或刚好有这类需求的同学,点赞、关注私信我发源码项目,相互学习。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐