non vorrei lavorare

昔はおもにプログラミングやガジェット系、今は?

WindowsでmrubyからGoで作ったライブラリを呼び出して、Webカメラの画像をコマンドプロンプトに表示した

おはようございます。相変わらずリビングでの雑魚寝なのですが、乳幼児用の敷布団と大人の布団1枚に子供2人と寝ていると、狭くなってきたので、もう一枚大人用の布団を購入して、大人用の布団2枚に子供たちと3人で寝ています。@kjunichiです。

背景

Windowsコマンドプロンプトにファイルや、URLを指定して、画像をコマンドプロンプトに表示する以下の記事を書いた。

abrakatabura.hatenablog.com

これを、mrubyから利用できれば、、作成済みのWebカメラの画像を取り込めるmrbgemのmrbgemmruby-webcamで取り込んだWebカメラの画像をLinuxmacOSのようにコマンドプロンプトに表示できるのでは

準備

Go側は、jpegpngをファイルを入力元としている、このままでは、mrubyからもファイルを経由して呼び出すことになり、 SSDへの負担が懸念されるw。もちろんスピードも。

Go側のAPIをなんとかする

前述のようにさすがにmruby側からファイルのパスを貰う仕掛けでは今回の様にWebカメラの画像を表示したいという用途では、 ファイルを介してのやり取りは避けたい。

mruby側で、unsigned charな配列と、画像サイズを渡して、Go側でImage型を作って、それを既存のGoの処理に流すという方法で 対応することにした。

Cから渡された先頭アドレスをGoのスライスとして扱うには

github.com

などを眺めて、どうやら、

の書き方で良さそう(容量が2Gバイトまでの制限がありそうだが。。)

func termPutImage(imageData *C.uchar, width, height C.int) {
    len := 3 * width * height
    slice := (*[1 << 30]C.uchar)(unsafe.Pointer(imageData))[:len:len]

Go言語のImage型に変換するには

以下の様に、C側からもらった配列(PPMのP6形式)をImage型に変換出来た。

img := image.NewRGBA(image.Rect(0, 0, int(width), int(height)))
for h := 0; h < int(height); h++ {
    for w := 0; w < int(width); w++ {
        r := uint8(slice[3*(h*int(width)+w)])
        g := uint8(slice[3*(h*int(width)+w)+1])
        b := uint8(slice[3*(h*int(width)+w)+2])
        c := color.RGBA{r, g, b, 0}
        img.SetRGBA(w, h, c)
    }
}

セットする際は0-255なのかぁ。取得時は0-65535なのに

libhogehoge.a形式をGoでつくるには

Windowsでは共有ライブラリこそ作成できないが、アーカイブ形式のライブラリの出力はサポートされている。

go build -buildmode=c-archive -o libimgtype.a main.go

これで、.a形式でライブラリが作成される。MinGWgcc環境なら、これを即利用できる。

この辺りは

mattn.kaoriya.net

の記事が大変参考になりました。ありがとうございます。

MSVCなmrubyなので、.libファイルを作ってリンクさせる

今回使おうとしているmruby-webcamというmrbgemはMSVC版のOpenCVを使っており、それに合わせて、MSVCを使うことに なる。

その為、defファイルを用意してこれと、先ほどの.a形式のライブラリから.dll形式を生成。また、MSVCの処理系でのリンク時に必要な.libファイルを.defから生成しておく。 といった具合になかなか面倒な作業が必要となる。

dllを作る

DLLはMinGW版のgcc(cgoで指定しているgcc)で行った。

gcc -m64 -shared -o imgtype.dll imgtype.def libimgtype.a -Wl,--allow-multiple-definition -static -lwinmm -lntdll -lWs2_32 

libファイルを作る

.libファイルはMSVC付属のlibコマンドで行う。

lib /machine:x64 /def:imgtype.def

これで、MSVCなmrubyに組み込む準備が出来た。

github.com

mrubyからGoの呼び出し

ここまでくれば、あとは、mrbgemを書くだけ。 作成した.libファイルをmrbgem.rakeに以下の様に記述して

spec.linker.flags_before_libraries << "imgtype.lib"

環境変数LIBにimgtype.libファイルへのパスを通しておき、rakeでGoのライブラリを組み込んだmruby.exe,mirb.exeが出来る。

github.com

動かす

今回必要なmrbgemは以下

conf.gembox 'default'
conf.gem '../mruby-webcam'
conf.gem '../mruby-imgtype'
conf.gem :github => 'matsumoto-r/mruby-sleep'  

Webカメラの映像をコマンドプロンプトVimの:terminalに表示するmrubyのスクリプトは以下。

Imgtype.init

cam = Webcam.new
cam.setFmt "ppm"
cam.capture {|img|
  Imgtype.imgtype img
}
while true
    cam.snap
    Sleep::usleep(1000)
    if Imgtype.get_key == "ESC" then
        break
    end
end
Imgtype.close

実行結果

Imgtype.get_keyがWindowsAPIを直接読んで対応している。キー入力をポーリングせずに取得するのに意外とハマった。

まとめ

今回はGo言語でライブラリを作成し、それをmrubyから呼び出し、 そこそこ実用的な処理を動かすという実績が作れた。

今回の作業を通して、Go言語でのスライスの扱いも少しわかってきました。

参考資料

関連記事

7年前の記事

2年年前の記事