WindowsでmrubyからGoで作ったライブラリを呼び出して、Webカメラの画像をコマンドプロンプトに表示した
おはようございます。相変わらずリビングでの雑魚寝なのですが、乳幼児用の敷布団と大人の布団1枚に子供2人と寝ていると、狭くなってきたので、もう一枚大人用の布団を購入して、大人用の布団2枚に子供たちと3人で寝ています。@kjunichiです。
背景
Windowsのコマンドプロンプトにファイルや、URLを指定して、画像をコマンドプロンプトに表示する以下の記事を書いた。
これを、mrubyから利用できれば、、作成済みのWebカメラの画像を取り込めるmrbgemのmrbgemmruby-webcamで取り込んだWebカメラの画像をLinuxやmacOSのようにコマンドプロンプトに表示できるのでは
準備
Go側は、jpegやpngをファイルを入力元としている、このままでは、mrubyからもファイルを経由して呼び出すことになり、 SSDへの負担が懸念されるw。もちろんスピードも。
Go側のAPIをなんとかする
前述のようにさすがにmruby側からファイルのパスを貰う仕掛けでは今回の様にWebカメラの画像を表示したいという用途では、 ファイルを介してのやり取りは避けたい。
mruby側で、unsigned charな配列と、画像サイズを渡して、Go側でImage型を作って、それを既存のGoの処理に流すという方法で 対応することにした。
Cから渡された先頭アドレスをGoのスライスとして扱うには
などを眺めて、どうやら、
の書き方で良さそう(容量が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形式でライブラリが作成される。MinGWのgcc環境なら、これを即利用できる。
この辺りは
の記事が大変参考になりました。ありがとうございます。
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に組み込む準備が出来た。
mrubyからGoの呼び出し
ここまでくれば、あとは、mrbgemを書くだけ。 作成した.libファイルをmrbgem.rakeに以下の様に記述して
spec.linker.flags_before_libraries << "imgtype.lib"
環境変数LIBにimgtype.libファイルへのパスを通しておき、rakeでGoのライブラリを組み込んだmruby.exe,mirb.exeが出来る。
動かす
今回必要な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がWindowsのAPIを直接読んで対応している。キー入力をポーリングせずに取得するのに意外とハマった。
Windowsの:terminalに対応したVimにWebカメラの映像を出してみた https://t.co/gtYXg0tOA3 @YouTubeさんから
— kjunichi (@kjunichi) 2017年8月31日
まとめ
今回はGo言語でライブラリを作成し、それをmrubyから呼び出し、 そこそこ実用的な処理を動かすという実績が作れた。
今回の作業を通して、Go言語でのスライスの扱いも少しわかってきました。
参考資料
関連記事
- mrubyでWebカメラを黒い画面にそのまま表示できるようにした
- RustでWebカメラの映像をコマンドプロンプトに出すコマンドを作った
- goroutineを使って高速にLAN内の*.localなホスト名の一覧を取得するツールを作った
7年前の記事
2年年前の記事
Goの符号なし整数の計算でハマった件
こんにちは。最近は奥さんは寝室で、子供たちと自分はエアコンのあるリビングで雑魚寝している@kjunichiです。
背景
Goで画像ファイルを読み込んで、Windowsのコマンドプロンプトに表示するimgtype
コマンドのコードがひどく、内部処理をRGBからHSVにして、もう少しコードをスマートにしようとしていた。
なんかGoの整数演算はキャストの仕方がCと異なる
Cだと、より表現範囲の広い型への代入はキャストしなくても、少なくともコンパイラには怒られないが、 Goだと、この辺も指摘される。
画像処理なら符号なし整数型も割と使う
割とCを知っていると、数値計算は浮動小数点より整数型での計算の方が早いことを体験しているし、 さらに画像処理なんかやってると、符号なしの整数側で画素値を扱うことが多い。
そんなバックグラウンドがあってか
RGB2HSV変換を見よう見まねで符号なし整数を使っておこなって、 以下のようなコードを書いていた
h := int32((60.0 * (g - b)) / (max-min))
なんか、表示が変だ!
安定のデバッグプリントで、hが1431655745とやらかしている値だった。 Cだとsinged unsignedわりと、いい感じに適当にやってても扱ってくれてた気がしたんだけど、 goだと明確に否定された感じ
答えがマイナスになる演算をすると駄目
Cではこの辺はよきには計らってくれる気がしなくもないが、Goでは明確に駄目だった。
(int32(a) - int32(b))/int32(c)
というように、明示的に符号付整数型に変換して演算しないといけない。
Rustだと
定数の演算場合、コンパイル時にエラー、動的に発生させると、実行時にパニックとなった。
まとめ
16色しか使えないので、当初、まぁ、色が白とびするのは仕方ないのかなぁ、 でも、HSV変換ではなく、
本人が汚いっては言っているけど 久々にすごいネストを見たhttps://t.co/cGB9ATqwbC
— ABAB↑↓BA (@ababupdownba) 2017年8月19日
と感想持たれた、R,G,Bの組み合わせで色を決定する方式だと、それっぽく着色されており、モヤモヤしていた。
テスト大事
関連記事
- Go言語でコマンドプロンプトに画像を表示するコマンドを作って、Vimの:terminalで表示させた
- node-ffiをGoで入門してElectronを使って応用してみる
- GIMPのプラグインをGoで書く(その1)
- GoでFaceTime HDカメラやWebカメラを使う
- Goオールスターズ2に行くので、gopheronをGo言語で動かせるようにした #websocket
- Goオールスターズ2に行った
- Goの学習メモ~ファイルの存在チェックとWindowsでのファイルパスの扱い~
- 過去8月にブログ記事が無かった日を挙げる
ハマったつながり
1年後の記事
2年後の記事
Go言語でコマンドプロンプトに画像を表示するコマンドを作って、Vimの:terminalで表示させた
おはようございます。保育参観後、次男は熱を出し、また、長期化して、実家暮らしかもと冷や冷やしましたが、すぐに回復して、長男の誕生日イベントに奥さんと映画に行ってこれました。@kjunichiです。
背景
ANSIエスケープで端末に画像を表示するものは多く出回っていて、自分もmacOS上ではnode.js実装のansi-canvasのexampleのimgcatを 愛用している。
しかし、事前準備なしでWindowsのコマンドプロンプトに表示するものはなかなか見つからなかった。コマンドプロンプトにも一部の文字に 色を付けて表示することは可能なので、色数は限定されそうだが、原理的には出来なくなさそうだった。が、 Windowsの直接のAPIだと、Windowsでの開発歴が浅い自分には、難易度が高かった、ANSIエスケープ並みの手軽なAPIで これを実現するものがないものかと、物色していたところ、termbox-goなるライブラリを知った。
試行錯誤
8色しか出せなそう?
多分、vimの:terninalって訳ではなく、windowsだと、256色表示に対応出来ないのかも https://t.co/JxnJTW3fmk pic.twitter.com/fJOPPxMJKt
— kjunichi (@kjunichi) 2017年8月11日
16色行けそう
vimの:ternimalモードを試して、termbox-goでアトリビュートの値で512位足すと明るい色が使えそうと言う知見を得たのが今年の夏休みの唯一の成果とならないように、自由時間を満喫したいでも、ガンダムオリジン見始めてしまいヤバみ
— kjunichi (@kjunichi) 2017年8月12日
GoのImageの内部のピクセル値は最大65535だった
このツイートのように、termbox-goで任意の場所に色を出せるようになったものの、JPEGファイルをGoで処理して画素値を取得して、 termbox-goでの色に割り当てようとして、なかなか画素値が最大255という思い込みがあり、上手くできないでいた。
コマンドプロンプトに8色しかないないとはいえ、全然、真っ白な画像だったりで、ダメだなぁと諦めかけていたが、golangのColor型のRGBAの戻り値が最大65535なのしってようやく、なにか出せる気がしてきたw pic.twitter.com/BOMibPZbXs
— kjunichi (@kjunichi) 2017年8月13日
インパクトがある画像が功を奏して、湘南新宿ラインの炎上ツイート以来のリツーイト数を獲得できた。
Vimの:terminalは発展中
vimの:terminalだと白がだせないっぽい。。 pic.twitter.com/rqNuofXUIO
— kjunichi (@kjunichi) 2017年8月13日
と、ツイートしてしまい、Vim界の大物であり、ツイートからSIer所属でお子さんもおり、自分と境遇が近く、勝手に親近感もってる@mattn_jpさんから
コード欲しいです!
— Vim界の声の大きい人 (@mattn_jp) 2017年8月13日
と返信あり、速攻でGistに張り付け、
ちょっと色の変換がツイート時のときと変わってますが、こちらです。(大変汚いソースで済みません。) https://t.co/uaXXr8xKSH
— kjunichi (@kjunichi) 2017年8月13日
返信したところ、すぐに確認して頂き、
最新版の vim/gvim だと白表示されてそうでした。 pic.twitter.com/jjhxLIxdlA
— Vim界の声の大きい人 (@mattn_jp) 2017年8月13日
とご回答頂き、自分のところのソースが古かったので、最新のVimでは白も無事表示されました。@mattn_jpさんありがとうございました!
成果物
初期公開時ではJPEGファイルのみだったが、今はURLにも対応して、PNGかJPEGなら表示出来る ようになった。