Java中集成OpenCV的操作教程
一、OpenCV 简介
OpenCV(Open Source Computer Vision Library) 是全球最流行的开源计算机视觉与图像处理库,最初由 Intel 于 1999 年发起,主体以 C/C++ 编写,官方提供 Java、Python、C++ 等多语言绑定。
它解决的核心问题是:把图像/视频这一「像素流」转换成可用数据——边框、轮廓、关键点、二值掩码、缩放/旋转后的图等「像素级结果」。
1.1 关键认知
- OpenCV 只做图像处理,不训练、也不推理神经网络。它产出「像素级结果」(边缘图、二值图、带框的图),而 DJL / SmartJavaAI 产出「语义结果」(这是什么物体、概率多少)。
- 在 Java 生态里,OpenCV 常作为深度学习视觉 pipeline 的预处理与后处理「手术刀」:切图、描边、找轮廓、算像素、画框。
- OpenCV 的 Java 绑定通过 JNI 调用原生 C++ 库,因此运行前必须先加载平台对应的
opencv_java*.dll / .so / .dylib。
1.2 资源
- 官方仓库:
https://github.com/opencv/opencv - 官方文档:
https://docs.opencv.org/4.x/ - 人脸/物体检测权重:
opencv/data/haarcascades/
二、核心功能概览
| 功能域 | 典型能力 | 主要 Java 类 |
|---|---|---|
| 图像 / 视频 IO | 读图、写图、摄像头捕获、视频读写 | Imgcodecs、VideoCapture、VideoWriter |
| 基础图像处理 | 灰度化、色彩空间转换、模糊、阈值、 morphological | Imgproc、Core |
| 几何变换 | 缩放、旋转、仿射/透 视变换、裁剪 | Imgproc、Core |
| 边缘与轮廓 | Canny 边缘检测、轮廓提取、轮廓属性分析 | Imgproc |
| 绘制 | 画矩形/圆/线/文字、填充 | Imgproc |
| 特征与轮廓描述 | 轮廓外接矩形/圆、面积、周长 | Imgproc、Core |
| 经典机器学习视觉 | Haar 级联人脸检测、HOG、特征点 | objdetect.CascadeClassifier |
| 高级模块 | 相机标定 calib3d、目标跟踪 video、背景减除 | Calib3d、Video、Photo |
三、核心概念与数据结构
| 概念 | 说明 |
|---|---|
| native 库(核心差异) | OpenCV 主体是 C++,Java 通过 JNI 调用。必须先加载平台对应的 .dll/.so/.tylib。工程用 nu.pattern.OpenCV.loadLocally() 自动下载并加载。 |
| Mat | OpenCV 的核心数据结构「多维稠密数组」,用于表示图像(H×W×C 像素矩阵)。release() 释放底层内存。Mat 不实现 AutoCloseable,必须手动 release()。 |
| Imgcodecs | 图像文件读写:imread(path) / imwrite(path, mat)。 |
| Imgproc | 图像处理算子大本营:cvtColor、GaussianBlur、Canny、threshold、findContours、rectangle、circle、line、putText、drawContours。 |
| Core | 基础运算与常量:Core.VERSION、Core.countNonZero 等。 |
| Scalar | 四通道颜色值 (B, G, R, A)。OpenCV 默认 BGR 顺序,不是 RGB。 |
| Size / Point / Rect | 几何基础类型:尺寸、点、矩形。 |
| CascadeClassifier | Haar/级联分类器,用于人脸等目标检测;构造需传入训练好的 .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-web、spring-boot-starter-test、lombok。
4.2 native 库加载
首次调用 OpenCV.loadLocally() 时:
- 探测 OS + 架构(Windows-x86_64 / linux-x86_64 / osx-aarch_64 …)
- 从 GitHub(
github.com/openpnp/opencv发布的 native 包)下载对应 zip - 解压到用户目录(如
C:\Users\<user>\.cache\opencv\)并System.load - 之后复用,无需重复下载
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 无法转换为 AutoCloseable | OpenCV 的 Mat/CascadeClassifier 不实现 AutoCloseable,必须用 try/finally + mat.release()。 |
| 颜色怪异(红蓝颠倒) | OpenCV 默认 BGR,Scalar 要写成 (B, G, R)。 |
| 人脸检测返回「缺少权重」 | 把 haarcascade_frontalface_default.xml 放到 ./models/。 |
| 图像读取为空 / 中文路径失败 | imread 对部分中文/特殊路径不友好,可先用 Files 复制到 ASCII 路径,或用 imdecode + BytePointer 读字节流。 |
| 内存持续增长 | 每个 Mat 用完必须 release(),尤其循环处理视频帧时。 |
接口返回 success=false | 多为离线无法下载 native;联网后重试。 |
最佳实践:
- 统一封装
try/finally释放:建议写一个Mat工具类或use(mat, m -> {...})函数式封装,杜绝泄漏。 - 先灰度后处理:绝大多数算子(Canny、阈值、级联检测)输入灰度的效果与性能都更好。
- 先降噪再边缘:Canny 前加
GaussianBlur可显著减少噪点边缘。 - 优雅降级:生产环境加载 native 失败时不应让应用崩溃,应告警并降级(参考本工程
OpenCvConfig)。
十、OpenCV 与 DJL / SmartJavaAI 的关系
┌─────────────────────┐
│ 你的业务系统(Spring) │
└──────────┬──────────┘
│
┌──────────────┼───────────────────┐
│ │ │
OpenCV(图像处理) DJL(张量/推理引擎) SmartJavaAI(封装好的 AI 能力)
像素级原语 NDArray + 模型推理 人脸识别/ OCR/ 检测…
│ │ │
└──────┬───────┴───────────────────┘
│ SmartJavaAI 底层大量调用 OpenCV 做预处理/画框
▼
OpenCV 是它们的「画笔与手术刀」
- OpenCV:给你「手术刀」——切图、描边、找轮廓、算像素。自己不产生语义结论。
- DJL:给你「大脑」——加载 ResNet/SSD/onnx 模型做张量推理,输出分类/检测框。
- SmartJavaAI:把上面两者包成「即开即用能力」——你只传图,它返回「这是猫 / 框出人脸」。
- 三者互补不替代:写一个 AI 应用通常 = OpenCV 做预处理与后处理 + DJL/SmartJavaAI 做推理。
附录:API 速查表
| 能力 | 核心 API | 关键参数/常量 | 备注 |
|---|---|---|---|
| 加载 native | OpenCV.loadLocally() | — | 首次联网下载,幂等 |
| 版本 | Core.VERSION | — | 字符串如 "4.7.0" |
| 读图 | Imgcodecs.imread(path) | 返回 Mat;空图用 mat.empty() 判错 | |
| 写图 | Imgcodecs.imwrite(path, mat) | 返回 boolean | |
| 颜色空间 | Imgproc.cvtColor(src, dst, code) | COLOR_BGR2GRAY 等 | OpenCV 默认 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的资料请关注脚本之家其它相关文章!


最新评论