OpenCV for Java at Scilab

:::note warn
**この記事は執筆時点の環境に基づいています。その他の環境では状況が異なる場合があります。ご留意ください。😅**

環境：
- Scilab 2023.1.0 ( windows x64 )
- IPCV 4.1.2 ( ATOMS package )
- OpenCV for Java 4.6.0 ( official release build )
:::

## はじめに

唐突ですが皆さんScilab使っていらっしゃいますか？
自分は割と昔（ver.2か3の頃）から使っていますが、使用頻度自体はそれほどでもなく、また用途も「簡単なプログラム電卓代わり」のライトユーザーです。💦
そんな自分ですが、たまたまScilabでOpenCVを使う機会があったのでちょいと足跡を残しておきます。
なお、**「OpenCVならPythonでしょ！」** というツッコミはご勘弁ください ｗ

## OpenCV at Scilab 

ScilabでOpenCVを使用するなら、ATOMS ( AuTomatic mOdules Management for Scilab ) で IPCV ( Image Processing and Computer Vision Toolbox ) を追加して用いるのが一番簡単だと思います。
ただ、メンテナンス頻度はそれほど高くなく（現在の内包OpenCVは4.1.2）、また主なOpenCV関数は（Scilabで使用できるようにラッピングや代替えで）実装されてる様ですが、未実装な関数もそれなりにはある様です。

## OpenCV for Java at Scilab

ここからが本題なのですが、上記IPCVに未実装のOpenCV関数が使いたいなあというケースがありまして、どうしたものかなあといろいろと悩んでみました。
真っ先に思いつくのはIPCVの改造ですけど、ScilabでCとかC++の追加モジュールをいじるのは割とめんどくさい印象があります。（食わず嫌いな部分もあるとは思いますけど…💦）
で、思い出したのがScilabは5.xの頃からGUIがJavaベースになった関係でJREを内包しているということです。
調べてみたところ（と言ってもWeb上にもほとんど情報がないようなマイナーネタっぽくて純正Help頼みでしたが…）、なんとかJava関数が使えそうな感じだったのでとりあえずやってみました！😀

### 下準備その1 （ OpenCV for Java 設定 ）

official release build の [OpenCV (opencv-4.6.0-vc14_vc15.exe)](https://github.com/opencv/opencv/releases/tag/4.6.0) をとりあえずダウンロードして展開しますと `\opencv4.6.0\build\java\` のところに `opencv-460.jar` があり、これを `\scilab\thirdparty\` にコピーして置いておきます。
また、 `\opencv4.6.0\build\java\x64\` に `opencv_java460.dll` があり、これを `\scilab\bin\` にコピーして置いておきます。
そして、 `\scilab\etc\` にある `classpath.xml` の31行目辺り（`<!-- Mandatory on startup -->`の項）に `<path value="$SCILAB/thirdparty/opencv-460.jar"/>` とjarのパス情報を追記しておきます。

:::note info
なお、OpenCVの4.6.0より新しいバージョンの公式ビルドはJava8よりも新しいものでビルドされているっぽくて、現時点の内包JREがJava8のScilabでは使用できませんでした。
また、JREの公式配布はJava8が最後であり、今後のScilabのJavaの組込み方がどのようになるのかは現時点では不明です。
:::

### 下準備その2 （ 簡易的な便利関数作成 ）

とりあえず最低限「Scilab(IPCV RGB)形式 ⇔ OpenCV(Mat)形式」くらいの関数はあった方が便利だろうということで、自分が使えればいい程度の簡易的なものですが作成してみましたのでサンプルとして掲載しておきます。
（簡易的とはいえScilab-Javaの参考情報がまるでないので試行錯誤に明け暮れました…😭）

<details><summary>サンプルコード ( OpenCVJ.sci )</summary>

```OpenCVJ.sci
jimport org.opencv.core.Core;
jimport org.opencv.core.Mat;
jimport org.opencv.core.CvType;
jimport java.lang.System;
System.loadLibrary(Core.NATIVE_LIBRARY_NAME); // DLLの読み込み


function [imMat] = im2matCVJ(imRGB)
  // Scilab(IPCV RGB)形式からOpenCV(Mat)形式へ変換
  // CV_8UC3&C1相当画像のみの簡易対応（エラー処理なし）
  [h, w, p] = size(imRGB);

  if(p == 3) then
    imMat = Mat.zeros(h, w, CvType.CV_8UC3);
    blMat = Mat.zeros(h, w, CvType.CV_8UC1);
    grMat = Mat.zeros(h, w, CvType.CV_8UC1);
    reMat = Mat.zeros(h, w, CvType.CV_8UC1);

    blMat.put(0, 0, matrix(imRGB(:,:,3)', w, h));
    grMat.put(0, 0, matrix(imRGB(:,:,2)', w, h));
    reMat.put(0, 0, matrix(imRGB(:,:,1)', w, h));

    mv = ArrayList.new(jvoid);
    mv.add(blMat);
    mv.add(grMat);
    mv.add(reMat);
    Core.merge(mv, imMat);
  elseif(p == 1) then
    imMat = Mat.zeros(h, w, CvType.CV_8UC1);
    imMat.put(0, 0, matrix(imRGB(:,:,1)', w, h));
  else
    imMat = Mat.zeros(h, w, CvType.CV_8UC1);
  end
endfunction


function [imRGB] = mat2imCVJ(imMat)
  // OpenCV(Mat)形式からScilab(IPCV RGB)形式へ変換
  // CV_8UC3&C1相当画像のみの簡易対応（エラー処理なし）
  if(imMat.type() == CvType.CV_8UC3) then
    h = imMat.height();
    w = imMat.width();

    blMat = Mat.zeros(h, w, CvType.CV_8UC1);
    grMat = Mat.zeros(h, w, CvType.CV_8UC1);
    reMat = Mat.zeros(h, w, CvType.CV_8UC1);

    mv = ArrayList.new(3);
    Core.split(imMat, mv);
    blMat = mv.get(0);
    grMat = mv.get(1);
    reMat = mv.get(2);

    // Java primitive byte[h*w]生成
    jbyteB = jwrap(int8(zeros(1:h*w)));
    jbyteG = jwrap(int8(zeros(1:h*w)));
    jbyteR = jwrap(int8(zeros(1:h*w)));

    blMat.get(0, 0, jbyteB);
    grMat.get(0, 0, jbyteG);
    reMat.get(0, 0, jbyteR);
  
    imRGB(:,:,3) = (matrix(uint8(junwrap(jbyteB)), double(w), double(h)))';
    imRGB(:,:,2) = (matrix(uint8(junwrap(jbyteG)), double(w), double(h)))';
    imRGB(:,:,1) = (matrix(uint8(junwrap(jbyteR)), double(w), double(h)))';
  elseif(imMat.type() == CvType.CV_8UC1) then
    h = imMat.height();
    w = imMat.width();

    // Java primitive byte[h*w]生成
    jbyteMono = jwrap(int8(zeros(1:h*w)));

    imMat.get(0, 0, jbyteMono);
    imRGB = (matrix(uint8(junwrap(jbyteMono)), double(w), double(h)))';
  else
    imRGB = 0; 
  end
endfunction


function [imMat] = imfreadCVJ(filename)
  // OpenCV(Mat)形式で画像ファイル読み込み
  // 簡易対応（エラー処理なし）
  jimport org.opencv.imgcodecs.Imgcodecs;

  imMat = Imgcodecs.imread(filename);
endfunction
```
</details>

### 実行
こちらも簡易的なサンプルを載せておきます。
<details><summary>サンプルコード ( opencv_java_sample.sce )</summary>

```opencv_java_sample.sce
clear;
// ----- OpenCV for Java at Scilab 関数を読み込み -----
exec('.\OpenCVJ.sci', -1);

// ----- 必要な JavaLib (OpenCV-Lib) を読み込み -----
jimport org.opencv.core.Scalar;
jimport org.opencv.core.Size;
jimport org.opencv.imgproc.Imgproc;
jimport java.util.ArrayList;

my_handle = scf(100001);
clf;
f = get("current_figure")
f.figure_size = [800, 400]

my_plot_desc = _("OpenCV for Java at Scilab");
my_handle.figure_name = my_plot_desc;

im = imread(fullpath(getIPCVpath() + "/images/balloons.png"));  // IPCV関数
imMat = im2matCVJ(im);  // IPCV→Mat変換

// グレースケール化
grayMat = Mat.new(jvoid);
Imgproc.cvtColor(imMat, grayMat, Imgproc.COLOR_RGB2GRAY);

// ガウシアンフィルター平滑化
gauMat = Mat.new(jvoid);
Imgproc.GaussianBlur(grayMat, gauMat, Size.new(5, 5), 5.0);

// 2値化
bwMat = Mat.new(jvoid);
Imgproc.adaptiveThreshold(gauMat, bwMat, 255, Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C, Imgproc.THRESH_BINARY_INV, 11, 2);

subplot(121);
  im = mat2imCVJ(bwMat);  // Mat→IPCV変換
  imshow(im);  // IPCV関数

// 輪郭を抽出
hierarchy = Mat.new(jvoid);
contours = ArrayList.new(jvoid);
Imgproc.findContours(bwMat, contours, hierarchy, Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE)

colorsc = Scalar.new(0, 255, 0);  // color B,G,R

for i = 0:(contours.size()-1) // ※CVJ添字開始は0 (Scilab添字開始は1)
  area = Imgproc.contourArea(contours.get(i));
  if(1e2 > area | 1e5 < area) then
    continue
  end
  Imgproc.drawContours(imMat, contours, i, colorsc, 2);
end

subplot(122);
  im = mat2imCVJ(imMat);  // Mat→IPCV変換
  imshow(im);  // IPCV関数
```
</details>

上記サンプルコードの実行結果画像です。

![opencv_java_sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680157/21170cb0-dcab-eadd-b5b7-45e5d4579f40.png)

## おわりに

巨匠のMatlab(商用)はともかくScilabをはじめOSS界隈の近縁種であるOctaveやその他もろもろはPython(NumPy)の人気に押されてか、ここ最近は元気がないように思え寂しい限りです。(QiitaにSyntaxもないのね…)
だがしかし、Scilabは気づけばあのDassaultがバックに付いていた！（というか拾われた？💦）ということで、今後のテコ入れにちょっと期待していたりもします。
この記事も少しはScilab界隈に貢献できるといいな。
ついでにどなたかOpenCV for Java at Scilabをもう少しまともなものに仕立て直してATOMS化してくれないかなぁ～ |ω• `)ﾁﾗｯﾁﾗｯ✧