Java实现证件照质量检测(检测结果导出为EXCEL)

Java实现证件照质量检测

以下是一个使用Java实现的证件照质量检测程序,主要使用OpenCV库进行图像处理和分析:

  1. 项目依赖

首先需要在pom.xml中添加OpenCV依赖:

<dependencies>
    <dependency>
        <groupId>org.openpnp</groupId>
        <artifactId>opencv</artifactId>
        <version>4.5.1-2</version>
    </dependency>
</dependencies>

2.证件照质量检测主类

import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;
import org.springframework.boot.system.ApplicationHome;
import org.springframework.util.ResourceUtils;

import java.io.File;
import java.net.URLDecoder;
import java.util.*;
import java.util.List;
import org.apache.poi.ss.usermodel.*;

import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;

import static com.dx.utils.DateUtils.getDate;


public class IDphotoUtils {
    String rootPath = "";

    {
        try {
            String osName = System.getProperties().getProperty("os.name");
            System.out.println(osName);
            if(osName.equals("Linux"))
            {
                ApplicationHome home = new ApplicationHome(getClass());
                File jarFile = home.getSource();
                rootPath = jarFile.getParentFile().toString();
            }
            else
            {
                rootPath = ResourceUtils.getURL("classpath:").getPath() + "static";
                rootPath = URLDecoder.decode(rootPath,"utf-8");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 静态常量:证件照标准尺寸映射(key为尺寸类型,value为宽×高,单位:毫米)
     * 包含1寸、2寸、护照、签证四种常见尺寸
     */
    private static final Map<String, Size> STANDARD_SIZES = new HashMap<>();
    static {
        STANDARD_SIZES.put("1inch", new Size(25, 35));     // 1寸:25mm × 35mm
        STANDARD_SIZES.put("2inch", new Size(35, 49));     // 2寸:35mm × 49mm
        STANDARD_SIZES.put("passport", new Size(33, 48));  // 护照:33mm × 48mm
        STANDARD_SIZES.put("visa", new Size(35, 45));      // 签证:35mm × 45mm
    }

    /**
     * 静态常量:质量标准阈值(用于判断各维度是否合格)
     * 包含亮度、对比度、清晰度、背景噪声、人脸占比的上下限
     */
    private static final int MIN_BRIGHTNESS = 100;    // 最低亮度阈值
    private static final int MAX_BRIGHTNESS = 200;    // 最高亮度阈值
    private static final int MIN_CONTRAST = 40;       // 最低对比度阈值
    private static final int MIN_SHARPNESS = 50;      // 最低清晰度阈值
    private static final int MAX_NOISE = 40;          // 最高背景噪声阈值
    private static final double FACE_RATIO_MIN = 0.10;// 人脸占图像最小比例(15%)
    private static final double FACE_RATIO_MAX = 0.45;// 人脸占图像最大比例(25%)

    // 人脸检测器(基于OpenCV的Haar级联分类器)
    private CascadeClassifier faceDetector;

    /**
     * 构造方法:初始化OpenCV本地库与人脸检测器
     * 注意:当前人脸检测器未实际加载(load方法被注释),需在实际使用时打开
     */
    public IDphotoUtils() {
        // 加载OpenCV本地库(nu.pattern.OpenCV为第三方封装,简化库加载流程)
        nu.pattern.OpenCV.loadLocally();

        // 初始化人脸检测器:拼接haar分类器文件路径(根路径+分类器文件名)
        // String haarPath = "path/to/haarcascade_frontalface_default.xml"; // 示例路径
        String haarPath = "C:\\Users\\Administrator\\Desktop\\update\\haarcascade_frontalface_default.xml" ;// 实际路径(Windows分隔符)
        // 在实际使用中,需要提供正确的haar分类器路径(确保文件存在)
//        System.err.println(haarPath);
//        this.faceDetector = new CascadeClassifier(haarPath); // 实例化人脸检测器
//        this.faceDetector.load(haarPath); // 注释:未加载分类器文件,当前人脸检测为模拟逻辑
        this.faceDetector = new CascadeClassifier();
        // 加载模型文件
        boolean isLoaded = this.faceDetector.load(haarPath);
        if (!isLoaded) {
            throw new RuntimeException("人脸模型加载失败,请检查路径:" + haarPath);
        }

    }

    /**
     * 加载图像文件
     * @param imagePath 图像文件路径(绝对路径)
     * @return 加载后的OpenCV图像对象(Mat)
     * @throws IllegalArgumentException 若文件不存在或无法读取,抛出参数异常
     */
    public Mat loadImage(String imagePath) {
        File file = new File(imagePath);
        // 校验文件是否存在
        if (!file.exists()) {
            throw new IllegalArgumentException("图片文件不存在: " + imagePath);
        }

        // 用OpenCV的Imgcodecs读取图像(支持常见格式如JPG/PNG)
        Mat image = Imgcodecs.imread(imagePath);
        // 校验图像是否读取成功(empty()返回true表示读取失败)
        System.err.println("--------------"+image.empty());
        if (image.empty()) {
            throw new IllegalArgumentException("无法读取图片文件: " + imagePath);
        }

        return image;
    }

    /**
     * 检查图像分辨率是否符合证件照标准
     * @param image 待检查的图像对象(Mat)
     * @param dpi 图像的DPI(每英寸像素数,默认300)
     * @return 分辨率检查结果(包含像素尺寸、实际英寸尺寸、是否达标)
     */
    public ResolutionResult checkResolution(Mat image, int dpi) {
        Size size = image.size(); // 获取图像像素尺寸(宽×高)
        double widthPixels = size.width;  // 像素宽度
        double heightPixels = size.height; // 像素高度

        // 计算实际物理尺寸(英寸):像素数 / DPI
        double widthInch = widthPixels / dpi;
        double heightInch = heightPixels / dpi;

        // 判断是否符合标准:宽≥1英寸且高≥1.4英寸(对应常见证件照最小尺寸)
        boolean meetsStandard = widthInch >= 1 && heightInch >= 1.4;

        // 返回封装后的检查结果
        return new ResolutionResult(
                (int) widthPixels, (int) heightPixels,
                widthInch, heightInch, dpi, meetsStandard
        );
    }

    /**
     * 检查图像的亮度与对比度
     * @param image 待检查的图像对象(Mat,BGR格式)
     * @return 亮度对比度检查结果(包含具体数值与是否合格)
     */
    public BrightnessContrastResult checkBrightnessContrast(Mat image) {
        // 1. 将BGR图像转为灰度图像(亮度与对比度分析基于灰度图)
        Mat gray = new Mat();
        Imgproc.cvtColor(image, gray, Imgproc.COLOR_BGR2GRAY);

        // 2. 计算亮度:灰度图像的平均像素值(均值越大,亮度越高)
        Scalar mean = Core.mean(gray);
        double brightness = mean.val[0]; // Scalar.val[0]为灰度图的均值

        // 3. 计算对比度:灰度图像的标准差(标准差越大,对比度越高)
        MatOfDouble meanMat = new MatOfDouble(); // 存储均值(备用)
        MatOfDouble stdMat = new MatOfDouble();  // 存储标准差
        Core.meanStdDev(gray, meanMat, stdMat);  // 计算均值与标准差
        double contrast = stdMat.get(0, 0)[0];   // 获取标准差数值

        // 4. 判断是否合格:亮度在[100,200]之间,对比度≥40
        boolean brightnessOk = brightness >= MIN_BRIGHTNESS && brightness <= MAX_BRIGHTNESS;
        boolean contrastOk = contrast >= MIN_CONTRAST;

        // 返回封装后的检查结果
        return new BrightnessContrastResult(brightness, contrast, brightnessOk, contrastOk);
    }


    /**
     * 检查图像清晰度(模糊程度)
     * 原理:拉普拉斯算子检测边缘,方差越大,边缘越清晰(图像越清晰)
     * @param image 待检查的图像对象(Mat,BGR格式)
     * @return 清晰度检查结果(包含清晰度分数与是否合格)
     */
    public SharpnessResult checkSharpness(Mat image) {
        // 1. 转为灰度图像(清晰度分析基于灰度图)
        Mat gray = new Mat();
        Imgproc.cvtColor(image, gray, Imgproc.COLOR_BGR2GRAY);

        // 2. 应用拉普拉斯算子(64位浮点型,保留边缘细节)
        Mat laplacian = new Mat();
        Imgproc.Laplacian(gray, laplacian, CvType.CV_64F);

        // 3. 计算拉普拉斯结果的均值与标准差
        MatOfDouble mean = new MatOfDouble();
        MatOfDouble std = new MatOfDouble();
        Core.meanStdDev(laplacian, mean, std);

        // 4. 计算清晰度分数:标准差的平方(放大差异,便于判断)
        double sharpness = Math.pow(std.get(0, 0)[0], 2);
        // 判断是否合格:清晰度分数≥50
        boolean sharpnessOk = sharpness >= MIN_SHARPNESS;

        // 返回封装后的检查结果
        return new SharpnessResult(sharpness, sharpnessOk);
    }

    /**
     * 检查背景均匀性(是否有杂色/噪声)
     * 原理:取图像四个边缘区域,计算像素值标准差,标准差越小,背景越均匀
     * @param image 待检查的图像对象(Mat,BGR格式)
     * @return 背景检查结果(包含均匀性分数与是否合格)
     */
    public BackgroundResult checkBackground(Mat image) {
        // 获取图像的高度(rows)与宽度(cols)
        int h = image.rows();
        int w = image.cols();

        // 定义四个边缘区域(各占图像的1/10大小)
        List<Mat> borderRegions = new ArrayList<>();
        borderRegions.add(image.submat(0, h/10, 0, w)); // 上边:顶部1/10高度
        borderRegions.add(image.submat(h - h/10, h, 0, w));  // 下边:底部1/10高度
        borderRegions.add(image.submat(0, h, 0, w/10)); // 左边:左侧1/10宽度
        borderRegions.add(image.submat(0, h, w - w/10, w));  // 右边:右侧1/10宽度

        // 计算所有边缘区域的标准差平均值
        double totalStd = 0;
        int validRegions = 0; // 有效区域计数(避免空区域影响结果)

        for (Mat region : borderRegions) {
            if (!region.empty()) { // 跳过空区域
                MatOfDouble mean = new MatOfDouble();
                MatOfDouble std = new MatOfDouble();
                Core.meanStdDev(region, mean, std); // 计算区域的均值与标准差
                totalStd += std.get(0, 0)[0]; // 累加标准差
                validRegions++;
            }
        }

        // 计算平均背景标准差(均匀性分数:分数越低,背景越均匀)
        double avgBackgroundStd = validRegions > 0 ? totalStd / validRegions : 0;
        // 判断是否合格:平均标准差≤30(噪声不超过阈值)
        boolean backgroundOk = avgBackgroundStd <= MAX_NOISE;

        // 返回封装后的检查结果
        return new BackgroundResult(avgBackgroundStd, backgroundOk);
    }

    /**
     * 释放 OpenCV Mat 资源
     */
    /**
     * 释放 OpenCV Mat 资源(兼容所有版本)
     */
    private void releaseResources(Mat... mats) {
        for (Mat mat : mats) {
            // 检查 mat 不为 null,且未释放(用 empty() 替代 isReleased())
            if (mat != null && !mat.empty()) {
                mat.release(); // 释放资源
            }
        }
    }


    /**
     * 检测图像中的人脸(位置、数量、尺寸占比)
     * 注意:当前为模拟逻辑(分类器未加载),实际使用需打开faceDetector.load()
     * @param image 待检查的图像对象(Mat,BGR格式)
     * @return 人脸检测结果(包含是否检测到人脸、位置、尺寸是否合格)
     */
    public FaceDetectionResult detectFace(Mat image) {
        // 1. 转为灰度图像(人脸检测基于灰度图,减少计算量)
        Mat gray = new Mat();
        Imgproc.cvtColor(image, gray, Imgproc.COLOR_BGR2GRAY);

        // 2. 图像预处理:转为灰度图(减少计算量,符合模型要求)

        // 3. (可选)直方图均衡化:增强对比度,提升检测效果
//        Imgproc.equalizeHist(grayImage, grayImage);

        // 4. 检测人脸:结果存入 MatOfRect
        MatOfRect faces = new MatOfRect();
        faceDetector.detectMultiScale(
                gray,       // 输入灰度图
                faces       // 输出:人脸矩形集合
//                1.1,             // 缩放因子(每次缩小 10%)
//                5,               // 最小邻域数(值越大,检测越严格)
//                0,               // 旧版本参数,设为 0 即可
//                new Size(50, 50),// 最小人脸尺寸(过滤过小区域)
//                new Size()       // 最大人脸尺寸(无限制)
        );

        // 5. 解析检测结果
        Rect[] faceArray = faces.toArray();
        boolean faceDetected = faceArray.length > 0;
        int faceCount = faceArray.length;

        // 若未检测到人脸,直接返回结果
        if (!faceDetected) {
            releaseResources(image, gray, faces);
            return new FaceDetectionResult(false, 0, 0, false, false, null);
        }

        // 6. 分析第一张人脸(默认取最大/最可能的人脸,证件照通常只有一张)
        Rect mainFace = faceArray[0];  // 取第一个检测到的人脸
        int imgWidth = image.cols();   // 图像宽度
        int imgHeight = image.rows();  // 图像高度

        // 计算人脸占比(面积比)
        double faceArea = mainFace.width * mainFace.height;
        double imgArea = imgWidth * imgHeight;
        double faceRatio = faceArea / imgArea;

        // 检查人脸位置是否居中(计算人脸中心与图像中心的偏移)
        Point imgCenter = new Point(imgWidth / 2, imgHeight / 2);  // 图像中心
        Point faceCenter = new Point(
                mainFace.x + mainFace.width / 2,
                mainFace.y + mainFace.height / 2
        );  // 人脸中心
        double offset = Math.sqrt(
                Math.pow(faceCenter.x - imgCenter.x, 2) +
                        Math.pow(faceCenter.y - imgCenter.y, 2)
        );  // 偏移距离(欧几里得距离)
        boolean positionOk = offset < Math.min(imgWidth, imgHeight) * 0.1;  // 偏移 < 10% 最小边长

        // 检查人脸大小是否在合格范围
        boolean sizeOk = faceRatio >= FACE_RATIO_MIN && faceRatio <= FACE_RATIO_MAX;

        // 7. 释放资源(避免内存泄漏)、
        releaseResources(image, gray, faces);
        // 8. 返回封装结果
        return new FaceDetectionResult(
                true, faceCount, faceRatio, positionOk, sizeOk, mainFace
        );
//        // 2. 实际人脸检测逻辑(当前注释,需加载分类器后启用)
//         MatOfRect faces = new MatOfRect(); // 存储检测到的人脸矩形
//
//         faceDetector.detectMultiScale(gray, faces); // 多尺度检测人脸
//
//        // 3. 模拟人脸检测结果(替代实际检测,便于测试)
////        boolean faceDetected = true; // 模拟:检测到人脸(实际应为faces.toArray().length > 0)
//        boolean faceDetected = faces.toArray().length > 0 ;
////        int faceCount = 1; // 模拟:检测到1张人脸(实际应为faces.toArray().length)
//        int faceCount = faces.toArray().length;
//
//        // 若未检测到人脸,直接返回失败结果
//        if (!faceDetected) {
//            return new FaceDetectionResult(false, 0, 0, false, false, null);
//        }
//
//        // 4. 模拟人脸区域(实际应从faces中获取Rect对象)
//        int imgHeight = image.rows(); // 图像高度
//        int imgWidth = image.cols();  // 图像宽度
////        int faceWidth = (int) (imgWidth * 0.2);  // 人脸宽度:图像宽度的20%
////        int faceHeight = (int) (imgHeight * 0.3); // 人脸高度:图像高度的30%
//        int faceWidth = faces.width();
//        int faceHeight= faces.height();
//        System.err.println("人脸宽度*高度: "+faceWidth +" * " + faceHeight);
//        int faceX = (imgWidth - faceWidth) / 2;   // 人脸X坐标:水平居中
//        int faceY = (imgHeight - faceHeight) / 3; // 人脸Y坐标:垂直偏上(1/3高度处)
//        Rect faceRect = new Rect(faceX, faceY, faceWidth, faceHeight); // 人脸矩形
//
//        // 5. 计算人脸占比:人脸面积 / 图像面积
//        double faceRatio = (faceWidth * faceHeight) / (double) (imgWidth * imgHeight);
//
//        // 6. 检查人脸位置是否居中
//        int centerX = imgWidth / 2;          // 图像中心点X坐标
//        int centerY = imgHeight / 2;         // 图像中心点Y坐标
//        int faceCenterX = faceX + faceWidth / 2; // 人脸中心点X坐标
//        int faceCenterY = faceY + faceHeight / 2; // 人脸中心点Y坐标
//        // 计算人脸中心与图像中心的距离(欧几里得距离)
//        double positionOffset = Math.sqrt(
//                Math.pow(faceCenterX - centerX, 2) + Math.pow(faceCenterY - centerY, 2)
//        );
//        // 位置合格条件:偏移距离 < 图像最小边长的10%
//        boolean positionOk = positionOffset < Math.min(imgWidth, imgHeight) * 0.1;
//        // 尺寸合格条件:人脸占比在[15%,25%]之间
//        boolean sizeOk = faceRatio >= FACE_RATIO_MIN && faceRatio <= FACE_RATIO_MAX;
//
//        // 返回封装后的人脸检测结果
//        return new FaceDetectionResult(
//                true, faceCount, faceRatio, positionOk, sizeOk, faceRect
//        );
    }

    /**
     * 检查图像的色彩平衡(判断是否存在偏色,如偏红、偏蓝等)
     * 原理:通过分析RGB三通道的平均亮度差异,差异越小说明色彩越平衡
     * @param image 待检查的图像(OpenCV的Mat对象,BGR格式,注意OpenCV默认通道顺序为BGR而非RGB)
     * @return 色彩平衡检查结果(包含平衡分数和是否合格的判断)
     */
    public ColorBalanceResult checkColorBalance(Mat image) {
        // 1. 分离图像的BGR三通道(OpenCV中图像默认存储为BGR格式,而非RGB)
        if (image.empty()) {
            System.err.println("错误:图像读取失败,路径可能错误或文件损坏。");
        }

        List<Mat> channels = new ArrayList<>();
        Core.split(image, channels); // 将图像拆分为单个通道,存储到List中

        // 2. 分别获取B、G、R通道(注意索引对应顺序:0=Blue,1=Green,2=Red)
        Mat b = channels.get(0);  // 蓝色通道
        Mat g = channels.get(1);  // 绿色通道
        Mat r = channels.get(2);  // 红色通道

        // 3. 计算每个通道的平均像素值(反映该颜色通道的整体亮度)
        Scalar meanB = Core.mean(b);  // 蓝色通道的均值(Scalar是OpenCV中存储多元素值的结构)
        Scalar meanG = Core.mean(g);  // 绿色通道的均值
        Scalar meanR = Core.mean(r);  // 红色通道的均值

        // 4. 提取均值的数值(Scalar.val[0]表示该通道均值的具体数值)
        double avgB = meanB.val[0];  // 蓝色通道平均亮度
        double avgG = meanG.val[0];  // 绿色通道平均亮度
        double avgR = meanR.val[0];  // 红色通道平均亮度

        // 5. 计算三通道中最亮(maxAvg)和最暗(minAvg)的平均亮度
        double maxAvg = Math.max(avgB, Math.max(avgG, avgR));  // 取三通道最大值
        double minAvg = Math.min(avgB, Math.min(avgG, avgR));  // 取三通道最小值

        // 6. 计算色彩平衡分数:(最亮 - 最暗) / 最亮
        // 含义:差异占最亮通道的比例,值越小说明三通道亮度越接近,色彩越平衡
        // 特殊处理:若最亮通道为0(全黑图像),则平衡分数为0
        double colorBalance = maxAvg > 0 ? (maxAvg - minAvg) / maxAvg : 0;

        // 7. 判断色彩是否合格:允许最大30%的差异(即平衡分数<0.3)
        boolean colorOk = colorBalance < 0.3;

        // 返回封装后的色彩平衡检查结果
        return new ColorBalanceResult(colorBalance, colorOk);
    }

    /**
     * 完整的证件照质量校验方法
     * 功能:整合所有单项检查(分辨率、亮度、人脸等),生成综合校验结果
     * @param imagePath 待校验的证件照文件路径
     * @param dpi 图像的DPI(每英寸像素数),可为null(默认300)
     * @return 包含所有检查结果和总体评分的ValidationResult对象
     */
    public ValidationResult validatePhoto(String imagePath, Integer dpi) {
        try {
            // 处理DPI参数:若为null则使用默认值300(证件照常见DPI标准)
            if (dpi == null) {
                dpi = 300; // 默认DPI
            }

            // 1. 加载图像文件(内部会校验文件存在性和可读性)
            Mat image = loadImage(imagePath);
            if (image.empty()){
                System.err.println("错误:图像读取失败,路径可能错误或文件损坏。");
            } else {
                System.err.println("图像读取成功~~~~");
            }

            // 2. 初始化校验结果对象,用于存储各项检查结果
            ValidationResult result = new ValidationResult();

            // 3. 执行各项专项检查,并将结果存入result对象
            result.setResolution(checkResolution(image, dpi)); // 分辨率检查
            result.setBrightnessContrast(checkBrightnessContrast(image)); // 亮度对比度检查
            result.setSharpness(checkSharpness(image)); // 清晰度检查
            result.setBackground(checkBackground(image)); // 背景均匀性检查
            result.setColorBalance(checkColorBalance(image)); // 色彩平衡检查
            result.setFaceDetection(detectFace(image)); // 人脸检测与位置检查

            // 4. 根据各项检查结果计算总体评分(合格项占比)
            result.calculateOverallScore();

            // 返回完整的校验结果
            return result;

        } catch (Exception e) {
            // 若校验过程中发生异常(如文件不存在、图像损坏等)
            // 生成错误结果对象,存储错误信息并返回
            ValidationResult errorResult = new ValidationResult();
            errorResult.setError(e.getMessage());
            return errorResult;
        }
    }

    /**
     * 生成证件照质量校验的文字报告
     * 功能:将ValidationResult中的各项检查结果格式化为人易读的文本报告
     * @param result 完整的校验结果对象(包含各项检查数据和总体评分)
     * @return 格式化的校验报告字符串
     */
    public String generateReport(ValidationResult result) {
        // 1. 处理异常情况:若校验过程有错误,直接返回错误信息
        if (result.getError() != null) {
            return "校验过程中出现错误: " + result.getError();
        }

        // 2. 初始化字符串构建器,用于拼接报告内容
        StringBuilder report = new StringBuilder();

        // 3. 报告头部:标题、总体评分、是否合格
        report.append("=== 证件照质量校验报告 ===\n");
        // 总体评分保留1位小数(如85.0/100)
        report.append(String.format("总体评分: %.1f/100\n", result.getOverallScore()));
        // 合格状态根据总体评分判断(默认≥70分为合格)
        report.append(String.format("是否合格: %s\n", result.isAcceptable() ? "是" : "否"));
        report.append("\n"); // 空行分隔头部与详情

        // 4. 分辨率检查结果
        ResolutionResult res = result.getResolution();
        report.append("1. 分辨率检查:\n");
        report.append(String.format("   尺寸: %dx%d 像素\n", res.getWidthPixels(), res.getHeightPixels())); // 像素尺寸
        report.append(String.format("   DPI: %d\n", res.getDpi())); // 图像DPI值
        report.append(String.format("   是否符合标准: %s\n", res.isMeetsStandard() ? "是" : "否")); // 达标状态

        // 5. 亮度与对比度检查结果
        BrightnessContrastResult bc = result.getBrightnessContrast();
        report.append("2. 亮度对比度检查:\n");
        // 亮度值保留1位小数,同时显示合格状态
        report.append(String.format("   亮度: %.1f (%s)\n", bc.getBrightness(), bc.isBrightnessOk() ? "合格" : "不合格"));
        // 对比度值保留1位小数,同时显示合格状态
        report.append(String.format("   对比度: %.1f (%s)\n", bc.getContrast(), bc.isContrastOk() ? "合格" : "不合格"));

        // 6. 清晰度检查结果
        SharpnessResult sharp = result.getSharpness();
        report.append("3. 清晰度检查:\n");
        // 清晰度分数保留1位小数,显示合格状态
        report.append(String.format("   清晰度分数: %.1f (%s)\n", sharp.getSharpness(), sharp.isSharpnessOk() ? "合格" : "不合格"));

        // 7. 背景均匀性检查结果
        BackgroundResult bg = result.getBackground();
        report.append("4. 背景检查:\n");
        // 背景均匀性分数保留1位小数,显示合格状态(分数越低越均匀)
        report.append(String.format("   背景均匀性: %.1f (%s)\n", bg.getBackgroundUniformity(), bg.isBackgroundOk() ? "合格" : "不合格"));

        // 8. 人脸检测结果(分情况处理:检测到人脸/未检测到人脸)
        FaceDetectionResult face = result.getFaceDetection();
        report.append("5. 人脸检测:\n");
        if (face.isFaceDetected()) {
            // 检测到人脸:显示数量、占比、位置是否合格
            report.append(String.format("   检测到人脸数量: %d\n", face.getFaceCount()));
            report.append(String.format("   人脸比例: %.3f (%s)\n", face.getFaceRatio(), face.isFaceSizeOk() ? "合格" : "不合格"));
            report.append(String.format("   人脸位置: %s\n", face.isFacePositionOk() ? "合格" : "不合格"));
        } else {
            // 未检测到人脸:直接提示
            report.append("   未检测到人脸!\n");
        }

        // 9. 色彩平衡检查结果
        ColorBalanceResult color = result.getColorBalance();
        report.append("6. 色彩平衡检查:\n");
        // 色彩平衡分数保留3位小数,显示合格状态(分数越低越平衡)
        report.append(String.format("   色彩平衡分数: %.3f (%s)\n", color.getColorBalance(), color.isColorOk() ? "合格" : "不合格"));

        // 返回完整的报告字符串
        return report.toString();
    }

    /**
     * 生成证件照质量校验报告
     * @param result
     * @return
     */
    public Map<String,Object> generateReportMap(ValidationResult result) {
        Map<String,Object> map = new HashMap<>();
        if (result.getError() != null) {
            map.put("errmsg",result.getError());
        } else {
            map.put("errmsg","");
        }

        // 2. 初始化字符串构建器,用于拼接报告内容
//        StringBuilder report = new StringBuilder();

        // 3. 报告头部:标题、总体评分、是否合格
//        report.append("=== 证件照质量校验报告 ===\n");
        // 总体评分保留1位小数(如85.0/100)
        map.put("allscore",String.format("%.1f/100\n", result.getOverallScore()));
        // 合格状态根据总体评分判断(默认≥70分为合格)
        map.put("isQualified",String.format("%s\n", result.isAcceptable() ? "合格" : "不合格"));

        // 4. 分辨率检查结果
        ResolutionResult res = result.getResolution();
        map.put("pixels",String.format("%dx%d\n", res.getWidthPixels(), res.getHeightPixels())); // 像素尺寸
        map.put("dpi",String.format("%d\n", res.getDpi())); // 图像DPI值
        map.put("meetsStandard",String.format("%s\n", res.isMeetsStandard() ? "是" : "否")); // 达标状态

        // 5. 亮度与对比度检查结果
        BrightnessContrastResult bc = result.getBrightnessContrast();
        // 亮度值保留1位小数,同时显示合格状态
        map.put("brightness",String.format("%.1f (%s)\n", bc.getBrightness(), bc.isBrightnessOk() ? "合格" : "不合格"));
        // 对比度值保留1位小数,同时显示合格状态
        map.put("contrast",String.format("%.1f (%s)\n", bc.getContrast(), bc.isContrastOk() ? "合格" : "不合格"));

        // 6. 清晰度检查结果
        SharpnessResult sharp = result.getSharpness();
        // 清晰度分数保留1位小数,显示合格状态
        map.put("sharpness",String.format("%.1f (%s)\n", sharp.getSharpness(), sharp.isSharpnessOk() ? "合格" : "不合格"));

        // 7. 背景均匀性检查结果
        BackgroundResult bg = result.getBackground();
        // 背景均匀性分数保留1位小数,显示合格状态(分数越低越均匀)
        map.put("backgroundUniformity",String.format("%.1f (%s)\n", bg.getBackgroundUniformity(), bg.isBackgroundOk() ? "合格" : "不合格"));

        // 8. 人脸检测结果(分情况处理:检测到人脸/未检测到人脸)
        FaceDetectionResult face = result.getFaceDetection();
        if (face.isFaceDetected()) {
            // 检测到人脸:显示数量、占比、位置是否合格
            map.put("faceCount",String.format("%d", face.getFaceCount()));
            map.put("faceRatio",String.format("%.3f (%s)", face.getFaceRatio(), face.isFaceSizeOk() ? "合格" : "不合格"));
            map.put("isFacePositionOk",String.format("%s", face.isFacePositionOk() ? "合格" : "不合格"));
        } else {
            // 未检测到人脸:直接提示
            map.put("errmsg","未检测到人脸!");
            map.put("faceCount","");
            map.put("faceRatio","不合格");
            map.put("isFacePositionOk","不合格");
        }


        // 9. 色彩平衡检查结果
        ColorBalanceResult color = result.getColorBalance();
        // 色彩平衡分数保留3位小数,显示合格状态(分数越低越平衡)
        map.put("colorBalance",String.format("%.3f (%s)", color.getColorBalance(), color.isColorOk() ? "合格" : "不合格"));

        return map;
    }

    // 图像预处理方法
    // 功能:对输入图像进行去噪和对比度增强,优化图像质量以提升后续分析(如人脸检测)的准确性
    public Mat preprocessImage(Mat image) {
        // 克隆原始图像,避免直接修改输入图像(保护原始数据)
        Mat processed = image.clone();

        // 1. 高斯模糊去噪
        // 原理:通过高斯核平滑图像,减少高频噪声(如斑点、颗粒)
        // 参数说明:
        // - processed:输入输出图像(此处输入输出为同一对象,原地处理)
        // - new Size(3, 3):高斯核大小(3x3,较小的核可保留更多细节)
        // - 0:高斯函数在X方向的标准差(设为0时由核大小自动计算)
        Imgproc.GaussianBlur(processed, processed, new Size(3, 3), 0);

        // 2. 直方图均衡化增强对比度(基于YCrCb色彩空间,避免色彩失真)
        // 步骤1:将BGR图像转换为YCrCb色彩空间
        // YCrCb中Y通道表示亮度,Cr和Cb表示色度,仅对Y通道处理可避免色彩失真
        Mat ycrcb = new Mat();
        Imgproc.cvtColor(processed, ycrcb, Imgproc.COLOR_BGR2YCrCb);

        // 步骤2:分离YCrCb的三个通道
        List<Mat> channels = new ArrayList<>();
        Core.split(ycrcb, channels); // channels[0]=Y(亮度), channels[1]=Cr, channels[2]=Cb

        // 步骤3:对Y通道(亮度通道)进行直方图均衡化
        // 直方图均衡化通过拉伸像素值分布范围,增强图像明暗对比
        Imgproc.equalizeHist(channels.get(0), channels.get(0));

        // 步骤4:合并处理后的通道,还原为YCrCb图像
        Mat merged = new Mat();
        Core.merge(channels, ycrcb);

        // 步骤5:将YCrCb图像转换回BGR格式(OpenCV默认处理格式)
        Imgproc.cvtColor(ycrcb, processed, Imgproc.COLOR_YCrCb2BGR);

        // 返回预处理后的图像
        return processed;
    }

    /**
     * 保存带标记的结果图像
     * 功能:在原始图像上标注检测到的人脸区域及相关信息,并保存为新图像
     * 便于直观查看人脸检测结果(位置、占比等)
     * @param image 原始图像(Mat对象,BGR格式)
     * @param faceResult 人脸检测结果(包含是否检测到人脸、人脸矩形区域等信息)
     * @param outputPath 标注后图像的保存路径(含文件名及格式,如"result.jpg")
     */
    public void saveAnnotatedImage(Mat image, FaceDetectionResult faceResult, String outputPath) {
        // 克隆原始图像,避免直接修改输入图像(保护原始数据)
        Mat annotated = image.clone();

        // 仅当检测到人脸且人脸区域有效时,进行标注
        if (faceResult.isFaceDetected() && faceResult.getFaceRect() != null) {
            // 1. 绘制人脸框:用绿色矩形框标记人脸位置
            // 参数说明:
            // - annotated:目标图像(在该图像上绘制)
            // - faceResult.getFaceRect():人脸区域的矩形坐标(x,y,宽,高)
            // - new Scalar(0, 255, 0):矩形颜色(OpenCV中为BGR格式,此处0,255,0表示绿色)
            // - 2:矩形边框厚度(2像素)
            Imgproc.rectangle(annotated, faceResult.getFaceRect(),
                    new Scalar(0, 255, 0), 2);

            // 2. 添加文本标注:显示人脸占比信息
            // 格式化文本内容:人脸占比(百分比,保留1位小数)
            String label = String.format("Face: %.1f%%", faceResult.getFaceRatio() * 100);
            // 文本位置:位于人脸框上方10像素处(与框左侧对齐)
            Point textPos = new Point(faceResult.getFaceRect().x,
                    faceResult.getFaceRect().y - 10);
            // 绘制文本
            // 参数说明:
            // - annotated:目标图像
            // - label:文本内容
            // - textPos:文本起始坐标
            // - Imgproc.FONT_HERSHEY_SIMPLEX:字体样式(简洁无衬线字体)
            // - 0.5:字体大小(缩放比例)
            // - new Scalar(0, 255, 0):文本颜色(绿色,与边框保持一致)
            // - 1:文本线条厚度(1像素)
            Imgproc.putText(annotated, label, textPos,
                    Imgproc.FONT_HERSHEY_SIMPLEX, 0.5, new Scalar(0, 255, 0), 1);
        }

        // 将标注后的图像保存到指定路径(支持常见格式如JPG、PNG等)
        Imgcodecs.imwrite(outputPath, annotated);
    }

    // Java 8 兼容:用 Arrays.asList 替代 List.of
    private static final List<String> PHOTO_EXTENSIONS = Arrays.asList(
            "jpg", "jpeg", "png", "gif", "bmp", "tiff", "webp","jfif"
    );

    // 读取文件夹内所有照片文件
    public static List<String> readAllPhotos(String folderPath) {
        List<String> photoPaths = new ArrayList<>();
        File folder = new File(folderPath);

        if (!folder.exists() || !folder.isDirectory()) {
            System.out.println("文件夹不存在或路径错误:" + folderPath);
            return photoPaths;
        }

        traverseFolder(folder, photoPaths);
        return photoPaths;
    }

    private static void traverseFolder(File currentFile, List<String> photoPaths) {
        if (currentFile.isFile()) {
            String fileExtension = getFileExtension(currentFile.getName()).toLowerCase();
            if (PHOTO_EXTENSIONS.contains(fileExtension)) {
                photoPaths.add(currentFile.getAbsolutePath());
            }
            return;
        }

        File[] subFiles = currentFile.listFiles();
        if (subFiles == null) {
            System.out.println("无法访问文件夹:" + currentFile.getAbsolutePath());
            return;
        }

        for (File subFile : subFiles) {
            traverseFolder(subFile, photoPaths);
        }
    }

    private static String getFileExtension(String fileName) {
        int lastDotIndex = fileName.lastIndexOf(".");
        return lastDotIndex > 0 && lastDotIndex < fileName.length() - 1
                ? fileName.substring(lastDotIndex + 1)
                : "";
    }

    public static void main(String[] args) {

        IDphotoUtils validator = new IDphotoUtils();

        String folderPath = "D:\\2025xyrsphoto\\backup\\candidate_photo\\2025\\";
        String folderPath1 = "D:\\2025xyrsphoto\\minface"; // 检测有问题的图片文件夹
        List<String> allPhotos = readAllPhotos(folderPath);
        System.out.println("找到 " + allPhotos.size() + " 张照片:");

        // 1. 创建工作簿(XSSFWorkbook 对应 .xlsx,HSSFWorkbook 对应 .xls)
        Workbook workbook = new XSSFWorkbook();
        // 2. 创建工作表(指定工作表名称)
        Sheet sheet = workbook.createSheet("证件照质量检测数据");

        // 3. 创建单元格样式(表头:加粗、居中;数据行:居中)
        // 表头样式
        CellStyle headerStyle = workbook.createCellStyle();
        Font headerFont = workbook.createFont();
        headerFont.setBold(true); // 字体加粗
        headerStyle.setFont(headerFont);
        headerStyle.setAlignment(HorizontalAlignment.CENTER); // 水平居中
        headerStyle.setVerticalAlignment(VerticalAlignment.CENTER); // 垂直居中

        // 数据行样式
        CellStyle dataStyle = workbook.createCellStyle();
        dataStyle.setAlignment(HorizontalAlignment.CENTER);
        dataStyle.setVerticalAlignment(VerticalAlignment.CENTER);

        // 4. 创建表头行(第 0 行,Excel 行号从 0 开始)
        Row headerRow = sheet.createRow(0);
        String[] headers = {"照片路径", "总体评分", "最终评判", "尺寸(像素)","DPI","尺寸是否符合","亮度",
                "对比度","清晰度分数","背景均匀性","检测到人脸数量","人脸比例","人脸位置","色彩平衡分数",
                "证件照质量","对比过程中的错误信息"}; // 表头内容
        for (int i = 0; i < headers.length; i++) {
            Cell cell = headerRow.createCell(i); // 创建单元格
            cell.setCellValue(headers[i]); // 设置单元格内容
            cell.setCellStyle(headerStyle); // 应用表头样式
            sheet.autoSizeColumn(i); // 自动调整列宽(适配内容)
        }

        // 5. 填充数据行(从第 1 行开始,对应表头下的第一行数据)
        for (int i = 0; i < allPhotos.size(); i++) {
            System.out.println(allPhotos.get(i));
            String path = allPhotos.get(i);
//            String imagePath = "C:\\Users\\Administrator\\Pictures\\61272319870318004X.jpg";
            // 执行校验
            ValidationResult result = validator.validatePhoto(path, 300);
            // 生成报告
            Map<String, Object> map = validator.generateReportMap(result);

            if (map.get("errormsg") != null){
                throw new RuntimeException("出现错误");
            }
//            String report = validator.generateReport(result);
//            System.out.println(report);
//
//            // 根据结果判断是否合格
//            if (result.isAcceptable()) {
//                System.out.println("✓ 证件照质量合格");
//            } else {
//                System.out.println("✗ 证件照质量不合格,请重新拍摄");
//            }


            Row dataRow = sheet.createRow(i + 1); // 数据行号 = 索引 + 1(跳过表头)

            // 填充每列数据,并应用样式
            Cell cell0 = dataRow.createCell(0);
            cell0.setCellValue(path);// 照片路径
            cell0.setCellStyle(dataStyle);

            Cell cell1 = dataRow.createCell(1);
            cell1.setCellValue(map.get("allscore").toString()); // 总体评分
            cell1.setCellStyle(dataStyle);

            Cell cell2 = dataRow.createCell(2);
            cell2.setCellValue(map.get("isQualified").toString()); // 最终评判
            cell2.setCellStyle(dataStyle);

            Cell cell3 = dataRow.createCell(3);
            cell3.setCellValue(map.get("pixels").toString()); // 尺寸(像素)
            cell3.setCellStyle(dataStyle);

            Cell cell4 = dataRow.createCell(4);
            cell4.setCellValue(map.get("dpi").toString()); // DPI
            cell4.setCellStyle(dataStyle);

            Cell cell5 = dataRow.createCell(5);
            cell5.setCellValue(map.get("meetsStandard").toString()); // 尺寸是否符合标准
            cell5.setCellStyle(dataStyle);

            Cell cell6 = dataRow.createCell(6);
            cell6.setCellValue(map.get("brightness").toString()); // 亮度
            cell6.setCellStyle(dataStyle);

            Cell cell7 = dataRow.createCell(7);
            cell7.setCellValue(map.get("contrast").toString()); // 对比度
            cell7.setCellStyle(dataStyle);

            Cell cell8 = dataRow.createCell(8);
            cell8.setCellValue(map.get("sharpness").toString()); // 清晰度分数
            cell8.setCellStyle(dataStyle);

            Cell cell9 = dataRow.createCell(9);
            cell9.setCellValue(map.get("backgroundUniformity").toString()); // 背景均匀性
            cell9.setCellStyle(dataStyle);

            Cell cell10 = dataRow.createCell(10);
            cell10.setCellValue(map.get("faceCount").toString()); // 检测到人脸数量
            cell10.setCellStyle(dataStyle);

            Cell cell11 = dataRow.createCell(11);
            cell11.setCellValue(map.get("faceRatio").toString()); // 人脸比例
            cell11.setCellStyle(dataStyle);

            Cell cell12 = dataRow.createCell(12);
            cell12.setCellValue(map.get("isFacePositionOk").toString()); // 人脸位置
            cell12.setCellStyle(dataStyle);

            Cell cell13 = dataRow.createCell(13);
            cell13.setCellValue(map.get("colorBalance").toString()); // 色彩平衡分数
            cell13.setCellStyle(dataStyle);

            if (result.isAcceptable()
//                    && !(map.get("faceRatio").toString().contains("不合格")) // 不满足人脸比例要求的
                    ) {
                System.out.println("✓ 证件照质量合格");
                map.put("quality","证件照质量合格");
            } else {
//                System.out.println("✗ 证件照质量不合格,请重新拍摄");
                map.put("quality","证件照质量不合格");
            }
            Cell cell14 = dataRow.createCell(14);
            cell14.setCellValue(map.get("quality").toString()); // 证件照质量
            cell14.setCellStyle(dataStyle);

            Cell cell15 = dataRow.createCell(15);
            cell15.setCellValue(map.get("errmsg").toString()); // 对比过程中的错误信息
            cell15.setCellStyle(dataStyle);
        }
        // 6. 写入文件(通过 FileOutputStream 输出到指定路径)
        String dateform = getDate();
        String outputPath = "C:\\Users\\Administrator\\Desktop\\update\\EasyExcel"+dateform+".xlsx";
        try (FileOutputStream fos = new FileOutputStream(outputPath)) {
            workbook.write(fos);
            System.out.println("Excel 导出成功!保存路径:" + outputPath);
        } catch (IOException e) {
            System.out.println("Excel 导出失败:" + e.getMessage());
            e.printStackTrace();
        } finally {
            // 7. 关闭工作簿,释放资源
            try {
                workbook.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
}

/**
 * 分辨率检查结果类
 * 功能:封装证件照分辨率检查的各项数据,包括像素尺寸、物理尺寸、DPI及是否符合标准
 */
class ResolutionResult {
    // 图像宽度(像素数)
    private int widthPixels;
    // 图像高度(像素数)
    private int heightPixels;
    // 图像实际宽度(英寸,根据像素数和DPI计算)
    private double widthInch;
    // 图像实际高度(英寸,根据像素数和DPI计算)
    private double heightInch;
    // 图像的DPI(每英寸像素数,影响实际打印尺寸)
    private int dpi;
    // 分辨率是否符合证件照标准(true=符合,false=不符合)
    private boolean meetsStandard;

    /**
     * 构造方法:初始化分辨率检查结果的所有属性
     * @param widthPixels 图像宽度(像素)
     * @param heightPixels 图像高度(像素)
     * @param widthInch 实际宽度(英寸)
     * @param heightInch 实际高度(英寸)
     * @param dpi 图像的DPI值
     * @param meetsStandard 是否符合标准
     */
    public ResolutionResult(int widthPixels, int heightPixels, double widthInch,
                            double heightInch, int dpi, boolean meetsStandard) {
        this.widthPixels = widthPixels;
        this.heightPixels = heightPixels;
        this.widthInch = widthInch;
        this.heightInch = heightInch;
        this.dpi = dpi;
        this.meetsStandard = meetsStandard;
    }

    // getter和setter方法:用于访问和修改私有属性

    /**
     * 获取图像宽度(像素)
     * @return 宽度像素数
     */
    public int getWidthPixels() {
        return widthPixels;
    }

    /**
     * 设置图像宽度(像素)
     * @param widthPixels 宽度像素数
     */
    public void setWidthPixels(int widthPixels) {
        this.widthPixels = widthPixels;
    }

    /**
     * 获取图像高度(像素)
     * @return 高度像素数
     */
    public int getHeightPixels() {
        return heightPixels;
    }

    /**
     * 设置图像高度(像素)
     * @param heightPixels 高度像素数
     */
    public void setHeightPixels(int heightPixels) {
        this.heightPixels = heightPixels;
    }

    /**
     * 获取实际宽度(英寸)
     * @return 宽度(英寸)
     */
    public double getWidthInch() {
        return widthInch;
    }

    /**
     * 设置实际宽度(英寸)
     * @param widthInch 宽度(英寸)
     */
    public void setWidthInch(double widthInch) {
        this.widthInch = widthInch;
    }

    /**
     * 获取实际高度(英寸)
     * @return 高度(英寸)
     */
    public double getHeightInch() {
        return heightInch;
    }

    /**
     * 设置实际高度(英寸)
     * @param heightInch 高度(英寸)
     */
    public void setHeightInch(double heightInch) {
        this.heightInch = heightInch;
    }

    /**
     * 获取图像DPI值
     * @return DPI(每英寸像素数)
     */
    public int getDpi() {
        return dpi;
    }

    /**
     * 设置图像DPI值
     * @param dpi DPI(每英寸像素数)
     */
    public void setDpi(int dpi) {
        this.dpi = dpi;
    }

    /**
     * 判断分辨率是否符合标准
     * @return true=符合标准,false=不符合
     */
    public boolean isMeetsStandard() {
        return meetsStandard;
    }

    /**
     * 设置分辨率是否符合标准
     * @param meetsStandard true=符合标准,false=不符合
     */
    public void setMeetsStandard(boolean meetsStandard) {
        this.meetsStandard = meetsStandard;
    }
}

/**
 * 亮度对比度检查结果类
 * 功能:封装证件照亮度和对比度的检测数据,包括具体数值与是否合格的判断
 */
class BrightnessContrastResult {
    // 亮度值:基于图像灰度通道的平均像素值(范围通常0-255)
    private double brightness;
    // 对比度值:基于图像灰度通道的像素标准差(值越大,对比度越高)
    private double contrast;
    // 亮度是否合格(true=符合阈值范围,false=不符合)
    private boolean brightnessOk;
    // 对比度是否合格(true=高于最小阈值,false=低于最小阈值)
    private boolean contrastOk;

    /**
     * 构造方法:初始化亮度和对比度的检测结果
     * @param brightness 亮度具体数值
     * @param contrast 对比度具体数值
     * @param brightnessOk 亮度是否合格
     * @param contrastOk 对比度是否合格
     */
    public BrightnessContrastResult(double brightness, double contrast,
                                    boolean brightnessOk, boolean contrastOk) {
        this.brightness = brightness;
        this.contrast = contrast;
        this.brightnessOk = brightnessOk;
        this.contrastOk = contrastOk;
    }

    // getter和setter方法:用于安全访问和修改私有属性

    /**
     * 获取对比度数值
     * @return 对比度(标准差)
     */
    public double getContrast() {
        return contrast;
    }

    /**
     * 设置对比度数值
     * @param contrast 对比度(标准差)
     */
    public void setContrast(double contrast) {
        this.contrast = contrast;
    }

    /**
     * 判断亮度是否合格
     * @return true=合格,false=不合格
     */
    public boolean isBrightnessOk() {
        return brightnessOk;
    }

    /**
     * 设置亮度是否合格的状态
     * @param brightnessOk true=合格,false=不合格
     */
    public void setBrightnessOk(boolean brightnessOk) {
        this.brightnessOk = brightnessOk;
    }

    /**
     * 判断对比度是否合格
     * @return true=合格,false=不合格
     */
    public boolean isContrastOk() {
        return contrastOk;
    }

    /**
     * 设置对比度是否合格的状态
     * @param contrastOk true=合格,false=不合格
     */
    public void setContrastOk(boolean contrastOk) {
        this.contrastOk = contrastOk;
    }

    /**
     * 获取亮度数值
     * @return 亮度(灰度通道平均值)
     */
    public double getBrightness() {
        return brightness;
    }

    /**
     * 设置亮度数值
     * @param brightness 亮度(灰度通道平均值)
     */
    public void setBrightness(double brightness) {
        this.brightness = brightness;
    }
}

/**
 * 清晰度检查结果类
 * 功能:封装证件照清晰度的检测数据,包括清晰度分数与是否合格的判断
 */
class SharpnessResult {
    // 清晰度分数:基于拉普拉斯算子结果的方差(值越大,图像越清晰)
    private double sharpness;
    // 清晰度是否合格(true=高于最小阈值,false=低于最小阈值)
    private boolean sharpnessOk;

    /**
     * 构造方法:初始化清晰度的检测结果
     * @param sharpness 清晰度分数
     * @param sharpnessOk 清晰度是否合格
     */
    public SharpnessResult(double sharpness, boolean sharpnessOk) {
        this.sharpness = sharpness;
        this.sharpnessOk = sharpnessOk;
    }

    // getter和setter方法:用于安全访问和修改私有属性

    /**
     * 获取清晰度分数
     * @return 清晰度(拉普拉斯方差)
     */
    public double getSharpness() {
        return sharpness;
    }

    /**
     * 设置清晰度分数
     * @param sharpness 清晰度(拉普拉斯方差)
     */
    public void setSharpness(double sharpness) {
        this.sharpness = sharpness;
    }

    /**
     * 判断清晰度是否合格
     * @return true=合格,false=不合格
     */
    public boolean isSharpnessOk() {
        return sharpnessOk;
    }

    /**
     * 设置清晰度是否合格的状态
     * @param sharpnessOk true=合格,false=不合格
     */
    public void setSharpnessOk(boolean sharpnessOk) {
        this.sharpnessOk = sharpnessOk;
    }
}

/**
 * 背景检查结果类
 * 功能:封装证件照背景均匀性的检测数据,包括均匀性分数与是否合格的判断
 */
class BackgroundResult {
    // 背景均匀性分数:基于图像边缘区域的像素标准差(值越小,背景越均匀)
    private double backgroundUniformity;
    // 背景是否合格(true=均匀性达标,false=存在明显杂色/噪声)
    private boolean backgroundOk;

    /**
     * 构造方法:初始化背景均匀性的检测结果
     * @param backgroundUniformity 背景均匀性分数
     * @param backgroundOk 背景是否合格
     */
    public BackgroundResult(double backgroundUniformity, boolean backgroundOk) {
        this.backgroundUniformity = backgroundUniformity;
        this.backgroundOk = backgroundOk;
    }

    // getter和setter方法:用于安全访问和修改私有属性

    /**
     * 获取背景均匀性分数
     * @return 均匀性(边缘区域标准差)
     */
    public double getBackgroundUniformity() {
        return backgroundUniformity;
    }

    /**
     * 设置背景均匀性分数
     * @param backgroundUniformity 均匀性(边缘区域标准差)
     */
    public void setBackgroundUniformity(double backgroundUniformity) {
        this.backgroundUniformity = backgroundUniformity;
    }

    /**
     * 判断背景是否合格
     * @return true=合格,false=不合格
     */
    public boolean isBackgroundOk() {
        return backgroundOk;
    }

    /**
     * 设置背景是否合格的状态
     * @param backgroundOk true=合格,false=不合格
     */
    public void setBackgroundOk(boolean backgroundOk) {
        this.backgroundOk = backgroundOk;
    }
}

/**
 * 人脸检测结果类
 * 功能:封装证件照人脸检测的全量数据,包括检测状态、人脸参数与位置信息
 */
class FaceDetectionResult {
    // 是否检测到人脸(true=检测到,false=未检测到)
    private boolean faceDetected;
    // 检测到的人脸数量(证件照通常需且仅需1张人脸)
    private int faceCount;
    // 人脸占比:人脸面积 / 图像总面积(反映人脸大小是否合适)
    private double faceRatio;
    // 人脸位置是否合格(true=居中,false=偏移过多)
    private boolean facePositionOk;
    // 人脸大小是否合格(true=占比在阈值范围内,false=过大/过小)
    private boolean faceSizeOk;
    // 人脸矩形区域:存储人脸在图像中的坐标(x,y,宽度,高度),用于后续标注
    private Rect faceRect;

    /**
     * 构造方法:初始化人脸检测的全量结果
     * @param faceDetected 是否检测到人脸
     * @param faceCount 人脸数量
     * @param faceRatio 人脸占比
     * @param facePositionOk 位置是否合格
     * @param faceSizeOk 大小是否合格
     * @param faceRect 人脸矩形区域
     */
    public FaceDetectionResult(boolean faceDetected, int faceCount, double faceRatio,
                               boolean facePositionOk, boolean faceSizeOk, Rect faceRect) {
        this.faceDetected = faceDetected;
        this.faceCount = faceCount;
        this.faceRatio = faceRatio;
        this.facePositionOk = facePositionOk;
        this.faceSizeOk = faceSizeOk;
        this.faceRect = faceRect;
    }

    // getter和setter方法:用于安全访问和修改私有属性

    /**
     * 判断是否检测到人脸
     * @return true=检测到,false=未检测到
     */
    public boolean isFaceDetected() {
        return faceDetected;
    }

    /**
     * 设置是否检测到人脸的状态
     * @param faceDetected true=检测到,false=未检测到
     */
    public void setFaceDetected(boolean faceDetected) {
        this.faceDetected = faceDetected;
    }

    /**
     * 获取检测到的人脸数量
     * @return 人脸数量
     */
    public int getFaceCount() {
        return faceCount;
    }

    /**
     * 设置检测到的人脸数量
     * @param faceCount 人脸数量
     */
    public void setFaceCount(int faceCount) {
        this.faceCount = faceCount;
    }

    /**
     * 获取人脸占比
     * @return 人脸占比(0-1之间的小数)
     */
    public double getFaceRatio() {
        return faceRatio;
    }

    /**
     * 设置人脸占比
     * @param faceRatio 人脸占比(0-1之间的小数)
     */
    public void setFaceRatio(double faceRatio) {
        this.faceRatio = faceRatio;
    }

    /**
     * 判断人脸位置是否合格
     * @return true=合格,false=不合格
     */
    public boolean isFacePositionOk() {
        return facePositionOk;
    }

    /**
     * 设置人脸位置是否合格的状态
     * @param facePositionOk true=合格,false=不合格
     */
    public void setFacePositionOk(boolean facePositionOk) {
        this.facePositionOk = facePositionOk;
    }

    /**
     * 判断人脸大小是否合格
     * @return true=合格,false=不合格
     */
    public boolean isFaceSizeOk() {
        return faceSizeOk;
    }

    /**
     * 设置人脸大小是否合格的状态
     * @param faceSizeOk true=合格,false=不合格
     */
    public void setFaceSizeOk(boolean faceSizeOk) {
        this.faceSizeOk = faceSizeOk;
    }

    /**
     * 获取人脸矩形区域
     * @return 人脸坐标与尺寸(Rect对象)
     */
    public Rect getFaceRect() {
        return faceRect;
    }

    /**
     * 设置人脸矩形区域
     * @param faceRect 人脸坐标与尺寸(Rect对象)
     */
    public void setFaceRect(Rect faceRect) {
        this.faceRect = faceRect;
    }
}

/**
 * 色彩平衡检查结果类
 * 功能:封装证件照色彩平衡的检测数据,包含量化的平衡分数与是否合格的判断结果
 * 用于直观呈现图像是否存在偏色(如偏红、偏蓝、偏黄),确保色彩符合证件照标准
 */
class ColorBalanceResult {
    // 色彩平衡分数:量化色彩偏移程度的核心指标
    // 计算逻辑:(RGB三通道中最亮均值 - 最暗均值) / 最亮均值
    // 数值范围:0~1,值越接近0表示三通道亮度越均衡(色彩越平衡),值越大表示偏色越严重
    private double colorBalance;

    // 色彩是否合格:基于平衡分数的最终判断结果
    // 合格标准:一般设定为平衡分数<0.3(允许≤30%的色彩差异,避免明显偏色)
    private boolean colorOk;

    /**
     * 构造方法:初始化色彩平衡检查的核心数据
     * @param colorBalance 色彩平衡分数(0~1,值越小越平衡)
     * @param colorOk 色彩是否合格(true=合格,false=不合格)
     */
    public ColorBalanceResult(double colorBalance, boolean colorOk) {
        this.colorBalance = colorBalance;
        this.colorOk = colorOk;
    }

    // getter和setter方法:实现私有属性的安全访问与修改,符合面向对象封装原则

    /**
     * 获取色彩平衡分数
     * @return 平衡分数(0~1)
     */
    public double getColorBalance() {
        return colorBalance;
    }

    /**
     * 设置色彩平衡分数
     * @param colorBalance 平衡分数(0~1)
     */
    public void setColorBalance(double colorBalance) {
        this.colorBalance = colorBalance;
    }

    /**
     * 判断色彩是否合格
     * @return true=色彩平衡达标(无明显偏色),false=色彩失衡(偏色严重)
     */
    public boolean isColorOk() {
        return colorOk;
    }

    /**
     * 设置色彩是否合格的状态
     * @param colorOk true=合格,false=不合格
     */
    public void setColorOk(boolean colorOk) {
        this.colorOk = colorOk;
    }
}

/**
 * 完整的验证结果类
 * 功能:聚合所有专项检查结果(分辨率、亮度、人脸等),计算总体评分并判断是否合格
 * 是证件照质量校验的最终结果载体,包含全量校验数据与结论
 */
class ValidationResult {
    // 分辨率检查结果(关联ResolutionResult类)
    private ResolutionResult resolution;
    // 亮度对比度检查结果(关联BrightnessContrastResult类)
    private BrightnessContrastResult brightnessContrast;
    // 清晰度检查结果(关联SharpnessResult类)
    private SharpnessResult sharpness;
    // 背景均匀性检查结果(关联BackgroundResult类)
    private BackgroundResult background;
    // 人脸检测结果(关联FaceDetectionResult类)
    private FaceDetectionResult faceDetection;
    // 色彩平衡检查结果(关联ColorBalanceResult类)
    private ColorBalanceResult colorBalance;
    // 总体评分(所有合格项占比,满分100分)
    private double overallScore;
    // 是否合格(总体评分≥70分为合格)
    private boolean acceptable;
    // 错误信息(校验过程中若发生异常,存储异常描述)
    private String error;

    /**
     * 计算总体评分与合格状态
     * 逻辑:统计所有专项检查的合格项数量,按占比计算评分,70分以上判定为合格
     */
    public void calculateOverallScore() {
        // 1. 初始化列表,存储所有专项检查的合格状态(true=合格,false=不合格)
        List<Boolean> allChecks = new ArrayList<>();

        // 2. 添加固定专项检查的合格状态(共6项)
        allChecks.add(resolution.isMeetsStandard()); // 分辨率是否合格
        allChecks.add(brightnessContrast.isBrightnessOk()); // 亮度是否合格
        allChecks.add(brightnessContrast.isContrastOk()); // 对比度是否合格
        allChecks.add(sharpness.isSharpnessOk()); // 清晰度是否合格
        allChecks.add(background.isBackgroundOk()); // 背景是否合格
        allChecks.add(colorBalance.isColorOk()); // 色彩平衡是否合格

        // 3. 条件添加人脸相关检查(仅当检测到人脸时,才统计人脸大小和位置的合格状态)
        if (faceDetection.isFaceDetected()) {
            allChecks.add(faceDetection.isFaceSizeOk()); // 人脸大小是否合格
            allChecks.add(faceDetection.isFacePositionOk()); // 人脸位置是否合格
        }

        // 4. 统计合格项数量
        int passed = 0;
        for (Boolean check : allChecks) {
            if (check) passed++; // 若某项合格,合格计数器+1
        }

        // 5. 计算总体评分:(合格项数 / 总检查项数) * 100(保留小数精度)
        this.overallScore = (double) passed / allChecks.size() * 100;
        // 6. 判断是否合格:总体评分≥70分为合格
        this.acceptable = this.overallScore >= 65;
    }

    // getter和setter方法:用于安全访问和修改所有私有属性,实现数据封装

    /**
     * 获取分辨率检查结果
     * @return 分辨率结果对象(ResolutionResult)
     */
    public ResolutionResult getResolution() {
        return resolution;
    }

    /**
     * 设置分辨率检查结果
     * @param resolution 分辨率结果对象(ResolutionResult)
     */
    public void setResolution(ResolutionResult resolution) {
        this.resolution = resolution;
    }

    /**
     * 获取亮度对比度检查结果
     * @return 亮度对比度结果对象(BrightnessContrastResult)
     */
    public BrightnessContrastResult getBrightnessContrast() {
        return brightnessContrast;
    }

    /**
     * 设置亮度对比度检查结果
     * @param brightnessContrast 亮度对比度结果对象(BrightnessContrastResult)
     */
    public void setBrightnessContrast(BrightnessContrastResult brightnessContrast) {
        this.brightnessContrast = brightnessContrast;
    }

    /**
     * 获取清晰度检查结果
     * @return 清晰度结果对象(SharpnessResult)
     */
    public SharpnessResult getSharpness() {
        return sharpness;
    }

    /**
     * 设置清晰度检查结果
     * @param sharpness 清晰度结果对象(SharpnessResult)
     */
    public void setSharpness(SharpnessResult sharpness) {
        this.sharpness = sharpness;
    }

    /**
     * 获取背景检查结果
     * @return 背景结果对象(BackgroundResult)
     */
    public BackgroundResult getBackground() {
        return background;
    }

    /**
     * 设置背景检查结果
     * @param background 背景结果对象(BackgroundResult)
     */
    public void setBackground(BackgroundResult background) {
        this.background = background;
    }

    /**
     * 获取人脸检测结果
     * @return 人脸检测结果对象(FaceDetectionResult)
     */
    public FaceDetectionResult getFaceDetection() {
        return faceDetection;
    }

    /**
     * 设置人脸检测结果
     * @param faceDetection 人脸检测结果对象(FaceDetectionResult)
     */
    public void setFaceDetection(FaceDetectionResult faceDetection) {
        this.faceDetection = faceDetection;
    }

    /**
     * 获取色彩平衡检查结果
     * @return 色彩平衡结果对象(ColorBalanceResult)
     */
    public ColorBalanceResult getColorBalance() {
        return colorBalance;
    }

    /**
     * 设置色彩平衡检查结果
     * @param colorBalance 色彩平衡结果对象(ColorBalanceResult)
     */
    public void setColorBalance(ColorBalanceResult colorBalance) {
        this.colorBalance = colorBalance;
    }

    /**
     * 获取总体评分
     * @return 总体评分(0-100分)
     */
    public double getOverallScore() {
        return overallScore;
    }

    /**
     * 设置总体评分(一般通过calculateOverallScore()自动计算,非手动设置)
     * @param overallScore 总体评分(0-100分)
     */
    public void setOverallScore(double overallScore) {
        this.overallScore = overallScore;
    }

    /**
     * 判断证件照是否合格
     * @return true=合格,false=不合格
     */
    public boolean isAcceptable() {
        return acceptable;
    }

    /**
     * 设置是否合格(一般通过calculateOverallScore()自动判断,非手动设置)
     * @param acceptable true=合格,false=不合格
     */
    public void setAcceptable(boolean acceptable) {
        this.acceptable = acceptable;
    }

    /**
     * 获取校验过程中的错误信息
     * @return 错误描述(无错误时为null)
     */
    public String getError() {
        return error;
    }

    /**
     * 设置校验过程中的错误信息(发生异常时调用)
     * @param error 错误描述
     */
    public void setError(String error) {
        this.error = error;
    }
}

注意事项

  1. OpenCV安装:确保正确安装OpenCV并配置Java绑定
  2. 人脸检测器:需要提供正确的Haar级联分类器文件路径
  3. 性能优化:对于大量图片处理,可以考虑使用线程池
  4. 内存管理:及时释放Mat对象避免内存泄漏
  5. 异常处理:增强错误处理和日志记录

这个Java实现的证件照质量检测工具提供了全面的质量检查功能,可以根据实际需求进行调整和扩展

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值