基于OpenCv做照片分析(Java)

前言

现在基于人体照片做相关业务的场景越来越多了,但是开源的、精细化的那就很少。基于OpenCV实际上是借助它暴露的Api做业务,简化过程,它的成功就在于暴露Api,让专门的事情专门做,业务的事情业务做。

专业知识

1.图片分析原理

基于 OpenCV 的图片分析原理涉及多个计算机视觉领域的核心技术。以下是详细的原理分析:

1.1图像表示原理

数字图像基础

OpenCV 使用 Mat(矩阵) 对象表示图像。
像素存储格式:

•灰度图:单通道,每个像素 0-255 的亮度值

•彩色图:三通道(BGR),每个通道 0-255

•透明度:四通道(BGRA),增加 alpha 通道

1.2图像预处理原理

1.2.1色彩空间转换

1.2.2图像滤波原理

**高斯模糊:**使用高斯函数加权平均,减少噪声

**中值滤波:**用邻域中值替换当前像素,有效去除椒盐噪声。

1.3特征检测原理

1.3.1边缘检测(Canny算法)

1.3.2角点检测
Harris角点检测原理:

  • 计算图像在各个方向的梯度变化
  • 通过特征值判断是否为角点
  • 响应函数:R = det(M) - k*(trace(M))^2

1.4机器学习检测原理

1.4.1 Haar级联分类器原理

工作原理:

  • Haar特征:计算矩形区域的像素和之差
  • 积分图:快速计算任意矩形区域的像素和
  • AdaBoost:组合多个弱分类器形成强分类器
  • 级联结构:快速排除非目标区域
    1.4.2HOG(方向梯度直方图)+ SVM
    原理:
  • 计算图像的梯度方向和幅值
  • 将图像分成小细胞单元,统计梯度方向直方图
  • 对细胞单元进行对比度归一化
  • 使用SVM分类器进行目标检测

1.5深度学习原理

1.5.1基于深度神经网络的目标检测

YOLO/SSD原理:

•单次检测:直接在网络中预测边界框和类别概率

•锚点框:预定义不同尺度和长宽比的候选框

•非极大值抑制:去除重叠的检测框

1.6图像分割原理

1.6.1阈值分割

1.6.2基于边缘的分割

1.6.3分水岭算法

1.7特征匹配原理

1.7.1关键点检测和描述

1.7.2特征匹配

1.8模板匹配原理

相似度度量方法:

•平方差匹配(TM_SQDIFF)

•相关匹配(TM_CCORR)

•相关系数匹配(TM_CCOEFF)

1.9光学字符识别(OCR)原理

1.9.1基于Tesseract的OCR

1.10三维视觉原理

1.10.1相机标定

1.10.2立体视觉

2.核心技术原理总结

  • 数学基础:线性代数(矩阵运算)、概率统计、微积分
  • 信号处理:傅里叶变换、卷积运算、滤波器设计
  • 机器学习:特征工程、分类算法、聚类分析
  • 优化理论:梯度下降、非线性优化
  • 几何原理:投影几何、坐标系变换
    OpenCV 的成功在于它将这些复杂的计算机视觉原理封装成简单易用的API,让开发者可以专注于应用逻辑而不是底层数学实现。

3.基于OpenCv实现检测(Java)

3.1建一个SpringBoot项目

pom.xml

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.5.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.rs.cv</groupId>
    <artifactId>OpenCvPreStudy</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>OpenCvPreStudy</name>
    <description>OpenCvPreStudy</description>

    <properties>
        <java.version>21</java.version>

        <skipTests>true</skipTests>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.openpnp</groupId>
            <artifactId>opencv</artifactId>
            <version>4.9.0-0</version>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.32</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

OpenCvResult

java 复制代码
import lombok.AllArgsConstructor;
import lombok.Data;

/**
 * @author zwmac
 */
@Data
@AllArgsConstructor
public class OpenCvResult {
    /**
     * 是否健全
     **/
    private boolean healthy;
    /**
     * 提示信息
     **/
    private String message;
}

IOpenCvService

java 复制代码
import com.rs.cv.entity.OpenCvResult;

import java.io.File;

/**
 * @author zwmac
 */
public interface IOpenCvService {
    /**
     * 手掌检测
     *
     * @param imageFile 照片文件
     * @return 结果
     */
    OpenCvResult analyzeHand(File imageFile);

    /**
     * 人脸检测
     *
     * @param imageFile 照片文件
     * @return 结果
     */
    OpenCvResult analyzeFace(File imageFile);

    /**
     * 模型检测
     *
     * @param imageFile   待检图片文件
     * @param cascadeName 模型名称
     * @return 检测结果
     */
    OpenCvResult analyze(File imageFile, String cascadeName);
}

OpenCvServiceImpl

java 复制代码
import cn.hutool.core.date.DateUtil;
import com.rs.cv.entity.OpenCvResult;
import com.rs.cv.service.IOpenCvService;
import com.rs.cv.util.OpenCVUtil;
import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.io.File;

/**
 * @author zwmac
 */
@Service
public class OpenCvServiceImpl implements IOpenCvService {


    @Override
    public OpenCvResult analyzeHand(File imageFile) {
        //OpenCV.loadLocally();
        Mat src = Imgcodecs.imread(imageFile.getAbsolutePath());
        if (src.empty()) {
            return new OpenCvResult(false, "无法读取图片");
        }

        Mat gray = new Mat();
        Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);

        CascadeClassifier classifier = OpenCVUtil.getHandCascade();
        Assert.notNull(classifier, "手掌模型加载失败");
        MatOfRect matOfRect = new MatOfRect();
        classifier.detectMultiScale(gray, matOfRect);

        if (matOfRect.toArray().length == 0) {
            return new OpenCvResult(false, "未检测到手掌");
        }

        // TODO: 这里可以接入 MediaPipe Hands 获取关键点,判断 5 指是否健全
        // 目前简化为检测到手掌就返回"可能健全"
        return new OpenCvResult(true, "检测到手掌,可能健全");
    }

    @Override
    public OpenCvResult analyzeFace(File imageFile) {

        Mat src = Imgcodecs.imread(imageFile.getAbsolutePath());
        if (src.empty()) {
            return new OpenCvResult(false, "无法读取图片");
        }

        Mat gray = new Mat();
        Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);

        CascadeClassifier classifier = OpenCVUtil.getFaceCascade();
        Assert.notNull(classifier, "人脸模型加载失败");
        MatOfRect matOfRect = new MatOfRect();
        classifier.detectMultiScale(gray, matOfRect);

        if (matOfRect.toArray().length == 0) {
            return new OpenCvResult(false, "未检测到人脸");
        }

        //TOTO 还以做其他处理

        return new OpenCvResult(true, "检测到人脸");
    }

    @Override
    public OpenCvResult analyze(File imageFile, String cascadeName) {
        //读取检测项图片
        Mat src = Imgcodecs.imread(imageFile.getAbsolutePath());
        if (src.empty()) {
            return new OpenCvResult(false, "无法读取图片");
        }

        //灰度化
        Mat gray = new Mat();
        Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);

        //加载训练好的检测模型
        CascadeClassifier classifier = OpenCVUtil.getCascade(cascadeName);
        Assert.notNull(classifier, "模型:"+cascadeName+"加载失败");

        //检测检测项
        MatOfRect matOfRect = new MatOfRect();
        classifier.detectMultiScale(gray, matOfRect);

        //判断检测结果
        if (matOfRect.toArray().length == 0) {
            return new OpenCvResult(false, "未检测到测试项");
        }

        //TODO 还可以继续判断关键点,
        //这里先保存结果图
        // 5. 打印检测到的矩形框
        Rect[] rects = matOfRect.toArray();
        System.out.println("检测到检测项数量: " + rects.length);
        for (Rect rect : rects) {
            System.out.println("x=" + rect.x + ", y=" + rect.y +
                    ", width=" + rect.width + ", height=" + rect.height);

            // 在原图上画矩形
            Imgproc.rectangle(src,
                    new Point(rect.x, rect.y),
                    new Point(rect.x + rect.width, rect.y + rect.height),
                    new Scalar(0, 255, 0), 2);
        }

        // 6. 保存结果图
        String resultTime = DateUtil.format(DateUtil.date(), "yyyyMMddHHmmss");
        String resultPath = "/Users/zwmac/Downloads/test/result_"+resultTime+".jpg";
        Imgcodecs.imwrite(resultPath, src);
        System.out.println("结果已保存: "+resultPath);

        return new OpenCvResult(true, "检测到测试项");
    }

}

OpenCVUtil

java 复制代码
import org.opencv.objdetect.CascadeClassifier;

import java.net.URL;

/**
 * @author zwmac
 */
public class OpenCVUtil {
    static {
        //有OpenCV.loadLocally();就不需要这个
        //System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
    }

    private static CascadeClassifier cascadeClassifier;

    public static CascadeClassifier getHandCascade() {
        if (cascadeClassifier == null) {
            //cascadeClassifier = new CascadeClassifier("src/main/resources/haarcascades/haarcascade_hand.xml");
            // 获取 Haar cascade 文件的类路径 URL
            URL url = OpenCVUtil.class.getClassLoader().getResource("haarcascades/haarcascade_hand.xml");

            if (url != null) {
                String cascadePath = url.getPath();
                System.out.println("文件路径: " + cascadePath);

                // 对于 OpenCV CascadeClassifier
                cascadeClassifier = new CascadeClassifier();
                boolean loaded = cascadeClassifier.load(cascadePath);

                if (loaded) {
                    System.out.println("Haar cascade 文件加载成功");
                } else {
                    System.out.println("Haar cascade 文件加载失败");
                }
            } else {
                System.out.println("未找到 Haar cascade 文件");
            }
        }
        return cascadeClassifier;
    }

    public static CascadeClassifier getFaceCascade() {
        if (cascadeClassifier == null) {
            // 获取 Haar cascade 文件的类路径 URL
            URL url = OpenCVUtil.class.getClassLoader().getResource("haarcascades/haarcascade_frontalface_alt.xml");

            if (url != null) {
                String cascadePath = url.getPath();
                System.out.println("文件路径: " + cascadePath);

                // 对于 OpenCV CascadeClassifier
                cascadeClassifier = new CascadeClassifier();
                boolean loaded = cascadeClassifier.load(cascadePath);

                if (loaded) {
                    System.out.println("Haar cascade 文件加载成功");
                } else {
                    System.out.println("Haar cascade 文件加载失败");
                }
            } else {
                System.out.println("未找到 Haar cascade 文件");
            }
        }
        return cascadeClassifier;
    }

    public static CascadeClassifier getEyeCascade() {
        if (cascadeClassifier == null) {
            // 获取 Haar cascade 文件的类路径 URL
            URL url = OpenCVUtil.class.getClassLoader().getResource("haarcascades/haarcascade_eye.xml");

            if (url != null) {
                String cascadePath = url.getPath();
                System.out.println("文件路径: " + cascadePath);

                // 对于 OpenCV CascadeClassifier
                cascadeClassifier = new CascadeClassifier();
                boolean loaded = cascadeClassifier.load(cascadePath);

                if (loaded) {
                    System.out.println("Haar cascade 文件加载成功");
                } else {
                    System.out.println("Haar cascade 文件加载失败");
                }
            } else {
                System.out.println("未找到 Haar cascade 文件");
            }
        }
        return cascadeClassifier;
    }

    public static CascadeClassifier getCascade(String cascadeName) {
        if (cascadeClassifier == null) {
            // 获取 Haar cascade 文件的类路径 URL
            URL url = OpenCVUtil.class.getClassLoader().getResource("haarcascades/"+cascadeName);

            if (url != null) {
                String cascadePath = url.getPath();
                System.out.println("文件路径: " + cascadePath);

                // 对于 OpenCV CascadeClassifier
                cascadeClassifier = new CascadeClassifier();
                boolean loaded = cascadeClassifier.load(cascadePath);

                if (loaded) {
                    System.out.println("Haar cascade 文件加载成功");
                } else {
                    System.out.println("Haar cascade 文件加载失败");
                }
            } else {
                System.out.println("未找到 Haar cascade 文件");
            }
        }
        return cascadeClassifier;
    }
}

OpenCvPreStudyApplicationTests

java 复制代码
import com.rs.cv.entity.OpenCvResult;
import com.rs.cv.service.IOpenCvService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import nu.pattern.OpenCV;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.File;

@Slf4j
@SpringBootTest
class OpenCvPreStudyApplicationTests {

    @Resource
    private IOpenCvService openCvService;

    @Test
    void contextLoads() {
        log.info("--- OpenCvPreStudyApplicationTests ---");
        OpenCV.loadLocally();
        // 现在可以使用 OpenCV 了
        System.out.println("OpenCV 加载成功!");
        System.out.println("版本: " + org.opencv.core.Core.VERSION);

/*
        File temp1 = new File("/Users/zwmac/Downloads/test/hand-01.png");
        OpenCvResult handResult = openCvService.analyzeHand(temp1);
        System.out.println(handResult);


        log.info("-- test face --");
        File temp2 = new File("/Users/zwmac/Downloads/test/hand-01.png");
        OpenCvResult faceResult  = openCvService.analyzeFace(temp2);
        log.info("-- faceResult:{} --", faceResult);

        log.info("-- test eye --");
        File temp3 = new File("/Users/zwmac/Downloads/test/hand-03.png");
        OpenCvResult eyeResult  = openCvService.analyzeEye(temp3);
        log.info("-- eyeResult:{} --", eyeResult);*/

        log.info("-- test --");
        String imgPath = "/Users/zwmac/Downloads/test/hand-03.png";
        //String cascadeName = "haarcascade_eye.xml";
        //String cascadeName = "haarcascade_frontalcatface.xml";
        //String cascadeName = "haarcascade_lefteye_2splits.xml";
        //String cascadeName = "haarcascade_righteye_2splits.xml";
        String cascadeName = "haarcascade_smile.xml";
        //String cascadeName = "haarcascade_fullbody.xml";
        File temp = new File(imgPath);
        OpenCvResult result = openCvService.analyze(temp, cascadeName);
        System.out.println("-- test result:" + result);
    }

}

3.2设计思路

复制代码
其实这个检测是非常粗的,但是可以作为初检。细化关键点范围,然后可以配合其他算法做进一步验证检测。
流程:
  • 本地加载OpenCV
  • 待检图片读取
  • 灰度化处理
  • 加载训练好的检测模型
  • 检测检测项
  • 判断检测结果
  • 其他算法判断
  • 保存检测结果图片(这个是在原图上标记检测位置,上面的结果已经可以用于程序业务了)

3.3结果图片

复制代码
上面的设计思路,其实还有很多算法、细节可以提高精度,先看下这个初检效果。效果涉及肖像权,我就不展示了,大家自己可以试试。

反正你会看到笑脸检测几乎就乱了,这个模型显然不行,还需要训练。

总结

  • 首先,基于OpenCV的检测,不管是什么语种,肯定是可行的。要谈性能那就要看业务、配置、语种等细节了。
  • 其次,自研的难点倒不是应用,百度也就是对照片文件分析,对我们暴露的也就是传文件的Api接口,内部的一些接口应该属于高级了,一般自己花钱了也应该不会自己去写、去调用了。
  • 最后,基于OpenCV的自研检测关键是模型。目前OpenCV公开的模型就没有手指、鼻子、耳朵等部位的模型。
相关推荐
江畔柳前堤几秒前
github实战指南01-账号配置与 SSH 密钥
运维·人工智能·深度学习·ssh·github·pyqt·信号处理
workflower1 小时前
使用大语言模型处理用户需求
大数据·人工智能·设计模式·重构·动态规划
一生了无挂1 小时前
Java处理JSON技巧教学(从基础到高阶实战全覆盖)
java·开发语言·json
李白的天不白1 小时前
使用 SmartAdmin 进行前后端开发
java·前端
swordbob1 小时前
Spring 单例 Bean 是线程安全的吗?
java·开发语言
CodePlayer竟然被占用了2 小时前
没有生态的大模型不算前沿
人工智能
米小虾2 小时前
AI Agent 开发实战:2026年主流框架与MCP协议深度解析
人工智能·agent
米小虾2 小时前
2026年AI大模型半年报:从"参数军备"到"生态为王",谁在领跑下半场?
人工智能
m0_571186602 小时前
第五十周周报
人工智能