OpenCV 4.12.0
開源計算機視覺
載入中...
搜尋中...
無匹配項
OpenCV Clojure 開發簡介

上一教程: 在 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 的最小快速入門指南

注意
我假設您已經安裝了 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

安裝支援桌面 Java 的 OpenCV 後,唯一其他要求是安裝 Leiningen,它允許您管理 CLJ 專案的整個生命週期。

提供的安裝指南非常容易遵循

  1. 下載指令碼
  2. 將其放置在您的 $PATH 中(如果 /bin 在您的路徑中,則是一個不錯的選擇)。
  3. 將指令碼設定為可執行檔案。(例如 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 專案中都可用。

mkdir ~/.lein

在 ~/.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

請注意,這兩個構件的 groupIdopencv)是相同的。現在我們準備建立一個新的 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 {
現在讓我們嘗試將OpenCV Java 教程示例移植到 Clojure。我們將在 REPL 中評估它,而不是將其寫入原始檔。
以下是引用示例的原始 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 方法返回,並作為 GaussianBlurimwrite 方法的主要引數。

向專案新增影像
首先,我們希望將影像檔案新增到新建立的目錄中,用於儲存專案的靜態資源。

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))

#'user/blurred
nil

現在,使用 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 模組功能(例如 namedWindowimshow)。如果您想在從 REPL 與 OpenCV 互動時建立視窗並顯示影像,目前您需要自行解決。您可以利用 Java Swing 來彌補這一不足。

許可