上一教程: 在 Eclipse 中使用 OpenCV Java
下一教程: Android 开发入门
| |
| 原始作者 | Mimmo Cosenza |
| 兼容性 | OpenCV >= 3.0 |
- 警告
- 本教程可能包含过时的信息。
从 OpenCV 2.4.4 开始,OpenCV 支持桌面 Java 开发,其接口与 Android 开发的接口几乎相同。
Clojure 是一种现代 LISP 方言,由 Java 虚拟机托管,它与底层 JVM 提供了完全的互操作性。这意味着我们甚至可以将 Clojure REPL(读取-求值-打印循环)用作底层 OpenCV 引擎的交互式可编程接口。
本教程内容概要
本教程将帮助您设置一个基本的 Clojure 环境,以便在完全可编程的 Clojure REPL 中交互式地学习 OpenCV。
教程源代码
您可以在 OpenCV 仓库的 samples/java/clojure/simple-sample 文件夹中找到该示例的可运行源代码。按照教程中的说明安装 OpenCV 和 Clojure 后,执行以下命令从命令行运行示例。
cd path/to/samples/java/clojure/simple-sample
lein run
前言
有关安装支持桌面 Java 的 OpenCV 的详细说明,请参阅相应的教程。
如果您赶时间,这里有一个在 Mac OS X 上安装 OpenCV 的最小快速入门指南
- 注意
- 我假设您已经安装了 Xcode、JDK 和 CMake。
cd ~/
mkdir opt
git clone https://github.com/opencv/opencv.git
cd opencv
git checkout 2.4
mkdir build
cd build
cmake -DBUILD_SHARED_LIBS=OFF ..
...
...
make -j8
# optional
# make install
安装 Leiningen
安装支持桌面 Java 的 OpenCV 后,唯一其他要求是安装 Leiningen,它允许您管理 CLJ 项目的整个生命周期。
提供的安装指南非常容易遵循
- 下载脚本
- 将其放置在您的 $PATH 中(如果 /bin 在您的路径中,则是一个不错的选择)。
- 将脚本设置为可执行文件。(例如 chmod 755 /bin/lein)。
如果您在 Windows 上工作,请遵循此说明
您现在拥有 OpenCV 库和已完全安装的基本 Clojure 环境。现在需要配置 Clojure 环境以与 OpenCV 库进行交互。
安装 localrepo Leiningen 插件
Leiningen 原生支持的命令集(在 Leiningen 术语中称为任务)可以非常容易地通过各种插件进行扩展。其中之一是 lein-localrepo 插件,它允许将任何 JAR 库作为构件安装到您机器的本地 Maven 仓库中(通常在您用户名的 ~/.m2/repository 目录中)。
我们将使用这个 Lein 插件将 Java 和 Clojure 使用 OpenCV 库所需的 OpenCV 组件添加到本地 Maven 仓库中。
一般来说,如果您只想在项目中使用某个插件,可以直接将其添加到 Lein 创建的 CLJ 项目中。
相反,如果您希望某个插件在您用户名下的任何 CLJ 项目中都可用,您可以将其添加到 ~/.lein/ 目录中的 profiles.clj 文件中。
lein-localrepo 插件在我需要调用由 Java 接口封装的原生库的其他 CLJ 项目中将非常有用。因此,我决定让它在任何 CLJ 项目中都可用。
在 ~/.lein 目录中创建一个名为 profiles.clj 的文件,并将以下内容复制到其中
{:user {:plugins [[lein-localrepo "0.5.2"]]}}
这里我们表示 lein-localrepo 插件的“0.5.2”版本将对 Lein 创建的任何 CLJ 项目的 `:user` 配置可用。
您无需执行任何其他操作来安装该插件,因为它将在您首次执行任何 Lein 任务时自动从远程仓库下载。
将 Java 特定库安装为本地仓库
如果您按照标准文档在计算机上安装 OpenCV,您应该在构建 OpenCV 的目录中找到以下两个库
- build/bin/opencv-247.jar Java 库
- build/lib/libopencv_java247.dylib 原生库(如果您在 GNU/Linux 操作系统上构建 OpenCV,则为 .so 文件)
它们是 JVM 与 OpenCV 交互所需的唯一 OpenCV 库。
提取所需的 OpenCV 库
创建一个新目录来存储上述两个库。首先将 opencv-247.jar 库复制到其中。
cd ~/opt
mkdir clj-opencv
cd clj-opencv
cp ~/opt/opencv/build/bin/opencv-247.jar .
第一个库已完成。
现在,为了能够将 libopencv_java247.dylib 共享原生库添加到本地 Maven 仓库,我们首先需要将其打包为 JAR 文件。
原生库必须复制到模拟您的操作系统和架构名称的目录布局中。我使用的是 Mac OS X,采用 X86 64 位架构。因此我的布局将如下所示
mkdir -p native/macosx/x86_64
将 libopencv_java247.dylib 库复制到 x86_64 目录中。
cp ~/opt/opencv/build/lib/libopencv_java247.dylib native/macosx/x86_64/
如果您在不同的操作系统/架构组合上运行 OpenCV,这里是您可以选择的映射摘要。
操作系统
Mac OS X -> macosx
Windows -> windows
Linux -> linux
SunOS -> solaris
架构
amd64 -> x86_64
x86_64 -> x86_64
x86 -> x86
i386 -> x86
arm -> arm
sparc -> sparc
将原生库打包为 JAR
接下来,您需要使用 `jar` 命令从目录创建新的 JAR 文件,将原生库打包到 JAR 文件中。
jar -cMf opencv-native-247.jar native
请注意,M 选项指示 jar 命令不要为构件创建 MANIFEST 文件。
您的目录布局应如下所示
tree
.
|__ native
| |__ macosx
| |__ x86_64
| |__ libopencv_java247.dylib
|
|__ opencv-247.jar
|__ opencv-native-247.jar
3 directories, 3 files
本地安装 JAR 文件
现在,我们准备好借助 `lein-localrepo` 插件,将这两个 JAR 文件作为构件添加到本地 Maven 仓库中。
lein localrepo install opencv-247.jar opencv/opencv 2.4.7
这里,localrepo install 任务从 opencv-247.jar 库创建 opencv/opencv Maven 构件的 2.4.7 版本,然后将其安装到本地 Maven 仓库中。opencv/opencv 构件随后将对任何符合 Maven 规范的项目可用(Leiningen 内部基于 Maven)。
对先前已封装到新 JAR 文件中的原生库执行相同的操作。
lein localrepo install opencv-native-247.jar opencv/opencv-native 2.4.7
请注意,这两个构件的 groupId(opencv)是相同的。现在我们准备创建一个新的 CLJ 项目,开始与 OpenCV 进行交互。
创建项目
使用终端中的 lein new 任务创建一个新的 CLJ 项目。
# cd in the directory where you work with your development projects (e.g. ~/devel)
lein new simple-sample
正在基于 'default' 模板生成名为 simple-sample 的项目。
要查看其他模板(app, lein plugin, etc),请尝试 `lein help new`。
上述任务创建了以下 simple-sample 目录布局
tree simple-sample/
simple-sample/
|__ LICENSE
|__ README.md
|__ doc
| |__ intro.md
|
|__ project.clj
|__ resources
|__ src
| |__ simple_sample
| |__ core.clj
|__ test
|__ simple_sample
|__ core_test.clj
6 directories, 6 files
我们需要将这两个 OpenCV 构件添加为新创建项目的依赖项。打开 project.clj 并按如下方式修改其依赖项部分
(defproject simple-sample "0.1.0-SNAPSHOT"
description "FIXME: write description"
url "http://example.com/FIXME"
license {:name "Eclipse Public License"
url "http://www.eclipse.org/legal/epl-v10.html"}
dependencies [[org.clojure/clojure "1.5.1"]
[opencv/opencv "2.4.7"] ; 添加的行
[opencv/opencv-native "2.4.7"]]) ;添加的行
请注意,Clojure 编程语言也是一个 JAR 构件。这就是 Clojure 被称为托管语言的原因。
要验证一切是否正常,请执行 lein deps 任务。首次运行 Lein 任务时,在执行任务本身之前,它会花费一些时间下载所有必需的依赖项。
cd simple-sample
lein deps
...
deps 任务从 project.clj 和 ~/.lein/profiles.clj 文件中读取并合并 simple-sample 项目的所有依赖项,并验证它们是否已在本地 Maven 仓库中缓存。如果任务返回时没有显示无法检索这两个新构件的消息,则您的安装是正确的,否则请返回并仔细检查您是否一切都操作正确。
通过 REPL 使用 OpenCV
现在进入 simple-sample 目录并执行以下 Lein 任务
cd simple-sample
lein repl
...
...
nREPL server started on port 50907 on host 127.0.0.1
REPL-y 0.3.0
Clojure 1.5.1
Docs: (doc function-name-here)
(find-doc "part-of-name-here")
Source: (source function-name-here)
Javadoc: (javadoc java-object-or-class-here)
Exit: Control+D or (exit) or (quit)
Results: Stored in vars *1, *2, *3, an exception in *e
user=>
您可以立即通过发出任何 CLJ 表达式进行求值来与 REPL 交互。
user=> (+ 41 1)
42
user=> (println "Hello, OpenCV!")
Hello, OpenCV!
nil
user=> (defn foo [] (str "bar"))
#'user/foo
user=> (foo)
"bar"
从基于 Lein 的项目的主目录运行时,即使 lein repl 任务会自动加载所有项目依赖项,您仍然需要加载 OpenCV 原生库才能与 OpenCV 交互。
user=> (clojure.lang.RT/loadLibrary org.opencv.core.Core/NATIVE_LIBRARY_NAME)
nil
然后,您只需引用其类的完全限定名称即可开始与 OpenCV 交互。
- 注意
- 这里您可以找到完整的 OpenCV Java API。
user=> (org.opencv.core.Point. 0 0)
#<Point {0.0, 0.0}>
这里我们创建了一个二维 OpenCV Point 实例。即使 OpenCV Java 接口中包含的所有 Java 包都可以从 CLJ REPL 中立即使用,但用完全限定的包名作为 Point. 实例构造函数的前缀仍然非常烦人。
幸运的是,CLJ 提供了一种非常简单的方法来克服这种烦恼,即直接导入 Point 类。
user=> (import 'org.opencv.core.Point)
org.opencv.core.Point
user=> (def p1 (Point. 0 0))
#'user/p1
user=> p1
#<Point {0.0, 0.0}>
user=> (def p2 (Point. 100 100))
user=> (def p2 (Point. 100 100))
#'user/p2
我们甚至可以检查实例的类,并验证符号的值是否是 Java Point 类的一个实例。
org.opencv.core.Point
user=> (class p1)
true
user=> (instance? org.opencv.core.Point p1)
如果我们现在想使用 OpenCV Rect 类来创建一个矩形,即使它与 Point 类位于相同的 org.opencv.core 包中,我们仍然必须完全限定其构造函数。
user=> (org.opencv.core.Rect. p1 p2)
#<Rect {0, 0, 100x100}>
同样,CLJ 的导入功能非常方便,允许您一次性映射多个符号。
org.opencv.core.Size
user=> (import '[org.opencv.core Point Rect Size])
user=> (def r1 (Rect. p1 p2))
#'user/r1
user=> (org.opencv.core.Rect. p1 p2)
user=> r1
org.opencv.core.Rect
user=> (instance? org.opencv.core.Rect r1)
true
user=> (class r1)
user=> (instance? org.opencv.core.Rect r1)
user=> (Size. 100 100)
#<Size 100x100>
user=> (def sq-100 (Size. 100 100))
org.opencv.core.Size
#'user/sq-100
true
user=> (class sq-100)
user=> (instance? org.opencv.core.Size sq-100)
10000.0
显然,您也可以在实例上调用方法。
10000.0
user=> (.area r1)
user=> (.area sq-100)
10
user=> p1
或者修改成员字段的值。
user=> (set! (.x p1) 10)
10
#<Point {10.0, 0.0}>
10
显然,您也可以在实例上调用方法。
100.0
user=> (set! (.width sq-100) 10)
user=> (set! (.height sq-100) 10)
如果您不记得某个 OpenCV 类的行为,REPL 为您提供了方便地搜索相应 Javadoc 文档的机会
user=> (javadoc Rect)
"http://www.google.com/search?btnI=I%27m%20Feeling%20Lucky&q=allinurl:org/opencv/core/Rect.html"
在 REPL 中模拟 OpenCV Java 教程示例
import org.opencv.core.Mat;
import org.opencv.core.CvType;
import org.opencv.core.Scalar;
class SimpleSample {
以下是引用示例的原始 Java 源代码。
static{ System.loadLibrary("opencv_java244"); }
public static void main(String[] args) {
Mat mr1 = m.row(1);
mr1.setTo(new Scalar(1));
Mat mc5 = m.col(5);
mc5.setTo(new Scalar(5));
Mat m = new Mat(5, 10, CvType.CV_8UC1, new Scalar(0));
}
}
int main(int argc, char *argv[])
定义 highgui_qt.cpp:3
System.out.println("OpenCV Mat: " + m);
System.out.println("OpenCV Mat data:\n" + m.dump());
向项目添加注入
在开始编码之前,我们希望消除每次启动新的 REPL 与 OpenCV 交互时都需要交互式加载原生 OpenCV 库的繁琐操作。
首先,在 REPL 提示符下评估 (exit) 表达式来停止 REPL。
user=> (exit)
(defproject simple-sample "0.1.0-SNAPSHOT"
...
Bye for now!
然后打开您的 project.clj 文件并进行如下编辑
injections [(clojure.lang.RT/loadLibrary org.opencv.core.Core/NATIVE_LIBRARY_NAME)])
lein repl
这里我们表示无论何时运行 REPL,都将加载 OpenCV 原生库,这样我们就无需再手动记住加载它。
REPL-y 0.3.0
Clojure 1.5.1
Docs: (doc function-name-here)
(find-doc "part-of-name-here")
Source: (source function-name-here)
Javadoc: (javadoc java-object-or-class-here)
Exit: Control+D or (exit) or (quit)
Results: Stored in vars *1, *2, *3, an exception in *e
user=>
重新运行 lein repl 任务
nREPL server started on port 51645 on host 127.0.0.1
org.opencv.core.Scalar
导入感兴趣的 OpenCV Java 接口。
- user=> (import '[org.opencv.core Mat CvType Scalar])
- 我们将几乎逐字模仿原始的 OpenCV Java 教程来
- 创建一个所有元素初始化为 0 的 5x10 矩阵
- 将第二行的每个元素的值更改为 1
将第六列的每个元素的值更改为 5
打印所得矩阵的内容
user=> (def m (Mat. 5 10 CvType/CV_8UC1 (Scalar. 0 0)))
#'user/m
user=> (def mr1 (.row m 1))
#'user/mr1
user=> (.setTo mr1 (Scalar. 1 0))
#<Mat Mat [ 1*10*CV_8UC1, isCont=true, isSubmat=true, nativeObj=0x7fc9dac49880, dataAddr=0x7fc9d9c98d5a ]>
user=> (def mc5 (.col m 5))
#'user/mc5
user=> (.setTo mc5 (Scalar. 5 0))
[0, 0, 0, 0, 0, 5, 0, 0, 0, 0;
1, 1, 1, 1, 1, 5, 1, 1, 1, 1;
0, 0, 0, 0, 0, 5, 0, 0, 0, 0;
0, 0, 0, 0, 0, 5, 0, 0, 0, 0;
0, 0, 0, 0, 0, 5, 0, 0, 0, 0]
nil
#<Mat Mat [ 5*1*CV_8UC1, isCont=false, isSubmat=true, nativeObj=0x7fc9d9c995a0, dataAddr=0x7fc9d9c98d55 ]>
user=> (println (.dump m))
在开始编码之前,我们希望消除每次启动新的 REPL 与 OpenCV 交互时都需要交互式加载原生 OpenCV 库的繁琐操作。
首先,在 REPL 提示符下评估 (exit) 表达式来停止 REPL。
如果您习惯于函数式语言,那么所有这些被滥用和可变的“名词”会让人感到烦恼,因为您更偏爱“动词”。即使 CLJ 的互操作语法非常方便和完整,任何面向对象语言和任何函数式语言之间仍然存在阻抗不匹配(Scala 是一种混合范式编程语言)。
要在 REPL 提示符下退出 REPL,请键入 (exit)、Ctrl-D 或 (quit)。
- 交互式加载和模糊图像
- 在下一个示例中,您将学习如何使用以下 OpenCV 方法从 REPL 交互式加载和模糊图像
Highgui 类的 imread 静态方法,用于从文件读取图像
Highgui 类的 imwrite 静态方法,用于将图像写入文件
Imgproc 类的 GaussianBlur 静态方法,用于对原始图像应用高斯模糊
我们还将使用 Mat 类,该类由 imread 方法返回,并作为 GaussianBlur 和 imwrite 方法的主要参数。
首先,我们希望将图像文件添加到新创建的目录中,用于存储项目的静态资源。
mkdir -p resources/images
cp ~/opt/opencv/doc/tutorials/introduction/desktop_java/images/lena.png resource/images/
lein repl
REPL-y 0.3.0
Clojure 1.5.1
Docs: (doc function-name-here)
(find-doc "part-of-name-here")
Source: (source function-name-here)
Javadoc: (javadoc java-object-or-class-here)
Exit: Control+D or (exit) or (quit)
Results: Stored in vars *1, *2, *3, an exception in *e
现在照常启动 REPL,并首先导入我们将要使用的所有 OpenCV 类
nREPL server started on port 50624 on host 127.0.0.1
user=> (import '[org.opencv.core Mat Size CvType]
'[org.opencv.imgcodecs Imgcodecs]
'[org.opencv.imgproc Imgproc])
org.opencv.imgproc.Imgproc
现在从 resources/images/lena.png 文件中读取图像。
user=> (def lena (Highgui/imread "resources/images/lena.png"))
#'user/lena
user=> lena
#<Mat Mat [ 512*512*CV_8UC3, isCont=true, isSubmat=false, nativeObj=0x7f9ab3054c40, dataAddr=0x19fea9010 ]>
如您所见,通过简单地评估 lena 符号,我们知道 lena.png 是一个 512x512 的 CV_8UC3 元素类型的矩阵。让我们创建一个具有相同维度和元素类型的新 Mat 实例。
user=>
user=> (def blurred (Mat. 512 512 CvType/CV_8UC3))
现在,使用 lena 作为源矩阵,blurred 作为目标矩阵,应用高斯模糊滤镜。
user=> (Imgproc/GaussianBlur lena blurred (Size. 5 5) 3 3)
true
在开始编码之前,我们希望消除每次启动新的 REPL 与 OpenCV 交互时都需要交互式加载原生 OpenCV 库的繁琐操作。
首先,在 REPL 提示符下评估 (exit) 表达式来停止 REPL。
最后一步是将模糊后的矩阵保存到一个新的图像文件中。
user=> (Highgui/imwrite "resources/images/blurred.png" blurred)
以下是 Lena 的新模糊图像。
后续步骤
本教程仅介绍了与 CLJ REPL 中的 OpenCV 交互所需的最基本环境设置。
我建议任何 Clojure 初学者阅读Clojure Java 互操作章节,以了解与任何未封装在 Clojure 中以便在 Clojure 中以更惯用和函数式方式使用的普通 Java 库进行互操作所需的一切。
OpenCV Java API 不包含依赖于 Qt 的 highgui 模块功能(例如 namedWindow 和 imshow)。如果您想在从 REPL 与 OpenCV 交互时创建窗口并显示图像,目前您需要自行解决。您可以利用 Java Swing 来弥补这一不足。
许可