画像処理(初級)

多くのプログラマーが使うが、言語の美しさを求めるプログラマーが嫌う言語といえば、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’を追加.

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に渡しているだけ。

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

ApacheにGoogle謹製の高速化モジュールmod_spdyを入れる

SPDY

あんまりプロトコルについて調べてないので何ともいえないですが、HTTP Pipeliningをさらに改善したようなプロトコルです。
ひとつのTCP接続において複数のストリームが束ねられ、それらについてプライオリティをつけることができます。
ほかにもヘッダなどが圧縮・最適化されます。たとえば、User-Agentなどは一度のセッションにおいて変更されることはないといっていいので、それを省いたりします。
ファイアウォールをすり抜けられない可能性があるので、HTTPSのなかで隠蔽することでこの問題を回避します。

前準備

sudo apt-get install subversion curl g++ patch binutils make
mkdir modssl
cd ~/modssl/
svn export http://mod-spdy.googlecode.com/svn/trunk/src/build_modssl_with_npn.sh
./build_modssl_with_npn.sh
sudo cp mod_ssl.so /usr/lib/apache2/modules/
sudo a2enmod ssl

depot_tools

cd ~
cd depot_tools/
svn co http://src.chromium.org/svn/trunk/tools/depot_tools
cd ..
export PATH="$PATH":$HOME/depot_tools
echo $HOME
cd ~

いよいよmod_spdyのビルド

mkdir mod_spdy
cd mod_spdy/
~/depot_tools/depot_tools/gclient config http://mod-spdy.googlecode.com/svn/trunk/src
~/depot_tools/depot_tools/gclient sync --force
cd src/
make BUILDTYPE=Release
sudo cp out/Release/libmod_spdy.so /usr/lib/apache2/modules/mod_spdy.so

設定

sudo echo "LoadModule spdy_module /usr/lib/apache2/modules/mod_spdy.so" | sudo tee /etc/apache2/mods-available/spdy.load
sudo echo "SpdyEnabled on" | sudo tee /etc/apache2/mods-available/spdy.conf
sudo a2enmod spdy
sudo /etc/init.d/apache2 restart

設定が終わったら、サーバにアクセスして chrome://net-internals/#spdy でSPDYが有効かどうか確かめましょう。
たぶんブラウザの設定だと思いますが、SSLでないとSPDYが有効になりません。

コンテンツ保護用の難読化jQueryプラグインを公開

HTML5が流行っていますが、割と社会ではFlashが好まれます。なぜかというと、難読化なんかのノウハウがFlashでたまっているとか、そういう理由があります。

MissingLinkではHTML5のノベルゲームエンジンを作ってましたが、それはコンテンツ保護はできず、シナリオを抜こうと思えばいくらでも抜ける、そんな状態でした。
一応コピペはできない(右クリックはおろか、選択もできないし、JSをオフにすると文字がでない)ようになっていましたが、問題はコンテンツの部分でした。コンテンツはJSONで書かれており、ダウンロードしようと思えばできます。
その生データを読みにくくできないかということで、1時間くらいで書いたのがこれです。
ぶっちゃけ、ブラウザのHTML要素の部分をエンドポイントまで難読化しているもので、HTMLソースを見ていただけるとわかるように、XOR難読化されています。解読をするには、jQueryプラグインで$.javascrypt()というメソッドを実行する必要があります。
それによって難読化された要素が動的に復号化され、表示上は平文が表示されます。
以下のデモでは、クリックをすることによって解読処理がなされますが、もちろん$(document).ready()で読み込み直後にメソッドを実行することが可能です。

デモとソースコード

デモ: https://web.missinglink.co.jp/keiya/javascrypt/
ソースコード: https://github.com/MissinglinkCorp/javasCrypt
ライセンスは修正BSDです。

サーバ側のPHPソースコード例

全文: https://github.com/MissinglinkCorp/javasCrypt/blob/master/index.php

<div><span class='_k_javascrypt'><?php echo $encrypted ?></span></div>
<script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.1.min.js"></script>
<script src="https://web.missinglink.co.jp/keiya/javascrypt/jquery.javascrypt.js"></script>
<script>
		$('#dec').click(function(e){
			$('._k_javascrypt').javascrypt();
			$(this).hide();
		});
</script>

仕組み上、サーバとクライアントの時計がたぶん99秒以上ずれるとコンテンツの解読ができなくなります。この値はもっと厳しくすることが可能です。ソースコードを改造すると、1秒までしか許容できないようにすることが可能です。PHP側のコードを工夫すれば1msでもできるかもしれませんが、ネットワークの遅延があるので実用できないと思います。
この仕組みによってサーバから送信され時間が経ったコンテンツ、つまり保存したコンテンツを解読するのは難しくなります(ただ読み込んだ時間を控えていれば解読は可能です)。解読するにはブラウザのエクステンションなどで読み込んだ直後に難読化のシード(種)と難読化文を読み取る仕組みが必要となるわけです。

HTML5ノベルゲームの例では、暗号化JSONをJavaScriptで動的に読み込んで解読させるコードを追加する予定ですが、これによってChromeでInspectorを起動して何のJSONが読み込まれているのか判断し、ダウンロードして読もうとすると、わけのわからない数字の配列があるためにあきらめる、くらいにはできると思います。まぁ、これは暗号ではないので、すぐに解読できますし、ある程度のスキルがあればブラウザエクステンションなどをつくっていくらでもほじくりまわせるとは思います。
これに関しては「私的利用上の」DVDのリッピングや「私的利用上の」ノベルゲームからのシナリオ抽出と同じ部類に入ると思いますし、いたちごっこですので仕方ないと思います。(何せ、クライアント側のソースコードがオープンなのですからね。。)

注意点ですが、もちろんコンテンツの安全性を保障するわけではないので、クレカや住所などにつかわないでください。

今後は、JPEGやPNG、MP3などを独自のフォーマットにするコンテンツ保護を考えてみようと思います。幸い、HTML5のAPI(というか次世代のECMAScriptの仕様?)にArrayBufferという、バイナリを扱うクラスがありますので、それがつかえるんじゃないかと、適当に考えています。

MongoDBのメモ

コレクション

テーブルに相当するのがコレクション。
コレクションには普通のコレクション以外にcappedコレクションというコレクションの種類がある。高速で、コレクションに挿入された順を保持する。コレクションを作る際にサイズを指定しなければならない。

サイズに到達すると古いものから消される。
cappedコレクションからデータを削除できない。コレクションごとdropする必要がある。

インデックス

RDBおなじみのインデックス。使い勝手は似ているっぽい。

インデックスの文字列評価はcase-sensitive

http://www.mongodb.org/pages/viewpage.action?pageId=5800049

MongoDBさわってみて思ったこと

感じとしてはJSONのキーの部分で検索ができるようになった、というところ。スキーマを気にせずデータを放り込めるし、それでいて検索は楽にできる。これは素晴らしい。

この性質は、Facebook APIやTwitter APIのJSONをほうり投げる際に便利で、RDBでやっていた面倒なことすべてがばからしく思える。

問題はデータの整合性。RASISのI:Integrityの部分。なんか基本オンメモリで、数十秒に一度のディスクへのflushするらしいので、それでプロセスがコケるとデータが飛ぶとか。最近のバージョン1.8からはjournalingがついたようなんだが、それでデータ破損は抑えられたのだろうか。

あとAuto-sharding周りが不安定だと聞く。前にFoursquareで起きたトラブルもそれに起因するとか。

いずれにせよあと数年は人柱の覚悟が必要だろうなー、まあ次作るサイトで使うんだけど。