OpenCVでステレオマッチング

かなりの今更感はさておき、OpenCV でステレオ画像による深度計算をやっています。

環境

  • LMDE2 64bit
  • OpenCV2
  • Python2.7

スクリプト

入力画像と深度計算の結果

cv2.StereoSGBM() のパラメータをいじくり回したり、単眼のスマホカメラで撮影した入力画像の位置合わせをしたりしていると、それなりに深度が出るようになりました。

PCD 化

PCL(Point Cloud Library)のフォーマットである PCD 形式に変換し、pcl_viewer で確認します。

スクリプトのPCD化部分次のとおり。


pcl_viewer で開いたところ。

数百万円するレーザー計測器には到底及ばないものの、それなりにカラーボックスやその上に置いている箱の形状が出てますね! もっとフワフワな点群になるかと思っていたので、ちょっと予想外でした。

参考

Ruby と OpenCV でカメラを使う

前回に引き続き、ruby-opencv でカメラデバイスをソースにしてリアルタイム検知をやってみたいと思います。

使っているカメラ

Microsoft 社製の USB 接続 HD ウェブカムです。エンドユーザ向けにしてはちょっと高めですが画質も良いです。LMDE2 では UVC で使えています。

コード

カメラデバイスを使うには OpenCV::CvCapture.open() メソッドを使います。引数の番号はデバイス番号。対象のデバイスが /dev/video0 だったら 0 を指定すれば良いようです。

camera.rb

#!/usr/bin/env ruby

require 'opencv'
include OpenCV

camera = CvCapture.open(0)
camera.width = 320
camera.height = 240

window = GUI::Window.new('camera')
pattern_size = CvSize.new(7, 10)

while true

  image = camera.query
  gray = image.BGR2GRAY
  corners, found = gray.find_chessboard_corners(pattern_size, CV_CALIB_CB_ADAPTIVE_THRESH)

  if found
    corners = gray.find_corner_sub_pix(corners, CvSize.new(3, 3), CvSize.new(-1, -1), CvTermCriteria.new(20, 0.03))
    result = image.draw_chessboard_corners(pattern_size, corners, found)
    window.show result
  else
    window.show image
  end

  key = GUI::wait_key 100
  break if key == 'q'
end

CvCapture#query() メソッドで取得すると IplImage オブジェクトが返ってくるので、あとは前回と同様にチェッカーボード検知・描画を行なっているだけです。タイムアウト付きのキー入力待ち GUI::wait_key() で 100 ミリ秒待って、 Q キーが押下されなければ繰り返します。

実行

ruby camera.rb

実行するとこんな感じ↓

画面がちらついているのは、GTK recordMyDesktop で Cinnamon 上のウィンドウを撮影しているせいだと思います。 検知している時より、してない時の方がフレームレートが落ちてる気がするのはなぜだろう…。

Ruby と OpenCV でチェッカーボード検知

前日に引き続き、ruby-opencv でチェッカーボードを認識させてみたいと思います。チェッカーボードとは市松模様のパターンのことで、これを認識することにより映像中の3次元座標系(の行列)を確定させることができ、映像の上に3Dモデルをマッピングする第一歩となります。

1. コード

checkerboard.rb

#!/usr/bin/ruby

require 'opencv'
include OpenCV

if ARGV.size < 1
  STDERR.puts "#{$0} srcfile [destfile]"
  exit 1
end
src = ARGV.shift
dest = nil
if ARGV.size >= 1
  dest = ARGV.shift
end

mat = CvMat.load(src, 1)
gray = mat.BGR2GRAY
pattern_size = CvSize.new(7, 10)
corners, found = gray.find_chessboard_corners(pattern_size, CV_CALIB_CB_ADAPTIVE_THRESH)

if found
  puts "found"
  corners = gray.find_corner_sub_pix(corners, CvSize.new(3, 3), CvSize.new(-1, -1), CvTermCriteria.new(20, 0.03))
end

img = mat.draw_chessboard_corners(pattern_size, corners, found)

if dest
  img.save dest
else
  w = GUI::Window.new('Result')
  w.show img
  GUI::wait_key
end

映像をグレイスケールイメージに変換してから、 find_chessboard_corners でチェスボードを検知します。チェスボードのパターンはここと同じく 7×10 サイズのものでやってみました。

2. 実行

ruby checkerboard.rb src.jpg dest.jpg

とタイプして実行すると、

のように、ちゃんとパターンを認識できています。カメラから遠ざけると

のように検出した点に沿って線が出てきます。ここで検知した点列と各々の2次元座標値から3次元座標系を逆算していき…たいところですが、また次回です。

あ、部屋が汚いのは見ないでください… _(┐「ε:)_

Ruby と OpenCV で顔認識

去年の秋に組んだ新しいメインマシンは、MSI のB150M MORTAR ARCTIC (MS-7A45) という新しいマザボで、LMDE2 x64 を乗せて使おうとすると USB デバイスまわりが突然使用不能になったりして厄介でした。

USB デバイスの抜き差し時にホストコントローラが全部死ぬという惨事。PS/2キーボードもマウスもインターフェイスがないので、USB接続なんですが、それも使えなくなっちゃいます。何度かネットワーク経由でログインして再起動してました。Linux カーネル 4.x 系では USB デバイス関連のモジュールが充実していたようですが、なかなか時間が取れずに騙し騙し使っていました。ここ数ヶ月の LMDE のアップデートで同様の症状はなくなったみたいなので、ようやくメインマシンにカメラなどの USB デバイスを差して遊んでいます。ビバ他力本願。

仕事で上司が「ARをやってみたいな~」と言っていたので、久々に OpenCV を触ってみようと思い、今更感はあるものの Ruby で顔認識をやってみました。 OpenCV は2013年2月に Python でスネークさんのフィギュアの顔認識をして遊んだ程度…今度はちゃんと長続きするかな!

以下、自分用のメモです。

環境

  • LMDE2 (Linux Mint Debian Edition) x64
  • Ruby 2.1.0
  • ruby-opencv 0.0.7

1. ライブラリのインストール

sudo gem install opencv

2. 顔認識用の定義ファイルのダウンロード

wget https://raw.githubusercontent.com/opencv/opencv/master/data/haarcascades/haarcascade_frontalface_alt.xml

3. コード

ほぼサンプルのままです。認識した部分に赤い枠線を描いて保存します。

facedetect.rb

#!/usr/bin/ruby

require 'opencv'
include OpenCV

if ARGV.size < 2
  STDERR.puts "Usage: #{$0} srcfile destdest"
  exit 1
end

src = ARGV.shift
dest = ARGV.shift

img = IplImage::load src
d = CvHaarClassifierCascade::load "./haarcascade_frontalface_default.xml"

d.detect_objects(img).each do |r|
  img.rectangle! r.top_left, r.bottom_right, :color => CvColor::Red, :thickness => 3
end

img.save dest

4. 実行

ruby facedetect.rb srcfile.jpg destfile.jpg

動きました!

変なところを認識してるけれど、まずはひと安心 _(:3」∠)_

Raspberry Pi 3 で mruby を動かす

DSC_0193
DSC_0193

Raspberry Pi 3 が届きました!

発売より半年ほど出遅れましたが、ようやく Raspberry Pi 3 Model B を購入したので、組み込み向け軽量 Ruby である mruby を動かしていきたいと思います。

RPi3 は 1.2GHz クアッドコア 64bit ARM と 1GB の RAM を搭載したコンピュータです。私が4年ほど前まで使っていたネットブック LaVie Light PC-BL300 が Intel Atom N280 (1コア、1.66GHz) の 1GB RAM 構成でしたので、単純比較はできないものの35ドルで購入できるのはスゴいですね。

(おっと、mruby が想定している組み込み機器と比較してもはるかに高性能な上、MRI そのものも RPi で動いているので意味ないなんて言わないでください! mruby を ARM でセルフビルド・実行してみたかったんだよー!)

数年前に購入した初代 RPi Model B+ と比べても、Wi-Fi や Bluetooth 搭載であることはありがたいです。

RPi3 は Zero のように価格高騰もしていなかったので、普通に Amazon で購入しました。ソニーの英国工場で生産されているらしい RS 版と、中国で生産されている element14 版がありますが、ちょっとだけ安い element14 版、ケースとヒートシンク2個付きを購入しました。5,980円でした。購入時のレートで35ドルは3,722円。日本の Amazon で購入ができる本体のみは4,200円。差額は1,780円ですが、ケースが1,200円、ヒートシンクが2個で580円と考えると妥当な値段だと思います。

DSC_0184
DSC_0184

上が初代 Model B、下が購入した Pi 3 です。緑色のケースが「アリの巣コロリ」っぽくて気に入っています。 このケースがかもし出す昆虫採集感のおかげで、ラズパイを縁の下に設置しなければならなくなった時も安心です。

環境の確認

Raspbian の大きい方のディスクイメージを入れて、最初から色々とプログラミング環境が入っていました。

pi@raspberrypi:~ $ which ruby python perl node lua
/usr/bin/ruby
/usr/bin/python
/usr/bin/perl
/usr/bin/node
/usr/bin/lua

lua まで入っているんですねー。さすが教育用といったところか、私は使わないけど軽量 IDE の Geany もプリインストール済みです。X 環境が入っていない Raspbian LITE 版もありますが、ここまでは充実してないのかな。

pi@raspberrypi:~ $ ruby --version
ruby 2.1.5p273 (2014-11-13) [arm-linux-gnueabihf]
pi@raspberrypi:~ $ git --version
git version 2.1.4
pi@raspberrypi:~ $ gcc --version
gcc (Raspbian 4.9.2-10) 4.9.2
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
pi@raspberrypi:~ $ make --version
GNU Make 4.0
このプログラムは arm-unknown-linux-gnueabihf 用にビルドされました
Copyright (C) 1988-2013 Free Software Foundation, Inc.
ライセンス GPLv3+: GNU GPL バージョン 3 以降 <http://gnu.org/licenses/gpl.html>
これはフリーソフトウェアです: 自由に変更および配布できます.
法律の許す限り、 無保証 です.

基本的なものはすべて入っているようです。バージョンは念の為 apt upgrade した後のものです。

mruby の取得→セルフビルド

git で clone します。鍵ファイルを RPi に転送してないので、SSH ではなく HTTPS 経由で clone してます。

pi@raspberrypi:~ $ git clone https://github.com/mruby/mruby.git
Cloning into 'mruby'...
remote: Counting objects: 34141, done.
remote: Compressing objects: 100% (21/21), done.
remote: Total 34141 (delta 3), reused 0 (delta 0), pack-reused 34117
Receiving objects: 100% (34141/34141), 7.02 MiB | 951.00 KiB/s, done.
Resolving deltas: 100% (21335/21335), done.
Checking connectivity... done.
pi@raspberrypi:~ $ cd mruby/
pi@raspberrypi:~/mruby $ ls
AUTHORS          Makefile   appveyor.yml        doc       mrbgems               test
CONTRIBUTING.md  NEWS       appveyor_config.rb  examples  mrblib                travis_config.rb
ChangeLog        README.md  benchmark           include   mruby-source.gemspec
LEGAL            Rakefile   bin                 lib       src
MITL             TODO       build_config.rb     minirake  tasks

無事 clone できました。 minirake でビルドしてみます。

pi@raspberrypi:~/mruby $ ./minirake

できるかな…!

....
YACC  mrbgems/mruby-compiler/core/parse.y -> build/host/mrbgems/mruby-compiler/core/y.tab.c
sh: 1: bison: not found
sh: 1: bison: not found
rake aborted!
Command Failed: [bison -o "/home/pi/mruby/build/host/mrbgems/mruby-compiler/core/y.tab.c"
  "/home/pi/mruby/mrbgems/mruby-compiler/core/parse.y"]

bison がないって怒られてしまいました。さすがにないか~、ということは flex もなさそうですね。ささっと入れます。

pi@raspberrypi:~/mruby $ sudo apt install bison flex

私の環境では関連ライブラリも含めて3.7MBのファイルが入りました。さあ、気を取り直して再ビルドに挑戦です。

pi@raspberrypi:~/mruby $ ./minirake

結構時間がかかったものの、エラーなくビルドできました!

pi@raspberrypi:~/mruby $ ls bin/
mirb  mrbc  mrdb  mruby  mruby-strip
pi@raspberrypi:~/mruby $ bin/mruby --version
mruby 1.2.0 (2015-11-17)
pi@raspberrypi:~/mruby $ bin/mruby -e 'puts "hello, mruby on pi!"'
hello, mruby on pi!

わあい。ビルドが想像以上に長かったので、早いマシン上の mruby-cli でクロスビルドした方が良さそうです。

ビルド設定を変更

mruby-cli でのクロスビルドは後日として、とりあえずすぐに手元でできるビルド設定を変更していきます。 build_config.rb を開いて、enable_debug をコメントアウトします。念の為、MRuby::Build.new()host-debug, test, bench のところもブロックまるごとコメントアウト。(ここのところ触ってなかったのでもうちょっと良い方法があるのかもしれません)

mrbgems/default.gembox に次の使いたいライブラリを追加しました。

conf.gem :github => "mattn/mruby-xquote"
conf.gem :github => "matsumoto-r/mruby-sleep"
conf.gem :github => "iij/mruby-io"
conf.gem :github => "iij/mruby-regexp-pcre"
conf.gem :github => "iij/mruby-require"
conf.gem :github => "iij/mruby-dir"
conf.gem :github => "iij/mruby-env"

再度、

pi@raspberrypi:~/mruby $ ./minirake

すると、ビルドすることができました。

需要があるかどうか分かりませんが、上の構成でビルドして strip した mruby, mrib, mrbc, mrdb, mruby-strip のバイナリをこちらに置いておきます。Raspberry Pi と Raspbian があれば、ダウンロードして解凍するだけで使える…かもしれません。

次回は GPIO を制御して L チカをやってみたいと思います。

マルコフ連鎖を使って文章の自動生成を行う

cosmo

割と古典ですが、マルコフ連鎖の考え方を用いて、自動生成するスクリプトを書いてみました。入力した文章の単語同士の結合情報を重みづけをして「それっぽい文章」を生成してくれるやつです。以前からよく人工無能などでの実装で使われていますね。

ソースとなる文章をわかち書きに分割する処理に形態素解析コマンドの Chasen を使用しています。スクリプト本体は、文字分割の N-gram ではなく、単語分割したものを入力しますので、わかち書きが出せれば MeCab でも kakashi でも似たような感じでいけると思います。


単語リストに接尾単語リストを関連づけする方が精度が上がりますが、今回はちょっとしたテストコードですので、単語をリストで持たず1単語→接尾単語リストとして実装しています。

試しに、青空文庫で公開されている宮沢賢治の「銀河鉄道の夜」を入力してみると、次のように出力されました。

どうも今晩は見て、暗のところに見えるきれいだからこっちへかけて、 松や本の方はきちんとそろえて行きましょうと時計屋のしげみの底を 一つのやぐらが軽くひっぱましたともう着くころにふりうごかしました。 僕はそっちをもった。 孔雀が見えるのかたちが持っても手がだんだん横の旗を避けるようにした家でした。 いかがです。 美しい頬にふりを走っていました。 大きな黒い外套を揃えていましたよ。 ああそこでいて、おっかさんは空か。 わたくしたちが顔を大切に見えるのふしが云いました。 君もらわなかった。 ここはすぐ横手をつぶってるんなようにかかえられそうなことないか。 今日かがほとんどいちめんの口笛を擦りながら、私どものです。

意味はめちゃくちゃな文章ですが、スクリプトの短さの割にちょっと前の機械翻訳程度の和文は生成されますね。 Twitter や IRC, Slack などの bot や、音声合成システムの Open JTalk と組み合わせたら面白そうです。

参考リンク

佐世保港が一望できる弓張の丘ホテルに行ってきました

友人と佐世保港が一望できる弓張の丘ホテルに行ってきました。

[cardboard id=”2910″]

良いお天気に良い眺め。

[cardboard id=”2913″]

テラスでコーヒーを飲みながら雑談するのは楽しい時間です。

括弧を省略したLispコードのプロセッサーを書いてみました。

supoo(旧実装)

これまで業務でも趣味でも Lisp をガリガリ書くということはなく、せいぜい Emacs の設定ファイルを書くことにしか使ったことがありませんでした。 構文は非常にシンプルで、「これなら字句解析も構文解析も自分で書けそうだ。C と Lisp の勉強がてらやってみよう!」と思い立って書いたのが「supoo」です。

はい、考えが甘かったです。

lex や yacc を使わずにそれなりに動く実装を目指していたのですが、いつまで経ってもパーサ周りのエラーと例外処理に悩まされ続け、コードも複雑化してしまいました。C99ベースで正規表現ライブラリも使っていません。

なぜ既存の解析器を用いなかったかというと、単に lex や yacc ファイルの読み書きがあんまり出来なかったからというのも理由の一つですです。こちらが王道というのは薄々気付いていたので、過去にも何度も挑戦しましたが、その度に挫折…。これらを使った電卓レベルの入門ページはインターネットにたくさんあるんですが、それなりにプログラミング言語の形をしたものを作りたい場合はしっかりとマニュアルを読んで、実際に使用している他言語の実装を読まないとダメのようだーと思っていました。

streem

そういうのを書いたことも忘れて半年以上経った最近、まつもとゆきひろさんが一昨年の暮れあたりから開発している新プログラミング言語「streem」のコードを Github で見て、lex.lparse.yMakefile を眺めているうちに、自分でも書きたくなっていきました。ここ数年の一般的なツールや概念を用いて、何もないところから言語を組み立てていくところを見学することはとても勉強になります。

プログラミング言語を新しく作るところを見たいだけであれば、古い ML やら CVS やら漁れば色々と見れますが、その当時の歴史的な背景まで知っていなければ「なぜそういう実装になったのか」の経緯を本質的に知ることができないような気もしていました。今、リアルタイムで見るからこそ得るものもありそうです。

streem はパイプで連結してちょっとした処理をささっとこなすことができる「より良いシェル(スクリプト)」のようなものを目指しているのかな、と感じました。 パイプで繋げる考えをそのままにシェルスクリプトのようなものを進化させようとした Windows Power Shell を思い出しましたが、方針だけは評価できるものの、実際に出来上がったものは読みにくて書きにくい ガッカリ残念感の哀愁が漂っていたような印象を受けた覚えがあります1。 WPS と比較するのもアレなんですが、”顧客が本当に必要だったもの” は streem の方ですよね!

さて、そんな streem の lex.lparse.yMakefile をひっぱってきて、いろいろといじくり回しているうちに大体の記述は分かるようになってきました。まだパースだけで、式木の構築すらやっていないのですが、入れ子になった括弧を処理して、四則演算を行い、印字する程度のことはできました。

lex と yacc (正確にいうと flex と bison)を使ってなかった旧実装でも、関数定義式(defun)や制御式(if等)まで力技で実装していたので、それに比べたらまだまだラクガキレベルです。 ただ、やっぱり先人の知恵である解析器の便利さを実感することができました。

括弧

Clojure や Scheme を含む Lisp 系の言語は、S式を表現する際に括弧 () を多用します。Lisp 界隈のページを見ると、「括弧が書きづらさや読みにくさにつながって、初心者が寄りつかない」という記事をよく目にします。式木を表現したいだけであれば、Python のようにインデントでも可能ですので、そういう動きも以前からあったようです。

上記リンクでは、Scheme の ML のやりとりが紹介されています。自分も最初は、bison で () に加えてインデントによるネストをサポートしてみようかなと一瞬だけ考えましたが、電卓レベルと言えどちゃんと動いてる構文解析にこれから手を加えるのも億劫になるくらいなので、実用的に動いている実装のパーサから手を入れるのに及び腰になるのは理解できました。

ただ、コミュニティうんぬんの話は置いておいても、何十年も使い続けられている Lisp ですので時代ごとにこういう動きがあったと思いますが、未だに括弧を多用する構文が残っているということは、何か本質的な問題点があったのかもしれません。

supoo.rb

さて、そうは言ってもやっている事自体勉強&趣味なので、車輪の再発明上等でインデントでS式を書けるようにしてみました。

次のようなアプローチです。

  1. supoo はまだ電卓レベルなので parse.y を使いません。評価には GNU Common Lisp (clisp) を使います。
  2. 仮にちゃんと動いていても、yacc 構文では書きません。オレオレなインデント記法→ Lisp 記法を実現するフィルタとして実装します。

フィルタのスクリプト(supoo.rb)は次のようになりました。

中身はただのテキスト・プロセッサーです。要はインデントレベルを検知しながら、必要に応じて () を突っ込んでいるだけ。

次のように使います。

# 実行
ruby supoo.rb < indended-lisp.supoo | clisp -q -

# 通常の Lisp 構文に変換
ruby supoo.rb < indended-lisp.supoo > general-syntax.lisp

例えば、次のようなインデント記法のスクリプトがあったとします。

前置記法は変わらないですが、インデントによるS式のネスティングをしています。 必要以上に改行とインデントを入れなくても良いように、通常の () 構文も使えるようにしています2

このスクリプトを supoo.rb に食わせると

という Common Lisp なコードが印字されます。

clisp で実行すると

$ ruby supoo.rb < indended-lisp.supoo | clisp -q -
0 1 1 2 3
(1 . A)
--- TEST ---
1 2 3 4 5

のように正しく実行することができました。


  1. 結局 bash on Windows の提供という選択肢をとる Microsoft も最近では憎めないですが。 

  2. リストを各要素1行ずつに分けて書きたくないですよね。