Java中集成OpenCV的操作教程
一、OpenCV 簡(jiǎn)介
OpenCV(Open Source Computer Vision Library) 是全球最流行的開源計(jì)算機(jī)視覺與圖像處理庫(kù),最初由 Intel 于 1999 年發(fā)起,主體以 C/C++ 編寫,官方提供 Java、Python、C++ 等多語言綁定。
它解決的核心問題是:把圖像/視頻這一「像素流」轉(zhuǎn)換成可用數(shù)據(jù)——邊框、輪廓、關(guān)鍵點(diǎn)、二值掩碼、縮放/旋轉(zhuǎn)后的圖等「像素級(jí)結(jié)果」。
1.1 關(guān)鍵認(rèn)知
- OpenCV 只做圖像處理,不訓(xùn)練、也不推理神經(jīng)網(wǎng)絡(luò)。它產(chǎn)出「像素級(jí)結(jié)果」(邊緣圖、二值圖、帶框的圖),而 DJL / SmartJavaAI 產(chǎn)出「語義結(jié)果」(這是什么物體、概率多少)。
- 在 Java 生態(tài)里,OpenCV 常作為深度學(xué)習(xí)視覺 pipeline 的預(yù)處理與后處理「手術(shù)刀」:切圖、描邊、找輪廓、算像素、畫框。
- OpenCV 的 Java 綁定通過 JNI 調(diào)用原生 C++ 庫(kù),因此運(yùn)行前必須先加載平臺(tái)對(duì)應(yīng)的
opencv_java*.dll / .so / .dylib。
1.2 資源
- 官方倉(cāng)庫(kù):
https://github.com/opencv/opencv - 官方文檔:
https://docs.opencv.org/4.x/ - 人臉/物體檢測(cè)權(quán)重:
opencv/data/haarcascades/
二、核心功能概覽
| 功能域 | 典型能力 | 主要 Java 類 |
|---|---|---|
| 圖像 / 視頻 IO | 讀圖、寫圖、攝像頭捕獲、視頻讀寫 | Imgcodecs、VideoCapture、VideoWriter |
| 基礎(chǔ)圖像處理 | 灰度化、色彩空間轉(zhuǎn)換、模糊、閾值、 morphological | Imgproc、Core |
| 幾何變換 | 縮放、旋轉(zhuǎn)、仿射/透 視變換、裁剪 | Imgproc、Core |
| 邊緣與輪廓 | Canny 邊緣檢測(cè)、輪廓提取、輪廓屬性分析 | Imgproc |
| 繪制 | 畫矩形/圓/線/文字、填充 | Imgproc |
| 特征與輪廓描述 | 輪廓外接矩形/圓、面積、周長(zhǎng) | Imgproc、Core |
| 經(jīng)典機(jī)器學(xué)習(xí)視覺 | Haar 級(jí)聯(lián)人臉檢測(cè)、HOG、特征點(diǎn) | objdetect.CascadeClassifier |
| 高級(jí)模塊 | 相機(jī)標(biāo)定 calib3d、目標(biāo)跟蹤 video、背景減除 | Calib3d、Video、Photo |
三、核心概念與數(shù)據(jù)結(jié)構(gòu)
| 概念 | 說明 |
|---|---|
| native 庫(kù)(核心差異) | OpenCV 主體是 C++,Java 通過 JNI 調(diào)用。必須先加載平臺(tái)對(duì)應(yīng)的 .dll/.so/.tylib。工程用 nu.pattern.OpenCV.loadLocally() 自動(dòng)下載并加載。 |
| Mat | OpenCV 的核心數(shù)據(jù)結(jié)構(gòu)「多維稠密數(shù)組」,用于表示圖像(H×W×C 像素矩陣)。release() 釋放底層內(nèi)存。Mat 不實(shí)現(xiàn) AutoCloseable,必須手動(dòng) release()。 |
| Imgcodecs | 圖像文件讀寫:imread(path) / imwrite(path, mat)。 |
| Imgproc | 圖像處理算子大本營(yíng):cvtColor、GaussianBlur、Canny、threshold、findContours、rectangle、circle、line、putText、drawContours。 |
| Core | 基礎(chǔ)運(yùn)算與常量:Core.VERSION、Core.countNonZero 等。 |
| Scalar | 四通道顏色值 (B, G, R, A)。OpenCV 默認(rèn) BGR 順序,不是 RGB。 |
| Size / Point / Rect | 幾何基礎(chǔ)類型:尺寸、點(diǎn)、矩形。 |
| CascadeClassifier | Haar/級(jí)聯(lián)分類器,用于人臉等目標(biāo)檢測(cè);構(gòu)造需傳入訓(xùn)練好的 .xml 權(quán)重。 |
| CvType | 像素類型常量,如 CvType.CV_8UC3 表示 8 位無符號(hào)、3 通道(彩色)。 |
四、Java 環(huán)境準(zhǔn)備
4.1 依賴(Maven)
org.openpnp:opencv 是官方 Java 綁定的 Maven 封裝,會(huì)自動(dòng)下載對(duì)應(yīng)平臺(tái)的 native 庫(kù),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 庫(kù)加載
首次調(diào)用 OpenCV.loadLocally() 時(shí):
- 探測(cè) OS + 架構(gòu)(Windows-x86_64 / linux-x86_64 / osx-aarch_64 …)
- 從 GitHub(
github.com/openpnp/opencv發(fā)布的 native 包)下載對(duì)應(yīng) zip - 解壓到用戶目錄(如
C:\Users\<user>\.cache\opencv\)并System.load - 之后復(fù)用,無需重復(fù)下載
import nu.pattern.OpenCV;
import org.opencv.core.Core;
OpenCV.loadLocally();
System.out.println("OpenCV version = " + Core.VERSION);
4.3 人臉檢測(cè)權(quán)重(可選)
Haar 級(jí)聯(lián)分類器需要訓(xùn)練好的 .xml:
models/haarcascade_frontalface_default.xml
獲?。?code>https://raw.githubusercontent.com/opencv/opencv/master/data/haarcascades/haarcascade_frontalface_default.xml,放到工程根目錄 models/ 下即可。
五、核心 API 說明
約定:src 源 Mat,dst 目標(biāo) Mat,gray 灰度 Mat。OpenCV 算子幾乎都是 Imgproc 的靜態(tài)方法。
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 默認(rèn) BGR 通道順序,與常見 RGB 相反。
5.3 模糊 / 降噪 ——Imgproc.GaussianBlur
Imgproc.GaussianBlur(src, dst, new Size(5, 5), 0);
核大?。ǖ谌齻€(gè)參數(shù) Size)通常取奇數(shù),越大越模糊。
5.4 邊緣檢測(cè) ——Imgproc.Canny
Mat edges = new Mat(); Imgproc.Canny(gray, edges, 100, 200); // 雙閾值(低,高),結(jié)果二值圖 int edgePoints = Core.countNonZero(edges); // 統(tǒng)計(jì)邊緣像素?cái)?shù)
5.5 閾值 ——Imgproc.threshold
// 固定閾值
Imgproc.threshold(gray, binary, 127, 255, Imgproc.THRESH_BINARY);
// OTSU 自動(dòng)閾值(返回實(shí)際閾值)
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); // 入?yún)⒈仨毷嵌祱D
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=實(shí)心填充
Imgproc.circle(canvas, new Point(300, 200), 60, new Scalar(0, 255, 0), -1); // 綠圓實(shí)心
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 人臉檢測(cè) ——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 創(chuàng)建畫布 —— 構(gòu)造Mat
Mat canvas = new Mat(300, 400, CvType.CV_8UC3, new Scalar(255, 255, 255)); // 白底 400x300
new Mat(height, width, type, scalar):注意構(gòu)造順序是 (高, 寬),與日常 (寬, 高) 相反。
5.10 內(nèi)存釋放(重要)
Mat 不實(shí)現(xiàn) AutoCloseable,不能用 try-with-resources。必須 try/finally 手動(dòng)釋放,否則會(huì)內(nèi)存/顯存泄漏:
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();
}
六、基礎(chǔ)使用示例
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 邊緣檢測(cè)
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("邊緣點(diǎn)數(shù)=" + Core.countNonZero(edges));
} finally {
src.release(); gray.release(); edges.release();
}
6.3 不依賴任何圖片的「繪制」
Mat 可以直接當(dāng)畫布,因此可以離線、無輸入圖必跑,是驗(yàn)證環(huán)境是否就緒的最佳用例:
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();
七、完整可運(yùn)行示例工程
下面給出示例 opencvDemo 工程的核心代碼,工程目錄:
opencvDemo/ ├── pom.xml ├── models/ # 可選:haarcascade_frontalface_default.xml ├── src/main/java/com/badao/demo/ │ ├── DemoApplication.java │ ├── config/OpenCvConfig.java # 啟動(dòng)時(shí)預(yù)加載 native │ ├── service/OpenCvService.java # 圖像處理能力 │ └── controller/OpenCvController.java └── src/test/java/com/badao/demo/OpenCvAllFeatureTest.java
7.1 啟動(dòng)類
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 預(yù)加載配置
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 加載失?。x線環(huán)境將無法運(yùn)行圖像處理): {}", 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 加載失敗,需聯(lián)網(wǎng)下載原生庫(kù): " + 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 邊緣檢測(cè) */
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 邊緣檢測(cè)完成 -> %s (寫入%s, 邊緣點(diǎn)數(shù)=%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個(gè)輪廓) -> %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();
}
}
/** 人臉檢測(cè)(需 haarcascade 權(quán)重) */
public String faceDetect(String inPath, String outPath) {
Path xml = Path.of(FACE_CASCADE);
if (!Files.exists(xml)) {
throw new IllegalStateException(
"缺少人臉檢測(cè)權(quán)重 " + FACE_CASCADE + ",可從 OpenCV 倉(cāng)庫(kù)下載放入 ./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("人臉檢測(cè)完成(命中%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 運(yùn)行與調(diào)用
cd opencvDemo mvn compile mvn spring-boot:run # 端口 998 # 調(diào)用(首次會(huì)聯(lián)網(wǎng)下載 native,請(qǐng)耐心等待) 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 單元測(cè)試(優(yōu)雅降級(jí))
測(cè)試采用「能跑就跑、缺依賴就跳過」機(jī)制:version() / drawShapes() 只依賴 native;其余用例在離線或缺權(quán)重時(shí)通過 Assumptions.assumeTrue(false, ...) 自動(dòng)跳過,保證離線也能 BUILD SUCCESS。
mvn test
八、業(yè)務(wù)場(chǎng)景實(shí)戰(zhàn)
下面給出 6 個(gè)真實(shí)業(yè)務(wù)中常見的 OpenCV 落地場(chǎng)景,全部基于上文 API,可直接復(fù)用。
8.1 場(chǎng)景一:證件照/頭像自動(dòng)灰度與裁邊
業(yè)務(wù):用戶上傳證件照,系統(tǒng)統(tǒng)一轉(zhuǎn)灰度、自動(dòng)裁剪白邊、縮放到標(biāo)準(zhǔn)尺寸(如 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);
// 自動(dòng)閾值 -> 找前景輪廓 -> 取最大外接矩形裁邊
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)); // 標(biāo)準(zhǔn)一寸照
return resized;
} finally {
src.release(); gray.release(); binary.release();
}
}
8.2 場(chǎng)景二:工業(yè)零件缺陷檢測(cè)(邊緣 + 輪廓面積)
業(yè)務(wù):產(chǎn)線拍照檢測(cè)零件是否有缺角/毛刺——通過 Canny + 輪廓面積偏離正常區(qū)間來判定。
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 場(chǎng)景三:人臉打卡 / 門禁(Haar 人臉檢測(cè))
業(yè)務(wù):攝像頭抓拍后,用 Haar 級(jí)聯(lián)定位人臉區(qū)域,裁剪出人臉供后續(xù)比對(duì)或存檔。
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 場(chǎng)景四:文檔掃描 / 透 視矯正
業(yè)務(wù):手機(jī)拍的文檔歪斜,先找最大輪廓四邊形,再做透 視變換拉正。
// 關(guān)鍵步驟:找最大輪廓 -> 求外接四邊形四點(diǎn) -> warpPerspective 矯正
// 此處給出骨架,findContours + 多邊形逼近 + getPerspectiveTransform 即可實(shí)現(xiàn)。
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 個(gè)頂點(diǎn)則可用
// Core.getPerspectiveTransform(srcPts, dstPts) + Imgproc.warpPerspective(...) 完成矯正。
doc.release(); gray.release(); binary.release(); hier.release();
8.5 場(chǎng)景五:視頻抽幀 + 移動(dòng)偵測(cè)(背景減除)
業(yè)務(wù):監(jiān)控視頻中只對(duì)「有變化」的幀做處理/告警,降成本。
// 借助 video 模塊的背景減除器 // BackgroundSubtractorMOG2 mog = Video.createBackgroundSubtractorMOG2(); // mog.apply(frame, fgMask); -> fgMask 即前景(運(yùn)動(dòng)區(qū)域)二值圖 // 再對(duì) fgMask 做 findContours + 面積過濾即可定位移動(dòng)目標(biāo)。
8.6 場(chǎng)景六:AI 推理的前/后處理(OpenCV + DJL 協(xié)作)
業(yè)務(wù):目標(biāo)檢測(cè)模型輸出一堆坐標(biāo),OpenCV 負(fù)責(zé)畫框、裁剪、NMS 后處理。
// 假設(shè)模型返回 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();
九、常見問題與最佳實(shí)踐
| 現(xiàn)象 | 原因 / 解決 |
|---|---|
UnsatisfiedLinkError / native 加載失敗 | 首次運(yùn)行需聯(lián)網(wǎng)下載 native;離線環(huán)境會(huì)跳過相關(guān)測(cè)試/接口返回失敗。 |
try-with-resources 編譯報(bào)錯(cuò) Mat 無法轉(zhuǎn)換為 AutoCloseable | OpenCV 的 Mat/CascadeClassifier 不實(shí)現(xiàn) AutoCloseable,必須用 try/finally + mat.release()。 |
| 顏色怪異(紅藍(lán)顛倒) | OpenCV 默認(rèn) BGR,Scalar 要寫成 (B, G, R)。 |
| 人臉檢測(cè)返回「缺少權(quán)重」 | 把 haarcascade_frontalface_default.xml 放到 ./models/。 |
| 圖像讀取為空 / 中文路徑失敗 | imread 對(duì)部分中文/特殊路徑不友好,可先用 Files 復(fù)制到 ASCII 路徑,或用 imdecode + BytePointer 讀字節(jié)流。 |
| 內(nèi)存持續(xù)增長(zhǎng) | 每個(gè) Mat 用完必須 release(),尤其循環(huán)處理視頻幀時(shí)。 |
接口返回 success=false | 多為離線無法下載 native;聯(lián)網(wǎng)后重試。 |
最佳實(shí)踐:
- 統(tǒng)一封裝
try/finally釋放:建議寫一個(gè)Mat工具類或use(mat, m -> {...})函數(shù)式封裝,杜絕泄漏。 - 先灰度后處理:絕大多數(shù)算子(Canny、閾值、級(jí)聯(lián)檢測(cè))輸入灰度的效果與性能都更好。
- 先降噪再邊緣:Canny 前加
GaussianBlur可顯著減少噪點(diǎn)邊緣。 - 優(yōu)雅降級(jí):生產(chǎn)環(huán)境加載 native 失敗時(shí)不應(yīng)讓應(yīng)用崩潰,應(yīng)告警并降級(jí)(參考本工程
OpenCvConfig)。
十、OpenCV 與 DJL / SmartJavaAI 的關(guān)系
┌─────────────────────┐
│ 你的業(yè)務(wù)系統(tǒng)(Spring) │
└──────────┬──────────┘
│
┌──────────────┼───────────────────┐
│ │ │
OpenCV(圖像處理) DJL(張量/推理引擎) SmartJavaAI(封裝好的 AI 能力)
像素級(jí)原語 NDArray + 模型推理 人臉識(shí)別/ OCR/ 檢測(cè)…
│ │ │
└──────┬───────┴───────────────────┘
│ SmartJavaAI 底層大量調(diào)用 OpenCV 做預(yù)處理/畫框
▼
OpenCV 是它們的「畫筆與手術(shù)刀」
- OpenCV:給你「手術(shù)刀」——切圖、描邊、找輪廓、算像素。自己不產(chǎn)生語義結(jié)論。
- DJL:給你「大腦」——加載 ResNet/SSD/onnx 模型做張量推理,輸出分類/檢測(cè)框。
- SmartJavaAI:把上面兩者包成「即開即用能力」——你只傳圖,它返回「這是貓 / 框出人臉」。
- 三者互補(bǔ)不替代:寫一個(gè) AI 應(yīng)用通常 = OpenCV 做預(yù)處理與后處理 + DJL/SmartJavaAI 做推理。
附錄:API 速查表
| 能力 | 核心 API | 關(guān)鍵參數(shù)/常量 | 備注 |
|---|---|---|---|
| 加載 native | OpenCV.loadLocally() | — | 首次聯(lián)網(wǎng)下載,冪等 |
| 版本 | Core.VERSION | — | 字符串如 "4.7.0" |
| 讀圖 | Imgcodecs.imread(path) | 返回 Mat;空?qǐng)D用 mat.empty() 判錯(cuò) | |
| 寫圖 | Imgcodecs.imwrite(path, mat) | 返回 boolean | |
| 顏色空間 | Imgproc.cvtColor(src, dst, code) | COLOR_BGR2GRAY 等 | OpenCV 默認(rèn) BGR |
| 高斯模糊 | Imgproc.GaussianBlur(src, dst, new Size(k,k), 0) | 核大小需奇數(shù) | |
| 邊緣檢測(cè) | Imgproc.Canny(gray, edges, low, high) | 雙閾值 | 結(jié)果二值圖 |
| 閾值 | Imgproc.threshold(gray, dst, 0, 255, THRESH_BINARY|THRESH_OTSU) | OTSU 自動(dòng)閾值 | 返回實(shí)際閾值 |
| 輪廓 | Imgproc.findContours(bin, list, hier, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE) | 入?yún)⒍祱D | |
| 畫輪廓 | Imgproc.drawContours(img, list, -1, new Scalar(b,g,r), thick) | idx=-1 畫全部 | |
| 畫矩形 | Imgproc.rectangle(img, p1, p2, scalar, thick) | thick=-1 實(shí)心 | |
| 畫圓 | 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 | |
| 人臉檢測(cè) | new CascadeClassifier(xml); cascade.detectMultiScale(gray, MatOfRect) | 需 .xml 權(quán)重 | |
| 顏色 | new Scalar(B, G, R, A) | 注意 BGR 順序 | |
| 畫布 | new Mat(h, w, CvType.CV_8UC3, new Scalar(...)) | CV_8UC3=彩色 | 構(gòu)造順序(高,寬) |
| 像素統(tǒng)計(jì) | Core.countNonZero(mat) | 用于數(shù)邊緣/前景點(diǎn) | |
| 釋放 | mat.release() | 必須手動(dòng) | 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 不會(huì)自動(dòng)關(guān)閉,務(wù)必 release
}
以上就是Java中集成OpenCV的操作教程的詳細(xì)內(nèi)容,更多關(guān)于Java集成OpenCV的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
spring boot實(shí)現(xiàn)圖片上傳和下載功能
這篇文章主要為大家詳細(xì)介紹了spring boot實(shí)現(xiàn)圖片上傳和下載功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-02-02
javaweb圖書商城設(shè)計(jì)之分類模塊(2)
這篇文章主要為大家詳細(xì)介紹了javaweb圖書商城設(shè)計(jì)之分類模塊的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11
MyBatis處理懶加載和預(yù)加載的實(shí)戰(zhàn)詳解
在Java開發(fā)中,MyBatis是一個(gè)廣泛使用的持久層框架,它為開發(fā)者提供了靈活、強(qiáng)大的數(shù)據(jù)訪問能力,兩種常用的加載策略是懶加載和預(yù)加載,本文將介紹這兩種加載方式的概念、適用場(chǎng)景以及MyBatis中的實(shí)現(xiàn)方式,需要的朋友可以參考下2026-02-02
SpringBoot接口冪等性4種解決方案+避坑實(shí)戰(zhàn)指南
接口冪等性,看似簡(jiǎn)單,實(shí)則關(guān)乎資金安全、用戶體驗(yàn)和系統(tǒng)穩(wěn)定性,今天,咱們就來聊聊在SpringBoot項(xiàng)目中,如何真正落地接口冪等性,避免成為“背鍋俠”,感興趣的朋友跟隨小編一起看看吧2025-09-09

