当前位置: 代码网 > it编程>编程语言>Java > Java中集成OpenCV的操作教程

Java中集成OpenCV的操作教程

2026年07月29日 Java 我要评论
一、opencv 简介opencv(open source computer vision library) 是全球最流行的开源计算机视觉与图像处理库,最初由 intel 于 1999 年发起,主体以

一、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读图、写图、摄像头捕获、视频读写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 是它们的「画笔与手术刀」
  • opencv:给你「手术刀」——切图、描边、找轮廓、算像素。自己不产生语义结论。
  • djl:给你「大脑」——加载 resnet/ssd/onnx 模型做张量推理,输出分类/检测框。
  • smartjavaai:把上面两者包成「即开即用能力」——你只传图,它返回「这是猫 / 框出人脸」。
  • 三者互补不替代:写一个 ai 应用通常 = opencv 做预处理与后处理 + djl/smartjavaai 做推理。

附录: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的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com