JuliaからPyCall経由でカメラを起動する。

# 本日は

JuliaとPythonとOpenCVを仲良し連携する話です。

# JuliaからOpenCV呼び出しはできるか？

[OpenCV.jl](https://juliaopencv.github.io/latest/index.html) というパッケージがあるみたいですね。 できそうな感じはしますが、世の中そんなに甘くなく,このパッケージは [Cxx.jl](https://github.com/Keno/Cxx.jl) に依存しているようです。`Cxx.jl` は Julia の REPL 上で C++ のコードを書いてそれを呼び出すという若干黒魔術なことができるものですがJulia1.0は愚かJulia0.7でも動きません。メンテナンスがおいつていないないのが現状です。Ubuntu+Julia0.6では `Cxx.jl` の動作は確認していますが、Julia1.0が出て他のパッケージが0.6を捨てて1.０対応を進めている現状を考えると固執して0.6を続けるのは長い目で見ると現実的ではありません。

（まぁ直接共有ライブラリ呼ぶとかあると思いますが。）

# Python「うちに任せとき（龍驤風味）」

ということでJulia単体で何かをするのは諦めて `PyCall` 経由で Pythonの `cv2` を呼び出す方針をとってみます。

ここでは次のようなことをしてみようと思います。

- PCのカメラから画像を持ってくる
- その画像を９０度回転させる処理をJulia側で書く
- 元々の画像と上記で処理した画像を幅方向に連結した結果を表示する

環境の前提としては

- Julia1.0　および　PyCallは動作できていること
- Python　および `import cv2` が正常にできて、Python上でOpenCVのカメラ操作をすること

はできているものとします。

# 実装例

```julia:camera.jl
using PyCall: pyimport
using Images: warp, colorview, channelview, RGB, imresize
using CoordinateTransformations: LinearMap, RotMatrix

np = pyimport("numpy")
cv2 = pyimport("cv2")

function arr2rgb(img)
    img = Float32.(img ./ 255.0)
    #BGR2RGB
    img = img[:, :, reverse(1:end)]
    img = permutedims(img, (3,1,2))
    img = colorview(RGB, img)
    return img
end

function rgb2arr(img)
    img = channelview(img)
    img = permutedims(img, (2,3,1))
    #RGB2BGR
    img = img[:, :, reverse(1:end)]
    img = np[:asarray](255. .* img, dtype=np[:uint8])
    return img
end

function rotate(img, degree)
    rad = degree * pi / 180
    tfm = LinearMap(RotMatrix(rad))
    return warp(img, tfm)
end

function process_something(img)
    rot_img = rotate(img, 90)
    rot_img = imresize(rot_img, (224,224))
    return rot_img
end

function video()
    cap = cv2[:VideoCapture](0)
    if !cap[:isOpened]()
        print("Error opening video stream or file")
        exit(1)
    end

    while cap[:isOpened]()
        ret_val, img = cap[:read]()
        img = arr2rgb(img)
        img = imresize(img,(224,224))
        # do something on Julia
        new_img = process_something(img)
        # concat original and processed images
        concatenated = cat([img, new_img]..., dims=2)
        out = rgb2arr(concatenated)
        cv2[:imshow]("julia app press enter esc", out)
        if cv2[:waitKey](1) == 27
            #press `esc` key to exit
            break
        end
    end
end

video()
```

# 何をやっているか

## カメラから画像を得る

まず、PyCallの関数 `pyimport` で Python を呼び出せるようにしています。
ここでは `numpy` `cv2` が使えるようにしておきます。　`video`　関数の中に入ってPythonでやっていたように `cap`　オブジェクトを作る手続きをJulia上でも行います。

```python
ret_val, img = cap[:read]()
```

でカメラから撮ってきた画像を img として持たせます。

## Images.jlの形式に直す。

 `Images.jl` のAPIを用いて画像を処理したいので`arr2rgb`関数にてその形式に合わせます。

- Juliaの場合は 0から１の間の値に正規化された状態でデータを持たせる必要があります。
- OpenCVで得た画像はBGRの順番なのでチャンネルの順番を変えてRGBに直します。
- (`np.transpose` に相当する `permutedims`にて)データ形式をHWCからCHWに直します。
このあとは Juliaの関数の　`colorview`　で　 `Images.jl` 上でハンドルできるようにします。各画素の位置に対してR,G,B の色情報を持ったデータが格納されるようですね。ですので `size(img)` とかすると `(224,224)` と帰って来ます。



## 回転とかの何かの処理をする

何か書いてみます。 `Images.jl` を眺めて適当な例を書いてみます。

```julia
function rotate(img, degree)
    rad = degree * pi / 180
    tfm = LinearMap(RotMatrix(rad))
    return warp(img, tfm)
end

function process_something(img)
    rot_img = rotate(img, 90)
    rot_img = imresize(rot_img, (224,224))
    return rot_img
end
```

回転前に(224,224)にリサイズしているはずなんですけれども回転後に１ピクセル単位で幅が増えてしまうのでリサイズし直します（闇）

画像の連結は

```julia
concatenated = cat([img, new_img]..., dims=2)
```

のようにします。

# Python側に渡す。

基本的にImages.jlに合わせるの逆をしていきます。

`channelview(img)`で多次元の配列に直して（厳密にいうとviewらしいですが）
軸を直して値を０から２５５の幅にします。データのキャストはPython側で処理させました。

最後は

```julia
cv2[:imshow]("julia app press enter esc", out)
```

で出力したいデータを表示させます。

実行方法書いてありませんでしたが、

```shell-session
$ julia camera.jl
```

でOKです。ちゃんと動いてびっくりしました。

# まとめ

- JuliaからPyCall経由でOpenCVを呼び出す方法でカメラを起動することができました。
- Juliaで完結できなくても豊富なPythonの資産を使ってみんな仲良くできることがわかりました。
- 今回は例のために画像の処理をJuliaでやりましたが、Python側でのデータ構造とJulia側でのデータ構造の整合性をとるのに苦労してました。実際にはこういう処理もなれているPython側でやったほうがいいんじゃないかなという気はしています。
- `PyCall`ありがとう。

# おまけ
ちなみにNimはNimからOpenCVを呼びだせました。やりますね。