本日は
JuliaとPythonとOpenCVを仲良し連携する話です。
JuliaからOpenCV呼び出しはできるか?
OpenCV.jl というパッケージがあるみたいですね。 できそうな感じはしますが、世の中そんなに甘くなく,このパッケージは Cxx.jl に依存しているようです。Cxx.jl
は Julia の REPL 上で C++ のコードを書いてそれを呼び出すという若干黒魔術なことができるものですがJulia1.0は愚かJulia0.7でも動きません。メンテナンスがおいつていないないのが現状です。Ubuntu+Julia0.6では Cxx.jl
の動作は確認していますが、Julia1.0が出て他のパッケージが0.6を捨てて1.0対応を進めている現状を考えると固執して0.6を続けるのは長い目で見ると現実的ではありません。
(まぁ直接共有ライブラリ呼ぶとかあると思いますが。)
Python「うちに任せとき(龍驤風味)」
ということでJulia単体で何かをするのは諦めて PyCall
経由で Pythonの cv2
を呼び出す方針をとってみます。
ここでは次のようなことをしてみようと思います。
- PCのカメラから画像を持ってくる
- その画像を90度回転させる処理をJulia側で書く
- 元々の画像と上記で処理した画像を幅方向に連結した結果を表示する
環境の前提としては
- Julia1.0 および PyCallは動作できていること
- Python および
import cv2
が正常にできて、Python上でOpenCVのカメラ操作をすること
はできているものとします。
実装例
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上でも行います。
ret_val, img = cap[:read]()
でカメラから撮ってきた画像を img として持たせます。
Images.jlの形式に直す。
Images.jl
のAPIを用いて画像を処理したいのでarr2rgb
関数にてその形式に合わせます。
- Juliaの場合は 0から1の間の値に正規化された状態でデータを持たせる必要があります。
- OpenCVで得た画像はBGRの順番なのでチャンネルの順番を変えてRGBに直します。
- (
np.transpose
に相当するpermutedims
にて)データ形式をHWCからCHWに直します。
このあとは Juliaの関数のcolorview
でImages.jl
上でハンドルできるようにします。各画素の位置に対してR,G,B の色情報を持ったデータが格納されるようですね。ですのでsize(img)
とかすると(224,224)
と帰って来ます。
回転とかの何かの処理をする
何か書いてみます。 Images.jl
を眺めて適当な例を書いてみます。
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)にリサイズしているはずなんですけれども回転後に1ピクセル単位で幅が増えてしまうのでリサイズし直します(闇)
画像の連結は
concatenated = cat([img, new_img]..., dims=2)
のようにします。
Python側に渡す。
基本的にImages.jlに合わせるの逆をしていきます。
channelview(img)
で多次元の配列に直して(厳密にいうとviewらしいですが)
軸を直して値を0から255の幅にします。データのキャストはPython側で処理させました。
最後は
cv2[:imshow]("julia app press enter esc", out)
で出力したいデータを表示させます。
実行方法書いてありませんでしたが、
$ julia camera.jl
でOKです。ちゃんと動いてびっくりしました。
まとめ
- JuliaからPyCall経由でOpenCVを呼び出す方法でカメラを起動することができました。
- Juliaで完結できなくても豊富なPythonの資産を使ってみんな仲良くできることがわかりました。
- 今回は例のために画像の処理をJuliaでやりましたが、Python側でのデータ構造とJulia側でのデータ構造の整合性をとるのに苦労してました。実際にはこういう処理もなれているPython側でやったほうがいいんじゃないかなという気はしています。
-
PyCall
ありがとう。
おまけ
ちなみにNimはNimからOpenCVを呼びだせました。やりますね。