上一教程: 使用Eclipse进行OpenCV Java开发
下一教程: Android开发入门
| |
原作者 | Mimmo Cosenza |
兼容性 | OpenCV >= 3.0 |
- 警告
- 本教程可能包含过时信息。
从OpenCV 2.4.4版本开始,OpenCV支持使用与Android开发几乎相同的接口进行桌面Java开发。
Clojure 是一种现代的Lisp方言,运行于Java虚拟机 (JVM) 上,并提供与底层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
# 可选
# make install
安装Leiningen
安装支持桌面Java的OpenCV后,唯一其他的要求是安装Leiningen,它允许您管理整个CLJ项目的生命周期。
提供的安装指南非常容易遵循。
- 下载脚本
- 将其放在您的$PATH中(如果您的路径中有/bin,则/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文件。
本机库必须复制到一个目录布局中,该布局模拟您的操作系统和体系结构的名称。我使用的是具有X86 64位体系结构的Mac OS X。所以我的布局如下:
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 个目录,3 个文件
本地安装 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 仓库。然后,任何兼容 Maven 的项目(Leiningen 内部基于 Maven)都可以使用 opencv/opencv 构件。
对之前打包到新 jar 文件中的原生库执行相同的操作。
lein localrepo install opencv-native-247.jar opencv/opencv-native 2.4.7
请注意,两个构件的 groupId (opencv) 是相同的。现在,我们可以创建一个新的 CLJ 项目来开始与 OpenCV 交互。
创建项目
使用终端中的 lein new 任务创建一个新的 CLJ 项目。
# cd 到你用于开发项目的目录 (例如 ~/devel)
lein new simple-sample
基于“default”模板生成名为 simple-sample 的项目。
要查看其他模板 (app,lein plugin 等),请尝试 `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 个目录,6 个文件
我们需要将这两个 opencv 构件添加为新创建项目的依赖项。打开 project.clj 并修改其 dependencies 部分,如下所示:
(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 仓库中。如果任务返回时没有关于无法检索这两个新构件的消息,则你的安装是正确的,否则请返回并仔细检查你是否正确执行了所有操作。
使用 OpenCV 进行 REPL
现在 cd 到 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
文档:(doc function-name-here)
(find-doc "part-of-name-here")
源代码:(source function-name-here)
Javadoc:(javadoc java-object-or-class-here)
退出:Control+D 或 (exit) 或 (quit)
结果:存储在变量 *1、*2、*3 中,异常在 *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 repl 任务会自动加载所有项目依赖项,但在基于 lein 的项目的 home 目录中运行时,你仍然需要加载 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/p2
我们甚至可以检查实例的类,并验证符号的值是否是 Point Java 类的实例。
user=> (class p1)
org.opencv.core.Point
user=> (instance? org.opencv.core.Point p1)
true
如果我们现在想要使用 opencv Rect 类创建一个矩形,我们仍然必须完全限定其构造函数,即使它位于与 Point 类相同的 org.opencv.core 包中。
user=> (org.opencv.core.Rect. p1 p2)
#<Rect {0, 0, 100x100}>
同样,CLJ 导入功能非常方便,可以让你一次性映射更多符号。
user=> (import '[org.opencv.core Point Rect Size])
org.opencv.core.Size
user=> (def r1 (Rect. p1 p2))
#'user/r1
user=> r1
#<Rect {0, 0, 100x100}>
user=> (class r1)
org.opencv.core.Rect
user=> (instance? org.opencv.core.Rect r1)
true
user=> (Size. 100 100)
#<Size 100x100>
user=> (def sq-100 (Size. 100 100))
#'user/sq-100
user=> (class sq-100)
org.opencv.core.Size
user=> (instance? org.opencv.core.Size sq-100)
true
显然,你也可以调用实例上的方法。
user=> (.area r1)
10000.0
user=> (.area sq-100)
10000.0
或者修改成员字段的值。
user=> (set! (.x p1) 10)
10
user=> p1
#<Point {10.0, 0.0}>
user=> (set! (.width sq-100) 10)
10
user=> (set! (.height sq-100) 10)
10
user=> (.area sq-100)
100.0
如果你发现自己不记得 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 教程示例
现在让我们尝试将 OpenCV Java 教程示例 移植到 Clojure。我们不会将其写入源文件,而是在 REPL 中进行计算。
以下是所引用示例的原始 Java 源代码。
import org.opencv.core.Mat;
import org.opencv.core.CvType;
import org.opencv.core.Scalar;
class SimpleSample {
static{ System.loadLibrary("opencv_java244"); }
public static void main(String[] args) {
Mat m = new Mat(5, 10, CvType.CV_8UC1, new Scalar(0));
System.out.println("OpenCV Mat: " + m);
Mat mr1 = m.row(1);
mr1.setTo(new Scalar(1));
Mat mc5 = m.col(5);
mc5.setTo(new Scalar(5));
System.out.println("OpenCV Mat data:\n" + m.dump());
}
}
int main(int argc, char *argv[])
定义 highgui_qt.cpp:3
向项目添加注入
在开始编码之前,我们希望消除每次启动新的 REPL 与 OpenCV 交互时都需要手动加载原生 opencv 库的繁琐操作。
首先,在 REPL 提示符下输入 (exit) 表达式来停止 REPL。
然后打开你的 project.clj 文件并按如下方式编辑它
(defproject simple-sample "0.1.0-SNAPSHOT"
...
injections [(clojure.lang.RT/loadLibrary org.opencv.core.Core/NATIVE_LIBRARY_NAME)])
在这里,我们指定在每次运行 REPL 时加载 opencv 原生库,这样我们就无需再手动加载它了。
重新运行 lein repl 任务
lein repl
nREPL 服务器已在主机 127.0.0.1 的 51645 端口启动。
REPL-y 0.3.0
Clojure 1.5.1
文档:(doc function-name-here)
(find-doc "part-of-name-here")
源代码:(source function-name-here)
Javadoc:(javadoc java-object-or-class-here)
退出:Control+D 或 (exit) 或 (quit)
结果:存储在变量 *1、*2、*3 中,异常在 *e 中
user=>
导入感兴趣的 OpenCV Java 接口。
user=> (import '[org.opencv.core Mat CvType Scalar])
org.opencv.core.Scalar
我们将几乎逐字地模仿原始 OpenCV Java 教程来
- 创建一个 5x10 矩阵,其所有元素都初始化为 0
- 将第二行的每个元素的值更改为 1
- 将第 6 列的每个元素的值更改为 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))
#<Mat Mat [ 5*1*CV_8UC1, isCont=false, isSubmat=true, nativeObj=0x7fc9d9c995a0, dataAddr=0x7fc9d9c98d55 ]>
user=> (println (.dump m))
[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
如果您习惯于函数式语言,那么所有这些被滥用和改变的名词都会让您对动词的偏好感到恼火。即使 CLJ 的互操作语法非常方便和完整,但在任何面向对象语言和任何函数式语言之间仍然存在阻抗不匹配(Scala 是一种混合范式编程语言)。
要退出 REPL,请在 REPL 提示符下输入 (exit)、ctr-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/
读取图像
现在像往常一样启动 REPL,并从导入我们将要使用的所有 OpenCV 类开始
lein repl
nREPL 服务器已在主机 127.0.0.1 的 50624 端口启动。
REPL-y 0.3.0
Clojure 1.5.1
文档:(doc function-name-here)
(find-doc "part-of-name-here")
源代码:(source function-name-here)
Javadoc:(javadoc java-object-or-class-here)
退出:Control+D 或 (exit) 或 (quit)
结果:存储在变量 *1、*2、*3 中,异常在 *e 中
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=> (def blurred (Mat. 512 512 CvType/CV_8UC3))
#'user/blurred
user=>
现在使用 lena 作为源矩阵和 blurred 作为目标矩阵应用 GaussianBlur 滤镜。
user=> (Imgproc/GaussianBlur lena blurred (Size. 5 5) 3 3)
nil
最后一步是将模糊矩阵保存到新的图像文件中。
user=> (Highgui/imwrite "resources/images/blurred.png" blurred)
true
user=> (exit)
再见!
以下是 Lena 的新的模糊图像。
下一步
本教程仅介绍了能够在 CLJ REPL 中与 OpenCV 交互的最基本的环境设置。
我建议任何 Clojure 新手阅读Clojure Java 互操作章节,以了解所有你需要知道的内容,以便与任何未在 Clojure 中包装以使其在 Clojure 中以更惯用和函数式的方式可用的普通 Java 库进行互操作。
OpenCV Java API 并没有包装依赖于 Qt 的 highgui 模块功能(例如 namedWindow 和 imshow)。如果您想在从 REPL 与 OpenCV 交互时创建窗口并在其中显示图像,目前只能靠你自己了。您可以使用 Java Swing 来填补这个空白。
许可证
版权所有 © 2013 Giacomo (Mimmo) Cosenza 又名 Magomimmo
根据 BSD 3 条款许可证分发。