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

本文详细介绍如何使用Quarkus框架集成OpenCV进行Java图像处理,包括项目设置、实时编码功能实现、图像加载保存以及高斯模糊和灰度转换等滤镜应用,展示完整的开发流程和代码示例。

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

如果你对计算机视觉感兴趣,可能已经熟悉OpenCV。这是一个令人惊叹的库,几乎包含进行2D和3D处理所需的一切功能。手势识别、人脸检测、运动跟踪,任何与图像处理相关的需求,OpenCV都可以成为你的首选。它基于BSD许可证,你可以直接下载并开始使用。

OpenCV是用C语言编写的,也有很好的Java绑定。如果你像我一样是Java开发者,不想陷入加载和构建本地绑定的麻烦,请继续阅读。在本文中,我将展示如何将OpenCV与流行的新框架Quarkus结合使用,而无需担心安装库或重新加载整个应用程序。

Quarkus是一个容器优先、Kubernetes原生的框架,由Red Hat发起,完全专注于开发者体验。我认为最有趣的部分是实时编码功能,我们将在应用程序中看到这一点。即使使用JNI,我也不需要不断构建和重新加载应用程序,可以轻松地持续编码。我真的很喜欢这个功能!它让生活变得更简单。

设置

假设你有一个可用的Java环境,包括Java运行时、Maven或Gradle构建系统。你可能认为需要下载OpenCV库才能使JNI工作等等,但猜猜看,你不需要!Quarkus OpenCV扩展将处理这些问题。Quarkus扩展是为核心Quarkus框架启用更多功能的方式。Quarkiverse是包含所有扩展的中心。

我们将使用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"

现在我们应该有一个可以开始工作的项目。在IDE中打开它,然后检查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依赖项,我还将删除org.acme.GreetingResource.java中的REST端点及其测试。

现在我们有一个干净的项目可以开始了。

创建CLI主程序

Quarkus提供了创建命令行应用程序的选项,这非常酷,因为你可以将这些编译成本地代码。在本博客中,我们只是为演示目的制作一个简单的应用程序。

在我的应用程序中,我尽量保持简单。让我们创建基础的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
~/demos/getting-started mvn quarkus:dev

什么是实时编码?只需继续编码,不用担心重新启动应用程序、添加新依赖项和配置或重新构建等等。你不需要每次都重新启动应用程序!(*几乎)

我将保持终端运行,并返回到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);
}

/**
 * 将图像保存到targetPath
 */
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,应该在那里看到一个创建的图像。此时它将看起来与源图像相同,因为我还没有对其进行任何处理。猜猜看?这正是我现在计划要做的事情。

高斯模糊

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
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对象。

RGB2Grey

将以下内容添加到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
17
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的演练。

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