※ パクレゼルヴではWeb開発エンジニアを大募集中!詳細はこちら

Archive

Author Archive

OpenCV2  

2011/10/26 水曜日 16:52:38

こんにちは。しゅうです。

今回は前回に引き続きOpenCVを取り上げます。
今回はSURFアルゴリズムを使った特徴点抽出をやってみようと思います。
アルゴリズムの詳細に関しては私も熟知していないためここでは割愛します。
ただざっくり言うと画像の中の人や物を認識・抽出するためのものです。

OpenCVのpython用ライブラリでSURFによる特徴点抽出を行うにはcv.ExtractSURFを使います。

ソースは以下の通りです。

import cv
from sys import *

im =  cv.LoadImageM(argv[1] , cv.CV_LOAD_IMAGE_GRAYSCALE)
(keypoints , descriptors) = cv.ExtractSURF(im , None , cv.CreateMemStorage() , (1 , 500, 3, 4))

for ((x , y) , laplacian , size , dir , hessian) in keypoints:
    cv.Circle(im , (x,y) , int(hessian/1000) , cv.RGB(255 , 255 , 255) , 1 , cv.CV_AA , 0)

cv.SaveImage('surf_' + argv[1] , im)

cv,Circleで円を描画していますが、半径としてヘッシアン値を1000で割ったものを使用します。
ヘッシアン値は特徴点の強さを示します。

上記をsurf.pyとして保存します。

テスト用の画像としては前回と同じ画像を使用します。

では以下のコマンドを実行してSURF特徴点を抽出してみましょう。

python surf.py people_image.jpg

上記コマンド実行後、同一階層ディレクトリ内に「surf_people_image.jpg」が作成されます。
その画像が以下となります。

surf_people_image

画質の問題もあり、正直なところ微妙な精度なような気がしますが
このように写真内の人(今回はワンちゃんも)や物を認識させるのに利用できます。

ちなみにOpenCVにはandroid用のライブラリが付属しており、それを使用するとカメラでの動的認識が可能である
サンプルアプリが付属しています。
andoroid用アプリを動かすにはまたいくつか手順を踏む必要があります。
興味がある方はネット上にもいくつか情報が存在しますので試してみてください。

駆け足となってしまいましたが今回はここまで・・・

shu 未分類

OpenCV  

2011/9/9 金曜日 18:22:01

こんにちは。しゅうです。

前にkazuさんが紹介していた「phpで顔認識」ですがちょっと面白いと思って他のライブラリがないか探してみました。
そこで発見したのがOpenCVというライブラリです。
このライブラリは別に顔認識するためだけのライブラリではなく画像処理全般をサポートしているライブラリです。
このライブラリはかなり前から存在していてそれなりに有名らしいのですが
phperはまず使用しないため(*1)phpのお兄さん的存在(!?)のpythonから試してみることにしました。

先にも書いたように顔認識以外の色々な画像処理が可能ですがここで試すのは顔認識機能のみとします。まずOpenCVをインストールします。
インストール過程も一から紹介しようと思ったのですが…
OpenCVのインストールを一から紹介すると相当長くなってしまうためここでは割愛させていただきます。
インストール詳細に関してはhttp://opencv.willowgarage.com/wiki/InstallGuideを参照してください。
とりあえず必要なライブラリ等を以下に示します。

  1. OpenCV(http://opencv.jp/downloadからVer2.2をダウンロードしてください)
  2. cmake
  3. python2.6以上
  4. g++
  5. libjpeg
  6. libpng
  7. libtiff
  8. Jasper(jpeg2000用ライブラリ)

上に挙げたライブラリがインストールされていれば最低でもpythonでOpenCVで顔認識処理を実行することは出来きます。

注:上記はlinuxにOpenCVをインストールする場合です。それ以外のプラットフォームにインストールする場合にはまた別のライブラリが必要かもしれません。
詳しくはhttp://opencv.willowgarage.com/wiki/InstallGuideを参照してください。
また付属するpythonサンプルを実行するには上記ライブラリに加え、gtk+2.xをインストール必要があります。

・・・さてOpenCVと必要なライブラリ群をすべてインストールしました。(と仮定します)
OpenCVのpythonバインディングで顔認識をするにはHaarDetectObjectsというメソッドを使用します。
このメソッドはHaar-like特徴を利用して顔認識を行います。
以下にコードを示します。

import sys
import cv

if len(sys.argv) == 1:
    sys.exit(0)

#分類器データ読み込み
hc = cv.Load('/root/OpenCV-2.2.0/data/haarcascades/haarcascade_frontalface_alt.xml')

#画像ファイル読み込み
img = cv.LoadImage(sys.argv[1] , 0)
#顔認識処理
faces = cv.HaarDetectObjects(img , hc , cv.CreateMemStorage())

#返却された座標と幅・高さを元に四角形を描画
for (x,y,w,h) , n in faces:
    cv.Rectangle(img , (x,y) , (x+w , y+h) , 255)

#新しい画像ファイルとして出力
cv.SaveImage("new_" + sys.argv[1] , img)

OpenCVではあらかじめ多くの分類器ファイルが用意されており使用する分類器ファイルを変更することによって別の分類器ファイルでは認識できなくとも変更後の分類器ファイルで可能となる場合があります。
(cv.Loadの引数が分類器ファイルのパスです。)

上記pythonをsample.pyとして保存します。

テスト用の画像は以下の画像を使用します。(実際のサイズは500X376です。)
people_image

上記画像を先に保存したsample.pyと同一階層のディレクトリにpeople_image.jpgという名前で保存し、以下のコマンドを実行します。

python sample.py people_image.jpg

実行結果が以下の画像になります。

new_people_image

正面を向いているおじさんの顔は認識できていますが横を向いていたり、また小さい顔は認識できません。
正しく認識させるためにはそれなりに大きな顔の画像が必要となります。

次の機会には同じくOpenCVを使いSURFアルゴリズムによる特徴量抽出をご紹介できたらと思います。
これは顔に限らず画像・動画の人物・物体を認識するためのものです。

今回はこの辺で。

shu 未分類

HTML5(FileAPI)  

2011/7/26 火曜日 14:29:45

こんにちは。しゅうです。
今回はHTML5のFileAPIでマルチアップローダーを作ってみました。
これは指定された領域に複数ファイルを一括してドラッグ&ドロップするとそのファイルがサーバー側に
アップロードされるというものです。
今回アップロード可能なファイル種類はイメージに限定させてもらいました。

基本的な処理の流れはドロップ対象領域で「drop」イベントが発生したらevent.dataTransfer.filesプロパティから
Fileオブジェクトを抽出し、FileReaderクラスを使用して読み込みます。
その後読み込み完了を待って、jquery.ajaxを使用してサーバー側のphpに読み込んだファイルのデータを
base64形式で送信し、ファイルとして保存します。
ファイル名は元ファイル名と同じになるようにしてあります。
そしてファイル保存完了後完了メッセージを表示して処理終了です。

以下がクライアント側のHTMLとJavascriptのソースコードです。

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf8">
<title>Insert title here</title>
<script src="jquery.js"></script>
<script >
//ロード時
$(document).ready(function() {
	//jQuery.EventオブジェクトへFileAPIオブジェクト群のプロパティ追加
	//これがないとイベントリスナー側でFileAPI独自プロパティが参照できないため
	jQuery.event.props.push("dataTransfer");
	jQuery.event.props.push("total");
	jQuery.event.props.push("loaded");
	jQuery.event.props.push("lengthComputable");

	//ドラッグ&ドロップ領域のdragoverイベントとdropイベントにイベントリスナー設定
	$('#drop_field').bind('dragover' , function(e) {
		//ブラウザ既定の処理をキャンセル
		//(これがないとドロップしたファイルが単にブラウザに表示されるだけ)
		e.preventDefault();
	}).bind('drop' , function(e) {
		//ブラウザ既定の処理をキャンセル
		//(これがないとドロップしたファイルが単にブラウザに表示されるだけ)
		e.preventDefault();
		for (var i = 0; i < e.dataTransfer.files.length; i++)
			(new fileUploader(e.dataTransfer.files[i])).upload();
	})
});

//ファイルアップローダークラス定義
var fileUploader = function (file) {
	this._file = file
	this._size = this._file.size;
	this._name = this._file.name;
	this._type = this._file.type;
	this._reader = new FileReader();
	this._save_url = 'save_file.php';
	//ファイルロード完了時のイベントリスナー設定
	$(this._reader).bind('load' , {reader:this._reader,save_url:this._save_url , name:this._name} , this._upload);
	//ファイルロード中イベントリスナー設定
	$(this._reader).bind('progress' , {file_name:this._name} ,this._progress);
	//ファイルロード中止時イベントリスナー設定
	$(this._reader).bind('abort' , this._abort);
	return this;
};

fileUploader.prototype = {
	//アップロード
	upload:function() {
		if (this._type.match(/^image/))
			this._upload_image();
		else if (this._type.match(/^video/))
			this._upload_video();
		else if (this._type.match(/^text/))
			this._upload_text();
		else
			this._upload_other();

		return this;
	},
	//イメージファイルアップロード
	_upload_image:function() {
		this._reader.readAsDataURL(this._file);
	},
	//動画アップロード(今回は無視)
	_upload_video:function() {
		return;//ignore
	},
	//テキストファイル(今回は無視)
	_upload_text:function() {
		return;//ignore
	},
	//その他ファイル(今回は無視)
	_upload_other:function() {
		return;//ignore
	},
	_progress:function(e){
	    //既にプログレスメッセージ表示領域が存在するかどうか
		if (typeof this._msg_container == "undefined")
		{
			this._msg_container = document.createElement('div');
			this._msg_container.id = 'info_' + this._name;
			$('#drop_field').append(this._msg_container);
		}

		//ファイルのサイズが判明しているかつファイルサイズが0より大きい場合
		if (e.lengthComputable == true && e.total > 0) {
			var rate = (e.loaded * 100 / e.total);
			var msg = "";
			msg += e.data.file_name + ":" + e.loaded + "Byte/" + e.total + "Btype ";
			msg += "(" + rate + "%)";
			this._msg_container.innerHTML = msg;
		}
	},
	_upload:function(e){
		$.ajax({
			url:e.data.save_url,
			type:'post',
			dataType:'text',
			cache:false,
			data:{file_stream:e.data.reader.result,file_name:e.data.name},
			success:function(res) {
				alert(e.data.name + "のアップロードが完了しました。");
			},
			error:function(res) {
				alert(e.data.name + "のアップロードに失敗しました。");
			}
		});
	},
	_abort:function(e) {
		alert('ファイルのアップロードがキャンセルされました。');
	}
};
</script>
</head>
<body>
<div id="drop_field" style="border:1px solid gray;width:300px;height:500px">
</div>
</body>
</html>

そして以下が送信されるファイルデータを受信するサーバー側のPHPのソースコードです。

<?
	$data = $_POST['file_stream'];
        $name = $_POST['file_name'];
	list ($header , $data) = explode('base64,'  , $data);
	$hd = fopen($name , 'w+');
	fwrite($hd , base64_decode($data));
	fclose($hd);
?>

それぞれサーバーにアップして処理を試してみてください。
複数イメージファイルの一括アップロードが出来るはずです。
(必要最小限の処理しか実装はしてませんが・・・)

尚、今回はファイル読み込みにFileReader.readAsDataURLを使用しましたが
これは対象ファイルをデータスキームURIという形式で読み込むためのメソッドです。
このメソッドでファイルを読み込むと

data:[<MIME-type>][;charset=<encoding>][;base64],<data>

の形式でファイルを読み込むことが可能です。
これは主に画像等のリソースをHTML内にインラインデータとして埋め込むために
用いるようです。
([]内は任意項目です。)
画像ファイルの場合には

data:image/(画像フォーマット);base64,<data>

の形式になります。
そのためサーバーサイドPHPで受信したファイルデータを文字列「base64,」でスプリットしています。
その他にもテキスト形式で読み込むFileReader.readAsTextとバイナリ形式で読み込む
FileReader.readAsBinaryStringがあります。
今回FileReader.readAsDataURLを使用したのは画像ファイルをアップロードして変換・保存するのが
一番簡単だったからです。

ちなみに今回公開しているサンプルは当然ながらIEでは動きませんのでご注意を。
では今回はこの辺で失礼致します。

shu HTML5

node.js VS LAMP  

2011/6/8 水曜日 18:37:09

こんにちは。しゅうです。

今日は巷で話題のnode.jsを触ってみました。
node.jsは簡単に言うとサーバーサイドスクリプトとしてjavascriptを使用するためのものです。

サーバーサイドスクリプトと言えば代表的なものとしてPHP,PERL,Pythonを挙げることができますが
node.jsを使用するとそれらスクリプト言語と同様の処理をjavascriptで実装できるようになります。

ちなみに実体としてnode.jsというjavascriptが存在するわけではなく、サーバサイド処理を実装するためのライブラリを指します。
javascriptエンジン自体はGoogle Chromeと同じV8が使用されています。

さて、、、、

これを使うと何のメリットがあるの?って思うと思います。
私はPHP使いですが、正直私もjavascriptでwebアプリケーション書いて何の得があるの?って思いました。
なので他のnode.jsの紹介記事等を少し調べてメリットをまとめてみました。

  1. みんな大好き(?)なjavascriptでサーバーサイド処理も実装できる。
  2. webアプリケーションを開発した場合、LAMP環境で開発した場合よりパフォーマンスが良い

1は。。。正直別にjavascriptが好きなわけではないのでどうでもいいのですが
2に関してとても興味がわきました。パフォーマンスを求めるならJAVA使えよって思うかもしれませんが
開発速度を担保できないので個人的にはPHPで開発するのがベストと思っているものの、アクセス数が
多い場合にはパフォーマンスが著しく低下するため、もし開発がPHPと同程度の速度で可能であり
かつパフォーマンスも良いと言われると惹かれるものがあります。
LAMP環境よりパフォーマンスが良いと言われる理由はapacheとnode.jsの並列処理の仕組みの違いが
あるためですが、今回は割愛します。

。。。。ということでとりあえずLAMP環境と対決させることにしてみます。

今回はリアルサーバーは使用せず、WindowのVMPlayer上で稼働するCentOS5上で試すことにします。
まずnode.jsをインストール!!
(ブログ執筆時点の最新バージョンは0.4.8です)


wget  http://nodejs.org/dist/node-v0.4.8.tar.gz
tar zxvf node-v0.4.8.tar.gz
cd node-v0.4.8

次にインストール!!


./configure
make
make install

インストールされているかを確認します。

node -v

上記の結果として「v0.4.8」が表示されればインストール完了です。
これでnode.jsを使用する準備が整いました。

それでは試にお馴染みのブラウザ上に「hello world!!」を表示するアプリを作ってみましょう。
・・・と思いましがhello world!!ではなく「こんにちは!!」にします。

var http = require('http');

http.createServer(function(req , res){
     var data = "はろー";
     res.writeHead(200 , {'Content-Type:' : 'text/html; charset=utf8'});
     res.end(data , "utf8");
}).listen(1338,'192.168.0.170');

console.log('node js web server is running....');

上記内容をviewtest.jsの名前で適当な場所に保存します。
そして以下のコマンドをコマンドライン上から実行します。

node viewtest.js

上記を実行すると
「node js web server is running…」というメッセージが表示されます。
これでport1338でhttpリクエストを待ち受ける準備が完了しました。

ではブラウザ上からhttp://192.168.0.170:1338/にアクセスしてみます。
ブラウザ上には「こんにちは!!」と表示されます。
キャプチャーは割愛させていただきますが実際に試していただくと表示されると思います。

それでは次にサードパーティー製ですがnode.js用mysqlライブラリをインストールします。
LAMP環境との対決ですからmysqlへのアクセスは必須です。
インストールと言ってもjavascriptで記述されているため、配置するだけです。

まず最初に適当な場所にnode.jsサードパーティー製ライブラリ用ディレクトリを作成します。
今回はnodelibという名前で作成します。
そしてカレントディレクトリをnodelib直下に変更します。

mkdir nodelib
cd nodelib

このライブラリはgitを使用して管理されているためgitコマンドにより次のように取得します。

git clone git://github.com/felixge/node-mysql.git mysql

これでnodelib内にmysqlというフォルダーでnode.js用mysqlライブラリがインストールされます。
次にデータベースの作成、テーブルの作成を行います。
mysqlにログインしてデータベース「nodejstest」の作成、テーブル「test1」の作成を行います。

mysql -u***** -p
Enter password:*******

mysql>create database nodejstest charset utf8;
mysql>use nodejstest
mysql>create table test1(
         > id int not null auto_increment,
         >data text default "",
        >primary key(id)
        >) Engine=MyISAM;

そしてテスト用データ10000万件を入れます。
dataは「あああああああああ。。。。。」とします。idはauto_incrementですので自動的に
採番・挿入されます。

テスト用データベース、テーブルの作成が完了したら次はリクエストパラメータ「id」を取得し
取得したidをキーとしてテーブルtest1を参照し、ブラウザ上にidと内容を出力するwebアプリケーション
をnode.js上で実行させるためにjavascriptで作成します。

require.paths.unshift('(インストールしたサードパーティー製mysqlライブラリルートへの絶対パス)');

var http = require('http');
var Client = require('mysql').Client;
var client = new Client();
var DATABASE_NAME = "nodejstest";
var TABLE_NAME = "test1";

/* 接続情報設定 */
client.user = '******';
client.password = '**********';
client.host = 'localhost';
client.port = 3306;
client.database = DATABASE_NAME;

http.createServer(function (req ,res) {

    /* mysqlサーバー接続 */
    client.connect();

    /* 文字コード設定 */
    client.query('set character set utf8');

    /* 使用DB選択 */
    client.query('use ' + DATABASE_NAME);

    /* 条件 */
    var urlParts = require('url').parse(req.url);
    var where = " id = " + urlParts.query.split('=')[1];

    /* データ取得・表示 */
    var query = client.query('select * from ' + TABLE_NAME + " where " + where);
    client.end();
    query.on('row' , function(row) {
       res.writeHead(200 , {'Content-Type' : 'text/html charset=utf8'});
       res.end(row.id + ':' + row.data , "utf8");
    });

}).listen(1338 , '192.168.0.170');
console.log('nodejs web server is running.....');

上記を「mysqltest.js」の名前で適当な場所に保存します。

では動作確認をします。次のコマンドをコマンドラインから入力後

node mysqltest.js

ブラウザからhttp://192.168.0.170:1338/?id=1にアクセスしてみます。
「1:ああああああああああああああああああああああああああああああ」
と表示されます。

では次にphpで同じ処理を行うwebアプリケーションを作成します。
極力phpのネイティブ関数・クラス以外の使用を避けます。

<?
$user = "******";
$pass = "*******";
$host = "localhost";
$database = "nodejstest";

/* 接続 */
if (!($con = mysql_connect($host , $user , $pass))) die('connect failed.');

/* DB選択 */
if (!mysql_select_db($database , $con)) die('select db failed');

/* クライアンエンコーディング設定 */
mysql_query("set character set utf8");

/* 条件 */
$where = " id = " . (isset($_GET['id']) ? $_GET['id'] : "");

/* 取得 */
$sql = "select * from test1 where " . $where;

if ($ret = mysql_query($sql , $con))
{
    if ($rec = mysql_fetch_assoc($ret))
        echo $rec['id'] . ":" . $rec['data'];
}
else die('failed query');

mysql_close($con);
?>

上記内容をindex.phpでドキュメントルートに保存します。
(apach関連の設定は割愛します。待ち受けポートは1337に設定してあります。)

では動作確認をしてみます。
ブラウザからhttp://192.168.0.170:1337/index.php?id=1にアクセスします。
「1:ああああああああああああああああああああああああああああああ」
が表示されます。

これでLAMP環境とnode.jsを対決させる準備がようやく整いました。
jmeterを使用し、javascriptで作成されたwebアプリケーション、phpで作成されたwebアプリケーションそれぞれに
同時接続数100、リクエストパラメータid=1で負荷をかけます。

その結果が以下です。結果は「統計レポート」として出力しました。

node.js上で実行させたwebアプリケーション

Average:3
Median:3
90% Line:4
Min:3
Max:6
Error(%):0
Throughput:10.1/sec

LAMP環境で実行させたwebアプリケーション

Average:4
Median:4
90% Line:5
Min:3
Max:7
Error(%):0
Throughput:10.1/sec

・・・・正直なところあまり変わりません。

では今度は同時接続数を400まで増やしてみましょう。
その結果が以下になります。

node.js上で実行させたwebアプリケーション

Average:4
Median:3
90% Line:4
Min:2
Max:207
Error(%):0
throughput:39.8/sec

LAMP環境上で実行させたwebアプリケーション

Average:3
Median:3
90% Line:4
Min:2
Max:15
Error(%):0
throughput:39.7/sec

・・・・あれれ!?また変わんない?・・・・
というよりもLAMP環境上で実行させたwebアプリケーションの方が
総合的に見て若干まし?

個人的にはnode.js圧勝!!を期待していたのですが・・・
最後に同時接続数500で実行してみます。

・・・・・果たして結果は・・・・?

結果、node.js上で実行されていたwebアプリケーションは死亡してしまいました。
出力されたエラーは以下の通りです。

events.js:45
        throw arguments[1]; // Unhandled 'error' event
                       ^
Error: Got packets out of order
    at Function._packetToUserObject (/root/nodelib/mysql/lib/mysql/client.js:342:7)
    at Client._handlePacket (/root/nodelib/mysql/lib/mysql/client.js:304:21)
    at Parser.<anonymous> (/root/nodelib/mysql/lib/mysql/client.js:83:14)
    at Parser.emit (events.js:64:17)
    at /root/nodelib/mysql/lib/mysql/parser.js:75:14
    at Parser.write (/root/nodelib/mysql/lib/mysql/parser.js:580:7)
    at Socket.<anonymous> (/root/nodelib/mysql/lib/mysql/client.js:63:16)
    at Socket.emit (events.js:64:17)
    at Socket._onReadable (net.js:678:14)
    at IOWatcher.onReadable [as callback] (net.js:177:10)

もう少し詳しく書くと、2リクエスト処理した時点で死亡しました。
正直なところ具体的な理由はわかりませんが、取得したパケットが多すぎる?みたいなエラーで落ちてるようです。
(詳細分かる人求む)

これはこれは残念・・・・まだ実用に耐える程の品質はないみたいです。

実はmysqlにアクセスせず(単純に固定文字列をブラウザに出力するだけ)
かつ同時接続数が多い場合(同時接続数1000でテストしました)にはnode.jsが圧勝でした。

実際webアプリケーションを開発する場合、DBへのアクセスは必須になりますので
現状、node.jsは実用的なレベルに達してない・・・?と言わざるを得ないかもしれません。

別の方で同じような試みをした方がいらっしゃり、かつ私の結果と正反対だったという方が
いらっしゃったら一言いただけたら幸いです。

では今日はこの辺で・・・・

shu Javascript