画像処理(初級)

多くのプログラマーが使うが、言語の美しさを求めるプログラマーが嫌う言語といえば、PHPだと思うのだが(いろいろな関数が同じ名前空間にある・・・)、あまり深追いしない趣味程度で書くときは何も考えずに書けて良い。
とくにGDという画像処理ライブラリがインストール不要でレンタルサーバにインストールされていたので、監視カメラの画像処理に使うことにした。

パターン認識等はOpenCVの出番だが、今回は人の動きをトレースするだけだ。
動体検知ソフトで撮影した9枚の連続画像を使って明度の差で背景と人物を分離し、時間軸方向で人の動きを見る。そして入室か退出かを判断する。

まずこれは、オリジナル画像。退出時の画像だ。
動体検知をしてくれるフリーソフトを使っている。
ちょうど5枚目が動体検知の瞬間である。このソフトは検知前数秒を遡って画像にしてくれる。日付が記入されない2枚目の画像を利用し、これを背景とみなして全ピクセルの明度を記憶する。
明度は、単純にRGBの平均(R+G+B)/3とする。明度には他にも加重平均するなどの算出方法がある。
out-orig
これが処理後の画像。人間の中心線(緑)と移動ベクトル(水色)が出ている。
out-vect

そして記憶した2枚目と3枚目以降を比較し、明度の差を使って、人物だけを浮かび上がらせるようにする。
差が《明度差閾値》を超えたピクセルを人物がいそうな怪しいピクセルとすれば良い。
そのピクセルを縦方向(Y軸)に沿って加算すると、あるX軸(X)において、差が《明度差閾値》を超えたピクセルの総和が出る。

これをX軸方向に順次繰り返し、配列 $detect_x に格納する。格納した配列を見れば、X軸方向に《明度差閾値》を超えたピクセルの数が入っている。

《明度差閾値》はここでは20である。

$detect_x = array();
for ($iy=0;$iy<HEIGHT;$iy++) {
    for ($ix=0;$ix<WIDTH;$ix++) {
        $rgb = imagecolorat($img, $ix,$iy);
        $matrix = (
            (($rgb >> 16) & 0xFF)+
            (($rgb >> 8) & 0xFF)+
            ($rgb & 0xFF)) /3;

        if (abs($matrix-$base_rgb[$iy][$ix]) > 20) {
            $detect_x[$ix]++;
        }
        else {
            imagesetpixel($img,$ix,$iy,$marker);
        }

    }
}
$detect_x = array_filter($detect_x,function($v){return ($v > 150);});

この $detect_x を以下のコードに通すと、キーをX座標、値をX軸上で閾値を超えたピクセルの数の「部分配列」ができる。

人がいそうなX座標の「クラスタ」、「グループ」を出してくれるといったイメージである。

arsort($detect_x);
$last = 0;
$last_sub_array = array();
$sub_arrays = array();
foreach ($detect_x as $k => $v)
{
    if ($k - $last < 3) {
        $last_sub_array[] = $k;
    }
    else {
        $sub_arrays[] = $last_sub_array;
        $last_sub_array = array($k);
    }
    $last = $k;   
}
$sub_arrays[] = $last_sub_array;

このクラスタを走査して、最大のクラスタを見つけ、クラスタの先頭と最後尾のX座標を足して2で割ると、クラスタの中央のX座標がでる。これが「軸」、人がいると思われる「重心」だ。

$max = 0;
$axis = null;
foreach ($sub_arrays as $sub_array)
{
    $c = count($sub_array);
    if ($max < $c) {
        $max = $c;

        if (($sub_array[0] - $sub_array[$c-1])>10) {
            $axis = ($sub_array[0] + $sub_array[$c-1])/2;

        }
    }
}

先ほどの画像をもう一度貼る。この$axisが緑の線だ。
out-vect

$axisを、前の画像と比べることで移動した距離のベクトルを出せる。それが水色の線。

「入室」時。
in-orig

条件が良ければ、PHPだけで入退室くらいは知ることができそうだ。
in-vect

二枚目で人がいると、
bad
以下のように背景としてしまい、おかしなことになる。
bad-vect

scrapy-sentryを使う

Scrapyの便利なExtension/Middlewareにscrapy-sentryがありますが、READMEに書かれていることだけでは動かなかったので、settings.pyに数行追加する必要があります。

https://github.com/llonchj/scrapy-sentry のREADMEによると、SENTRY_DSNとEXTENSIONSだけでいいことになっていますが、SPIDER_MIDDLEWARESにクラスを追加する必要があります。


SENTRY_DSN = 'https://***:***@app.getsentry.com/***'

EXTENSIONS = {
'scrapy_sentry.extensions.Errors':10,
}

# ↓追加
SPIDER_MIDDLEWARES = {
'scrapy_sentry.middlewares.SentryMiddleware':2
}

IPアドレスを二進⇔十進変換(ビット演算)

Python3で行います.

def binary_ip(ip):
    octets = [int(o) for o in ip.split('.')]
    ip = octets[0] << 24 | octets[1] << 16 | octets[2] << 8 | octets[3]
    return ip

def decimal_ip(bin_ip):
    return (bin_ip >> 24, bin_ip >> 16 & 0xff, bin_ip >> 8 & 0xff, bin_ip & 0xff)

binary_ip(’192.168.0.5′)を呼び出すとそれの数値表現 3232235525 が返ります.
decimal_ip(3232235525 )とするとタプル (192,168,0,5) が返ります.
ビット演算(AND, OR),ビットシフトしか使用しておらず,型変換も最小限に抑えているのでオーバーヘッドはないと思います.

つぎに,例を挙げます.
ホストマシンが192.168.10.5,サブネットマスクが255.255.255.0のネットワークのネットワークアドレスは何でしょうか.
ネットワークアドレスを求めるにはIPアドレスとサブネットマスクのANDを取るだけです.
これがソースとなります.

ip = '192.168.10.5'
mask = '255.255.255.0'
net = binary_ip(ip) & binary_ip(mask)
net_h = decimal_ip(net)
print('subnet ip: {0[0]}.{0[1]}.{0[2]}.{0[3]}'.format(net_h))

192.168.10.5は

1100 0000  1010 1000  0000 1010  0000 0101

とあらわされます.

一方,255.255.255.0は

1111 1111  1111 1111  1111 1111  0000 0000

です.
これのANDを取ると,

1100 0000  1010 1000  0000 1010  0000 0000

となります.10進では 3232238080 です.

3232238080が結果ですが,見慣れたIPアドレスではありません.人間に読みやすいようにオクテットごとに分解する書き方で出力させましょう.

3232238080をdecimal_ipにて右シフトを行います.
上位8ビット(第一オクテット)から見ていきます.
24ビットの右シフトを行うと,

0000 0000  0000 0000  0000 0000  1100 0000

となります.これは192です.
第二オクテットを算出するために,16ビットの右シフトをします.

0000 0000  0000 0000  1100 0000  1010 1000

49320となってしまいます.これは,第一オクテットが入っているためです.
第二オクテットだけを取り出すには,ビットシフト後の結果から下位8ビットだけを取り出します.それには,0xFF

0000 0000  0000 0000  0000 0000  1111 1111

とのANDを取ればいいです.結果として

0000 0000  0000 0000  0000 0000  1010 1000

168が出てきます.
これを第三,第四オクテットにも適用しますが,略します.

出力はこうなります:

subnet ip: 192.168.10.0

EC2上でPython3によるDjangoを動かす

Amazon Web Services(AWS);Amazon Linux AMI 2013.03.1 でPython 3+Green Unicorn+Django 1.5を動かすメモです.
まっさらなInstanceからのインストールです.

Python install

基本的にはhttp://librabuch.jp/2013/01/python33-install/が参考になります.

Python 3コンパイル

ここは普通.
yum install gcc make openssl-devel bzip2-devel expat-devel gdbm-devel readline-devel sqlite-devel
wget http://www.python.org/ftp/python/3.3.2/Python-3.3.2.tar.xz
tar -Jxf Python-3.3.2.tar.xz
cd Python-3.3.2
./configure --prefix=/usr/local/python --enable-shared
make
sudo make install
sudo sh -c "echo '/usr/local/python/lib' >> /etc/ld.so.conf"
sudo ldconfig
sudo ln -s /usr/local/python/bin/python3 /usr/local/bin/python3

Distributeインストール

パスに注意しながら.
wget https://pypi.python.org/packages/source/d/distribute/distribute-0.6.45.tar.gz
tar xzf distribute-0.6.45.tar.gz
sudo /usr/local/bin/python3 distribute-0.6.45/setup.py install
sudo /usr/local/python/bin/easy_install pip

Django, Green Unicorn, nginx周り

こっからはhttp://adrian.org.ar/django-nginx-green-unicorn-in-an-ubuntu-11-10-ec2-instance/
http://dat.plastica-romantica.com/log699.htmlを参考に.

Virtualenv, Djangoインストール

sudo /usr/local/python/bin/pip install virtualenv
/usr/local/python/bin/virtualenv --no-site-packages django_app
cd django_app
source bin/activate


/usr/local/python/bin/django-admin.py startproject app
cd app
/usr/local/python/bin/gunicorn_django -b 0.0.0.0:8000

ホストのtcp/8000にブラウザでアクセスすると見えます.(Security Groupでtcp/8000を一時的に開ける必要あり)

sudo yum install nginx

sudo mkdir -p /opt/django/logs/nginx/
mkdir $HOME/django_app/static
mkdir $HOME/django_app/templates
sudo ln -s $HOME/django_app/static /opt/django

nginx設定:リバースProxyでgunicornにつなぐ

sudo vim /etc/nginx/conf.d/gunicorn.conf
こちらをもとにしています
https://bitbucket.org/deccico/django_gunicorn/raw/tip/server/etc/nginx/sites-available/default

server {
    listen   80;
    server_name localhost;

    access_log /opt/django/logs/nginx/vc_access.log;
    error_log  /opt/django/logs/nginx/vc_error.log;

    # no security problem here, since / is alway passed to upstream
    root /opt/django/;
    # serve directly - analogous for static/staticfiles
    location /media/ {
        # if asset versioning is used
        if ($query_string) {
            expires max;
        }
    }
    location /admin/media/ {
        # this changes depending on your python version
        root /usr/local/python/lib/python3.3/site-packages/django/contrib;
    }
    location /static/ {
        # if asset versioning is used
        if ($query_string) {
            expires max;
        }
    }
    location / {
        proxy_pass_header Server;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Scheme $scheme;
        proxy_connect_timeout 10;
        proxy_read_timeout 10;
        proxy_pass http://localhost:8000/;
    }
    # what to serve if upstream is not available or crashes
    error_page 500 502 503 504 /media/50x.html;
}

sudo /etc/init.d/nginx start

settings.pyのTEMPLATE_DIRS = に’/home/ec2-user/django_app/templates’を追加.

それなりに高機能なライフゲーム

ライフゲームとは

Wikipediaからのコピペですが、ライフゲームはこういうものです。

  • セルオートマトンの一種
  • セルの上に生命体をつくり、誕生から死亡をシミュレートする
  • 生命のステータスは4つ(以下)
誕生
死んでいるセルに隣接する生きたセルがちょうど3つあれば、次の世代が誕生する。
生存
生きているセルに隣接する生きたセルが2つか3つならば、次の世代でも生存する。
過疎
生きているセルに隣接する生きたセルが1つ以下ならば、過疎により死滅する。
過密
生きているセルに隣接する生きたセルが4つ以上ならば、過密により死滅する。

この単純なルールで生命体を表現するのがライフゲームです。
とりあえずそれを作ってみました。

デモ

グライダー

まずは”グライダー”をご覧ください。Runをクリックしてください。
このページへ

ほかの物体はリンク先の下のほうにあるリンクで辿れます。

銀河

こんなのもいいですね。
このページへ

Numを押して生命数の表示を有効化すると、セルの周りの生命体数が表示され、ルールと照らし合わせて確認できます。が!ブラウザによっては固まってしまうことも・・・。重いようです。

ほかにはこういうのも

パルサーといわれるようです。たしかに、天体の一種に見えなくもない。
このページへ

グライダーを量産するグライダー銃

グライダーを製造しまくります。
幅が広いので、このページで見てください。

ほかに・・・

というわけで、いろいろ作れます。セルをクリックすると生命体を作れるので、やってみてください。一つや二つのセルに生命を置いても、過疎で死んでしまいますし、4つ以上を隣接させるとそれはそれで過密で死にます。

more..

see wikipedia
たくさんあります。なにか新しいものを作ったら、リンクをGetURIで生成して、コメントにでも書き込んでくださいね。

プログラムの工夫

  • とりあえずHTML5です
  • セルの内容をURIにシリアライズできます。
    URIの後ろの#以降は、実は生命体の座標が入っています。いい感じの生命ができたら、GetURIでURIを作成して共有できるというわけ。
    上のサンプルのリンクへいくと、URIが長いのが分かります。

Shallow copy / Deep Copy

さて、このライフゲームを作っているときに、JavaScriptの代入で困っていました。たとえば、tmp = obj.aのようなことをすると、tmpにはobj.aの参照が入ります。これはShallow Copyというものです。これに対して、丸ごとコピーすることをDeep Copyといいます。
Deep Copyをする方法はググればいろいろなライブラリがあるようです。

Base64

URI生成でシリアライズはJSONとBase64URLを利用しました。JSON+encodeURIComponentだとどうしても大きくなるからです。Base64URLとは、Base64で利用する文字種について、URIでディレクトリセパレータなどになってしまう文字を置き換えたりしたものです。

code review

Kinetic.jsをつかってみましたが、何か遅いです。newが重いのか、描画が重いのか定かではありません。ブラウザ別で早かった順で行くと、IE10, Chrome, Safari==Firefoxくらいになりました。
requestAnimationFrameを利用して垂直同期と同じくらいの間隔でレンダリングをしようと思ったのですが、最速のIE10でもワンステップのシミュレーションに20msほどかかるので、setTimeoutにて行っています。
Kinetic.jsでリフレッシュにstage.draw()を使うと遅かったので、layerごとにdraw()しています。この場合、枠のレイヤーはレンダリングされません。

WebRTCで複数台監視可能なカメラをつくる

WebRTC
WebRTCはブラウザからカメラやマイクを使える楽しいAPIです。というわけで、WebRTC+Canvas+WebSocket+node.jsと豪華な最新ラインナップで監視カメラを作りましょう。
サーバには、node.jsをインストールします。
npm install wsでwsパッケージもいれておきます。
そして、以下のコードを書きます。

var WebSocketServer = require('ws').Server
, wss = new WebSocketServer({port: 8080});
var slot = [];
var maxid = 0;
wss.on('connection', function(ws) {
var id = maxid;
console.log('connection open #%d',id);
ws.on('message', function(data) {
broadcast('DAT',id,data);
});
ws.on('close', function() {
console.log('connection closed #%d',id);
// 接続切れのソケットを配列から除外
slot = slot.filter(function (conn, i) {
return (conn === ws) ? false : true;
});
broadcast('CTL','close',id);
});
slot[id] = ws;
++maxid;
});

function broadcast(type,id,data) {
var buf = '';
slot.forEach(function(socket,i){
buf = type + ' ' + id + ' ';
buf += data;
try {
socket.send(buf);
}
catch (e) {};
});
}

https://github.com/keiya/WebRTC-CCTV/blob/master/server.js

HTMLを書きます。
https://github.com/keiya/WebRTC-CCTV/blob/master/rtc.htm
※jQueryが必要です

まとめてダウンロード
iPadからも当然見れます!
CCTV on iOS

サーバにアクセスし、カメラの動作を許可すれば、データがWebSocketにより送信され、Canvas上のあたらしい表示領域が自動で作られます。
動作するブラウザはChrome、Firefoxで、Windows/OSX/Ubuntuで動作を確認しています。
UVC対応カメラであればどのOSでもデバイスドライバのインストールなしにブラウザに認識されます。

こちらではUbuntu上のChromeで数週間の連続稼働を確認しています。

手書き文字認識APIをテスト運用中

Zinniaを利用した高速な手書き文字認識APIを実装しました。
http://hwr.missinglink.co.jp/vec.html にてデモが使えます。
利用方法は、 http://hwr.missinglink.co.jp/v1/? へ以下のようなJSONをGETにて送信します。(?以降のQUERY_STRINGにそのままJSONをつなげます)

[
{
"canvas_size":[300,300]
},
{
"strokes_points":[
[
[x,y], ...
], ...
]

canvas_size は手書き認識エリアのサイズのx,yです。
stroke_pointsは、pointsを格納します。pointsは、strokeの集合です。strokeはx,yから成り、ストロークの座標です。始点/終点だけとは限りません。デモ画面では、スムージングした座標を利用しています。
送信すると、JSONPで返却されます。
コールバック関数は _mlHWRCallback です。
「知」を書いたときの結果は以下のようになっています。

{"result":[{"value":"知","score":0.192947},{"value":"肴","score":-0.376623},{"value":"刹","score":-0.540289},{"value":"氛","score":-0.600198},{"value":"頚","score":-0.652695},{"value":"午","score":-0.663520},{"value":"帥","score":-0.690676},{"value":"竍","score":-0.710740},{"value":"剥","score":-0.762234},{"value":"朋","score":-0.766667},]}

サーバサイドは、C++で実装されています。spawn-fastcgiとnginxにより、高速なレスポンスが期待出来ます。混雑していない場合は、都内から30ms程度で認識結果が返ります。(DNS解決にかかる時間は除く。また、サーバはEC2 micro instanceのため、複数のアクセスがあると急激にレスポンスが低下します)
ソースコードはgithubにあります。予告なく非公開にする場合があります。

KinectServerをつくりました

ソースはこちら。
https://github.com/MissinglinkInc/KineticSignage

Kinectの骨格情報をネットワークのすべてのクライアント、または(コードを書き換えれば)任意のクライアントへの送信するものです。
KineticSignageはそのデモプログラムです。デキは微妙ですが、腕の角度で写真が切り替わるようにできています。
サーバ・クライアント型なので、KinectServerとKineticSignageは別のマシン、UDPが疎通すれば別のネットワークに配置することが可能です。

ソースコード自体はそれ以上大したことはやっていません。骨格情報をMessagePackに押し込んで送るだけです。このプロトコルは100%、MessagePackのおかげです。まだ慣れていないので、全部配列型でやってしまっているのはビューティフォーじゃない点です。

MessagePackはかなり多くの実装、アーキテクチャで動くので、Pythonやnode.jsでUDPを受信してWebSocketとかにつなげれば面白いはずです。まぁ、KinectServerにWebSocket実装を書けばいいだけですけど。

C++のSTLいいですね。最近Cで文字列処理や動的配列確保を書いていて辟易していたのですが、std::stringやstd::vectorが強すぎて世界観が変わりました。

PHPでオーバーロード、メソッドチェーンの練習。O/RMつくってみた

→ ‎https://gist.github.com/3224802

こんなかんじで使える:

$db = new KDB('root','','sso_cp','localhost');
$db->users->insert($profile)->query();

これの実行時点で$dbのインスタンス変数にはusersっていう変数はないので、普通は$db->usersをやった時点でそんな変数ないがな、と言われるはずだが、KDBクラスの中の__get()ってやつでオーバーロードしているので、こういう技が使える。

public function __get($table) {
	$this->table = $table;
	return $this;
}

この存在しないはずの “users” 変数を指定することで、O/RMにusersテーブルを指定することができる($this->tableに代入)。
insert()メソッドに連想配列を渡すと、$this->sqlとかにいろいろビルドしたSQL文を代入した後に、インスタンス($this)を返してくれるので、

public function insert($assoc) {
	$this->sql = "INSERT INTO {$this->table} SET ";
	foreach ($assoc as $k => $v) {
		$this->sql .= "`{$k}`='".$this->mysqli->real_escape_string($v).'\',';
	}
	$this->sql = substr($this->sql, 0, -1);
	return $this;
}

そのままメソッドチェーンでつないでquery()が実行できる、という仕組み。

public function query() {
	$res = $this->mysqli->query($this->sql);
	if ($res === true) {
	}
	else if ($res === false) {
		error_log('mysqli:(query failed) '.$this->mysqli->error);
		return false;
	}
	else {
		$array = array();
		while ($row = $res->fetch_assoc()) {
			$array[] = $row;
		}
		return $array;
	}
}

query()の中では、$this->sqlをそのままMySQLに渡しているだけ。

というように、メソッドチェーンはいったん変数を経由することなく、おなじインスタンスを引き継いでくれるので、便利。