阅读(3794) (1)

介绍OpenCV开发与Clojure

2017-08-24 11:04:34 更新

从OpenCV 2.4.4开始,OpenCV支持桌面Java开发,使用与Android开发几乎相同的界面。

Clojure是由Java虚拟机托管的当代LISP方言,它提供与底层JVM的完全互操作性。这意味着我们甚至可以使用Clojure REPL(Read Eval Print Loop)作为底层OpenCV引擎的交互式可编程接口。

我们将在本教程中做什么

本教程将帮助您在完全可编程的CLojure REPL中设置交互式学习OpenCV的基本Clojure环境。

教程源代码

您可以samples/java/clojure/simple-sample在OpenCV存储库的文件夹中找到可运行的示例源代码。按照教程中的说明安装OpenCV和Clojure后,请执行以下命令以从命令行运行示例。

cd path/to/samples/java/clojure/simple-sample
lein run

前言

有关使用桌面Java支持安装OpenCV的详细说明,请参阅相应的教程

如果你是匆忙,这里是一个最小的快速入门指南在Mac OS X上安装OpenCV:

注意
我假设你已经安装了xcodejdkCmake
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

一旦您安装OpenCV与桌面java支持,唯一的其他要求是安装Leiningeng,它允许您管理CLJ项目的整个生命周期。

可用的安装指南非常容易遵循:

  1. 下载脚本
  2. 将它放在$ PATH上(如果你的路径是cf./bin是一个不错的选择)
  3. 将脚本设置为可执行。(即chmod 755 / bin / lein)。

如果您在Windows上工作,请按照此说明进行操作

您现在拥有OpenCV库和完全安装的基本Clojure环境。现在需要配置Clojure环境与OpenCV库进行交互。

安装localrepo Leiningen插件

由Leiningen本人支持的一组命令(Leiningen的任务)可以通过各种插件轻松扩展。其中一个是lein-localrepo插件,允许在机器的本地maven存储库中安装任何jar lib作为工件(通常位于用户名的/.m2/repository目录中)。

我们将使用这个lein插件来添加到本地的maven存储库,Java和Clojure需要使用opencv lib的opencv组件。

一般来说,如果您只想在项目基础上使用插件,则可以直接添加到由lein创建的CLJ项目中。

相反,当您希望插件可用于用户名空间中的任何CLJ项目时,可以将其添加到/ .lein /目录中的profiles.clj中。

lein-localrepo插件对于我在其他CLJ项目中将是有用的,我需要调用Java接口包装的本地库。所以我决定把它提供给任何CLJ项目:

mkdir〜/ .lein

在/ .lein目录中创建一个名为profiles.clj的文件,并将其复制到以下内容中:

{:user {:plugins [[lein-localrepo“0.5.2”]]}}

这里我们说,lein-localrepo插件的版本“0.5.2”将可用于由lein创建的任何CLJ项目的用户配置文件。

您不需要做任何其他安装插件,因为它将在您首次发布任何lein任务时从远程存储库自动下载。

将java特定的lib安装为本地存储库

如果您在计算机上遵循用于安装OpenCV的标准文档,您应该在构建OpenCV的目录下找到以下两个lib:

  • build / bin / opencv-247.jar java lib
  • build / lib / libopencv_java247.dylib本机库(或.so中,您构建OpenCV是GNU / Linux操作系统)

它们是JVM与OpenCV交互所需的唯一opencv库。

拆分所需的opencv库

创建一个新的目录以存储在上述两个库中。首先将其复制到opencv-247.jar lib中。

cd〜/ opt
mkdir clj-opencv
cd clj-opencv
cp〜/ opt / opencv / build / bin / opencv-247.jar。

首先完成

现在,为了能够将本地maven库中的libopencv_java247.dylib共享的本地lib添加到我们首先需要将其打包成一个jar文件。

本机库必须被复制到一个模拟操作系统和体系结构名称的目录布局中。我正在使用具有X86 64位架构的Mac OS X。所以我的布局将如下所示:

mkdir -p native / macosx / x86_64

复制到x86_64目录中的libopencv_java247.dylib lib。

cp〜/ opt / opencv / build / lib / libopencv_java247.dylib native / macosx / x86_64 /

如果您从不同的OS / Architecture对运行OpenCV,则可以从中选择映射的摘要。

OS
Mac OS X -> macosx
Windows  -> windows
Linux    -> linux
SunOS    -> solaris
Architectures
amd64    -> x86_64
x86_64   -> x86_64
x86      -> x86
i386     -> x86
arm      -> arm
sparc    -> sparc

将本机lib打包成jar

接下来,您需要使用jar命令从一个目录创建一个新的jar文件,将本机的lib打包到一个jar文件中。

jar -cMf opencv-native-247.jar native

请注意,ehe M选项指示jar命令不为该工件创建一个MANIFEST文件。

您的目录布局应如下所示:

tree
.
|__ native
|   |__ macosx
|       |__ x86_64
|           |__ libopencv_java247.dylib
|
|__ opencv-247.jar
|__ opencv-native-247.jar
3 directories, 3 files

在本地安装jars

我们现在准备在lein-localrepo插件的帮助下,将两个jar作为工件添加到本地的maven仓库中。

lein localrepo install opencv-247.jar opencv/opencv 2.4.7

这里的localrepo安装任务创建了2.4.7。从opencv-247.jar lib释放opencv / opencv maven工件,然后将其安装到本地的maven存储库中。然后,opencv / opencv工件将可用于任何符合maven的项目(Leiningen内部基于maven)。

与之前包装在一个新的jar文件中的native lib做同样的事情。

lein localrepo install opencv-native-247.jar opencv/opencv-native 2.4.7

请注意,两个工件的groupId,opencv是相同的。现在我们准备开创一个新的CLJ项目,开始与OpenCV进行互动。

创建一个项目

通过使用终端的lein新任务创建一个新的CLJ项目。

# cd in the directory where you work with your development projects (e.g. ~/devel)
lein new simple-sample
Generating a project called simple-sample based on the 'default' template.
To see other templates (app, lein plugin, etc), try `lein help new`.

上述任务创建以下简单示例目录布局:

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"] ; added line
                 [opencv/opencv-native "2.4.7"]]) ;added line

请注意,Clojure编程语言也是一个jar工件。这就是为什么Clojure被称为托管语言。

验证一切正确的问题,lein deps任务。在第一次运行lein任务时,在执行任务本身之前,需要一些时间才能下载所有必需的依赖项。

cd simple-sample
lein deps
...

deps任务从project.clj和/ .lein / profiles.clj文件中读取并合并所有依赖关系的简单示例,并验证它们是否已被缓存在本地的maven存储库中。如果任务返回没有消息,无法检索两个新的工件,您的安装是正确的,否则返回并仔细检查您做的一切正确。

REPLing with OpenCV

现在cd在简单样本目录中,并发出以下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/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教程示例

我们现在尝试将Clojure的opencv java教程示例移植到其中。而不是将它写入源文件,我们将在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());
  }
}

向项目进行补充

在开始编码之前,我们希望在我们开始一个新的REPL与之交互的时候,消除对交互式加载本机opencv lib的无聊需求。

首先,通过在REPL提示符处评估(exit)表达式来停止REPL。

user=> (exit)
Bye for now!

然后打开你的project.clj文件,并按如下所示进行编辑:

(defproject simple-sample "0.1.0-SNAPSHOT"
  ...
injections [(clojure.lang.RT/loadLibrary org.opencv.core.Core/NATIVE_LIBRARY_NAME)])

这里我们要说,在我们运行REPL的时候加载opencv native lib,这样我们再也不用记得手动执行了。

重新运行lein repl任务

lein repl
nREPL server started on port 51645 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=>

导入感兴趣的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 interop语法非常方便和完整,任何OOP语言和任何FP语言之间仍然存在阻抗不匹配(Scala是混合范式编程语言)。

要在REPL提示符处退出REPL类型(退出),ctr-D或(退出)。

user=> (exit)
Bye for now!

交互式加载和模糊图像

在下一个示例中,您将学习如何使用以下OpenCV方法从REPL交互式加载和模糊和映像:

  • 来自Highgui类的imread静态方法从文件读取图像
  • 来自Highgui类的imwrite静态方法将图像写入文件
  • 来自Imgproc类的GaussianBlur静态方法应用于模糊原始图像

我们还将使用从imread方法返回的Mat类,并将其作为GaussianBlur和imwrite方法的主要参数。

将图像添加到项目中

首先,我们要将图像文件添加到新创建的目录中,用于存储项目的静态资源。

介绍OpenCV开发与Clojure

mkdir -p resources/images
cp ~/opt/opencv/doc/tutorials/introduction/desktop_java/images/lena.png resource/images/

查看图片

现在,照常启动REPL,首先导入我们要使用的所有OpenCV类:

lein repl
nREPL server started on port 50624 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=> (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是一个512×512矩阵的CV_8UC3元素类型。我们来创建一个新的Mat实例,它的尺寸和元素类型相同。

user=> (def blurred (Mat. 512 512 CvType/CV_8UC3))
#'user/blurred
user=>

现在,使用lena作为源矩阵应用GaussianBlur滤波器,并将其模糊为目标矩阵。

user=> (Imgproc/GaussianBlur lena blurred (Size. 5 5) 3 3)
nil

作为最后一步,只需将模糊矩阵保存在新的图像文件中。

user=> (Highgui/imwrite "resources/images/blurred.png" blurred)
true
user=> (exit)
Bye for now!

以下是莉娜的新模糊形象。

介绍OpenCV开发与Clojure

下一步

本教程仅介绍了在CLJ REPL中与OpenCV进行交互的基本环境。

我推荐任何Clojure新手阅读Clojure 教程,以获得所有您需要知道的任何与Clojure中未包装的任何普通的java lib进行互操作,以便在Clojure中以更为惯用和功能的方式使用。

OpenCV Java API不会根据Qt包装highgui模块的功能(例如namedWindow和imshow)。如果要在REPL中与OpenCV进行交互时创建窗口并显示图像,那么当您自己离开时。您可以使用Java Swing填补空白。