前回はWSGIアプリケーションからOpenCVを利用する例を挙げましたが、今回はクライアントをFlashとHTML5の両方で試してみます。といっても特別なことをする必要はなくて、公開されたURIからリソースを取得すればいいだけです。
、、
ということで既に先が見えて飽きてしまいましたが、簡単なデモと方針だけでも気力を振り絞って書いておきます。後はアプリ層の方々におまかせ;;
OpenCVはPythonバインディングを使いますので、この部分は前回と同じくFlaskを使うことにします。クライアントがFlashの場合はテンプレートエンジン不要で、URIルーティングを行うためのWerkzeugがあれば十分です。ただ、Flaskなら情熱がなくてもたぶん最後まで書ききれるのでオススメ。最後に処理結果ですが、今回はStar Detectorのデモを作ろうと思うので、以下のようなJSON形式で返すことにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
{ 'width':256, 'height':256, 'keypoints':[ { 'x':107, 'y':83, 'response':-34.138824462890625, 'size':12 }, ... ] } |
* Star Detector
Starキーポイント検出器、例では1つのパラメータを公開しています。また、画像の読み込みはI/Oが発生する重い処理ですので memcached にキャッシュしておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
from flask import Flask, abort, jsonify import cv, numpy as np import memcache import os application = Flask(__name__) application.debug = True ## memcachedクライアント mc = memcache.Client(['localhost:11211']) ## パラメータを1つ公開する場合 @application.route('/starkeypoints/<image>') @application.route('/starkeypoints/<image>/maxsize/<int:maxsize>') def getstarkeypoints(image=None, maxsize=45): path = os.path.join(application.root_path, "/path/to/images/") ## numpy.ndarrayオブジェクトをmemcachedにキャッシュ img = mc.get(str(image)) if img == None: img = cv.imread(path + image + '.jpg', 0) mc.set(str(image), img, time=60*60*24) params = (maxsize, 30, 10, 8, 5) storage = cv.CreateMemStorage() keypoints = cv.GetStarKeypoints(img, storage, params) datalist = [] for keypoint in keypoints: data = {'x':keypoint[0][0], 'y':keypoint[0][1], 'size':keypoint[1], 'response':keypoint[2]} datalist.append(data) return jsonify(width=img.shape[0], height=img.shape[1], keypoints=datalist) ## エラーハンドラ @application.errorhandler(404) def notfound(error): return 'page not found', 404 ## 以下エラーハンドラが続く.. |
画像のストレージ周りは各々の環境に合った方法で。レスポンスをJSONにするなら flask.jsonify() を使うと楽です。以下、クライアントをFlash(Flex)で作ったデモになります。
Demo: Flash + OpenCV Integration
デモのソースコードを上げておきます。特に難しい所はないはず。
(zip: opencv_client)
次はこれをHTML5 Canvasと併せてみます。Canvasに画像データをdrawImageして上からデータを乗せていくだけ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
// jQuery を利用 $(function() { var context = document.querySelector('#Canvas').getContext('2d'); context.fillStyle = '#ffffff'; context.strokeStyle = 'rgb(0, 196, 160)'; var image = new Image(); image.src = '/path/to/image/foo.jpg'; image.addEventListener('load', function() { context.drawImage(image, 0, 0); }, false); image.addEventListener('error', function() { alert('Cannot Load Image Data'); }, false); $('#Button').click(function() { var url = 'http://bar.com/cv/starkeypoints/foo'; // JSONデータを取得 $.getJSON(url, null, function(data) { plot(context, image, data); } }); }); // データをCanvasにプロット function plot(context, image, data) { var ctx = context; var keypoints = data.keypoints; var len = data.keypoints.length; var twopi = 2*Math.PI; ctx.fillRect(0, 0, data.width, data.height); ctx.drawImage(image, 0, 0); for(var i=0; i<len; i++) { var pt = keypoints[i]; var r = pt.size/2; ctx.beginPath(); ctx.arc(pt.x, pt.y, r, 0, twopi, false); ctx.moveTo(pt.x - r, pt.y - r); ctx.lineTo(pt.x + r, pt.y + r); ctx.moveTo(pt.x + r, pt.y - r); ctx.lineTo(pt.x - r, pt.y + r); ctx.closePath(); ctx.stroke(); } } |
もしクライアント側でのデータ処理が複雑になってきた場合は、Web Workersを使って後ろに処理を投げておけばいいかと。ここで一番重要なのは、サーバ側のコード修正は必要ないということです。つまりクライアントはURIを知っていればいいということになります。
HTML5版ではパラメータを全て設定できるようにしました。
(Firefox3.6, Safari5.0, Chrome10.0, Opera11.0で動作確認)
Demo: HTML5 Canvas + OpenCV Integration
Star Detectorのアルゴリズムは厳密には知らないのですが、星形範囲(正方形とそれを45度回転したものを重ねた範囲)を積分する際、SURFと同様にIntegral Imageを使うことでO(1)で求めています。
また、ライン(エッジ)上に特徴が出るのを抑えていますが、その辺りはしきい値としてAPIで細かく指定できました。SIFTと比べて精度は劣るようですが、高速なのは魅力的です。Star Detectorって名前も綺麗ですね。
(参考: Common Interfaces of Feature Detectors)
今回のようにWebアプリケーションとして作る場合、サーバ側では「いかにI/Oを発生させないか」ということに集中し、画像処理のロジックはOpenCVを便利に使う感じでいいかと。基本的にはRESTful APIを提供する形で作ると綺麗になります。