3

ruby-opencvをインストールして顔認識した

ruby-opencvは5年ぐらい前に停止したopencvというgemを、ser1zwさんという方がOpenCV2.x系に対応させ、Ruby2.0に対応させ、と着実にアップデートしているプロジェクト。

先日これをみて、そういえば良いプロジェクトなのに全く試してなかったと思い出してまずインストールした。
ruby-opencvの進捗の話(2014年2月版) – ser1zw's blog


試しに作ったものはここに置いてある
https://github.com/shokai/ruby-opencv-study

感想

まだ大した事試してないけど、感想としては、ドキュメントが無い。でもOpenCV本体のドキュメント見てそのままRubyの常識にそって書き直したらふつうに動くのでドキュメントなくてもいいかなと思った。

Rubyだとそのオブジェクトが持っているメソッド一覧はObject#methodsなどで見れるし、ruby-opencvのリポジトリをcloneしてきて git grep キーワード ext/ すればC++と対応してるRubyのメソッドはすぐ見つかる。

あとはそれぞれのC++ラッパーのオブジェクトにinspectメソッドがあると、見やすくて良くなるかと思った。例えばCvRectは4上下左右4座標が入っているんだけど、putsやpで4座標が標準出力できると便利だと思う。


Macにインストール

Mac (OSX 10.9.1)の場合、まずhomebrewでopencvを入れてからgem installしたら簡単にインストールできた。

% brew search opencv
% brew tap homebrew/science

% brew info opencv
opencv: stable 2.4.7.1

% brew install opencv
特にインストールオプション付けずにopencv 2.4.7.1が入った。

% gem install ruby-opencv
0.0.12が入った。


カメラでキャプチャして顔認識

最近追加されたEigenfacesやFisherfacesもあるけどとりあえず古典的なhaarlike cascadeでやってみる。

分類器は
/usr/local/share/OpenCV/haarcascades/haarcascade_frontalface_default.xml
をコピーしてきて使った。

ほぼサンプルまま。カメラでキャプチャして顔を認識する
capture_face_detect.rb
require 'opencv'

window = OpenCV::GUI::Window.new "face detect"
capture = OpenCV::CvCapture.open
detector = OpenCV::CvHaarClassifierCascade::load "./haarcascade_frontalface_default.xml"

loop do
image = capture.query
image = image.resize OpenCV::CvSize.new 640, 360
detector.detect_objects(image).each do |rect|
puts "detect!! : #{rect.top_left}, #{rect.top_right}, #{rect.bottom_left}, #{rect.bottom_right}"
image.rectangle! rect.top_left, rect.bottom_right, :color => OpenCV::CvColor::Red
end
window.show image
break if OpenCV::GUI::wait_key(100)
end

ウィンドウが開いて顔の部分に赤枠がでる


顔認識して画像ファイルを保存


Rubyなのでサーバーで使うような事を考えると、windowで画像を表示するのではなく顔認識した結果をファイルに書き出したほうがそれっぽいかなあとか考えてた

face_detect.rb
require 'opencv'

if ARGV.size < 2
STDERR.puts " % ruby #{$0} input.jpg output.jpg"
exit 1
end

input_filename = ARGV.shift
output_filename = ARGV.shift

image = OpenCV::IplImage::load input_filename
haar_xml_file = File.expand_path File.dirname(__FILE__), 'haarcascade_frontalface_default.xml'
detector = OpenCV::CvHaarClassifierCascade::load haar_xml_file

detector.detect_objects(image).each do |rect|
puts "detect!! : #{rect.top_left}, #{rect.top_right}, #{rect.bottom_left}, #{rect.bottom_right}"
image.rectangle! rect.top_left, rect.bottom_right, :color => OpenCV::CvColor::Red
end

image.save output_filename

% ruby face_detect.rb input.jpg output.jpg

結果
detect!! : <OpenCV::CvPoint:(125,9)>, <OpenCV::CvPoint:(472,9)>, <OpenCV::CvPoint:(125,356)>, <OpenCV::CvPoint:(472,356)>

シンプルに書けてよい。

0

if a == b

typo

if a == b
のつもりで
if a = b
と書くと、常に真になり、文法も間違っていないので普通に実行されてエラーでない。

このtypoに気づくのに1時間以上かかった。

coffee-scriptだったので今後は==はやめてisを使うようにしたい。
if a is b
isも==もcoffeeではコンパイルすると===になる。怖いからもう==使いたくない。

ちなみにisntは!==になる。


Rubyでis, isnt


class Object
alias_method :is, :==
alias_method :isnt, :!=
end

puts 1.is 1 # => true
puts 1.is 2 # => false
puts 1.isnt 2 # => true
puts 1 is 1 # = > syntax error

Rubyでは==や!=もObjectに定義されたメソッドなのでis/isntできるかと思ったけど、スペース空けると文法エラーになる

もしくはif文と同じ行で代入してたら警告する機能とかほしい

0

Ruby版socket.io用クライアント 自動再接続機能付けた

node版にもともとある機能だけど、切断されたら自動的に再接続する機能をv0.0.3から付けた。
あとTravis CIでテストするようにした。

https://github.com/shokai/ruby-socket.io-client-simple


もともとwebsocket-client-simple gemに切断時のイベントがあるんだけどnode版と同じようにサーバーからのheartbeatのタイムアウトで判定するようにした。

切断されたらランダムに20〜40秒待ってから再接続する。


サンプルコード、rubyのとcoffee-scriptと入れておいた。ほぼ同じ感じで書けてよい。

0

Ruby用のsocket.ioクライアント作った

node.jsのsocket.ioのRubyクライアントライブラリ作った。

rubygems.orgに既に他に2つ実装があるものの、1つは古く、もう一つはruby2.0で動かずプロジェクトが生きてない感じだったので、車輪再発明した。

socket.ioはwebsocketとかajax pollingとかflashとかiframeとかいろいろ使うけど、rubyなのでwebsocketしか実装しないことにした。

昨日のmix-inした関数を上書き定義前にalias_methodで退避するで、event_emitterとwebsocket-client-simpleにほとんど任せてるのでそんなに面倒でもなかった。70行ぐらいで実装できた。


インストール

gem install socket.io-client-simple


ソースコード

https://github.com/shokai/ruby-socket.io-client-simple

不具合などあったら@shokaiかgithubのissueにお願いします


使い方


接続
samples/sample.rb
require 'rubygems'
require 'socket.io-client-simple'

socket = SocketIO::Client::Simple.connect 'http://localhost:3000'

socket.on :connect do ## 接続イベント
puts "connect!!!"
end

socket.on :disconnect do ## 切断イベント
puts "disconnected!!"
end

## サーバーからイベント"chat"がemitされたら、このコールバックが呼ばれる
socket.on :chat do |data|
puts "> " + data['msg']
end

puts "please input and press Enter key"
loop do
msg = STDIN.gets.strip
next if msg.empty?

## サーバーにchatイベントを送る
socket.emit :chat, {:msg => msg, :at => Time.now}
end


サーバー側の実装は普通にsocket.ioのリファレンス実装みたいな感じ。

samples/chat_server.js
// socket.io chat server

var io = require('socket.io').listen(3000);

io.sockets.on('connection', function(socket){
socket.emit('chat', {msg: 'hello world (from server)'});
socket.on('chat', function(data){
console.log(data);
socket.emit('chat', data); // echo
});
});


まだ切断したら自動再接続する機能ないけど、すぐ実装する予定。

0

mix-inした関数を上書き定義前にalias_methodで退避する

用途

Ruby版EventEmitterなどのincludeして既存クラスを拡張するタイプのライブラリを使う時に、mix-inされる関数と自分の関数の名前が衝突してしまう場合に有用。

Ruby用のsocket.io実装を作っているんだけど、EventEmitter gemで定義されているemit関数を__emitに退避してemitを新たに自分で定義するのに使った。


alias_methodで関数名の衝突を回避する

module Fooをclass Barにincludeした時に、FooにもBarにも同じ関数を定義している場合、クラスの方に定義した関数の方が優先される。
モジュールの方の、せっかくincludeでmix-inした関数は消滅してしまう

includeしたmoduleの方の関数fooも使いたい場合、includeしてすぐにalias_methodで別名に退避するとよい。

module Foo
def foo
puts "foo called!!"
end
end

class Bar
include Foo
alias_method :__foo, :foo ## fooを別名(__foo)に退避

def bar
puts "bar called!!"
end

def foo
puts "Bar's foo called.."
end
end


bar = Bar.new
bar.bar
bar.__foo # include後に退避したmoduleの関数が呼ばれる
bar.foo # include後に上書きした関数が呼ばれる


結果
bar called!!
foo called!!
Bar's foo called..


alias_methodのタイミング

関数の上書き定義後にalias_methodを使っても、退避できない。

module Foo
def foo
puts "foo called!!"
end
end

class Bar
include Foo

def bar
puts "bar called!!"
end

def foo
puts "Bar's foo called.."
end

alias_method :__foo, :foo ## 上書き定義した後にエイリアスでの退避を試みる
end


bar = Bar.new
bar.bar
bar.__foo
bar.foo

fooも__fooも、どちらも上書き定義されたメソッドが呼ばれる。
bar called!!
Bar's foo called..
Bar's foo called..

ちなみにもちろんinclude前にalias_methodしても、NameError(undefined method)例外が起こる。