2017年4月24日月曜日

clojureでopencv3.2を呼び出し、javax.swingで画像を表示する方法


背景

opencvにはjava向けのapiが提供されています。
そのapiの設定次第でclojureからopencvを扱えます。

しかし、プロジェクトの設定方法や画像の表示など、調査や試行錯誤に時間がかかることが多かったので、自分の分かった情報を共有します。

全体像

この順に共有していきます。
  1. 使ったもの
  2. opencvのビルド
  3. opencvのためのclojureプロジェクトの設定
  4. プログラムの説明
  5. 実行

使ったもの

  • linux(ubuntu)OSをインストールしたPC
    opencvのビルドとclojureプロジェクトの設定は、linuxを想定して説明します。
  • leiningen 2.7.1
    leinスクリプトなどでインストールしてください。
  • opencv 3.2
    ビルドコマンドを共有します。

opencvのビルド

opencvはjava向けのapiを提供しています。
そのapiを使うのに必要なファイルを、下記のコマンドでビルドします。

sudo apt install cmake ant openjdk-8-jdk git

mkdir gitprojects
cd gitprojects
git clone git@github.com:opencv/opencv.git opencv_source
cd opencv_source
git checkout tags/3.2.0
# git fetch -p # if you cannot find 3.2.0 tag
mkdir build
cd build
export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64/
cmake -D BUILD_SHARED_LIBS=OFF ..
make -j4

opencvのためのclojureプロジェクトの設定

今回はclj-opencv-exampleというプロジェクトを作り、opencvの操作を試みます。
cd gitprojects
lein new clj-opencv-swing-example

ビルドしたopencvのjarファイルとsoファイルを、clojureのプロジェクトのlibに配置します。
cd ~/gitprojects/clj-opencv-swing-example
mkdir lib
cp ~/gitprojects/opencv_source/build/bin/opencv-320.jar lib/
cp ~/gitprojects/opencv_source/build/lib/libopencv_java320.so lib/

今回はlinuxなのでsoファイルを必要としますが、macの場合はdylib、windowsの場合はdllファイルがそれぞれ必要なようです。


libに配置したopencvのファイルを読み込めるように、project.cljを下記のように記述します。
~/gitprojects/clj-opencv-swing-example/project.clj
(defproject clj-opencv-swing-example "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.8.0"]
                 [org.clojure/core.async "0.3.442"]]
  :jvm-opts ["-Djava.library.path=lib"]
  :resource-paths ["lib/opencv-320.jar"]
  :injections [(clojure.lang.RT/loadLibrary org.opencv.core.Core/NATIVE_LIBRARY_NAME)]
  :main clj-opencv-swing-example.core)

サンプル画像として使いたいので、lenaも配置します。
cd ~/gitprojects/clj-opencv-swing-example
mkdir img
cp ~/gitprojects/opencv_source/samples/data/lena.jpg img/

replが立ち上がることを確認します。
cd ~/gitprojects/clj-opencv-swing-example
lein repl

自分の環境だとleinコマンド実行時に下記のwarningが出ました。
OpenJDK 64-Bit Server VM warning: You have loaded library /home/asuki/gitprojects/clj-opencv-swing-example/lib/libopencv_java320.so which might have disabled stack guard. The VM will try to fix the stack guard now.
It's highly recommended that you fix the library with 'execstack -c <libfile>', or link it with '-z noexecstack'.

warningの指示通り下記のようにexecstackを実行すると、warningは出なくなります。
sudo apt install execstack
cd ~/gitprojects/clj-opencv-swing-example
execstack -c lib/libopencv_java320.so

プロジェクトの設定ができました。

プログラムの説明

core.cljを下記のように記述しました。
定義した関数を説明します。

~/gitprojects/clj-opencv-swing-example/src/clj_opencv_swing_example/
(ns clj-opencv-swing-example.core
  (:import [java.awt.image BufferedImage]
           [javax.swing JFrame JLabel ImageIcon WindowConstants SwingUtilities]
           [org.opencv.core Core Mat CvType]
           [org.opencv.imgcodecs Imgcodecs]
           [org.opencv.imgproc Imgproc])
  (:require [clojure.core.async :as async]))

(def lena (Imgcodecs/imread "img/lena.jpg"))
(def frame (new JFrame))

(defn mat->buffered-image [mat]
  (let [gray? (= (.channels mat) 1)
        type (if gray?
               BufferedImage/TYPE_BYTE_GRAY
               BufferedImage/TYPE_3BYTE_BGR)
        image (new BufferedImage (.width mat) (.height mat) type)
        target-pixels (-> image
                          (.getRaster)
                          (.getDataBuffer)
                          (.getData))
        raster (.getRaster image)
        buffer (byte-array (* (.width mat) (.height mat) (if gray? 1 3)))]
    (prn :gray gray?)
    (.get mat 0 0 buffer)
    (System/arraycopy buffer 0 target-pixels 0 (alength buffer))
    image))

(defn show-mat [frame mat & {:keys [title set-visible close-operation]}]
  (when title
    (.setTitle frame title))
  (when-not (nil? set-visible)
    (.setVisible frame set-visible))
  (-> frame
      (.getContentPane)
      (.removeAll))
  (.setSize frame (.width mat) (.height mat))
  (.add frame (->> (mat->buffered-image mat)
                   (new ImageIcon)
                   (new JLabel)))
  (.revalidate frame)
  (.repaint frame)
  (when close-operation
    (.setDefaultCloseOperation frame close-operation)))

(defn color->gray [mat]
  (let [gray-mat (new Mat)]
    (Imgproc/cvtColor mat gray-mat Imgproc/COLOR_RGB2GRAY)
    gray-mat))

(defn -main []
  (show-mat frame lena
            :title "opencv mat on swing"
            :set-visible true
            :close-operation WindowConstants/EXIT_ON_CLOSE)
  (async/go-loop [seconds 1]
    (async/<! (async/timeout 1000))
    (show-mat frame (if (even? seconds)
                      lena
                      (color->gray lena)))
    (recur (inc seconds))))

mat->buffered-image

opencvは3.0からjava apiではhighGUIの提供しなくなっています。
そのため、このプログラムではjavaxのswingを画像の表示に利用しています。
mat->buffered-imageで、swingに渡すBufferedImageをMatから作成しています。

show-mat

先程説明したmat->buffered-imageも使い、swingでmatを表示する関数です。
getContentPaneのremoveAllで古い画像を削除し、revalidate、repaintで情報を上書きすることで、フレームの画像を更新します。

titleでJFrameのwindowタイトルを設定できます。

set-visibleを指定すると、JFrameのwindowが最前列に来ます。
show-matを初めて使うときは、set-visibleをtrueにして、JFrameを表示する必要があります。

close-operatoinをWindowConstants/EXIT_ON_CLOSEにすると、windowの閉じるボタンが押されたら、プログラムを終了します。

color->gray

カラー画像から白黒画像を作る関数です。
opencvの関数を使っています。

main

lenaのカラー画像を白黒画像を交互に表示します。

実行

下記のコマンドで実行できます。
cd ~/gitprojects/clj-opencv-practice
lein run

lenaの画像が表示されます。


1秒ごとにカラーと白黒が入れ替わります。


今回作成したプロジェクトはこちらです。

https://github.com/asukiaaa/clj-opencv-swing-example

共有する情報は以上です。

参考

ClojureでOpenCV 3.0と戯れる
milq/scripts/bash/install-opencv.sh
Introduction to Java Development -- OpenCV3.0.0-dev
http://docs.opencv.org/java/3.1.0/
二次元絵の顔を検出する
SwingでMat型画像をGUI上に表示
Clojureでjavax.swing.JFrameを使ってみる
Opencv java - Load image to GUI
How to add an image to a JPanel?
ImShow-Java-OpenCV/ImShow_JCV/src/com/atul/JavaOpenCV/Imshow.java
How to remove all components from a JFrame in Java?

変更情報

2017.09.17
color->grayの処理の一部が間違っていたため、修正しました。
「apt install」が「apt-install」になっていたので、修正しました。

0 件のコメント :

コメントを投稿