从 OpenCV 2.4.4 开始,OpenCV 支持使用与 Android 开发几乎相同的接口进行桌面 Java 开发。
Clojure 是由 Java 虚拟机托管的一种现代 LISP 方言,它提供了与底层 JVM 的完全互操作性。这意味着我们甚至应该能够使用 Clojure REPL(Read Eval Print Loop)作为底层 OpenCV 引擎的交互式可编程接口。
我们将在本教程中执行的操作
本教程将帮助您设置一个基本的 Clojure 环境,以便在完全可编程的 CLojure REPL 中以交互方式学习 OpenCV。
教程源代码
您可以在 OpenCV 存储库的文件夹中找到该示例的可运行源代码。按照教程中的说明安装 OpenCV 和 Clojure 后,发出以下命令以从命令行运行示例。samples/java/clojure/simple-sample
序言
有关安装具有桌面 Java 支持的 OpenCV 的详细说明,请参阅相应的教程”。
如果您赶时间,以下是在 Mac OS X 上安装 OpenCV 的最低快速入门指南:
注意
我假设您已经安装了 xcode、jdk 和 Cmake。
安装 Leiningen
一旦你安装了支持桌面java的OpenCV,唯一的其他要求就是安装Leiningeng,它允许你管理CLJ项目的整个生命周期。
可用的安装指南非常容易遵循:
- 下载脚本
- 把它放在你的$PATH上(如果它在你的路径上,cf./bin 是一个不错的选择。
- 将脚本设置为可执行。(即 chmod 755/bin/lein)。
如果您在 Windows 上工作,请按照以下说明操作
现在,您已经拥有了 OpenCV 库和完全安装的基本 Clojure 环境。现在需要的是配置 Clojure 环境以与 OpenCV 库进行交互。
安装 localrepo Leiningen 插件
Leiningen 原生支持的命令集(用 Leiningen 术语来说就是任务)可以很容易地通过各种插件进行扩展。其中之一是 lein-localrepo 插件,它允许将任何 jar lib 作为工件安装在机器的本地 maven 存储库中(通常在用户名的 /.m2/repository 目录中)。
我们将使用这个 lein 插件将 Java 和 Clojure 使用 opencv 库所需的 opencv 组件添加到本地 maven 存储库中。
一般来说,如果你只想在项目基础上使用一个插件,可以直接添加到lein创建的CLJ项目中。
相反,当您希望插件可用于用户名空间中的任何 CLJ 项目时,您可以将其添加到 /.lein/ 目录中的 profiles.clj。
lein-localrepo 插件在其他 CLJ 项目中对我有用,在这些项目中,我需要调用由 Java 接口包装的本机库。因此,我决定将其提供给任何 CLJ 项目:
在 /.lein 目录中创建名为 profiles.clj 的文件,并将以下内容复制到其中:
在这里,我们说 lein-localrepo 插件的版本“0.5.2”将可用于 lein 创建的任何 CLJ 项目的 :user 配置文件。
您无需执行任何其他操作即可安装该插件,因为当您第一次发出任何 lein 任务时,它将自动从远程存储库下载。
将特定于 java 的库安装为本地存储库
如果您遵循了在计算机上安装 OpenCV 的标准文档,您应该在构建 OpenCV 的目录下找到以下两个库:
- build/bin/opencv-247.jar java 库
- build/lib/libopencv_java247.dylib 原生库(或 .so 在你构建的 OpenCV 一个 GNU/Linux 操作系统中)
它们是 JVM 与 OpenCV 交互所需的唯一 opencv 库。
拆解所需的 opencv 库
创建一个新目录以存储在上述两个库中。首先将 opencv-247.jar 库复制到其中。
第一个库完成。
现在,为了能够将 libopencv_java247.dylib 共享的原生库添加到本地 maven 存储库中,我们首先需要将其打包为 jar 文件。
必须将本机库复制到模仿操作系统和体系结构名称的目录布局中。我正在使用具有 X86 64 位架构的 Mac OS X。所以我的布局如下:
将 libopencv_java247.dylib 库复制到 x86_64 目录中。
如果您从不同的操作系统/体系结构对运行 OpenCV,以下是您可以选择的映射摘要。
将本机 lib 打包为 jar
接下来,您需要使用 jar 命令从目录创建新的 jar 文件,将本机 lib 打包到 jar 文件中。
请注意,ehe M 选项指示 jar 命令不为工件创建 MANIFEST 文件。
目录布局应如下所示:
本地安装 jar
现在,我们已准备好借助 lein-localrepo 插件将这两个 jar 作为工件添加到本地 maven 存储库中。
在这里,localrepo 安装任务创建了 2.4.7。从 opencv-247.jar 库发布 OpenCV/OpenCV Maven 工件,然后将其安装到本地 Maven 存储库中。然后,opencv/opencv 工件将可用于任何符合 maven 的项目(Leiningen 内部基于 maven)。
对之前包装在新 jar 文件中的本机库执行相同的操作。
请注意,这两个工件的 groupId opencv 是相同的。现在,我们已准备好创建一个新的 CLJ 项目来开始与 OpenCV 进行交互。
创建项目
使用终端中的 lein new 任务创建新的 CLJ 项目。
上述任务将创建以下简单示例目录布局:
我们需要添加两个 opencv 工件作为新创建项目的依赖项。打开 project.clj 并修改其 dependencies 部分,如下所示:
(defproject simple-sample “0.1.0-SNAPSHOT”description “FIXME: write description”url “http://example.com/FIXME”license {:name “Eclipse 公共许可证”url “http://www.eclipse.org/legal/epl-v10.html”}依赖项 [[org.clojure/clojure “1.5.1”][opencv/opencv “2.4.7”] ;添加行[opencv/opencv-本机 “2.4.7”]]);添加行
请注意,Clojure 编程语言也是一个 jar 工件。这就是 Clojure 被称为托管语言的原因。
要验证一切是否正常,请发出 lein deps 任务。第一次运行 lein 任务时,在执行任务本身之前,需要一些时间来下载所有必需的依赖项。
deps 任务从 project.clj 和 /.lein/profiles.clj 文件中读取和合并 simple-sample 项目的所有依赖项,并验证它们是否已缓存在本地 maven 存储库中。如果任务返回时没有关于无法检索两个新工件的消息,则您的安装是正确的,否则请返回并仔细检查您是否正确执行了所有操作。
使用 OpenCV 进行 REPLING
现在 cd 在 simple-sample 目录中并发出以下 lein 任务:
CD 简单样本
莱恩·雷普尔
..
nREPL 服务器在主机 127.0.0.1 上的端口 50907 上启动
REPL-y 0.3.0 版本
Clojure 1.5.1 版本
文档:(doc function-name-here)
(find-doc “部分名称在这里”)
源:(source function-name-here)
Javadoc:(javadoc java-object-or-class-here)
退出:Ctrl+D 或(退出)或(退出)
结果:存储在变量 *1、*2、*3 中,异常存储在 *e 中
用户=>
您可以通过发出任何要计算的 CLJ 表达式来立即与 REPL 进行交互。
用户=> (+ 41 1)
42
user=> (println “你好,OpenCV!”)
你好,OpenCV!
零
user=> (defn foo [] (str “bar”))
#'用户/foo
用户=> (foo)
“酒吧”
当从基于 lein 的项目的主目录运行时,即使 lein repl 任务自动加载所有项目依赖项,您仍然需要加载 opencv 本机库才能与 OpenCV 交互。
user=> (clojure.lang.RT/loadLibrary org.opencv.core.Core/NATIVE_LIBRARY_NAME)
零
然后,您可以通过引用其类的完全限定名称来开始与 OpenCV 交互。
注意
在这里,您可以找到完整的 OpenCV Java API。
user=> (org.opencv.core.Point. 0 0)
#<点 {0.0, 0.0}>
在这里,我们创建了一个二维的 opencv Point 实例。即使 OpenCV 的 java 接口中包含的所有 java 包都可以立即从 CLJ REPL 获得,在 Point 前面加上前缀也是非常烦人的。具有完全限定包名称的实例构造函数。
幸运的是,CLJ 提供了一种非常简单的方法,通过直接导入 Point 类来克服这种烦恼。
user=> (import 'org.opencv.core.Point)
org.opencv.core.Point(组织.opencv.core.Point)
user=> (def p1 (点 0 0))
#'用户/p1
用户=> p1
#<点 {0.0, 0.0}>
user=> (def p2 (点 100 100))
#'用户/p2
我们甚至可以检查实例的类,并验证符号的值是否是 Point java 类的实例。
user=>(类 P1)
org.opencv.core.Point(组织.opencv.core.Point)
user=> (instance? org.opencv.core.Point p1)
真
如果我们现在想使用 opencv Rect 类来创建一个矩形,我们再次必须完全限定它的构造函数,即使它留在 Point 类的同一个 org.opencv.core 包中。
user=> (org.opencv.core.Rect. p1, p2)
#<矩形 {0, 0, 100x100}>
同样,CLJ 导入工具非常方便,可让您一次映射更多符号。
user=> (import '[org.opencv.core 点矩形大小])
org.opencv.core.Size
user=> (def r1 (Rect. p1 p2))
#'用户/r1
用户=> r1
#<矩形 {0, 0, 100x100}>
user=>(类 R1)
org.opencv.core.Rect
user=> (instance? org.opencv.core.Rect r1)
真
user=>(大小:100、100)
#<尺寸 100x100>
user=> (def sq-100 (大小 100 100))
#'用户/sq-100
user=>(SQ-100 类)
org.opencv.core.Size
user=> (instance? org.opencv.core.Size sq-100)
真
显然,您也可以在实例上调用方法。
用户=> (.area r1)
10000.0
用户=> (.area sq-100)
10000.0
或者修改成员字段的值。
user=> (set!(.x p1) 10)
10
用户=> p1
#<点 {10.0, 0.0}>
user=> (set!(.宽度 sq-100) 10)
10
user=> (set!(.高度 sq-100) 10)
10
用户=> (.area sq-100)
100.0
如果您发现自己不记得 OpenCV 类的行为,REPL 让您有机会轻松搜索相应的 javadoc 文档:
user=> (javadoc 矩形)
“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 源代码。
向项目添加注入
在开始编码之前,我们希望消除在启动新的 REPL 以交互方式加载原生 opencv 库时与之交互的无聊需求。
首先,通过在 REPL 提示符下计算 (exit) 表达式来停止 REPL。
然后打开 project.clj 文件并按如下方式进行编辑:
在这里,我们说在运行 REPL 时加载 opencv 本机库,这样我们就不必再记住手动执行。
重新运行 lein repl 任务
导入感兴趣的 OpenCV java 接口。
我们将几乎逐字逐句地模仿原始的 OpenCV java 教程:
- 创建一个 5x10 矩阵,其所有元素都初始化为 0
- 将第二行的每个元素的值更改为 1
- 将第 6 列的每个元素的值更改为 5
- 打印得到的矩阵的内容
user=> (def m (Mat. 5 10 CvType/CV_8UC1 (Scalar. 0 0)))#'用户/m用户=> (def mr1 (.row m 1))#'用户/mr1user=> (.setTo mr1 (标量 1 0))#<垫垫 [ 1*10*CV_8UC1, isCont=true, isSubmat=true, nativeObj=0x7fc9dac49880, dataAddr=0x7fc9d9c98d5a ]>用户=> (def mc5 (.col m 5))#'用户/mc5user=> (.setTo mc5 (标量 5 0))#<Mat Mat [ 5*1*CV_8UC1, isCont=false, isSubmat=true, nativeObj=0x7fc9d9c995a0, dataAddr=0x7fc9d9c98d55 ]>用户=> (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]
如果你习惯了一种功能语言,那么所有那些被滥用和变异的名词都会激怒你对动词的偏好。即使 CLJ 互操作语法非常方便和完整,任何 OOP 语言和任何 FP 语言(即 Scala 是一种混合范式编程语言)之间仍然存在阻抗不匹配。
要退出 REPL,请在 REPL 提示符下键入 (exit)、ctr-D 或 (quit)。
以交互方式加载和模糊图像
在下一个示例中,您将学习如何使用以下 OpenCV 方法以交互方式从 REPL 加载、模糊和图像:
- Highgui 类中的 imread 静态方法,用于从文件中读取图像
- Highgui 类中的 imwrite 静态方法,用于将图像写入文件
- Imgproc 类中的 GaussianBlur 静态方法,用于模糊原始图像
我们还将使用 Mat 类,该类从 imread 方法返回并被接受为 GaussianBlur 和 imwrite 方法的主要参数。
将图像添加到项目
首先,我们要将一个镜像文件添加到新创建的目录中,用于存储项目的静态资源。
阅读图片
现在像往常一样启动 REPL,然后从导入我们将要使用的所有 OpenCV 类开始:
莱恩·雷普尔nREPL 服务器在主机 127.0.0.1 上的端口 50624 上启动REPL-y 0.3.0 版本Clojure 1.5.1 版本文档:(doc function-name-here)(find-doc “部分名称在这里”)源:(source function-name-here)Javadoc:(javadoc java-object-or-class-here)退出:Ctrl+D 或(退出)或(退出)结果:存储在变量 *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”))#'用户/莉娜用户=> Lena#<垫垫 [ 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))#'用户/模糊用户=>现在应用 GaussianBlur 过滤器,使用 lena 作为源矩阵,blurred 作为目标矩阵。
user=> (Imgproc/GaussianBlur lena blurred (Size. 5, 5), 3, 3)零最后一步,只需将模糊的矩阵保存在新的图像文件中即可。
user=> (Highgui/imwrite “resources/images/blurred.png” blured)真user=>(退出)再见!
以下是莉娜的新模糊图像。
后续步骤
本教程仅介绍在 CLJ REPL 中设置的非常基本的环境,以便能够与 OpenCV 进行交互。
我建议任何 Clojure 新手阅读 Clojure Java Interop 一章,以获得与任何未包装在 Clojure 中的纯 Java 库进行互操作所需的所有知识,以使其在 Clojure 中以更惯用和功能化的方式使用。
OpenCV Java API 不会根据 Qt 封装 highgui 模块功能(例如 namedWindow 和 imshow.如果您想创建窗口并在从 REPL 与 OpenCV 交互时将图像显示在其中,那么目前您只能自己动手。你可以使用 Java Swing 来填补这个空白。