java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java集成OpenCV

Java中集成OpenCV的操作教程

作者:霸道流氓气质

本文是一篇结构化的 OpenCV 技术教程,涵盖简介、核心功能、API 说明、基础使用示例、完整可运行示例工程、业务场景实战、常见问题与最佳实践,以及 OpenCV 与 DJL / SmartJavaAI 的关系定位,需要的朋友可以参考下

一、OpenCV 简介

OpenCV(Open Source Computer Vision Library) 是全球最流行的开源计算机视觉与图像处理库,最初由 Intel 于 1999 年发起,主体以 C/C++ 编写,官方提供 Java、Python、C++ 等多语言绑定。

它解决的核心问题是:把图像/视频这一「像素流」转换成可用数据——边框、轮廓、关键点、二值掩码、缩放/旋转后的图等「像素级结果」。

1.1 关键认知

1.2 资源

二、核心功能概览

功能域典型能力 主要 Java 类
图像 / 视频 IO读图、写图、摄像头捕获、视频读写ImgcodecsVideoCaptureVideoWriter
基础图像处理灰度化、色彩空间转换、模糊、阈值、 morphologicalImgprocCore
几何变换缩放、旋转、仿射/透 视变换、裁剪ImgprocCore
边缘与轮廓Canny 边缘检测、轮廓提取、轮廓属性分析Imgproc
绘制画矩形/圆/线/文字、填充Imgproc
特征与轮廓描述轮廓外接矩形/圆、面积、周长ImgprocCore
经典机器学习视觉Haar 级联人脸检测、HOG、特征点objdetect.CascadeClassifier
高级模块相机标定 calib3d、目标跟踪 video、背景减除Calib3dVideoPhoto

三、核心概念与数据结构

概念说明
native 库(核心差异)OpenCV 主体是 C++,Java 通过 JNI 调用。必须先加载平台对应的 .dll/.so/.tylib。工程用 nu.pattern.OpenCV.loadLocally() 自动下载并加载。
MatOpenCV 的核心数据结构「多维稠密数组」,用于表示图像(H×W×C 像素矩阵)。release() 释放底层内存。Mat 不实现 AutoCloseable,必须手动 release()
Imgcodecs图像文件读写:imread(path) / imwrite(path, mat)
Imgproc图像处理算子大本营:cvtColorGaussianBlurCannythresholdfindContoursrectanglecirclelineputTextdrawContours
Core基础运算与常量:Core.VERSIONCore.countNonZero 等。
Scalar四通道颜色值 (B, G, R, A)OpenCV 默认 BGR 顺序,不是 RGB。
Size / Point / Rect几何基础类型:尺寸、点、矩形。
CascadeClassifierHaar/级联分类器,用于人脸等目标检测;构造需传入训练好的 .xml 权重。
CvType像素类型常量,如 CvType.CV_8UC3 表示 8 位无符号、3 通道(彩色)。

四、Java 环境准备

4.1 依赖(Maven)

org.openpnp:opencv 是官方 Java 绑定的 Maven 封装,会自动下载对应平台的 native 库,API 与 OpenCV 官方文档完全一致

<properties>
    <java.version>17</java.version>
    <opencv.version>4.7.0-0</opencv.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.openpnp</groupId>
        <artifactId>opencv</artifactId>
        <version>${opencv.version}</version>
    </dependency>
</dependencies>

本工程是 Spring Boot 工程,还引入了 spring-boot-starter-webspring-boot-starter-testlombok

4.2 native 库加载

首次调用 OpenCV.loadLocally() 时:

  1. 探测 OS + 架构(Windows-x86_64 / linux-x86_64 / osx-aarch_64 …)
  2. 从 GitHub(github.com/openpnp/opencv 发布的 native 包)下载对应 zip
  3. 解压到用户目录(如 C:\Users\<user>\.cache\opencv\)并 System.load
  4. 之后复用,无需重复下载
import nu.pattern.OpenCV;
import org.opencv.core.Core;

OpenCV.loadLocally();
System.out.println("OpenCV version = " + Core.VERSION);

4.3 人脸检测权重(可选)

Haar 级联分类器需要训练好的 .xml

models/haarcascade_frontalface_default.xml

获取:https://raw.githubusercontent.com/opencv/opencv/master/data/haarcascades/haarcascade_frontalface_default.xml,放到工程根目录 models/ 下即可。

五、核心 API 说明

约定:src 源 Mat,dst 目标 Mat,gray 灰度 Mat。OpenCV 算子几乎都是 Imgproc静态方法

5.1 图像 IO ——Imgcodecs

Mat src = Imgcodecs.imread("D:/in.jpg");   // 读图,失败返回 empty Mat
if (src.empty()) { /* 处理读取失败 */ }
boolean ok = Imgcodecs.imwrite("D:/out.png", src);  // 写图,返回是否成功

5.2 颜色空间 ——Imgproc.cvtColor

Mat gray = new Mat();
Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);   // BGR -> 灰度
Imgproc.cvtColor(src, dst, Imgproc.COLOR_BGR2HSV);    // BGR -> HSV(常用于颜色分割)

OpenCV 默认 BGR 通道顺序,与常见 RGB 相反。

5.3 模糊 / 降噪 ——Imgproc.GaussianBlur

Imgproc.GaussianBlur(src, dst, new Size(5, 5), 0);

核大小(第三个参数 Size)通常取奇数,越大越模糊。

5.4 边缘检测 ——Imgproc.Canny

Mat edges = new Mat();
Imgproc.Canny(gray, edges, 100, 200);   // 双阈值(低,高),结果二值图
int edgePoints = Core.countNonZero(edges);  // 统计边缘像素数

5.5 阈值 ——Imgproc.threshold

// 固定阈值
Imgproc.threshold(gray, binary, 127, 255, Imgproc.THRESH_BINARY);
// OTSU 自动阈值(返回实际阈值)
double t = Imgproc.threshold(gray, binary, 0, 255,
        Imgproc.THRESH_BINARY | Imgproc.THRESH_OTSU);

5.6 轮廓提取 ——Imgproc.findContours+drawContours

List<MatOfPoint> contours = new ArrayList<>();
Mat hierarchy = new Mat();
Imgproc.findContours(binary, contours, hierarchy,
        Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);  // 入参必须是二值图
Imgproc.drawContours(src, contours, -1, new Scalar(0, 0, 255), 2);  // -1 画全部轮廓

常用轮廓属性:

for (MatOfPoint c : contours) {
    Rect r = Imgproc.boundingRect(c);     // 外接矩形
    double area = Imgproc.contourArea(c); // 面积
    // 外接圆
    Point center = new Point();
    float[] radius = new float[1];
    Imgproc.minEnclosingCircle(new MatOfPoint2f(c.toArray()), center, radius);
}

5.7 绘制 ——Imgproc.rectangle / circle / line / putText

Scalar red = new Scalar(0, 0, 255);     // BGR:红
Imgproc.rectangle(canvas, new Point(30, 30), new Point(170, 130), red, 3);  // 线宽3
Imgproc.rectangle(canvas, new Point(30,30), new Point(170,130), red, -1);   // 线宽-1=实心填充
Imgproc.circle(canvas, new Point(300, 200), 60, new Scalar(0, 255, 0), -1); // 绿圆实心
Imgproc.line(canvas, new Point(20, 260), new Point(380, 260), new Scalar(0,0,255), 2);
Imgproc.putText(canvas, "Hello OpenCV", new Point(60, 290),
        Imgproc.FONT_HERSHEY_SIMPLEX, 1.0, new Scalar(0,0,0), 2);

5.8 人脸检测 ——objdetect.CascadeClassifier

CascadeClassifier cascade = new CascadeClassifier("models/haarcascade_frontalface_default.xml");
MatOfRect faces = new MatOfRect();
cascade.detectMultiScale(gray, faces);   // 输入灰度图
for (Rect r : faces.toArray()) {
    Imgproc.rectangle(src, r, new Scalar(0, 255, 0), 2);  // 给每张脸画绿框
}

5.9 创建画布 —— 构造Mat

Mat canvas = new Mat(300, 400, CvType.CV_8UC3, new Scalar(255, 255, 255)); // 白底 400x300

new Mat(height, width, type, scalar):注意构造顺序是 (高, 宽),与日常 (宽, 高) 相反。

5.10 内存释放(重要)

Mat 不实现 AutoCloseable,不能用 try-with-resources。必须 try/finally 手动释放,否则会内存/显存泄漏:

Mat src = Imgcodecs.imread(in);
try {
    Mat dst = new Mat();
    Imgproc.cvtColor(src, dst, Imgproc.COLOR_BGR2GRAY);
    Imgcodecs.imwrite(out, dst);
    dst.release();
} finally {
    src.release();
}

六、基础使用示例

6.1 灰度化(最小可用)

Mat src = Imgcodecs.imread("D:/in.jpg");
Mat gray = new Mat();
try {
    Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
    Imgcodecs.imwrite("D:/gray.jpg", gray);
} finally {
    src.release(); gray.release();
}

6.2 Canny 边缘检测

Mat src = Imgcodecs.imread("D:/in.jpg");
Mat gray = new Mat();
Mat edges = new Mat();
try {
    Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
    Imgproc.GaussianBlur(gray, gray, new Size(3, 3), 0); // 先降噪效果好
    Imgproc.Canny(gray, edges, 100, 200);
    Imgcodecs.imwrite("D:/edges.png", edges);
    System.out.println("边缘点数=" + Core.countNonZero(edges));
} finally {
    src.release(); gray.release(); edges.release();
}

6.3 不依赖任何图片的「绘制」

Mat 可以直接当画布,因此可以离线、无输入图必跑,是验证环境是否就绪的最佳用例:

Mat canvas = new Mat(300, 400, CvType.CV_8UC3, new Scalar(255, 255, 255));
Imgproc.rectangle(canvas, new Point(30,30), new Point(170,130), new Scalar(255,0,0), 3);
Imgproc.putText(canvas, "OpenCV", new Point(60,290),
        Imgproc.FONT_HERSHEY_SIMPLEX, 1.0, new Scalar(0,0,0), 2);
Imgcodecs.imwrite("D:/draw.png", canvas);
canvas.release();

七、完整可运行示例工程

下面给出示例 opencvDemo 工程的核心代码,工程目录:

opencvDemo/
├── pom.xml
├── models/                          # 可选:haarcascade_frontalface_default.xml
├── src/main/java/com/badao/demo/
│   ├── DemoApplication.java
│   ├── config/OpenCvConfig.java     # 启动时预加载 native
│   ├── service/OpenCvService.java   # 图像处理能力
│   └── controller/OpenCvController.java
└── src/test/java/com/badao/demo/OpenCvAllFeatureTest.java

7.1 启动类

package com.badao.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

7.2 native 预加载配置

package com.badao.demo.config;

import lombok.extern.slf4j.Slf4j;
import nu.pattern.OpenCV;
import org.opencv.core.Core;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Slf4j
@Configuration
public class OpenCvConfig {
    @Bean
    public Object openCvInitializer() {
        try {
            OpenCV.loadLocally();
            log.info("OpenCV native 已加载,版本 = {}", Core.VERSION);
        } catch (Exception | UnsatisfiedLinkError e) {
            log.warn("OpenCV native 加载失败(离线环境将无法运行图像处理): {}", e.getMessage());
        }
        return new Object();
    }
}

7.3 核心 Service(灰度 / 边缘 / 轮廓 / 绘制 / 人脸)

package com.badao.demo.service;

import nu.pattern.OpenCV;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.MatOfRect;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;
import org.springframework.stereotype.Service;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

@Service
public class OpenCvService {

    private static final String FACE_CASCADE = "models/haarcascade_frontalface_default.xml";

    private void ensureLoaded() {
        try {
            OpenCV.loadLocally();
        } catch (Exception | UnsatisfiedLinkError e) {
            throw new IllegalStateException("OpenCV native 加载失败,需联网下载原生库: " + e.getMessage(), e);
        }
    }

    public String version() {
        ensureLoaded();
        return Core.VERSION;
    }

    /** 灰度化 */
    public String grayscale(String inPath, String outPath) {
        Mat src = read(inPath);
        Mat dst = new Mat();
        try {
            Imgproc.cvtColor(src, dst, Imgproc.COLOR_BGR2GRAY);
            boolean ok = Imgcodecs.imwrite(outPath, dst);
            return String.format("灰度化完成 -> %s (写入%s, 尺寸=%dx%d)",
                    outPath, ok ? "成功" : "失败", dst.cols(), dst.rows());
        } finally {
            src.release(); dst.release();
        }
    }

    /** Canny 边缘检测 */
    public String canny(String inPath, String outPath) {
        Mat src = read(inPath);
        Mat gray = new Mat();
        Mat edges = new Mat();
        try {
            Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
            Imgproc.GaussianBlur(gray, gray, new Size(3, 3), 0);
            Imgproc.Canny(gray, edges, 100, 200);
            boolean ok = Imgcodecs.imwrite(outPath, edges);
            return String.format("Canny 边缘检测完成 -> %s (写入%s, 边缘点数=%d)",
                    outPath, ok ? "成功" : "失败", Core.countNonZero(edges));
        } finally {
            src.release(); gray.release(); edges.release();
        }
    }

    /** 轮廓提取 + 绘制 */
    public String contours(String inPath, String outPath) {
        Mat src = read(inPath);
        Mat gray = new Mat();
        Mat binary = new Mat();
        Mat hierarchy = new Mat();
        try {
            Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
            Imgproc.threshold(gray, binary, 127, 255, Imgproc.THRESH_BINARY);
            List<MatOfPoint> contours = new ArrayList<>();
            Imgproc.findContours(binary, contours, hierarchy,
                    Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
            Imgproc.drawContours(src, contours, -1, new Scalar(0, 0, 255), 2);
            boolean ok = Imgcodecs.imwrite(outPath, src);
            return String.format("轮廓提取完成(共%d个轮廓) -> %s (写入%s)",
                    contours.size(), outPath, ok ? "成功" : "失败");
        } finally {
            src.release(); gray.release(); binary.release(); hierarchy.release();
        }
    }

    /** 图形绘制(不依赖输入图片,必定可跑) */
    public String drawShapes(String outPath) {
        ensureLoaded();
        Mat canvas = new Mat(300, 400, CvType.CV_8UC3, new Scalar(255, 255, 255));
        try {
            Imgproc.rectangle(canvas, new Point(30, 30), new Point(170, 130), new Scalar(255, 0, 0), 3);
            Imgproc.circle(canvas, new Point(300, 200), 60, new Scalar(0, 255, 0), -1);
            Imgproc.line(canvas, new Point(20, 260), new Point(380, 260), new Scalar(0, 0, 255), 2);
            Imgproc.putText(canvas, "Hello OpenCV", new Point(60, 290),
                    Imgproc.FONT_HERSHEY_SIMPLEX, 1.0, new Scalar(0, 0, 0), 2);
            boolean ok = Imgcodecs.imwrite(outPath, canvas);
            return String.format("绘制图形完成 -> %s (写入%s, 画布=400x300)",
                    outPath, ok ? "成功" : "失败");
        } finally {
            canvas.release();
        }
    }

    /** 人脸检测(需 haarcascade 权重) */
    public String faceDetect(String inPath, String outPath) {
        Path xml = Path.of(FACE_CASCADE);
        if (!Files.exists(xml)) {
            throw new IllegalStateException(
                    "缺少人脸检测权重 " + FACE_CASCADE + ",可从 OpenCV 仓库下载放入 ./models/");
        }
        Mat src = read(inPath);
        Mat gray = new Mat();
        CascadeClassifier cascade = new CascadeClassifier(xml.toString());
        try {
            Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
            MatOfRect faces = new MatOfRect();
            cascade.detectMultiScale(gray, faces);
            for (Rect r : faces.toArray()) {
                Imgproc.rectangle(src, r, new Scalar(0, 255, 0), 2);
            }
            boolean ok = Imgcodecs.imwrite(outPath, src);
            return String.format("人脸检测完成(命中%d张脸) -> %s (写入%s)",
                    faces.toArray().length, outPath, ok ? "成功" : "失败");
        } finally {
            src.release(); gray.release();
        }
    }

    private Mat read(String inPath) {
        ensureLoaded();
        Mat mat = Imgcodecs.imread(inPath);
        if (mat.empty()) {
            throw new IllegalArgumentException("无法读取图片: " + inPath);
        }
        return mat;
    }
}

7.4 HTTP 演示接口

package com.badao.demo.controller;

import com.badao.demo.service.OpenCvService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/opencv")
public class OpenCvController {

    @Autowired
    private OpenCvService openCvService;

    @GetMapping("/version")
    public Map<String, Object> version() {
        return wrap(() -> openCvService.version());
    }

    @GetMapping("/grayscale")
    public Map<String, Object> grayscale(@RequestParam String in, @RequestParam String out) {
        return wrap(() -> openCvService.grayscale(in, out));
    }

    @GetMapping("/canny")
    public Map<String, Object> canny(@RequestParam String in, @RequestParam String out) {
        return wrap(() -> openCvService.canny(in, out));
    }

    @GetMapping("/contours")
    public Map<String, Object> contours(@RequestParam String in, @RequestParam String out) {
        return wrap(() -> openCvService.contours(in, out));
    }

    @GetMapping("/draw")
    public Map<String, Object> draw(@RequestParam String out) {
        return wrap(() -> openCvService.drawShapes(out));
    }

    @GetMapping("/face")
    public Map<String, Object> face(@RequestParam String in, @RequestParam String out) {
        return wrap(() -> openCvService.faceDetect(in, out));
    }

    private Map<String, Object> wrap(Op op) {
        Map<String, Object> resp = new HashMap<>();
        try {
            resp.put("success", true);
            resp.put("message", op.run());
        } catch (Exception e) {
            resp.put("success", false);
            resp.put("message", e.getMessage());
        }
        return resp;
    }

    @FunctionalInterface
    private interface Op { String run(); }
}

7.5 运行与调用

cd opencvDemo
mvn compile
mvn spring-boot:run          # 端口 998

# 调用(首次会联网下载 native,请耐心等待)
curl "http://localhost:998/opencv/version"
curl "http://localhost:998/opencv/draw?out=./out/draw.png"
curl "http://localhost:998/opencv/grayscale?in=D:/t.jpg&out=./out/gray.png"
curl "http://localhost:998/opencv/canny?in=D:/t.jpg&out=./out/canny.png"
curl "http://localhost:998/opencv/contours?in=D:/shapes.png&out=./out/contours.png"
curl "http://localhost:998/opencv/face?in=D:/faces.jpg&out=./out/faces.png"

7.6 单元测试(优雅降级)

测试采用「能跑就跑、缺依赖就跳过」机制:version() / drawShapes() 只依赖 native;其余用例在离线或缺权重时通过 Assumptions.assumeTrue(false, ...) 自动跳过,保证离线也能 BUILD SUCCESS

mvn test

八、业务场景实战

下面给出 6 个真实业务中常见的 OpenCV 落地场景,全部基于上文 API,可直接复用。

8.1 场景一:证件照/头像自动灰度与裁边

业务:用户上传证件照,系统统一转灰度、自动裁剪白边、缩放到标准尺寸(如 295×413 一寸照)。

public Mat normalizeIdPhoto(String inPath) {
    Mat src = Imgcodecs.imread(inPath);
    Mat gray = new Mat();
    Mat binary = new Mat();
    try {
        Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
        // 自动阈值 -> 找前景轮廓 -> 取最大外接矩形裁边
        Imgproc.threshold(gray, binary, 0, 255, Imgproc.THRESH_BINARY_INV | Imgproc.THRESH_OTSU);
        List<MatOfPoint> contours = new ArrayList<>();
        Mat hier = new Mat();
        Imgproc.findContours(binary, contours, hier, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
        Rect best = null;
        for (MatOfPoint c : contours) {
            Rect r = Imgproc.boundingRect(c);
            if (best == null || r.area() > best.area()) best = r;
        }
        Mat crop = new Mat(src, best != null ? best : new Rect(0, 0, src.cols(), src.rows()));
        Mat resized = new Mat();
        Imgproc.resize(crop, resized, new Size(295, 413)); // 标准一寸照
        return resized;
    } finally {
        src.release(); gray.release(); binary.release();
    }
}

8.2 场景二:工业零件缺陷检测(边缘 + 轮廓面积)

业务:产线拍照检测零件是否有缺角/毛刺——通过 Canny + 轮廓面积偏离正常区间来判定。

public boolean isDefect(String partPath, double stdArea, double tol) {
    Mat src = Imgcodecs.imread(partPath);
    Mat gray = new Mat(), edges = new Mat(), binary = new Mat(), hier = new Mat();
    try {
        Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
        Imgproc.GaussianBlur(gray, gray, new Size(5, 5), 0);
        Imgproc.Canny(gray, edges, 50, 150);
        Imgproc.threshold(edges, binary, 0, 255, Imgproc.THRESH_BINARY | Imgproc.THRESH_OTSU);
        List<MatOfPoint> contours = new ArrayList<>();
        Imgproc.findContours(binary, contours, hier, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
        double area = contours.stream().mapToDouble(Imgproc::contourArea).sum();
        return Math.abs(area - stdArea) > stdArea * tol; // 偏离阈值即判定缺陷
    } finally {
        src.release(); gray.release(); edges.release(); binary.release(); hier.release();
    }
}

8.3 场景三:人脸打卡 / 门禁(Haar 人脸检测)

业务:摄像头抓拍后,用 Haar 级联定位人脸区域,裁剪出人脸供后续比对或存档。

public List<Mat> detectFaces(String photoPath) {
    CascadeClassifier cascade = new CascadeClassifier("models/haarcascade_frontalface_default.xml");
    Mat src = Imgcodecs.imread(photoPath);
    Mat gray = new Mat();
    List<Mat> faces = new ArrayList<>();
    try {
        Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
        MatOfRect rects = new MatOfRect();
        cascade.detectMultiScale(gray, rects);
        for (Rect r : rects.toArray()) {
            faces.add(new Mat(src, r)); // 裁剪出每张脸
        }
        return faces;
    } finally {
        src.release(); gray.release();
    }
}

8.4 场景四:文档扫描 / 透 视矫正

业务:手机拍的文档歪斜,先找最大轮廓四边形,再做透 视变换拉正。

// 关键步骤:找最大轮廓 -> 求外接四边形四点 -> warpPerspective 矫正
// 此处给出骨架,findContours + 多边形逼近 + getPerspectiveTransform 即可实现。
Mat doc = Imgcodecs.imread("D:/doc.jpg");
Mat gray = new Mat(), binary = new Mat();
Imgproc.cvtColor(doc, gray, Imgproc.COLOR_BGR2GRAY);
Imgproc.threshold(gray, binary, 0, 255, Imgproc.THRESH_BINARY | Imgproc.THRESH_OTSU);
List<MatOfPoint> contours = new ArrayList<>(); Mat hier = new Mat();
Imgproc.findContours(binary, contours, hier, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
// 取面积最大的轮廓,近似多边形(Imgproc.approxPolyDP),若 4 个顶点则可用
// Core.getPerspectiveTransform(srcPts, dstPts) + Imgproc.warpPerspective(...) 完成矫正。
doc.release(); gray.release(); binary.release(); hier.release();

8.5 场景五:视频抽帧 + 移动侦测(背景减除)

业务:监控视频中只对「有变化」的帧做处理/告警,降成本。

// 借助 video 模块的背景减除器
// BackgroundSubtractorMOG2 mog = Video.createBackgroundSubtractorMOG2();
// mog.apply(frame, fgMask);  -> fgMask 即前景(运动区域)二值图
// 再对 fgMask 做 findContours + 面积过滤即可定位移动目标。

8.6 场景六:AI 推理的前/后处理(OpenCV + DJL 协作)

业务:目标检测模型输出一堆坐标,OpenCV 负责画框、裁剪、NMS 后处理。

// 假设模型返回 detections: List<Detection{x,y,w,h,label}>
Mat img = Imgcodecs.imread("D:/scene.jpg");
for (Detection d : detections) {
    Rect r = new Rect(d.x, d.y, d.w, d.h);
    Imgproc.rectangle(img, r, new Scalar(0, 255, 0), 2);
    Imgproc.putText(img, d.label, new Point(d.x, d.y - 5),
            Imgproc.FONT_HERSHEY_SIMPLEX, 0.6, new Scalar(0, 255, 0), 2);
}
Imgcodecs.imwrite("D:/detected.jpg", img);
img.release();

九、常见问题与最佳实践

现象原因 / 解决
UnsatisfiedLinkError / native 加载失败首次运行需联网下载 native;离线环境会跳过相关测试/接口返回失败。
try-with-resources 编译报错 Mat 无法转换为 AutoCloseableOpenCV 的 Mat/CascadeClassifier 不实现 AutoCloseable,必须用 try/finally + mat.release()
颜色怪异(红蓝颠倒)OpenCV 默认 BGRScalar 要写成 (B, G, R)
人脸检测返回「缺少权重」haarcascade_frontalface_default.xml 放到 ./models/
图像读取为空 / 中文路径失败imread 对部分中文/特殊路径不友好,可先用 Files 复制到 ASCII 路径,或用 imdecode + BytePointer 读字节流。
内存持续增长每个 Mat 用完必须 release(),尤其循环处理视频帧时。
接口返回 success=false多为离线无法下载 native;联网后重试。

最佳实践

  1. 统一封装 try/finally 释放:建议写一个 Mat 工具类或 use(mat, m -> {...}) 函数式封装,杜绝泄漏。
  2. 先灰度后处理:绝大多数算子(Canny、阈值、级联检测)输入灰度的效果与性能都更好。
  3. 先降噪再边缘:Canny 前加 GaussianBlur 可显著减少噪点边缘。
  4. 优雅降级:生产环境加载 native 失败时不应让应用崩溃,应告警并降级(参考本工程 OpenCvConfig)。

十、OpenCV 与 DJL / SmartJavaAI 的关系

            ┌─────────────────────┐
            │  你的业务系统(Spring) │
            └──────────┬──────────┘
                       │
        ┌──────────────┼───────────────────┐
        │              │                   │
  OpenCV(图像处理)   DJL(张量/推理引擎)   SmartJavaAI(封装好的 AI 能力)
   像素级原语         NDArray + 模型推理     人脸识别/ OCR/ 检测…
        │              │                   │
        └──────┬───────┴───────────────────┘
               │  SmartJavaAI 底层大量调用 OpenCV 做预处理/画框
               ▼
          OpenCV 是它们的「画笔与手术刀」

附录:API 速查表

能力核心 API关键参数/常量备注
加载 nativeOpenCV.loadLocally()首次联网下载,幂等
版本Core.VERSION字符串如 "4.7.0"
读图Imgcodecs.imread(path)返回 Mat;空图用 mat.empty() 判错
写图Imgcodecs.imwrite(path, mat)返回 boolean
颜色空间Imgproc.cvtColor(src, dst, code)COLOR_BGR2GRAYOpenCV 默认 BGR
高斯模糊Imgproc.GaussianBlur(src, dst, new Size(k,k), 0)核大小需奇数
边缘检测Imgproc.Canny(gray, edges, low, high)双阈值结果二值图
阈值Imgproc.threshold(gray, dst, 0, 255, THRESH_BINARY|THRESH_OTSU)OTSU 自动阈值返回实际阈值
轮廓Imgproc.findContours(bin, list, hier, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE)入参二值图
画轮廓Imgproc.drawContours(img, list, -1, new Scalar(b,g,r), thick)idx=-1 画全部
画矩形Imgproc.rectangle(img, p1, p2, scalar, thick)thick=-1 实心
画圆Imgproc.circle(img, center, r, scalar, thick)
画线Imgproc.line(img, p1, p2, scalar, thick)
写字Imgproc.putText(img, text, p, FONT_HERSHEY_SIMPLEX, scale, scalar, thick)
外接矩形Imgproc.boundingRect(contour)返回 Rect
轮廓面积Imgproc.contourArea(contour)double
人脸检测new CascadeClassifier(xml); cascade.detectMultiScale(gray, MatOfRect).xml 权重
颜色new Scalar(B, G, R, A)注意 BGR 顺序
画布new Mat(h, w, CvType.CV_8UC3, new Scalar(...))CV_8UC3=彩色构造顺序(高,宽)
像素统计Core.countNonZero(mat)用于数边缘/前景点
释放mat.release()必须手动Mat 非 AutoCloseable

通用范式

Mat src = Imgcodecs.imread(in);
try {
    Mat dst = new Mat();
    Imgproc.cvtColor(src, dst, Imgproc.COLOR_BGR2GRAY);
    Imgcodecs.imwrite(out, dst);
    dst.release();
} finally {
    src.release();   // OpenCV 的 Mat 不会自动关闭,务必 release
}

以上就是Java中集成OpenCV的操作教程的详细内容,更多关于Java集成OpenCV的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文