使用OpenCV与Quarkus在Java中处理图像的技术实践

本文详细介绍了如何利用Quarkus框架集成OpenCV库进行Java图像处理,包括环境配置、实时编码特性、高斯模糊和灰度转换等实际技术实现,展示了无需手动安装原生库的便捷开发流程。

使用OpenCV与Quarkus在Java中处理图像

如果你从事计算机视觉领域,可能对OpenCV并不陌生。这是一个功能强大的库,几乎涵盖所有二维和三维图像处理需求,包括手势识别、人脸检测、运动跟踪等。基于BSD许可证,你可以直接下载并使用它。

OpenCV使用C语言编写,同时提供优秀的Java绑定。如果你像我一样是Java开发者,不想折腾原生绑定的加载和构建,那么请继续阅读。本文将展示如何结合流行的Quarkus框架使用OpenCV,而无需安装库或重新加载整个应用。

Quarkus是一个容器优先的Kubernetes原生框架,由Red Hat发起,专注于提升开发体验。其亮点之一是实时编码功能,我们将在应用中体验这一点。即使使用JNI,也无需反复构建和重载应用,只需轻松持续编码。这极大地简化了开发流程!

环境设置

假设你已有可用的Java环境,包括Java运行时和Maven或Gradle构建系统。你可能认为需要下载OpenCV库以使JNI工作,但事实上并不需要!Quarkus OpenCV扩展会处理这一切。Quarkus扩展是为核心框架增强功能的方式,Quarkiverse是存放所有扩展的中心。例如,如果你需要生成文档的框架、使用Hibernate Search进行模糊搜索或集成Amazon服务等,可以在这里查找。

我们将使用Quarkus-OpenCV扩展。首先,从code.quarkus.io创建一个简单的Java CLI项目。有多种方式可以开始:访问code.quarkus.io创建项目、运行本地Maven命令或使用Quarkus CLI。我将采用最熟悉的方式,从Maven开始。

1
2
3
4
5
6
7
8
mvn io.quarkus.platform:quarkus-maven-plugin:2.7.5.Final:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=getting-started \
    -Dextensions="resteasy"

cd getting-started

./mvnw quarkus:add-extension -Dextensions="io.quarkiverse.opencv:quarkus-opencv"

以上命令从code.quarkus.io下载启动项目,项目名为getting-started,然后切换到新项目目录,最后从Quarkiverse添加OpenCV扩展。

现在我们应该有一个可工作的项目。在IDE中打开它,检查pom.xml确保依赖正确。如果跟随操作,请确保pom.xml中的依赖列表如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<dependencies>
  <dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-arc</artifactId>
  </dependency>
  <dependency>
    <groupId>io.quarkiverse.opencv</groupId>
    <artifactId>quarkus-opencv</artifactId>
    <version>LATEST</version>
  </dependency>
  <dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-junit5</artifactId>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>rest-assured</artifactId>
    <scope>test</scope>
  </dependency>
</dependencies>

接下来,由于我移除了REST-easy依赖,还将删除REST端点org.acme.GreetingResource.java及其测试。现在我们有一个干净的项目可以开始。

创建CLI主程序

Quarkus提供创建命令行应用的选项,这非常酷,因为你可以将它们编译为原生代码。在本博客中,我们将为演示目的制作一个简单应用。但如果你要创建更复杂的CLI应用,可以参考quarkus.io上的指南使用picocli或JBang。

在我的应用中,我尽量保持简单。创建基础的QMain类:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package org.acme;

import io.quarkus.runtime.QuarkusApplication;
import io.quarkus.runtime.annotations.QuarkusMain;

@QuarkusMain
public class QMain implements QuarkusApplication {

    @Override
    public int run(String... args) throws Exception {
        // TODO Auto-generated method stub
        return 0;
    }
}

现在在终端中运行此应用。命令应启动实时编码会话。

启动实时编码

1
2
~/demos/getting-started mvn quarkus:dev
Quarkus started

什么是实时编码?只需持续编码,无需担心重启应用、添加新依赖和配置或重新构建等。你几乎不需要每次重启应用!

我将保持终端运行,并返回QMain继续编码。添加两个类级别属性:一个用于读取图像,另一个用于保存图像。Quarkus通过Microprofile API提供@ConfigProperty,允许我将配置属性注入应用。实现方式如下:

1
2
3
4
5
6
7
// 在application.properties中设置这些
@ConfigProperty(name = "cli.sourceImagePath")
String testImage;

// 在application.properties中设置这些
@ConfigProperty(name = "cli.targetImagePath")
String targetImage;

在application.properties中,添加这些文件的路径:

1
2
cli.sourceImagePath=images/testImage.jpg
cli.targetImagePath=images/testImageDetected-output.jpg

cli.sourceImagePath必须存在才能处理,而cli.targetImagePath不需要存在,因为我将保存图像到该路径。在我的情况下,我将图像添加到项目根目录,因此路径为images/。

加载和保存图像

回到QMain,添加这些路径的加载和保存方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
/**
 * 加载图像
 */
public Mat loadImage(String imagePath) {
    Imgcodecs imageCodecs = new Imgcodecs();
    return imageCodecs.imread(imagePath);
}

/**
 * 保存图像到目标路径
 */
public void saveImage(Mat imageMatrix, String targetPath) {
    Imgcodecs imgcodecs = new Imgcodecs();
    imgcodecs.imwrite(targetPath, imageMatrix);
}

Mat有两部分:图像头和数据矩阵。头是常量,但矩阵大小取决于图像。Mat用于从路径加载图像,并构成我们执行图像操作的基本结构。

定义过滤器接口

为了处理多个图像过滤器,我将定义一个接口,接收Mat作为源,处理它并返回Mat。这样我可以在保存图像前应用多个过滤器。

1
2
3
4
5
6
7
package org.acme;

import org.opencv.core.Mat;

public interface Filter {
    public Mat process(Mat src);
}

连接各部分

最后,尝试通过将方法调用添加到QMain的run方法来连接结构,并激发一些乐趣!

1
2
3
4
5
6
@Override
public int run(String... args) throws Exception {
    Mat m = loadImage(testImage);
    saveImage(m, targetImage);
    return 0;
}

简单来说,以上代码加载图像并保存图像,尚未进行处理。如何测试?记住我们运行mvn quarkus:dev的终端,按空格键,程序将继续执行。多么酷!无需重建,所有配置、新类等都正常工作。这就是Quarkus带来的开发乐趣!

检查日志,它显示检测到application.properties的文件更改,并列出已更改的类。

发生了什么?Quarkus运行时执行了CLI应用,这意味着如果我转到cli.targetImagePath,应该在那里看到创建的图像。此时它看起来与源图像相同,因为我尚未进行任何处理。猜猜我接下来要做什么?

继续前进,我今天使用的图像由Shuttergames在Unsplash提供。

Quarkus基础图像

高斯模糊

我创建的第一个过滤器是高斯模糊过滤器。这是一个低通滤波器,意味着它衰减高频信号。整体视觉效果是平滑的模糊。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package org.acme;

import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;

public class GaussianBlur implements Filter {
    @Override
    public Mat process(Mat src) {
        if(src != null) {
            int MAX_KERNEL_LENGTH = 45;
            Mat dst = new Mat();
            for (int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2) {
                Imgproc.GaussianBlur(src, dst, new Size(i, i), 0, 0);
            }
            return dst;
        }
        else throw new IllegalArgumentException("Looking for Mat nothing found!, try passing org.opencv.core.Mat to process");
    }
}

以上我们定义了一个内核大小。内核是一个矩阵,定义锚定像素及其周围像素如何根据提供的函数更改。内核将定义卷积大小、权重和位于中心的锚点。process方法接收Mat对象,应用带有内核大小的高斯模糊过滤器,最后返回Mat对象。

RGB转灰度

在QMain的run方法中添加以下内容以调用我的过滤器:

1
2
3
4
5
6
7
@Override
public int run(String... args) throws Exception {
    Mat m = loadImage(testImage);
    m = new GaussianBlur().process(m);
    saveImage(m, targetImage);
    return 0;
}

完美,现在如果我在终端再次按空格键,它应执行所有更改。结果如下所示的模糊效果。模糊平滑,几乎像覆盖了镜头。

接下来,制作另一个过滤器。这次如何将图像转为灰度?OpenCV已经提供了一个简单的函数。再次使用Filter接口,因此传递Mat并接收一个返回。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package org.acme;

import org.opencv.core.Mat;
import org.opencv.imgproc.Imgproc;

public class RGB2Grey implements Filter{
    @Override
    public Mat process(Mat src) {
        if(src != null) {
            Mat dst = src;
            Imgproc.cvtColor(src, dst, Imgproc.COLOR_RGB2GRAY);
            return dst;
        }
        else throw new IllegalArgumentException("Looking for Mat nothing found!, try passing org.opencv.core.Mat to process");
    }
}

以上代码使用ImgProc提供的操作将所有像素从RGB转为灰度。

1
m = new RGB2Grey().process(m);

将以上添加到我的run方法,并在终端再次按空格键,应执行所有更改。瞧!我有了一个灰度图像。

希望你喜欢这篇关于如何结合使用Quarkus和OpenCV的指南。更多示例请查看带有代码示例的GitHub仓库。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计