<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/rss2japanesefull.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" version="2.0">

<channel>
	<title>Rest Term</title>
	
	<link>http://rest-term.com</link>
	<description>Development and Creative Playground::Flash, Flex, Javascript and Web Services... Web関連技術や雑記など</description>
	<lastBuildDate>Sun, 29 Jan 2012 15:59:50 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3</generator>
		<atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/rss+xml" href="http://feeds.feedburner.com/rest-term" /><feedburner:info uri="rest-term" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><feedburner:feedFlare href="http://paipo.jp/bookmarklet/?url=http%3A%2F%2Ffeeds.feedburner.com%2Frest-term" src="http://www.feedburner.jp/fb/i/subscribe_paipo.gif">Paipo???</feedburner:feedFlare><feedburner:feedFlare href="http://add.my.yahoo.co.jp/rss?url=http%3A%2F%2Ffeeds.feedburner.com%2Frest-term" src="http://i.yimg.jp/i/jp/my/addtomy/standard_bb.gif">myyahoo???</feedburner:feedFlare><feedburner:feedFlare href="http://www.newsgator.com/ngs/subscriber/subext.aspx?url=http%3A%2F%2Ffeeds.feedburner.com%2Frest-term" src="http://www.newsgator.com/images/ngsub1.gif">NewsGator Online???</feedburner:feedFlare><feedburner:feedFlare href="http://www.bloglines.com/sub/http://feeds.feedburner.com/rest-term" src="http://www.bloglines.com/images/sub_modern11.gif">Bloglines???</feedburner:feedFlare><feedburner:feedFlare href="http://fusion.google.com/add?feedurl=http%3A%2F%2Ffeeds.feedburner.com%2Frest-term" src="http://buttons.googlesyndication.com/fusion/add.gif">Google???</feedburner:feedFlare><feedburner:feedFlare href="http://r.hatena.ne.jp/append/http://feeds.feedburner.com/rest-term" src="http://r.hatena.ne.jp/images/addto_w.gif">???RSS???</feedburner:feedFlare><feedburner:feedFlare href="http://reader.livedoor.com/subscribe/http://feeds.feedburner.com/rest-term" src="http://image.reader.livedoor.com/img/banner/91_17_1.gif">Livedoor???????</feedburner:feedFlare><feedburner:feedFlare href="http://reader.goo.ne.jp/web/bookmarklet.html?,,http%3A%2F%2Ffeeds.feedburner.com%2Frest-term" src="http://reader.goo.ne.jp/web/img/addwebrss.gif">goo RSS???????</feedburner:feedFlare><feedburner:feedFlare href="http://reader.excite.co.jp/subscribe/?url=http%3A%2F%2Ffeeds.feedburner.com%2Frest-term" src="http://reader.excite.co.jp/images/add_rss_excitereader.gif">エキサイトリーダーに登録</feedburner:feedFlare><feedburner:feedFlare href="http://www.fenrir.co.jp/rd/?rss=http%3A%2F%2Ffeeds.feedburner.com%2Frest-term" src="http://images2.fenrir.co.jp/fb/sleipnir_feed.gif">Sleipnir に追加</feedburner:feedFlare><feedburner:feedFlare href="http://feedpath.jp/feedreader/feeds_add?url=http%3A%2F%2Ffeeds.feedburner.com%2Frest-term" src="http://feedpath.jp/common/images/sub_feedpath.gif">feedpath???</feedburner:feedFlare><item>
		<title>東京都内のアート/デザイン系イベントまとめ</title>
		<link>http://feedproxy.google.com/~r/rest-term/~3/cgqTmR90G_o/</link>
		<comments>http://rest-term.com/archives/2971/#comments</comments>
		<pubDate>Sun, 29 Jan 2012 15:53:21 +0000</pubDate>
		<dc:creator>wellflat</dc:creator>
				<category><![CDATA[design/art]]></category>
		<category><![CDATA[tech/study]]></category>
		<category><![CDATA[service]]></category>

		<guid isPermaLink="false">http://rest-term.com/?p=2971</guid>
		<description><![CDATA[東京都内のアート/デザイン系イベントの情報まとめサイトを作りました。 Art &#038; Design Events at Tokyo 以前公開したPC向けデモサイトを改修したもので、東京アートビートAPIをただ使って [...]]]></description>
			<content:encoded><![CDATA[<p>東京都内のアート/デザイン系イベントの情報まとめサイトを作りました。</p>
<p><a href="http://rest-term.com/labs/misc/artbeat">Art &#038; Design Events at Tokyo</a></p>
<p>以前公開したPC向けデモサイトを改修したもので、<a href="http://www.tokyoartbeat.com/resources/doc/api">東京アートビートAPI</a>をただ使っているだけのサイトです。ただ、公式サイトが情報の詰め込みすぎで重くなっていたので、スマートフォンでもさくさく見られるようなシンプルで軽いサイトが欲しくて作りました。APIのレスポンスはまとめて裏でキャッシュしているので、APIサーバへの負荷もほとんどかからないはずです。</p>
<p>僕の使っているGalaxy S2 LTEを横向きにしたときにちょうど見やすいように、完全に自分用にカスタマイズしてしまっています。。PCで見る分には気にならないとは思いますが。あと、HTML5 Geolocation APIがなんかうまく動作しないというか精度が悪くて、今はその機能をOFFにしています。せっかくAPIから位置情報が取得できるんだから上手く連動させたいものです。</p>
<p>インフラ寄りのエンジニアをしていると、技術の知識はかなりの勢いで増えていく一方、芸術的な感性というかそういうのが枯れる速度もきっと速いので、芸術にも触れる機会を増やさないとマズイと思ってます。30歳を超えたら考え方も変わってくるのかもしれませんが、とりあえず今の内は。</p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/rest-term?a=cgqTmR90G_o:cLJMX-t7pcI:spdCosxkSQE"><img src="http://feeds.feedburner.com/~ff/rest-term?d=spdCosxkSQE" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/rest-term?a=cgqTmR90G_o:cLJMX-t7pcI:OAQBO0PjnPA"><img src="http://feeds.feedburner.com/~ff/rest-term?d=OAQBO0PjnPA" border="0"></img></a>
</div>]]></content:encoded>
			<wfw:commentRss>http://rest-term.com/archives/2971/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://rest-term.com/archives/2971/</feedburner:origLink></item>
		<item>
		<title>JavaScriptで2D-FFTによるハイパス/ローパスフィルタ</title>
		<link>http://feedproxy.google.com/~r/rest-term/~3/Q877Oi6OlKw/</link>
		<comments>http://rest-term.com/archives/2966/#comments</comments>
		<pubDate>Wed, 04 Jan 2012 14:11:59 +0000</pubDate>
		<dc:creator>wellflat</dc:creator>
				<category><![CDATA[tech/study]]></category>
		<category><![CDATA[cv/im]]></category>
		<category><![CDATA[javascript]]></category>

		<guid isPermaLink="false">http://rest-term.com/?p=2966</guid>
		<description><![CDATA[Spatial Frequency Filtering by 2D-FFT これまでにJavaScriptとHTML5 Canvas APIでいくつかの画像処理を試してきましたが、今回は二次元離散フーリエ変換(2D-DF [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://rest-term.com/labs/html5/fft.html" title="Spatial Frequency Filtering by 2D-FFT"><img src="http://rest-term.com/wp-content/uploads/2012/01/highpassfilter.jpg" alt="highpassfilter" title="highpassfilter" width="600" height="203" class="alignnone size-full wp-image-2968" /><br />
Spatial Frequency Filtering by 2D-FFT</a></p>
<p>これまでにJavaScriptとHTML5 Canvas APIでいくつかの画像処理を試してきましたが、今回は二次元離散フーリエ変換(2D-DFT、実装上では2D-FFT)で得られた周波数スペクトルにハイパス/ローパスフィルタ(HPF/LPF)を適用します。</p>
<p>「フーリエ変換」は音声処理でよく耳にする単語かと思います。音声データをフーリエ変換してHTML5 Canvas上でビジュアライズするデモもたくさん公開されています(例: <a href="https://developer.mozilla.org/en/Visualizing_Audio_Spectrum" title="Visualizing an audio spectrum - MDN">Visualizing an audio spectrum &#8211; MDN</a>)。ただし、ここでは&#8221;音声&#8221;ではなく&#8221;画像&#8221;に対するフーリエ変換を行います。音声の場合はデータが一次元なので周波数は一つしか持っていませんが、画像の場合は水平/垂直方向の二つの周波数を持つことになります。なので画像データに対する2D-FFTは、</p>
<p>&emsp;x軸方向に一次元フーリエ変換&emsp;→&emsp;y軸方向に一次元フーリエ変換</p>
<p>という手順で処理します。</p>
<p>このブログでも2008年にFlashで2D-FFTの検証をしていたことがあるので参考までに。</p>
<p>&emsp;* <a href="http://rest-term.com/archives/1289/">二次元離散フーリエ変換 – AS3.0</a><br />
&emsp;* <a href="http://rest-term.com/archives/1345/">ハイパス/ローパスフィルタ</a><br />
&emsp;* <a href="http://rest-term.com/archives/1375/">位相画像</a><br />
&emsp;* <a href="http://rest-term.com/archives/1777/">位相限定相関法</a></p>
<p>ブラウザがCanvas APIをサポートするようになって割と早い段階でJavaScriptでも実装していたのですが、当時のブラウザの処理能力だと案の定まともに動作しませんでした。それが今ではどのブラウザも飛躍的に処理能力が向上したので、2D-FFTをWeb Workersを使わずに動かしても大丈夫なようになりました。</p>
<p>FFTおよび空間周波数フィルタのプログラムはGitHubに置きました。全て合わせて8KBちょっとです。外部ライブラリは用いておらず、Canvas APIを直接利用したPure JavaScriptなコードになっています。AS3版ではFFTと逆FFT、ハイパス/ローパスフィルタをそれぞれ一つの関数内で処理していたことで条件分岐が増えて遅くなっていましたが、JS版ではコードが多少冗長になるのを許容し、APIを分けることでコアな部分の処理内で条件分岐を削減しています。</p>
<p><a href="https://github.com/wellflat/jslib/tree/master/cv/fft" title="cv/fft at master from wellflat/jslib - GitHub">cv/fft at master from wellflat/jslib &#8211; GitHub</a></p>
<p>まずは一次元離散フーリエ変換からテストしてみます。</p>
<p>* <strong>1D-FFT</strong><br />
[javascript]<br />
var x1 = [], y1 = [], x2 = [], y2 = [], x3 = [], y3 = [], N = 32;<br />
for(var i=0; i<N; i++){<br />
  x1[i] = x2[i] = 6*Math.cos(6*Math.PI*i/N) + 4*Math.sin(18*Math.PI*i/N);<br />
  y1[i] = y2[i] = 0.0;<br />
}<br />
FFT.init(N);  // 初期化(三角関数テーブル、ビット逆順テーブルの生成)<br />
FFT.fft1d(x2, y2);  // 一次元FFT<br />
for(var j=0; j<N; j++) {<br />
  x3[j] = x2[j];<br />
  y3[j] = y2[j];<br />
}<br />
FFT.ifft1d(x3, y3);  // 一次元逆FFT<br />
var out = "";<br />
for(var i=0; i<N; i++){<br />
  out += i + ": (" + x1[i] + ", " +y1[i]+ ") (" +<br />
         x2[i] + ", " + y2[i] + ") (" + x3[i] + ", " + y3[i] + ")\n";<br />
}<br />
console.log("N: (元データ) (FFT) (逆FFT)");<br />
console.log(out);<br />
[/javascript]</p>
<pre>
N: (元データ) (FFT) (逆FFT)
0: (6, 0) (1.3100631690576847e-14, 0) (6, 0)
1: (8.911958795428193, 0) (-3.148595663620103e-15, -1.634119351805764e-15) (8.911958795428195, 2.220446049250315e-16)
2: (0.7653668647301803, 0) (-3.582018523007779e-14, 1.0273603512156518e-14) (0.76536686473018, 5.551115123125783e-17)
3: (-4.496420381306951, 0) (96.00000000000003, -4.440892098500626e-14) (-4.496420381306951, -4.440892098500624e-16)
&#8230; 省略
</pre>
<p>浮動小数点数の扱いがASと同じなので、JSでも同じ計算誤差が発生します(上の例でだいたい1.0e-15程度)。</p>
<p>次は本題の二次元離散フーリエ変換による空間周波数フィルタ処理を試してみます。</p>
<p>* <strong>2D-FFTによる空間周波数フィルタ(ハイパス/ローパスフィルタ)</strong><br />
[javascript]<br />
var spectrum = document.querySelector('#Spectrum').getContext('2d'),<br />
    result = document.querySelector('#Result').getContext('2d'),<br />
    image = new Image();<br />
image.src = '/path/to/image';<br />
image.addEventListener('load', function(e) {<br />
  var w = image.width,<br />
      h = image.height, // w == h<br />
      re = [],<br />
      im = [];</p>
<p>  // 初期化、次数Nは基数2<br />
  FFT.init(w);<br />
  FrequencyFilter.init(w);<br />
  // 初期化、表示用の CanvasRenderingContext2D を渡す<br />
  SpectrumViewer.init(spectrum);<br />
  spectrum.drawImage(image, 0, 0);<br />
  var src = spectrum.getImageData(0, 0, w, h),<br />
      data = src.data,<br />
      radius = 30,<br />
      i, val, p;<br />
  // 実数部、虚数部を格納する配列を生成<br />
  // 画像データは実数部配列に詰める<br />
  for(var y=0; y<h; y++) {<br />
    i = y*w;<br />
    for(var x=0; x<w; x++) {<br />
      re[i + x] = data[(i << 2) + (x << 2)];<br />
      im[i + x] = 0.0;<br />
    }<br />
  }<br />
  FFT.fft2d(re, im);  // 二次元FFT<br />
  FrequencyFilter.swap(re, im)  // 象限入れ替え<br />
  FrequencyFilter.HPF(re, im, radius); // ハイパスフィルタ<br />
  //FrequencyFilter.LPF(re, im, radius);  // ローパスフィルタ<br />
  //FrequencyFilter.BPF(re, im, radius, radius/2);  // バンドパスフィルタ<br />
  SpectrumViewer.render(re, im);  // スペクトル画像を描画<br />
  FrequencyFilter.swap(re, im);  // 象限入れ替え<br />
  FFT.ifft2d(re, im);  // 二次元逆FFT<br />
  // 実数部配列のデータを表示用の CanvasPixelArray に詰める<br />
  for(var y=0; y<h; y++) {<br />
    i = y*w;<br />
    for(var x=0; x<w; x++) {<br />
      val = re[i + x];<br />
      p = (i << 2) + (x << 2);<br />
      data[p] = data[p + 1] = data[p + 2] = val;<br />
    }<br />
  }<br />
  result.putImageData(src, 0, 0);<br />
}, false);<br />
[/javascript]</p>
<p>Demo: <a href="http://rest-term.com/labs/html5/fft.html" title="Spatial Frequency Filtering by 2D-FFT">Spatial Frequency Filtering by 2D-FFT</a><br />
(IE9.0, Firefox8.0, Chrome16.0, Safari5.0, Opera11.60で動作確認済み)</p>
<p>このデモでは周波数スペクトルを見やすいように緑色に着色しています。人間の目は「<strong>低周波成分には敏感だが、高周波成分には鈍感である</strong>」という性質があるので、画像の高周波成分を間引いても元画像との違いを判別することは難しいです。JPEG圧縮アルゴリズムはこの性質を利用してデータ量を減らしています。デモでローパスフィルタを使ってぜひ実感してみてください。</p>
<p>今回書いたモジュールは基数2以外の次数だと例外を投げるようにしています。基数4と余裕があれば8のやつも実装しようかと思ったのですが、式も図も書かずにバタフライ演算の部分を実装してたら混乱してきたので止めておきました。。C言語ならともかくJavaScriptのレベルだとCPUのキャッシュライン等を考えて実装したりはしないので、落とし所としてはこのあたりが妥協点かなと思います。とりあえずはこれで位相限定相関法など多くのアルゴリズムが記述できるので、適切にWeb Workersを併用することでより高度な画像解析も行うことができそうです。</p>
<h3 class="term">クロスブラウザ対応について</h3>
<p>FFTの処理自体はECMAScriptベースの言語ならどれも同じ結果が得られるはずですが、Canvas APIの実装は各ブラウザで異なっているのでこちらはケアしなければなりません。Firefox, Chrome, Safari, IE9では特に問題なく動作してコードの改修も必要なかったのですが、<strong>Operaの場合はサチュレーション(飽和演算)を手抜きしてると動作しない</strong>ようです(2012/01現在)。Opera以外のブラウザだと内部でクリッピングしてくれてるのだと思います。</p>
<p><code>min(max(<em>value</em>,0),255)</code> の処理を忘れずに。OpenCVでいうところの <code>saturate_cast&lt;<em>typename</em>&gt;</code> ですね。</p>
<h3 class="term">JavaScriptエンジンの進化</h3>
<p>ちょうどFirefoxが怒濤のラピッドリリースに突入した1年くらい前から(Firefox3.6の頃)、ブラウザのJavaScript処理能力が急速に上がっていったようで、もちろんFirefoxに限らずChromeやOperaも大きく進化しています。悪評高いInternet Explorerもversion9になってからはいろいろな面でまともに動作するようになっているようです。</p>
<p>これも昔Flashで作ったサンプルのCanvasへの移植なのですが、<a href="http://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%A9%E3%82%A4%E3%83%B3%E3%81%AE%E5%A3%BA">クラインの壺</a>をCanvas上で回してみました。お絵かき系の処理能力はまだまだといった印象ですが、こちらも急速に進化していくのではないかと思います。</p>
<p>Demo: <a href="http://rest-term.com/labs/html5/klein.html">Klein Bottle</a></p>
<p>やっぱり激化するブラウザ競争の中で生き残るためにどのベンダーも必死にがんばってるんでしょうね。こういうオープンな「競争」はとても刺激があって良いことだと思います。僕は普段仕事でJavaScriptを使うことがないので、もうちょっとフロントエンド寄りのプログラマやデザイナーと交流していろいろ学んでいきたいです。</p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/rest-term?a=Q877Oi6OlKw:eTy5zcJWigs:spdCosxkSQE"><img src="http://feeds.feedburner.com/~ff/rest-term?d=spdCosxkSQE" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/rest-term?a=Q877Oi6OlKw:eTy5zcJWigs:OAQBO0PjnPA"><img src="http://feeds.feedburner.com/~ff/rest-term?d=OAQBO0PjnPA" border="0"></img></a>
</div>]]></content:encoded>
			<wfw:commentRss>http://rest-term.com/archives/2966/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://rest-term.com/archives/2966/</feedburner:origLink></item>
		<item>
		<title>2012年スタート</title>
		<link>http://feedproxy.google.com/~r/rest-term/~3/q3cS04nMgsU/</link>
		<comments>http://rest-term.com/archives/2965/#comments</comments>
		<pubDate>Sun, 01 Jan 2012 05:47:18 +0000</pubDate>
		<dc:creator>wellflat</dc:creator>
				<category><![CDATA[nonsorted]]></category>

		<guid isPermaLink="false">http://rest-term.com/?p=2965</guid>
		<description><![CDATA[あけましておめでとうございます&#9834; 2012年が始まりました。 去年は十兎くらい追ったら一兎捕まえられた感じですが、 今年は立ちふさがる登竜門を越えるべくもっと努力を重ねたいと思います。 社会人4年目、これから [...]]]></description>
			<content:encoded><![CDATA[<p>あけましておめでとうございます&#9834;</p>
<p>2012年が始まりました。<br />
去年は十兎くらい追ったら一兎捕まえられた感じですが、<br />
今年は立ちふさがる登竜門を越えるべくもっと努力を重ねたいと思います。</p>
<p>社会人4年目、これからの身の振り方を真剣に考えてみます。<br />
正直どうなるかわからない一年になりそうな気はしていますが、<br />
このブログは今まで通り続けていくつもりです。</p>
<p>これからもRest Termをよろしくおねがいします。<br />
新年が良い年でありますように。</p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/rest-term?a=q3cS04nMgsU:mKGKJdAdoD4:spdCosxkSQE"><img src="http://feeds.feedburner.com/~ff/rest-term?d=spdCosxkSQE" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/rest-term?a=q3cS04nMgsU:mKGKJdAdoD4:OAQBO0PjnPA"><img src="http://feeds.feedburner.com/~ff/rest-term?d=OAQBO0PjnPA" border="0"></img></a>
</div>]]></content:encoded>
			<wfw:commentRss>http://rest-term.com/archives/2965/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://rest-term.com/archives/2965/</feedburner:origLink></item>
		<item>
		<title>2011年ブログ記事まとめ</title>
		<link>http://feedproxy.google.com/~r/rest-term/~3/xeH0sVHoLAg/</link>
		<comments>http://rest-term.com/archives/2963/#comments</comments>
		<pubDate>Fri, 30 Dec 2011 08:38:16 +0000</pubDate>
		<dc:creator>wellflat</dc:creator>
				<category><![CDATA[nonsorted]]></category>

		<guid isPermaLink="false">http://rest-term.com/?p=2963</guid>
		<description><![CDATA[SELECT post_title FROM wp_posts WHERE post_date BETWEEN "2011-01-01" AND "2011-12-31" AND post_status="publish [...]]]></description>
			<content:encoded><![CDATA[<p><code>SELECT post_title FROM wp_posts WHERE post_date BETWEEN "2011-01-01" AND "2011-12-31" AND post_status="publish" ORDER BY post_date</code></p>
<p>1&emsp;<a href="http://rest-term.com/archives/2845/">2011年スタート</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2845/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2845/" /></a><br />
2&emsp;<a href="http://rest-term.com/archives/2846/">パーティクルフィルタ</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2846/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2846/" /></a><br />
3&emsp;<a href="http://rest-term.com/archives/2848/">さくらVPSに移行しました</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2848/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2848/" /></a><br />
4&emsp;<a href="http://rest-term.com/archives/2849/">mod_dbdでデータベース操作</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2849/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2849/" /></a><br />
5&emsp;<a href="http://rest-term.com/archives/2850/">OpenXX まとめ</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2850/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2850/" /></a><br />
6&emsp;<a href="http://rest-term.com/archives/2851/">さくらVPSへの移行 その後</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2851/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2851/" /></a><br />
7&emsp;<a href="http://rest-term.com/archives/2856/">AS3でFunction.bind()</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2856/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2856/" /></a><br />
8&emsp;<a href="http://rest-term.com/archives/2857/">AS3で関数のカリー化</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2857/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2857/" /></a><br />
9&emsp;<a href="http://rest-term.com/archives/2858/">03/14</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2858/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2858/" /></a><br />
10&emsp;<a href="http://rest-term.com/archives/2859/">HTML5 playground</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2859/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2859/" /></a><br />
11&emsp;<a href="http://rest-term.com/archives/2860/">普通の日記は</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2860/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2860/" /></a><br />
12&emsp;<a href="http://rest-term.com/archives/2861/">さくらVPSにOpenCVをインストールしてPythonから使う</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2861/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2861/" /></a><br />
13&emsp;<a href="http://rest-term.com/archives/2863/">Flash, HTML5 Canvas + OpenCV</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2863/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2863/" /></a><br />
14&emsp;<a href="http://rest-term.com/archives/2867/">Flash Runtimeの未来について</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2867/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2867/" /></a><br />
15&emsp;<a href="http://rest-term.com/archives/2868/">Flash Runtimeの未来について (海外の反応)</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2868/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2868/" /></a><br />
16&emsp;<a href="http://rest-term.com/archives/2869/">データセンターの節電対応について</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2869/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2869/" /></a><br />
17&emsp;<a href="http://rest-term.com/archives/2873/">Flashのガベージコレクションに関する正しい理解</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2873/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2873/" /></a><br />
18&emsp;<a href="http://rest-term.com/archives/2876/">Flashのガベージコレクション &#8211; 遅延参照カウント</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2876/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2876/" /></a><br />
19&emsp;<a href="http://rest-term.com/archives/2882/">MMgc template</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2882/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2882/" /></a><br />
20&emsp;<a href="http://rest-term.com/archives/2886/">JavaScirptでサンドボックスパターン</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2886/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2886/" /></a><br />
21&emsp;<a href="http://rest-term.com/archives/2892/">WordPressサイトでリソースモニタリングするときの注意</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2892/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2892/" /></a><br />
22&emsp;<a href="http://rest-term.com/archives/2889/">HLAC: 高次局所自己相関特徴</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2889/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2889/" /></a><br />
23&emsp;<a href="http://rest-term.com/archives/2871/">World IPv6 Day</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2871/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2871/" /></a><br />
24&emsp;<a href="http://rest-term.com/archives/2898/">インメモリKVSのRedisについて</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2898/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2898/" /></a><br />
25&emsp;<a href="http://rest-term.com/archives/2900/">Redis AS3クライアント</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2900/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2900/" /></a><br />
26&emsp;<a href="http://rest-term.com/archives/2901/">commit my AS practice to github</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2901/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2901/" /></a><br />
27&emsp;<a href="http://rest-term.com/archives/2902/">Consistent Hashing in AS3</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2902/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2902/" /></a><br />
28&emsp;<a href="http://rest-term.com/archives/2906/">libmemcached c++ interface</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2906/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2906/" /></a><br />
29&emsp;<a href="http://rest-term.com/archives/2907/">RubyKaigi 2011に参加してきた</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2907/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2907/" /></a><br />
30&emsp;<a href="http://rest-term.com/archives/2912/">DotCloudが素晴らしい</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2912/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2912/" /></a><br />
31&emsp;<a href="http://rest-term.com/archives/2915/">DotCloudでのMySQL,Redis,MongoDBの使い方</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2915/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2915/" /></a><br />
32&emsp;<a href="http://rest-term.com/archives/2917/">CanvasとBitmapとよもやま話</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2917/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2917/" /></a><br />
33&emsp;<a href="http://rest-term.com/archives/2925/">AS3でNaive Bayesによる文書分類</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2925/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2925/" /></a><br />
34&emsp;<a href="http://rest-term.com/archives/2928/">JavaScriptで決定木</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2928/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2928/" /></a><br />
35&emsp;<a href="http://rest-term.com/archives/2933/">Perlの画像処理モジュールメモ</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2933/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2933/" /></a><br />
36&emsp;<a href="http://rest-term.com/archives/2934/">Google Maps API for Flash が 廃止</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2934/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2934/" /></a><br />
37&emsp;<a href="http://rest-term.com/archives/2935/">AS3SXはPaaSとして成功するか</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2935/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2935/" /></a><br />
38&emsp;<a href="http://rest-term.com/archives/2938/">AS3SXの裏側</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2938/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2938/" /></a><br />
39&emsp;<a href="http://rest-term.com/archives/2940/">JavaScriptでステレオ画像処理</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2940/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2940/" /></a><br />
40&emsp;<a href="http://rest-term.com/archives/2948/">MongoDB C++クライアント</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2948/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2948/" /></a><br />
41&emsp;<a href="http://rest-term.com/archives/2950/">FlashDevelop + GCCでANE入門</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2950/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2950/" /></a><br />
42&emsp;<a href="http://rest-term.com/archives/2956/">GALAXY S II LTE</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2956/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2956/" /></a><br />
43&emsp;<a href="http://rest-term.com/archives/2955/">ANEで画像処理</a>&emsp;<a href="http://b.hatena.ne.jp/entry/http://rest-term.com/archives/2955/"><img src="http://b.hatena.ne.jp/entry/image/http://rest-term.com/archives/2955/" /></a></p>
<p>今年は43本のブログ記事を書くことができました。週1ペースとまではいきませんでしたが上出来だと思います。昔の僕なら絶対に書かなかったような入門記事的なものも書いてみたりして、自分用のホリデープログラミングメモとしてのスタイルは崩れている感じもしますが、考え方の変化を実感できて面白いです。</p>
<p>仕事の役に立つか立たないかだけを基準に内容を考えてブログを書いていると感性がすぐに枯れそうな気がするので、20代の内はあえてムラを多くして仕事とは関係ない分野もいろいろ追っかけていこうと思っています。つまりこれまで通りということです。</p>
<p>では、よいお年を。</p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/rest-term?a=xeH0sVHoLAg:ZKeBjFm9JeE:spdCosxkSQE"><img src="http://feeds.feedburner.com/~ff/rest-term?d=spdCosxkSQE" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/rest-term?a=xeH0sVHoLAg:ZKeBjFm9JeE:OAQBO0PjnPA"><img src="http://feeds.feedburner.com/~ff/rest-term?d=OAQBO0PjnPA" border="0"></img></a>
</div>]]></content:encoded>
			<wfw:commentRss>http://rest-term.com/archives/2963/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://rest-term.com/archives/2963/</feedburner:origLink></item>
		<item>
		<title>ANEで画像処理</title>
		<link>http://feedproxy.google.com/~r/rest-term/~3/_BkoJa1NaLU/</link>
		<comments>http://rest-term.com/archives/2955/#comments</comments>
		<pubDate>Sat, 17 Dec 2011 11:20:32 +0000</pubDate>
		<dc:creator>wellflat</dc:creator>
				<category><![CDATA[tech/study]]></category>
		<category><![CDATA[air]]></category>
		<category><![CDATA[c/c++]]></category>
		<category><![CDATA[cv/im]]></category>

		<guid isPermaLink="false">http://rest-term.com/?p=2955</guid>
		<description><![CDATA[前回のエントリー、FlashDevelop + GCCでANE入門の続きになります。今回はANE(ActionScript Native Extensions)で画像処理を行う際のいくつかの注意点などをメモしていこうと思 [...]]]></description>
			<content:encoded><![CDATA[<p>前回のエントリー、<a href="http://rest-term.com/archives/2950/" title="FlashDevelop + GCCでANE入門" >FlashDevelop + GCCでANE入門</a>の続きになります。今回はANE(ActionScript Native Extensions)で画像処理を行う際のいくつかの注意点などをメモしていこうと思います。ANEの作成手順については前エントリーを参照してください。</p>
<p>ANEのC APIでは、ASのBitmapDataを扱う為の構造体 <strong>FREBitmapData</strong> というものが提供されています。<br />
[cpp]<br />
typedef struct {<br />
    uint32_t  width;           /* width of the BitmapData bitmap */<br />
    uint32_t  height;          /* height of the BitmapData bitmap */<br />
    uint32_t  hasAlpha;        /* if non-zero, pixel format is ARGB32, otherwise pixel format is _RGB32, host endianness */<br />
    uint32_t  isPremultiplied; /* pixel color values are premultiplied with alpha if non-zero, un-multiplied if zero */<br />
    uint32_t  lineStride32;    /* line stride in number of 32 bit values, typically the same as width */<br />
    uint32_t* bits32;          /* pointer to the first 32-bit pixel of the bitmap data */<br />
} FREBitmapData;<br />
[/cpp]<br />
OpenCVでいう簡易IplImageのような構造です。学生時代にやったプログラミング演習とかでこんな構造体作って画像触ってた気がします。懐かしい。</p>
<p>で、あれ？座標のオリジンはどこ見ればわかるの？と思ったらなんとAIR 3.1から <strong>FREBitmapData2</strong> という構造体が新しく追加されているようです。<br />
[cpp]<br />
typedef struct {<br />
    uint32_t  width;           /* width of the BitmapData bitmap */<br />
    uint32_t  height;          /* height of the BitmapData bitmap */<br />
    uint32_t  hasAlpha;        /* if non-zero, pixel format is ARGB32, otherwise pixel format is _RGB32, host endianness */<br />
    uint32_t  isPremultiplied; /* pixel color values are premultiplied with alpha if non-zero, un-multiplied if zero */<br />
    uint32_t  lineStride32;    /* line stride in number of 32 bit values, typically the same as width */<br />
    uint32_t  isInvertedY;     /* if non-zero, last row of pixels starts at bits32, otherwise, first row of pixels starts at bits32. */<br />
    uint32_t* bits32;          /* pointer to the first 32-bit pixel of the bitmap data */<br />
} FREBitmapData2;<br />
[/cpp]<br />
oh&#8230; 本当に学生のプログラミング演習課題の再提出みたいな、そんな対応。</p>
<p>新しく追加された isInvertedY というメンバが座標のオリジンを示しているようですが、この行き当たりばったりな対応を見る限り、Adobe内でリソース配分(人材他)に苦労してる様子がわかります。</p>
<p>しかもシビアな画像処理をいくつか試していた中で気付いたのですが、FREBitmapData に画像データをバインドする際にそのデータが壊れている時があったので(原因不明、単一色で塗りつぶした画像を渡してネイティブ側で全ピクセル値をログに出してみると(A, R, G, B) = (0, 0, 0, 0)で入っている画素がたまに出現した)、<strong>FREByteArray</strong> に画像データを入れてwidthとheightを併せてネイティブ側に渡す方針に変更しました。AS側での BitmapData#getPixels/setPixels と、ネイティブ側でのバイトオーダー判定等のコストが余計にかかってしまいますが、いくらか安全です。</p>
<p>画像にどこまで触れるかを知りたいだけなので、ここではソフトフォーカス風にレタッチするフィルタをANEで作成してみます。前回はPure Cで書いたのですが今回はC++を使います。</p>
<p>* <strong>画像データバッファ用クラス</strong> (bitmap.hpp)<br />
C++0xの std::tuple を利用していますが、普通に構造体でRGB値を扱った方が無難かも。<br />
[cpp]<br />
/** bitmap.hpp **/<br />
#ifndef ANE_BITMAP_H<br />
#define ANE_BITMAP_H</p>
<p>#include <tuple><br />
#include &#8220;FlashRuntimeExtensions.h&#8221;</p>
<p>#define EXPORT __declspec(dllexport)</p>
<p>enum { R, G, B };</p>
<p>namespace fre {<br />
  /* pixel data */<br />
  typedef std::tuple<uint8_t, uint8_t, uint8_t> pixel;</p>
<p>  /* buffer class for bitmap data */<br />
  class BitmapBuffer {<br />
  public:<br />
    // constructor<br />
    BitmapBuffer(const FREObject&#038; image, int step) :<br />
      _image(image), _step(step) {<br />
      _ret = FREAcquireByteArray(image, &#038;_ba);<br />
      if(_ret==FRE_OK) {<br />
        _data = pcast<uint32_t>(_ba.bytes);<br />
      }<br />
    }<br />
    // destructor<br />
    ~BitmapBuffer() {<br />
      FREReleaseByteArray(_image);<br />
    }<br />
    bool empty() const { return _ret!=FRE_OK; }<br />
    int size() const { return _ba.length; }<br />
    // returns a reference to pixel<br />
    template<typename T> T&#038; at(int x, int y) {<br />
      return (pcast<T>(_data + y*_step))[x];<br />
    }<br />
    template<typename T> const T&#038; at(int x, int y) const {<br />
      return (pcast<const T>(_data + y*_step))[x];<br />
    }<br />
    template <class T> T* pcast(void* p) {<br />
      return static_cast<T*>(p);<br />
    }<br />
    // returns a pixel data (easy access)<br />
    pixel operator()(int x, int y) const {<br />
      uint32_t p = *(_data + y*_step + x);<br />
      return pixel(p >> 8 &#038; 0xff, p >> 16 &#038; 0xff, p >> 24 &#038; 0xff);<br />
    }<br />
  private:<br />
    BitmapBuffer(const BitmapBuffer&#038;);<br />
    BitmapBuffer&#038; operator=(const BitmapBuffer&#038;);<br />
    const FREObject&#038; _image;<br />
    const int _step;<br />
    FREResult _ret;<br />
    FREByteArray _ba;<br />
    uint32_t* _data;<br />
  };<br />
}</p>
<p>#endif<br />
[/cpp]<br />
デストラクタで FREReleaseByteArray() を呼び出して画像データをアンロックします。あとはデータのアドレス計算部分を隠蔽してアクセサを提供しているくらいの薄いラッパーになっています。</p>
<p>* <strong>ソフトフォーカスフィルタ</strong> (effect.cpp)<br />
フィルタ処理の実体はPhotoshopでいうところの、元画像のレイヤーをコピーしてぼかしフィルタをかけ、それと元画像をスクリーンブレンドしたものと同じです。また、バイトオーダーがリトルエンディアンじゃなかったら何もしないという手抜き。。<br />
[cpp]<br />
/** effect.cpp **/<br />
#include &#8220;bitmap.hpp&#8221;<br />
using std::get;</p>
<p>/* applies soft-focus effect */<br />
FREObject effect(FREObject ctx, void* funcData,<br />
                 uint32_t argc, FREObject argv[]) {<br />
  // validates byte-order and arguments<br />
  int a = 0&#215;00000001;<br />
  if(!(*(char*)&#038;a) /* little endian ? */ || argc != 5) return nullptr;</p>
<p>  // binds variables<br />
  int32_t w, h, radius, offset;<br />
  if(FREGetObjectAsInt32(argv[1], &#038;w) ||<br />
     FREGetObjectAsInt32(argv[2], &#038;h) ||<br />
     FREGetObjectAsInt32(argv[3], &#038;radius) ||<br />
     FREGetObjectAsInt32(argv[4], &#038;offset)) {<br />
    return nullptr;<br />
  }<br />
  fre::BitmapBuffer bmp(argv[0], w);<br />
  if(bmp.empty()) return nullptr;<br />
  fre::pixel val, kval;<br />
  const double divisor = 1.0/((2*radius + 1)*(2*radius + 1));<br />
  int px, py, r, g, b, buff[] = {0, 0, 0};<br />
  offset += 1;</p>
<p>  for(int y=0; y<h; ++y) {<br />
    for(int x=0; x<w; ++x) {<br />
      buff[0] = buff[1] = buff[2] = 0;<br />
      // accumulates pixel data for image smoothing<br />
      for(int ky=-radius; ky<=radius; ++ky) {<br />
        py = y + ky;<br />
        if(py <= 0 || h <= py) py = y;<br />
        for(int kx=-radius; kx<=radius; ++kx) {<br />
          px = x + kx;<br />
          if(px <= 0 || w <= px) px = x;<br />
          kval = bmp(px, py);<br />
          buff[0] += get<R>(kval);<br />
          buff[1] += get<G>(kval);<br />
          buff[2] += get<B>(kval);<br />
        }<br />
      }<br />
      // applies screen-blend with smoothed image<br />
      val = bmp(x, y);<br />
      r = get<R>(val);<br />
      g = get<G>(val);<br />
      b = get<B>(val);<br />
      buff[0] *= divisor;<br />
      buff[1] *= divisor;<br />
      buff[2] *= divisor;<br />
      r = r + buff[0] &#8211; (r*buff[0] >> 8) &#8211; offset;<br />
      g = g + buff[1] &#8211; (g*buff[1] >> 8) &#8211; offset;<br />
      b = b + buff[2] &#8211; (b*buff[2] >> 8) &#8211; offset;<br />
      // saturation<br />
      r = r < 0 ? 0 : r;<br />
      g = g < 0 ? 0 : g;<br />
      b = b < 0 ? 0 : b;<br />
      bmp.at<uint32_t>(x, y) = b << 24 | g << 16 | r << 8;<br />
    }<br />
  }<br />
}</p>
<p>FRENamedFunction _methods[] = {<br />
  { (const uint8_t*)"effect", 0, effect }<br />
};</p>
<p>void _ctxInitializer(void* extData, const uint8_t* ctxType,<br />
                     FREContext ctx, uint32_t* numFunctionsToSet,<br />
                     const FRENamedFunction** functionsToSet) {<br />
  *numFunctionsToSet = sizeof(_methods)/sizeof(FRENamedFunction);<br />
  *functionsToSet = _methods;<br />
}</p>
<p>void _ctxFinalizer(FREContext ctx) { }</p>
<p>extern "C" {<br />
  EXPORT void extInitializer(void** extDataToSet,<br />
                             FREContextInitializer* ctxInitializerToSet,<br />
                             FREContextFinalizer* ctxFinalizerToSet) {<br />
    *extDataToSet = 0;<br />
    *ctxInitializerToSet = _ctxInitializer;<br />
    *ctxFinalizerToSet = _ctxFinalizer;<br />
  }</p>
<p>  EXPORT void extFinalizer(void* extData) { }<br />
}<br />
[/cpp]<br />
C++0xの nullptr を使っていますが、もしC++0xを使わない場合は定数0を返す方法が良いです。NULLマクロはC++ではあまり推奨されていません。</p>
<p>また、この場合はコンパイラの最適化オプションを付けてコンパイルしないと、インライン展開などの最適化が行われずアセンブラコードが肥大化してパフォーマンスが低下してしまいます。なのでgccなら -O3 あたりを付けてコンパイルすることをオススメします。逆に最適化オプションを付けた場合、メモリアドレスを直接ガリガリ計算したコードと比較しても実行効率はほとんど変わらないことを確認しました。最近のGCCはすごいです。</p>
<p>AS側で特筆すべきところはありません。<br />
[as]<br />
package com.example {<br />
  import flash.display.BitmapData;<br />
  import flash.external.ExtensionContext;<br />
  import flash.utils.ByteArray;</p>
<p>  public class ImageEffect {<br />
    private var ctx:ExtensionContext;</p>
<p>    public function ImageEffect() {<br />
      ctx = ExtensionContext.createExtensionContext("com.example", null);<br />
    }<br />
    public function apply(bmp:BitmapData, radius:int, offset:int):void {<br />
      var ba:ByteArray = bmp.getPixels(bmp.rect);<br />
      ctx.call("effect", ba, bmp.width, bmp.height, radius, offset);<br />
      ba.position = 0;<br />
      bmp.setPixels(bmp.rect, ba);<br />
    }<br />
    public function dispose():void {<br />
      return ctx.dispose();<br />
    }<br />
  }<br />
}<br />
[/as]</p>
<p>左が元画像、右がフィルタ適用後 (カーネルサイズ 9×9, 輝度オフセット -50)<br />
※ ブログ用にJPEG圧縮してるので、実際はもっとふわっと(?した効果が出ています。<br />
<img src="http://rest-term.com/wp-content/uploads/2011/12/softfocuseffect.jpg" alt="" title="softfocuseffect" width="550" height="874" class="alignnone size-full wp-image-2961" /><br />
下の画像はミッドタウンのガレリア1Fから。クリスマスモード全開ですね。</p>
<h3 class="term">パフォーマンスについて</h3>
<p>実行速度はカーネルサイズが3×3程度の小ささだとデータ転送のコストが高く付いてASオンリーの方が速かったですが、7×7よりも大きなカーネルならANEを使った方が速かったです。空間効率的には小さなバッファをずらしながら畳み込みとブレンド処理を同時に行えばBitmapData全体のコピーは不要なので効率は良くなります。</p>
<p>バッファ用クラスを用意せずにユーザーコード内で生ポインタのアドレスをガリガリ計算する従来の書き方でももちろんOKです。C++は恐いけどCなら大丈夫という方にとってはわかりやすいかもしれません。VMがメモリ上で画像データが置かれているチャンクを移動させないようにロックしてるはずですが、画素操作が終わった後に FREReleaseByteArray() でアンロックするのを忘れないように注意。また、前述のコードより実行効率が良さそうに見えますが(コードの見た目から判断する限り)、最適化オプションを付けてコンパイルすれば実行効率の差はほとんど消えてなくなるので個人的にはオススメしない書き方です。</p>
<p>* 従来のC Likeな書き方<br />
[cpp]<br />
/* applies soft-focus effect */<br />
FREObject effect(FREObject ctx, void* funcData,<br />
                 uint32_t argc, FREObject argv[]) {<br />
  int a = 0&#215;00000001;<br />
  if(!(*(char*)&#038;a) || argc != 5) return 0;<br />
  FREByteArray ba;<br />
  int32_t w, h, radius, offset;<br />
  if(FREAcquireByteArray(argv[0], &#038;ba) ||<br />
     FREGetObjectAsInt32(argv[1], &#038;w) ||<br />
     FREGetObjectAsInt32(argv[2], &#038;h) ||<br />
     FREGetObjectAsInt32(argv[3], &#038;radius) ||<br />
     FREGetObjectAsInt32(argv[4], &#038;offset)) {<br />
    return 0;<br />
  }<br />
  const double divisor = 1.0/((2*radius + 1)*(2*radius + 1));<br />
  const uint32_t* i;<br />
  const uint32_t* kp;<br />
  uint32_t* data = (uint32_t*)(ba.bytes);<br />
  uint32_t* p;<br />
  int r, g, b, px, py, buff[] = {0, 0, 0};<br />
  offset += 1;</p>
<p>  for(int y=0; y<h; ++y, data+=w) {<br />
    for(int x=0; x<w; ++x) {<br />
      p = data + x;<br />
      buff[0] = buff[1] = buff[2] = 0;<br />
      for(int ky=-radius; ky<=radius; ++ky) {<br />
        py = y + ky;<br />
        if(py <= 0 || h <= py) {<br />
          kp = p;<br />
        } else {<br />
          kp = p + ky*w;<br />
        }<br />
        for(int kx=-radius; kx<=radius; ++kx) {<br />
          px = x + kx;<br />
          if(px <= 0 || w <= px) {<br />
            i = kp;<br />
          } else {<br />
            i = kp + kx;<br />
          }<br />
          buff[0] += *i >> 8 &#038; 0xff;<br />
          buff[1] += *i >> 16 &#038; 0xff;<br />
          buff[2] += *i >> 24 &#038; 0xff;<br />
        }<br />
      }<br />
      r = *p >> 8 &#038; 0xff;<br />
      g = *p >> 16 &#038; 0xff;<br />
      b = *p >> 24 &#038; 0xff;<br />
      buff[0] *= divisor;<br />
      buff[1] *= divisor;<br />
      buff[2] *= divisor;<br />
      r = r + buff[0] &#8211; (r*buff[0] >> 8) &#8211; offset;<br />
      g = g + buff[1] &#8211; (g*buff[1] >> 8) &#8211; offset;<br />
      b = b + buff[2] &#8211; (b*buff[2] >> 8) &#8211; offset;<br />
      r = r < 0 ? 0 : r;<br />
      g = g < 0 ? 0 : g;<br />
      b = b < 0 ? 0 : b;<br />
      *p = b << 24 | g << 16 | r << 8;<br />
    }<br />
  }<br />
  FREReleaseByteArray(argv[0]); // 忘れないように<br />
}<br />
// ... 省略<br />
[/cpp]<br />
さすがに原始的すぎるのでANE C APIだけを使ったライブラリを作るのはオススメしません。まともな画像処理をしたい場合は積極的に外部ライブラリを使っていきたいところです。例えばOpenCVの <strong>cv::Mat</strong> あるいは <strong>IplImage</strong> フォーマットに相互変換できる小さなアダプタ関数だけ用意して画像処理自体はOpenCVに任せた方がいいかと思います。</p>
<p>ANEを使ったAndroidアプリも作っていたのですが、インラインアセンブラでARM命令をぺたぺた書いたコードよりもC++で素朴に書いたコードの方が速い場合が多かったのでショックでした。「最適化はコンパイラに任せた方がいい」という意見を身に染みて実感。ただ、<a href="http://en.wikipedia.org/wiki/ARM_architecture#Advanced_SIMD_.28NEON.29">NEON命令(Advanced SIMD)</a>を使えばさすがにもうちょっと速く動作すると思うのでそれも検証しておきたいです。</p>
<h3 class="term">おまけ: FlashDevelopでのANE開発時の作業効率化</h3>
<p>ANE開発で必要なファイル生成/配置などの作業はバッチで自動化させると楽ですが、圧縮ファイルの解凍は Lhaplus.exe を直接コマンドラインから叩けばできることを知ったのでこれをバッチに組み込んでいます。手動でaneを展開してextdirフォルダに置いたり、swcからlibrary.swfを取り出す作業も含めて自動化できます。<br />
[bash]<br />
## FlashDevelopプロジェクトに追加したバッチ処理の一部<br />
set ane=hello.ane  ## aneファイル名<br />
set unzip=&#8221;C:\Program Files\Lhaplus\Lhaplus.exe&#8221; ## コマンドラインから利用できる<br />
set from=&#8221;C:\Users\Ryo\Desktop\%ane%&#8221;  ## 解凍先はデフォルトでデスクトップになる<br />
set to=..\extdir</p>
<p>if not exist %to% (<br />
  md %to%<br />
)<br />
if exist .\%ane% (<br />
  copy .\%ane% .\%ane%.zip<br />
  %unzip% .\%ane%.zip<br />
  del .\%ane%.zip<br />
  if exist %to%\%ane% (<br />
    rd /S /Q %to%\%ane%<br />
  )<br />
  move %from% %to%  ## デスクトップに解凍されたフォルダをextdirに移動<br />
)<br />
[/bash]</p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/rest-term?a=_BkoJa1NaLU:pmXGrmzO56M:spdCosxkSQE"><img src="http://feeds.feedburner.com/~ff/rest-term?d=spdCosxkSQE" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/rest-term?a=_BkoJa1NaLU:pmXGrmzO56M:OAQBO0PjnPA"><img src="http://feeds.feedburner.com/~ff/rest-term?d=OAQBO0PjnPA" border="0"></img></a>
</div>]]></content:encoded>
			<wfw:commentRss>http://rest-term.com/archives/2955/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://rest-term.com/archives/2955/</feedburner:origLink></item>
		<item>
		<title>GALAXY S II LTE</title>
		<link>http://feedproxy.google.com/~r/rest-term/~3/x3XK3XmT-1A/</link>
		<comments>http://rest-term.com/archives/2956/#comments</comments>
		<pubDate>Sat, 10 Dec 2011 17:48:36 +0000</pubDate>
		<dc:creator>wellflat</dc:creator>
				<category><![CDATA[diary/work]]></category>

		<guid isPermaLink="false">http://rest-term.com/?p=2956</guid>
		<description><![CDATA[買いました。 写真の赤いUSBドングルがXiのデータ端末L-02C。今の時期はどこのショップでもキャンペーンやってると思いますが、僕の場合だとXi同時契約で端末代から3.5万円引き、ポケットチャージャー(モバイル充電器) [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.nttdocomo.co.jp/product/next/sc03d/index.html"><img src="http://rest-term.com/wp-content/uploads/2011/12/galaxy_s2_lte.jpg" alt="" title="galaxy_s2_lte" width="550" height="309" class="alignnone size-full wp-image-2957" /></a></p>
<p>買いました。</p>
<p>写真の赤いUSBドングルがXiのデータ端末L-02C。今の時期はどこのショップでもキャンペーンやってると思いますが、僕の場合だとXi同時契約で端末代から3.5万円引き、ポケットチャージャー(モバイル充電器)、あと細かいアクセサリをいくつか貰いました。</p>
<p>Xi(LTE)の対応エリアはここから見ることができます。<br />
<a href="http://www.nttdocomo.co.jp/support/area/kanto/xi/" title="関東・甲信越 「Xi」（クロッシィ）サービスエリア">関東・甲信越 「Xi」（クロッシィ）サービスエリア</a><br />
2011/12現在だと23区内でもまだスカスカな所があるみたいなので注意。</p>
<p><img src="http://rest-term.com/wp-content/uploads/2011/12/NEC_0070.jpg" alt="" title="NEC_0070" width="300" height="449" class="alignnone size-full wp-image-2958" /></p>
<p>4.5インチはやっぱり大きい。手が小さいので右上隅とか指ぜんぜん届かんしポケット入らへん(´・ω・`)<br />
使いこなせるようにならなくては。</p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/rest-term?a=x3XK3XmT-1A:XmrcCAZVEnI:spdCosxkSQE"><img src="http://feeds.feedburner.com/~ff/rest-term?d=spdCosxkSQE" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/rest-term?a=x3XK3XmT-1A:XmrcCAZVEnI:OAQBO0PjnPA"><img src="http://feeds.feedburner.com/~ff/rest-term?d=OAQBO0PjnPA" border="0"></img></a>
</div>]]></content:encoded>
			<wfw:commentRss>http://rest-term.com/archives/2956/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://rest-term.com/archives/2956/</feedburner:origLink></item>
		<item>
		<title>FlashDevelop + GCCでANE入門</title>
		<link>http://feedproxy.google.com/~r/rest-term/~3/Fmv_Ezx6FBo/</link>
		<comments>http://rest-term.com/archives/2950/#comments</comments>
		<pubDate>Sun, 27 Nov 2011 11:57:05 +0000</pubDate>
		<dc:creator>wellflat</dc:creator>
				<category><![CDATA[tech/study]]></category>
		<category><![CDATA[actionscript]]></category>
		<category><![CDATA[air]]></category>
		<category><![CDATA[c/c++]]></category>

		<guid isPermaLink="false">http://rest-term.com/?p=2950</guid>
		<description><![CDATA[Adobe AIR3から ANE (ActionScript Native Extensions) という機能が追加され、これを使うとAIRアプリケーションをネイティブコードで拡張できるらしいです。素晴らしいですね。 今 [...]]]></description>
			<content:encoded><![CDATA[<p>Adobe AIR3から <strong>ANE (ActionScript Native Extensions)</strong> という機能が追加され、これを使うとAIRアプリケーションをネイティブコードで拡張できるらしいです。素晴らしいですね。</p>
<p>今回はFlashDevelopを使ってWindows x86用のANEを作ろうかと思います。小さな<strong>dll</strong> (<a href="http://ja.wikipedia.org/wiki/%E3%83%80%E3%82%A4%E3%83%8A%E3%83%9F%E3%83%83%E3%82%AF%E3%83%AA%E3%83%B3%E3%82%AF%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA">Dynamic Link Library</a>)を作るだけなので、Visual Studio(Visual C++)のような大げさなIDEは使わずにGCCで。余計なプロジェクトフォルダやファイル等も作らなくていいのですっきり作れるんじゃないかなと。<strong>Windows環境へのGCCのインストール方法はこのエントリーの最後に紹介しています。</strong></p>
<p>Flash Builder + Visual Studio な環境の人は、agendy さんにてANEを利用したAIRアプリケーションの作り方を丁寧に解説されていますので、そちらの良エントリーを参考にしてください。<br />
参考: <a href="http://marbayclip.blogspot.com/2011/09/actionscript-native-extensions.html">ActionScript Native Extensionsのサンプルアプリ作成のまとめ</a></p>
<p>ここではFlashDevelop (Flex/AIR SDK)はインストール、設定完了済みとします。<br />
* <strong>環境</strong><br />
　FlashDevelop 4.0.0 RC2<br />
　gcc version 4.6.1</p>
<h3 class="term">FlashDevelopでAIRプロジェクトを作る</h3>
<p>[Project] &gt; [New Project] &gt; [AIR AS3 Projector] から HelloANE という名前のAIRプロジェクトを作ります。それから bat/CreateCertificate.bat を [右クリック] &gt; Execute して HelloANE.p12 を先に作っておきます。パスワードはデフォルトだと &#8220;fd&#8221; になるようですが、サンプルなのでここはなんでもいいです。証明書関連についてはこのエントリーの主旨ではないため説明は省きます。</p>
<h3 class="term">ActionScript3.0でインタフェースを決める</h3>
<p>まずはAPIのインタフェースを決めるところから。<br />
[as]<br />
package com.example {<br />
  import flash.external.ExtensionContext;</p>
<p>  public class HelloWorld {<br />
    private var ctx:ExtensionContext;</p>
<p>    public function HelloWorld() {<br />
      ctx = ExtensionContext.createExtensionContext(&#8220;com.example&#8221;, null);<br />
    }<br />
    public function say():String {<br />
      return ctx.call(&#8220;hello&#8221;) as String;<br />
    }<br />
    public function dispose():void {<br />
      return ctx.dispose();<br />
    }<br />
  }<br />
}<br />
[/as]<br />
コンストラクタで ExtensionContext のインスタンスを作ります。createExtensionContext メソッドの第1引数は拡張IDと呼ばれるものです。後で使うので他と衝突しないユニークなIDを付けておいてください。第2引数はコンテキストタイプと呼ばれるもので、ネイティブ側で複数の関数群を提供している場合にこのコンテキストタイプを使って呼び出せる関数群の判別を行ったりします。今回はネイティブ側で1つしか関数を提供しないのでnullを指定して構いません。</p>
<p>HelloWorld#say ではネイティブ側の hello という名前の関数を呼び出し、その関数の戻り値をStringとして返します。HelloWorld#dispose はコンテキストのリソース解放を行うためのメソッドです。これを明示的に呼び出さない場合はGCがリソースを回収してくれるので今回は特に気にしません。ネイティブ側で巨大な空間をmallocした時などはこれをきちんと呼び出してやる必要がありそうです。</p>
<h3 class="term">C言語でネイティブ関数を実装する</h3>
<p>作業用フォルダとして新規に ext フォルダを作り、{FLEX_SDK_PATH}\include\<strong>FlashRuntimeExtensions.h</strong> と {FLEX_SDK_PATH}\lib\win\<strong>FlashRuntimeExtensions.lib</strong> の2つをコピーして置きます。そして hello.c ファイルにC言語でネイティブ関数を実装します。<br />
[cpp]<br />
/* hello.c */<br />
#include <string.h><br />
#include &#8220;FlashRuntimeExtensions.h&#8221;<br />
#define EXPORT __declspec(dllexport)</p>
<p>// ネイティブ関数の本体<br />
// &#8220;Hello, World!&#8221; という文字列データをFREObject値として返す<br />
FREObject _hello(FREObject ctx, void* funcData,<br />
                 uint32_t argc, FREObject argv[]) {<br />
  FREObject ret;<br />
  const char* msg = (const char*)&#8221;Hello, World!&#8221;;<br />
  FRENewObjectFromUTF8(strlen(msg) + 1, (const uint8_t*)msg, &#038;ret);<br />
  return ret;<br />
}<br />
/** FREObject型<br />
 *    typedef void* FREObject<br />
 *<br />
 ** FRENewObjectFromUTF8関数<br />
 *    FREResult FRENewObjectFromUTF8(uint32_t        length,<br />
 *                                   const uint8_t*  value ,<br />
 *                                   FREObject*      object);<br />
 */</p>
<p>// 関数テーブル<br />
// &#8220;hello&#8221; がAIRランタイム側(AS)から呼び出される関数名<br />
// functionData はここでは使わない<br />
// _hello がネイティブ関数へのポインタ<br />
FRENamedFunction _methods[] = {<br />
  { (const uint8_t*)&#8221;hello&#8221;, NULL, _hello }<br />
};<br />
/** FRENamedFunction構造体<br />
 *     typedef struct FRENamedFunction_ {<br />
 *       const uint8_t* name;<br />
 *       void*          functionData;<br />
 *       FREFunction    function;<br />
 *     } FRENamedFunction;<br />
 */</p>
<p>// 拡張コンテキスト初期化時に呼ばれる<br />
void _ctxInitializer(void* extData, const uint8_t* ctxType,<br />
                     FREContext ctx, uint32_t* numFunctionsToSet,<br />
                     const FRENamedFunction** functionsToSet) {<br />
  *numFunctionsToSet = sizeof(_methods)/sizeof(FRENamedFunction); // == 1<br />
  *functionsToSet = _methods; // 関数テーブルの紐付け<br />
}</p>
<p>// 拡張コンテキスト破棄時に呼ばれる<br />
void _ctxFinalizer(FREContext ctx) { /* なにもしない */}</p>
<p>// アプリケーション初期化時に呼ばれる, DLLからエクスポート<br />
EXPORT void extInitializer(void** extDataToSet,<br />
                           FREContextInitializer* ctxInitializerToSet,<br />
                           FREContextFinalizer* ctxFinalizerToSet) {<br />
  *extDataToSet = NULL;  // 拡張データ, ここでは使わない<br />
  *ctxInitializerToSet = _ctxInitializer; // イニシャライザの紐付け<br />
  *ctxFinalizerToSet = _ctxFinalizer;     // ファイナライザの紐付け<br />
}</p>
<p>// アプリケーション終了時に呼ばれる, DLLからエクスポート<br />
EXPORT void extFinalizer(void* extData) { /* なにもしない */ }</p>
<p>[/cpp]<br />
C++ではないので <code>extern "C"</code> を書く必要はありません。また、最初に <code>#define EXPORT __declspec(dllexport)</code> をしています。これはDLLから関数をエクスポートする為に指定するものです。</p>
<p>FRENamedFunction 構造体のメンバになっている FREFunction というのが FREObject 型の値を返す関数へのポインタになっています。そしてその <code>FREObject</code> というのは単に void* を typedef しているだけ。ここでは関数テーブルでAIRランタイム側から呼び出せるネイティブ拡張関数群を管理しています。今回の例では1つしか関数を提供しないのでメリットがわからないかもしれませんが、提供する関数の数が増えてくると便利になります。その他のAPIや型情報などの細かい部分は公式のドキュメントを読んで理解しておきます。</p>
<p><a href="http://download.macromedia.com/pub/developer/devices/DevelopingActionScriptExtensionsForAdobeAIR.pdf" title="DevelopingActionScriptExtensionsForAdobeAIR.pdf">DevelopingActionScriptExtensionsForAdobeAIR.pdf</a></p>
<p>ネット上にあるANEのサンプルコードの中には、初期化時関数や終了時関数を紐付ける部分で、関数名の前にアドレス演算子 &#038; を付けている場合もあれば付けていない場合もあって混乱する人もいるかもしれません。が、それらはどれも問題なく動作するはずです。これはC言語の関数ポインタ(と文法)に関する少しややこしい話になるのでここでは割愛しますが、もし興味があれば以下のプログラムの実行結果を予想してから実際に確認してみてください。<br />
[cpp]<br />
#include <stdio.h></p>
<p>int test() {<br />
  return 1;<br />
}</p>
<p>int main(void) {<br />
  int (*func)() = test;<br />
  printf(&#8220;%d\n&#8221;, test());<br />
  printf(&#8220;%d\n&#8221;, (&#038;test)());<br />
  printf(&#8220;%d\n&#8221;, func());<br />
  printf(&#8220;%d\n&#8221;, (*func)());</p>
<p>  printf(&#8220;%p\n&#8221;, test);<br />
  printf(&#8220;%p\n&#8221;, &#038;test);<br />
  printf(&#8220;%p\n&#8221;, func);<br />
  printf(&#8220;%p\n&#8221;, *func);<br />
  printf(&#8220;%p\n&#8221;, ***********func);<br />
  return 0;<br />
}<br />
[/cpp]</p>
<p>* <strong>コンパイル</strong><br />
ネイティブ側の実装が済んだのでdllファイルをGCCで作ります。</p>
<pre>
C:\Users\Ryo\workspace\HelloANE\src\ext>gcc -c hello.c
C:\Users\Ryo\workspace\HelloANE\src\ext>gcc -shared -o hello.dll hello.o FlashRuntimeExtensions.lib
</pre>
<p>たった2回gccを叩くだけ、簡単ですね。FlashRuntimeExtensions.lib をリンクするのを忘れないように。これで hello.dll というファイルが生成されます。</p>
<h3 class="term">aneファイルの作成</h3>
<p>作成に必要なものは以下の5つ。</p>
<p>* 電子署名ファイル (HelloANE.p12 作成済)<br />
* dllファイル (hello.dll 作成済)<br />
* 拡張記述ファイル<br />
* swcファイル<br />
* library.swf</p>
<p>ということでまだ作っていない拡張記述ファイル(XML形式)から。名前は extension.xml とします。<br />
[xml]<br />
<extension xmlns="http://ns.adobe.com/air/extension/2.5"><br />
    <id>com.example</id><br />
    <versionNumber>1</versionNumber></p>
<platforms>
<platform name="Windows-x86">
        <applicationDeployment><br />
          <nativeLibrary>hello.dll</nativeLibrary><br />
          <initializer>extInitializer</initializer><br />
          <finalizer>extFinalizer</finalizer><br />
        </applicationDeployment>
      </platform>
    </platforms>
</extension><br />
[/xml]<br />
id 要素の値はAS側で指定した識別子です。今回はWindows用なので platform 要素の name 属性には &#8220;Windows-x86&#8243; と指定し、後はdllファイルの名前やエクスポートした関数の名前を指定します。</p>
<p>次は library.swf を作ります。swcファイルを展開すると入ってるので各々好きな方法で準備してください。ちょうどコマンドプロンプトが開いていたのでここでは acompc を使ってswcファイルを作ります。</p>
<pre>
## {FLEX_SDK_PATH}/bin へのPathは通っていることとする
C:\Users\Ryo\Documents\workspace\FlashDevelop\HelloANE>acompc ^
More? -target-player=13 ^
More? -source-path src ^
More? -include-classes com.example.HelloWorld ^
More? -output=hello.swc
設定ファイル "C:\Program Files\FlashDevelop\sdks\flex_sdk_4.5.1\frameworks\air-config.xml" をロードしています
C:\Users\Ryo\Documents\workspace\FlashDevelop\HelloANE\hello.swc (2094 bytes)
</pre>
<p>このswcファイルをzipとして展開して中にある library.swf を取り出しておきます。</p>
<p>* <strong>adtコマンドでaneファイルの作成</strong><br />
作業用フォルダとして新規に ane という名前のフォルダを作って必要な5つのファイルを入れておき、adt を使ってaneファイルを作成します。</p>
<pre>
C:\Users\Ryo\Documents\workspace\FlashDevelop\HelloANE\ane>adt -package ^
More? -storetype pkcs12 ^
More? -keystore HelloANE.p12 ^
More? -target ane hello.ane extension.xml ^
More? -swc hello.swc ^
More? -platform Windows-x86 library.swf hello.dll
password: パスワードを入力、デフォルトのままなら "fd"

C:\Users\Ryo\Documents\workspace\FlashDevelop\HelloANE\ane>
</pre>
<p>ここまでの作業でプロジェクトフォルダの中身は以下のようになっているはず。<br />
<img src="http://rest-term.com/wp-content/uploads/2011/11/fd_project.png" alt="" title="fd_project" width="251" height="556" class="alignnone size-full wp-image-2951" /></p>
<h3 class="term">動作確認</h3>
<p>ネイティブ関数の呼び出しテストを行う前に application.xml を少し編集します。以下の情報を好きな所に追記します。extensionID には com.example.HelloWorld.as の中で指定した識別子文字列を入れます。</p>
<p>* <strong>application.xmlの編集</strong><br />
[xml]<br />
<extensions><br />
    <extensionID>com.example</extensionID><br />
</extensions><br />
[/xml]</p>
<p>* <strong>テスト用ASファイル(Main.as)の編集</strong><br />
ネイティブ関数をcallしてその戻り値をTextFieldに書き出します。<br />
[as]<br />
package {<br />
  import flash.display.Sprite;<br />
  import com.example.HelloWorld;<br />
  import flash.text.TextField;</p>
<p>  public class Main extends Sprite {<br />
    private var ext:HelloWorld;</p>
<p>    public function Main():void {<br />
      ext = new HelloWorld();<br />
      var tf:TextField = new TextField();<br />
      tf.text = ext.say();  // HelloWorld#say の中でネイティブ関数をcall<br />
      addChild(tf);<br />
    }<br />
  }<br />
}<br />
[/as]</p>
<p>* <strong>extdirフォルダの作成</strong><br />
このままF5を押しても Not supported native extensions profile と言われて実行できないはずです。ここで <strong>extdir という名前のフォルダを作り、aneファイルを展開したものを置いておきます。</strong></p>
<p><img src="http://rest-term.com/wp-content/uploads/2011/11/extdir.png" alt="" title="extdir" width="204" height="213" class="alignnone size-full wp-image-2953" /></p>
<p>* <strong>Run.batの編集</strong><br />
次に Run.bat ファイルを1行だけ編集します。10行目あたりにあるadlコマンドを叩いている所を以下のように修正します。</p>
<pre>
adl -runtime "%FLEX_SDK%\runtimes\air\win" -profile extendedDesktop -extdir extdir "%APP_XML%" "%APP_DIR%"
</pre>
<p>* <strong>実行</strong><br />
今度こそ大丈夫、もう一度F5。<br />
<img src="http://rest-term.com/wp-content/uploads/2011/11/helloane.png" alt="" title="helloane" width="316" height="337" class="alignnone size-full wp-image-2952" /><br />
おつかれさまでした。</p>
<h3 class="term">感想</h3>
<p>AIRに触るのも初めてだったのですが特につまづく所はなかったです。ANEの作成手順自体は一見複雑なように見えますが、batファイルに手順をまとめて書いておくとか作業の効率化も難しくはありません。次はもう少し複雑な処理を試してみたいと思います。</p>
<p>enjoy!</p>
<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;</p>
<h3 class="term">おまけ: コンパイラ (GCC) の準備</h3>
<p>C言語で普段プログラムを書いている人にとって<a href="http://ja.wikipedia.org/wiki/GNU%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%A9%E3%82%B3%E3%83%AC%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3">GCC</a>はお馴染みですね。Windows環境にGCCを入れるには<a href="http://ja.wikipedia.org/wiki/MinGW">MinGW</a>がお手軽なのでこれを入れます。Visual Studioと比べれば大変小さなツールなのですぐにインストールできるはずです。Flasherの方々にとってはもしかしたら聞き慣れないツールかもしれませんが、Cygwinからフォークして作られた有名なものなので神経質になる必要はありません。</p>
<p><a href="http://sourceforge.net/projects/mingw/files/Installer/mingw-get-inst/">公式のインストーラ</a>を使って入れます。ここでは最新の mingw-get-inst-20111118 を使います。インストールはマウスでぽちぽちしていくだけなので簡単ですが、以下のサイトで丁寧に説明されているので参考までに。<br />
参考: <a href="http://www.kkaneko.com/rinkou/cygwin/mingw.html">Windows で MinGW バージョン 20110530 のインストールとテスト実行</a><br />
ANEを作るだけなら C Compiler と C++ Compiler だけ入れればOK、MSYSはお好みで。インストールできたら<strong>環境変数Path</strong>を編集しておきます(C:\Program Files\MinGW にインストールした場合は C:\Program Files\MinGW\bin をPathに追加)。<br />
参考: <a href="http://openlab.jp/fumio/windows_environment_variable_ja.html">Windows 環境変数 Path の設定方法</a></p>
<p>* 動作確認</p>
<pre>
C:\Users\Ryo>gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=c:/program files/mingw/bin/../libexec/gcc/mingw32/4.6.1/lto-wrapper.exe
Target: mingw32
Configured with: ../gcc-4.6.1/configure --enable-languages=c,c++,fortran,objc,obj-c++ --disable-sjlj
-exceptions --with-dwarf2 --enable-shared --enable-libgomp --disable-win32-registry --enable-libstdc
xx-debug --enable-version-specific-runtime-libs --build=mingw32 --prefix=/mingw
Thread model: win32
gcc version 4.6.1 (GCC)
</pre>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/rest-term?a=Fmv_Ezx6FBo:5wqaCSTrSyU:spdCosxkSQE"><img src="http://feeds.feedburner.com/~ff/rest-term?d=spdCosxkSQE" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/rest-term?a=Fmv_Ezx6FBo:5wqaCSTrSyU:OAQBO0PjnPA"><img src="http://feeds.feedburner.com/~ff/rest-term?d=OAQBO0PjnPA" border="0"></img></a>
</div>]]></content:encoded>
			<wfw:commentRss>http://rest-term.com/archives/2950/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://rest-term.com/archives/2950/</feedburner:origLink></item>
		<item>
		<title>MongoDB C++クライアント</title>
		<link>http://feedproxy.google.com/~r/rest-term/~3/wTu6NUaSgwY/</link>
		<comments>http://rest-term.com/archives/2948/#comments</comments>
		<pubDate>Sun, 13 Nov 2011 13:55:07 +0000</pubDate>
		<dc:creator>wellflat</dc:creator>
				<category><![CDATA[tech/study]]></category>
		<category><![CDATA[c/c++]]></category>
		<category><![CDATA[database]]></category>

		<guid isPermaLink="false">http://rest-term.com/?p=2948</guid>
		<description><![CDATA[現在担当している業務で、CassandraやRedisなどいくつかのNoSQLデータベースの導入検討をしてきましたが、最終的にMongoDBを使うことになりました。アプリケーションの上の層ではPythonやPHPで作りま [...]]]></description>
			<content:encoded><![CDATA[<p>現在担当している業務で、CassandraやRedisなどいくつかのNoSQLデータベースの導入検討をしてきましたが、最終的に<a href="http://www.mongodb.org/">MongoDB</a>を使うことになりました。アプリケーションの上の層ではPythonやPHPで作りますが、パフォーマンス要件が厳しい部分はC++ドライバを使う予定です。</p>
<p>まずはインストールから。<br />
* <strong>環境</strong><br />
Linux 2.6.18-194.26.1.el5 x86_64 (CentOS release 5.7)<br />
gcc version 4.4.4 20100726</p>
<h3 class="term">インストール</h3>
<p>C++ドライバは <a href="http://www.boost.org/">Boost</a> と <a href="http://www.pcre.org/">pcre</a> 、<a href="http://www.scons.org/">SCons</a> が別途必要なのでそちらを先にインストールします。Boostはインストール済みな人も多いかと思いますが、僕の環境ではバージョンが古めだったのでアップデートしました。v1.47からは bjam の他に b2 でもビルドできるようです。yumだと古いバージョンが入るのでソースからビルドします。<br />
[shell]<br />
$ wget http://sourceforge.net/projects/boost/files/boost/1.47.0/boost_1_47_0.tar.gz/download<br />
$ tar zxf boost_1_47_0.tar.gz<br />
$ cd boost_1_47_0<br />
$ ./bootstrap.sh<br />
$ sudo ./b2 install &#8211;prefix=/usr/local  ## インストールしたいディレクトリ<br />
[/shell]<br />
/usr/local/ 以下を汚したくない場合は prefix で任意のディレクトリを指定すればOKです。</p>
<p>次にpcreですが、こちらはyum(Debian系はapt)で入れても大丈夫です。バージョンの古さが気になる場合は Utter Ramblings レポジトリを追加してそこからインストールするといいです。<br />
[shell]<br />
$ sudo yum install pcre.x86_64 &#8211;enablerepo=utter<br />
[/shell]</p>
<p>MongoDB C++ドライバのビルドには SCons を使うので、もし入っていなければインストールします。<br />
[shell]<br />
$ sudo yum install scons<br />
[/shell]</p>
<p>目的のMongoDB C++ドライバ(ここではv2.0を使う)を入れます。頻繁に更新されているようなので注意してください。特にv2.0のドライバは古いものだとコンパイルに失敗することが多いようです。<br />
<a href="http://dl.mongodb.org/dl/cxx-driver/">C++ Driver Download</a><br />
[shell]<br />
## modified 2011-11-09 17:53:31 のドライバをビルド<br />
$ wget http://downloads.mongodb.org/cxx-driver/mongodb-linux-x86_64-v2.0-latest.tgz<br />
$ tar zxf mongodb-linux-x86_64-v2.0-latest.tgz<br />
$ cd cd mongo-cxx-driver-v2.0<br />
$ scons -c  ## 念のためビルド前にターゲットをクリーンアップしておく<br />
$ scons &#8211;extrapath=/usr/local  ## boostのインストールされているディレクトリを指定<br />
[/shell]<br />
scons でも make と同様に -j オプションで並列ビルドができますが今回は付けませんでした。案の定、コンパイルにはけっこうな時間がかかってしまいましたが。。</p>
<p>無事にビルドできたら、出来上がった libmongoclient.so を ldd で依存関係を確認しておきます。<br />
[shell]<br />
$ ldd ./libmongoclient.so<br />
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00002ae841b9e000)<br />
        libboost_thread.so.1.47.0 => /usr/local/lib/libboost_thread.so.1.47.0 (0x00002ae841db9000)<br />
        libboost_filesystem.so.1.47.0 => /usr/local/lib/libboost_filesystem.so.1.47.0 (0x00002ae841fd2000)<br />
        libboost_system.so.1.47.0 => /usr/local/lib/libboost_system.so.1.47.0 (0x00002ae8421f2000)<br />
        libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x00002ae8423f5000)<br />
        libm.so.6 => /lib64/libm.so.6 (0x00002ae8426f5000)<br />
        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00002ae842979000)<br />
        libc.so.6 => /lib64/libc.so.6 (0x00002ae842b87000)<br />
        /lib64/ld-linux-x86-64.so.2 (0x0000003cd5a00000)<br />
        librt.so.1 => /lib64/librt.so.1 (0x00002ae842edf000)<br />
## 確認したら ldconfig で共有ライブラリのリンク設定を更新<br />
$ sudo cp ./libmongoclient.so /usr/local/lib<br />
$ sudo ldconfig /usr/local/lib<br />
[/shell]</p>
<h3 class="term">動作確認</h3>
<p>インストールが終わったら動作確認。ここでは簡単なCRUD操作を試してみます。<br />
* データベース: test, コレクション: users<br />
[cpp]<br />
/* mongo_test.cpp */<br />
#include <iostream><br />
#include <mongo/client/dbclient.h></p>
<p>int main(void) {<br />
  using namespace std;<br />
  try {<br />
    /* connect */<br />
    mongo::DBClientConnection client;<br />
    client.connect(&#8220;localhost:27017&#8243;);</p>
<p>    /* database: test, collection: users */<br />
    const string ns = &#8220;test.users&#8221;;</p>
<p>    /* insert */<br />
    /* { &#8220;name&#8221;: &#8220;Ryo&#8221;, &#8220;age&#8221;: 26 } */<br />
    /* { &#8220;name&#8221;: &#8220;Ryo&#8221;, &#8220;age&#8221;: 26, &#8220;address&#8221;: &#8220;tokyo&#8221; } */<br />
    cout << "--- insert ---" << endl;<br />
    mongo::BSONObjBuilder builder;<br />
    builder.append("name", "Ryo").append("age", 26);  // method chain<br />
    mongo::BSONObj doc = builder.asTempObj();<br />
    cout << doc.toString() << endl;<br />
    client.insert(ns, doc);<br />
    builder.append("address", "tokyo");<br />
    mongo::BSONObj doc2 = builder.obj();<br />
    cout << doc2.toString() << endl;<br />
    client.insert(ns, doc2);</p>
<p>    /* find */<br />
    /* { } , fetch all documents */<br />
    cout << "--- find ---" << endl;<br />
    auto_ptr<mongo::DBClientCursor> cursor =<br />
      client.query(ns, mongo::BSONObj()); // represent { }<br />
    while(cursor->more()) {<br />
      mongo::BSONObj p = cursor->next();<br />
      mongo::OID oid = p["_id"].OID();  // retrieve ObjectId<br />
      string name = p["name"].str();  // retrieve string value<br />
      int age = p["age"].numberInt();  // retrieve integer value<br />
      string address = p["address"].str();<br />
      cout << "ObjectId: " << oid << endl;<br />
      cout << "name: " << name << endl;<br />
      cout << "age: " << age << endl;<br />
      cout << "address: " << address << endl;<br />
    }</p>
<p>    /* update (using BSON macro) */<br />
    /* { "$set": { "name": "Joe" } } */<br />
    cout << "--- update ---" << endl;<br />
    mongo::Query query(BSON("name" << "Ryo"));<br />
    mongo::BSONObj modifier = BSON("$set" << BSON("name" << "Joe"));<br />
    cout << modifier.toString() << endl;<br />
    client.update(ns, query, modifier,<br />
                  /* upsert */ false, /* multi */ true);</p>
<p>    cursor = client.query(ns, mongo::BSONObj());<br />
    while(cursor->more()) {<br />
      mongo::BSONObj p = cursor->next();<br />
      cout << p.toString() << endl; // JSON string output<br />
    }</p>
<p>    /* remove */<br />
    /* { "name": "Joe" } */<br />
    cout << "--- remove ---" << endl;<br />
    client.remove(ns, mongo::Query(BSON("name" << "Joe")));</p>
<p>  } catch(const mongo::ConnectException&#038; e) {<br />
    cerr << "connect error" << endl;<br />
    cerr << e.getCode() << endl;<br />
    cerr << e.what() << endl;<br />
  } catch(const mongo::DBException&#038; e) {<br />
    cerr << e.getCode() << endl;<br />
    cerr << e.what() << endl;<br />
  }<br />
  return 0;<br />
}<br />
[/cpp]<br />
* コンパイル/実行<br />
[shell]<br />
$ g++ -g -Wall -I/usr/local/include -L/usr/local/lib -lmongoclient  mongo_test.cpp -o mongo_test<br />
$ ./mongo_test<br />
[/shell]<br />
* 出力結果</p>
<pre>
<code>--- insert ---
{ name: "Ryo", age: 26 }
{ name: "Ryo", age: 26, address: "tokyo" }
--- find ---
ObjectId: 4ebfc78bb7e28e3bc501cd08
name: Ryo
age: 26
address:
ObjectId: 4ebfc78bb7e28e3bc501cd09
name: Ryo
age: 26
address: tokyo
--- update ---
{ $set: { name: "Joe" } }
{ _id: ObjectId('4ebfc78bb7e28e3bc501cd08'), name: "Joe", age: 26 }
{ _id: ObjectId('4ebfc78bb7e28e3bc501cd09'), name: "Joe", age: 26, address: "tokyo" }
--- remove ---</code>
</pre>
<p>Cドライバと比べるとインタフェースはかなり高級です。</p>
<p>mongo::BSONObjBuilder のメソッドは内部で return *this しているのでメソッドチェーンができるようです(実際使うかどうかは別として)。この mongo::BSONObjBuilder のインスタンスは使い回したいと思うかもしれませんが、規模が大きくなってくるといろいろ罠にハマりやすいので(例えば、obj() を一度呼んでいるのに後でうっかり append() してしまうとSEGVする等)、使う度に生成するようにした方がいいような気がします。コードが読み辛くなるのを許容できるなら、BSONObjBuilder を一切使わずに BSON マクロのみでクエリを組み立てるのもいいかもしれません。</p>
<p>また、mongo::DBClientConnection::query() は戻り値として std::auto_ptr&lt;mongo::DBClientCursor&gt; を返すようになっているので注意。boost必須のドライバなんだし boost::shared_ptr を返すようにすればいいのにと思いますが、。</p>
<p>今回はMongoDB C++ドライバを使って簡単なCRUD操作を試してみました。今回紹介できなかったクラスもまだたくさんあるので少しずつ調べていこうと思います。</p>
<p>* C++ドライバ APIリファレンス<br />
<a href="http://api.mongodb.org/cplusplus/current/annotated.html">doxygen API docs</a></p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/rest-term?a=wTu6NUaSgwY:AiK4UPZgRO4:spdCosxkSQE"><img src="http://feeds.feedburner.com/~ff/rest-term?d=spdCosxkSQE" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/rest-term?a=wTu6NUaSgwY:AiK4UPZgRO4:OAQBO0PjnPA"><img src="http://feeds.feedburner.com/~ff/rest-term?d=OAQBO0PjnPA" border="0"></img></a>
</div>]]></content:encoded>
			<wfw:commentRss>http://rest-term.com/archives/2948/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://rest-term.com/archives/2948/</feedburner:origLink></item>
		<item>
		<title>JavaScriptでステレオ画像処理</title>
		<link>http://feedproxy.google.com/~r/rest-term/~3/6K3ZyTYzjic/</link>
		<comments>http://rest-term.com/archives/2940/#comments</comments>
		<pubDate>Mon, 10 Oct 2011 15:34:45 +0000</pubDate>
		<dc:creator>wellflat</dc:creator>
				<category><![CDATA[tech/study]]></category>
		<category><![CDATA[cv/im]]></category>
		<category><![CDATA[javascript]]></category>

		<guid isPermaLink="false">http://rest-term.com/?p=2940</guid>
		<description><![CDATA[過去、OpenCVやFlashで簡単なステレオ画像処理を試してきましたが (ステレオ画像処理, Flashでステレオビジョン入門)、今回はJavaScriptとCanvas APIで同じものを作ってみます。 Demo:  [...]]]></description>
			<content:encoded><![CDATA[<p>過去、OpenCVやFlashで簡単なステレオ画像処理を試してきましたが (<a href="http://rest-term.com/archives/1072/" title="ステレオ画像処理 « Rest Term">ステレオ画像処理</a>, <a href="http://rest-term.com/archives/2816/" title="Flashでステレオビジョン入門 « Rest Term">Flashでステレオビジョン入門</a>)、今回はJavaScriptとCanvas APIで同じものを作ってみます。</p>
<p><a href="http://rest-term.com/labs/html5/stereo.html" title="HTML5 Stereo Vision"><img src="http://rest-term.com/wp-content/uploads/2011/10/stereo_matching.jpg" alt="" title="stereo_matching" width="550" height="141" class="alignnone size-full wp-image-2941" /><br />
Demo: HTML5 Stereo Vision</a><br />
Source Code: <a href="https://github.com/wellflat/jslib/tree/master/cv/stereo_matching" title="cv/stereo_matching at master from wellflat/jslib - GitHub">cv/stereo_matching at master from wellflat/jslib &#8211; GitHub</a></p>
<p>ここでは<a href="http://ja.wikipedia.org/wiki/Kinect">Kinect</a>のように赤外線センサーはもちろん利用できないので、純粋な画像解析のみで奥行きを計算します。</p>
<p>2枚の画像でステレオマッチングを行い、カメラから対象までの距離を濃淡で表す視差マップ (深度マップや距離画像と呼ばれることもある、CGの視差マッピングとは別物)を生成します。使用するアルゴリズムはこれまで同様に<a href="http://www.ece.cmu.edu/~ee899/project/deepak_mid.htm">ブロックマッチング</a>を、類似度評価には<a href="http://en.wikipedia.org/wiki/Sum_of_absolute_differences">SAD（Sum of Absolute Differences）</a>をコストとします。</p>
<p>また、<strong>Web Workers</strong>を使った開発にも慣れておこうと思っていたところなので、今回も動的計画法などは併用せずにナイーブな実装にしてWeb Workersの性能も併せて確かめました。</p>
<p>これまでに書いてきたエントリーではステレオマッチングについての説明をほとんどしていなかったので、今回は簡単に説明しておきます。ステレオマッチングには人間や動物の両眼視のように、三角測量の原理を用いて2つの画像から対応点を求める<strong>受動ステレオ法</strong>と、2つのカメラの内1つを光を投影する光源に置き換えて対応点を求める<strong>能動ステレオ法</strong>の大きく分けて2つの手法があります。</p>
<h3 class="term">ブロックマッチング法</h3>
<p>今回は人間や動物と同様に左右2つの目から視差を計算する受動ステレオ法の1つ、ブロックマッチング法について簡単に説明します。このアルゴリズムの原理はとても単純で、一方の画像を小面積の領域に分割し、各領域について他方の画像上で対応する位置を探す手法です。左目と右目で視差のあるステレオペア画像において、左目画像の探索領域と似ている領域を右目画像から探しだし、その領域の位置のずれが視差として求められます。この場合、左目画像に映っている対象物は右目画像内ではその位置よりも左側に映るので、探索開始位置から左側のみを調べれば良いことになります。</p>
<p><img src="http://rest-term.com/wp-content/uploads/2011/10/blockmatching.png" alt="Block Matching" title="blockmatching" width="358" height="245" class="alignnone size-full wp-image-2942" /></p>
<p>各領域について画像全体を走査すると膨大な計算量になってしまうため、<strong>エピポーラ線（Epipolar Line）</strong>というものが用いられます。以下の図に示すように、一方の画像上の1点に注目したとき、その点を通る視線はもう一方の画像上では1本の直線として映ります。また、その直線上の各点を通る視線は、元の画像上では1本の直線となります。したがって、対応点の対は左右の画像上の一対の直線上に存在するため、その直線上だけ探索を行えばいいことになります。<br />
<img src="http://rest-term.com/wp-content/uploads/2011/10/epipolarline.png" alt="Epipolar Line" title="epipolarline" width="300" height="328" class="alignnone size-full wp-image-2946" /></p>
<p>類似度の評価には、一般的なテンプレートマッチングでも用いられるSAD（Sum of Absolute Differences）という指標を用います。これは探索領域内で左目画像と右目画像の輝度値の差の絶対値を求めて総和を取るもので、この値が小さければ小さいほど似ている領域と判断することができます。</p>
<p><img src="http://rest-term.com/wp-content/uploads/2011/10/sad.png" alt="SAD" title="sad" width="350" height="77" class="alignnone size-full wp-image-2947" /><br />
この場合は<em>I</em>が左目画像、<em>T</em>が右目画像、<em>I(i,j)</em>と<em>T(i,j)</em>はそれぞれ座標<em>(i,j)</em>の画素の輝度値になります。また、他の類似度指標としてはSSD（Sum of Squared Differences）やNCC（Normalized Cross-Correlation）などがあります。</p>
<p>以上がブロックマッチング法の簡単な説明になります。</p>
<p>ソースコードはGitHubに。Canvasを扱う外部ライブラリは使ってなくて、Canvas APIを直接利用したネイティブなJavaScriptコードになっています。<br />
<a href="https://github.com/wellflat/jslib/tree/master/cv/stereo_matching" title="cv/stereo_matching at master from wellflat/jslib - GitHub">cv/stereo_matching at master from wellflat/jslib &#8211; GitHub</a></p>
<h4 class="term">ブラウザ毎の処理時間</h4>
<p>今回のデモが動作する各ブラウザで処理時間を計測してみました。10回計測した平均値(ms)です。ブロックマッチングのパラメータは探索ウィンドウサイズを7×7px、最大視差を10pxとしました。<br />
(Windows7, Core2 Quad 2.40GHz, L2 Cache 4MB×2, 4GB RAM)</p>
<table class="data-table" style="width:250px;">
<tr>
<td>Firefox 7.01</td>
<td>2159 ms</td>
</tr>
<tr>
<td>Chrome 14.0.835</td>
<td>3855 ms</td>
</tr>
<tr>
<td>Safari 5.0.5</td>
<td>4157 ms</td>
</tr>
<tr>
<td>Opera 11.51</td>
<td>3422 ms</td>
</tr>
</table>
<p>Firefoxがここまで速いのには驚きました。7未満のバージョンだと違った結果になるのでしょうか。Operaは少し昔のバージョンだとたぶん動かないと思いますが、現在の最新バージョンだと特に問題なく動作しました。また、Web Workersの生成およびメッセージパッシング部分のコストについては、今回のような重いCPUバウンドなデモであれば無視していいレベルかと思います。</p>
<h4 class="term">CanvasとFlash</h4>
<p>求めた視差データを使って三次元復元しようかと思ったのですが、CanvasはFlashと違って描画性能はまだまだ弱いので今回は諦めました。座標計算だけなら現在の処理系でも十分な性能は出せると思うのですが、描画までとなると現状はなかなかキビしいかと。逆に描画部分を含まない純粋な演算能力だけであればAVM2を凌ぐ勢いを感じたのでこれからに期待したいと思います。</p>
<p>一方、FlasnランタイムにおけるWeb Workersに相当する機能はFlash Player12, 13あたりで搭載されると思いますが、予定ではshared nothingなworkerのみとのことですので、Flasherのみなさんが共有リソースの泥沼にぞろぞろとハマるのは避けられそうです(参考: <a href="http://blog.neuromagic.com/ixdg/2011/10/adobe-max-2011-06.html" title="Adobe MAX 2011レポート：Flashランタイムの並行処理">Adobe MAX 2011レポート：Flashランタイムの並行処理</a>)。これはENTER_FRAMEでの負荷分散とは全く異なるものなので、Flasherの方々も将来に備えてメッセージパッシングによる並行処理の勘所をJavaScriptで事前に養っておくといいかもしれません。</p>
<p>まとめとして、今回はCanvasとWeb Workersを使ってJavaScriptで少しシビアな画像解析を試してみました。ActionScriptからの移植にも慣れてきたので、今後もいろいろ実験していきたいと思っています。</p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/rest-term?a=6K3ZyTYzjic:1yrJGlIO8d0:spdCosxkSQE"><img src="http://feeds.feedburner.com/~ff/rest-term?d=spdCosxkSQE" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/rest-term?a=6K3ZyTYzjic:1yrJGlIO8d0:OAQBO0PjnPA"><img src="http://feeds.feedburner.com/~ff/rest-term?d=OAQBO0PjnPA" border="0"></img></a>
</div>]]></content:encoded>
			<wfw:commentRss>http://rest-term.com/archives/2940/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://rest-term.com/archives/2940/</feedburner:origLink></item>
		<item>
		<title>AS3SXの裏側</title>
		<link>http://feedproxy.google.com/~r/rest-term/~3/nbSz9RX2B40/</link>
		<comments>http://rest-term.com/archives/2938/#comments</comments>
		<pubDate>Mon, 19 Sep 2011 07:01:38 +0000</pubDate>
		<dc:creator>wellflat</dc:creator>
				<category><![CDATA[tech/study]]></category>
		<category><![CDATA[actionscript]]></category>
		<category><![CDATA[network]]></category>
		<category><![CDATA[perl]]></category>
		<category><![CDATA[service]]></category>

		<guid isPermaLink="false">http://rest-term.com/?p=2938</guid>
		<description><![CDATA[前回のエントリー、AS3SXはPaaSとして成功するかの続き。 AS3版PaaSとも言えるAS3SXですが、サーバ側のシステム構成は少し変わっていることを前回述べました。今回はそのシステムと通信するクライアント側に焦点を [...]]]></description>
			<content:encoded><![CDATA[<p>前回のエントリー、<a href="http://rest-term.com/archives/2935/" title="AS3SXはPaaSとして成功するか" >AS3SXはPaaSとして成功するか</a>の続き。</p>
<p>AS3版PaaSとも言えるAS3SXですが、サーバ側のシステム構成は少し変わっていることを前回述べました。今回はそのシステムと通信するクライアント側に焦点を当ててみます。</p>
<h3 class="term">AS3SXのコンセプトを台無しにする方法</h3>
<p>前回のエントリーで少しだけ書いたのですが、AS3SXはソケットサーバをフロントに置き、クライアントはそのサーバに直接接続してTCPで通信を行っています(エンドポイントをHTTPに見せてるのは実はフェイク)。</p>
<p>AS3SXはその名前が示すように「クライアントもサーバもAS3で！」というのをコンセプトにサービス提供していますが、普通のTCP通信を行っているなら別に言語に縛られる必要はありません。さらに、実際に送信しているデータはクエリストリング形式の単純なASCIIデータなのでプロトコル解析の必要すらなく、自分の好きな言語で簡単にクライアントが書けます。</p>
<p>好きな言語といってもAS3SXのユーザーが好きな言語は当然ActionScriptだと思います。ただ、Flash Playerを必要としない環境で動作するクライアントが書けると自由にいろいろできるようになります。</p>
<h4 class="term">例えば <strong>Shutdown Time</strong> の60分制限を回避する</h4>
<p>AS3SXはアプリケーション毎の設定で「Shutdown Time」というものが設けられており、そこで設定した時間(最大60分)の間に新規リクエストがない場合、サーバ側のFlash Playerがシャットダウンされます。60分で勝手に落ちるものを&#8221;サーバ&#8221;とは到底呼べないので、定期的に<a href="http://e-words.jp/w/E3838FE383BCE38388E38393E383BCE38388.html">ハートビート</a>を送ってサーバ側のFlash Playerを落ちないようにしてあげます。</p>
<p>簡単なハートビート送信用クライアントモジュールの実装例を載せます。本当はPHPで書いた方が良かったのかもしれませんが、Perlの方が書き慣れているので。。</p>
<p>* クライアント (client.pl, perl 5.8以降で動作確認)<br />
[perl]<br />
#!/bin/env perl</p>
<p>use strict;<br />
use warnings;<br />
use AS3SX::Client;</p>
<p>## AS3SX::Client#new の引数は「AS3SX Admin Center」で取得したID<br />
my $client = AS3SX::Client->new(&#8216;your application_id here&#8217;);<br />
## 600秒(10分)ごとにハートビート送信<br />
$client->heartbeat(60*10);<br />
[/perl]</p>
<p>* AS3SX::Client モジュール (AS3SX/Client.pm, 標準モジュールのみ利用)<br />
[perl]<br />
#!/bin/env perl</p>
<p>package AS3SX::Client;<br />
use strict;<br />
use warnings;<br />
use Carp;<br />
use IO::Socket;</p>
<p>## コンストラクタ<br />
#  &#8220;ClientAS3SX.setEndPoint&#8221; に相当<br />
sub new {<br />
  my ($class, $appid) = @_;<br />
  my $socket = IO::Socket::INET->new(<br />
    PeerAddr => &#8220;as3sx.fdt.powerflasher.com&#8221;,<br />
    PeerPort => 4603,<br />
    Proto => &#8216;tcp&#8217;,<br />
    Timeout => 5,<br />
  ) or croak &#8220;connect failed: $!&#8221;;<br />
  my $uuid = `uuidgen`;  # UUID(GUID)を生成<br />
  chomp $uuid;<br />
  my @packed = (<br />
    pack(&#8220;H*&#8221;, &#8217;0103000000010000006518000000&#8242;),<br />
    $appid,<br />
    pack(&#8220;H*&#8221;, &#8217;24000000&#8242;),<br />
    $uuid,<br />
  );<br />
  my $data = &#8221;;<br />
  $data .= $_ for @packed;<br />
  $socket->send($data);<br />
  bless {<br />
    socket => $socket,<br />
  }, $class;<br />
}</p>
<p>sub close {<br />
  $_[0]->{socket}->close;<br />
}</p>
<p>## ハートビート送信 (フォアグラウンドで動作)<br />
#  &#8220;ClientAS3SX.sendRequest(true)&#8221; に相当<br />
#  サーバ側にはBoolean型のリクエストハンドラをハートビート受信用に書いておく<br />
sub heartbeat {<br />
  my ($self, $interval) = @_;<br />
  my $query = &#8220;DataService?data=BwAAAEJvb2xlYW4D&#038;type=Boolean&#8221;;<br />
  local $SIG{INT} = sub {<br />
    $self->close;<br />
    exit(0);<br />
  };<br />
  while(1) {<br />
    $self->request($query);<br />
    sleep($interval);<br />
  }<br />
}</p>
<p>## データ送信<br />
sub request {<br />
  my ($self, $query) = @_;<br />
  my $magic = &#8217;010200000001000000722e000000&#8242;;<br />
  $self->{socket}->send(pack(&#8220;H*&#8221;, $magic).$query);<br />
}</p>
<p>1;<br />
[/perl]<br />
* コマンドラインから実行<br />
[bash]<br />
$ ls -F<br />
AS3SX/  client.pl<br />
$ chmod +x client.pl<br />
$ ./client.pl</p>
<p>(CTRL+Cで止める)<br />
[/bash]<br />
データパケット内のGUIDに該当する部分を0(0&#215;30)とハイフン(0x2d)で埋めて5秒間隔で送り、サーバ側で <code>session.userId</code> をログに書き出した結果を載せます。ちゃんと足跡が付きました。<br />
(ログのtextareaが等幅フォントになってなくて見にくい、、)<br />
<img src="http://rest-term.com/wp-content/uploads/2011/09/as3sx_applog.jpg" alt="" title="as3sx_applog" width="550" height="202" class="alignnone size-full wp-image-2939" /><br />
このスクリプトはフォアグラウンドで動くので SIGINT(CTRL+C) で止める必要がありますが、実際はモジュールの <code>heartbeat</code> 内にあるループを取り除いてcronで定期的にキックするか、デーモンにしてバックグラウンドで走らせればいいと思います。</p>
<p>PaaSのようなサービスを開発するに際し、最初はインビテーションで限られたユーザーだけに使ってもらい、フィードバックを経て少しずつ改善していくのが普通だと思います。でもAS3SXはアルファ版と言いつつ誰でも使える状態でいきなり公開しました。さらに、検証中に向こうのサーバに適当なパケットを送ってみたところ、なんかのオンラインゲーム上で使っていると思われるレベルとか経験値などのユーザーデータが返ってきたので、AS3SXだけではなく複数のサービスでサーバを共用しているのでしょう。</p>
<p>&#8220;プラットフォーム&#8221;を提供するという意識が低いのではと思いました。やはり餅は餅屋ということでしょうか。</p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/rest-term?a=nbSz9RX2B40:SDw0_YoRJm4:spdCosxkSQE"><img src="http://feeds.feedburner.com/~ff/rest-term?d=spdCosxkSQE" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/rest-term?a=nbSz9RX2B40:SDw0_YoRJm4:OAQBO0PjnPA"><img src="http://feeds.feedburner.com/~ff/rest-term?d=OAQBO0PjnPA" border="0"></img></a>
</div>]]></content:encoded>
			<wfw:commentRss>http://rest-term.com/archives/2938/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://rest-term.com/archives/2938/</feedburner:origLink></item>
		<item>
		<title>AS3SXはPaaSとして成功するか</title>
		<link>http://feedproxy.google.com/~r/rest-term/~3/ggtfT-INMsU/</link>
		<comments>http://rest-term.com/archives/2935/#comments</comments>
		<pubDate>Sun, 11 Sep 2011 14:07:24 +0000</pubDate>
		<dc:creator>wellflat</dc:creator>
				<category><![CDATA[tech/study]]></category>
		<category><![CDATA[actionscript]]></category>
		<category><![CDATA[ops]]></category>
		<category><![CDATA[service]]></category>

		<guid isPermaLink="false">http://rest-term.com/?p=2935</guid>
		<description><![CDATA[AS3SX Server Side Action Script 3 Library &#38; Hosting - Fast Development with a painless Framework - Multius [...]]]></description>
			<content:encoded><![CDATA[<p><img src="http://rest-term.com/wp-content/uploads/2011/09/AS3SX_Home.png" alt="" title="AS3SX_Home" width="110" height="151" class="alignnone size-full wp-image-2936" /><br />
AS3SX Server Side Action Script 3 Library &amp; Hosting</p>
<blockquote><p>- Fast Development with a painless Framework<br />
- Multiuser, Realtime Events &#038; Sessions out of the box<br />
- Super Easy Database Access without any SQL<br />
- Fully Hosted in the Cloud</p></blockquote>
<p>[追記] 技術的な部分は馬鹿全さんのブログも参考になります。(内容が少しかぶったけど；；)<br />
<a href="http://blog.bk-zen.com/2011/09/11/523/" title="馬鹿全 - サーバーサイドASのAS3SXとその考察">馬鹿全 &#8211; サーバーサイドASのAS3SXとその考察</a></p>
<p>サーバサイドActionScriptという取り組み自体は何年も前からありますし、オープンソースに目を向けてみてもいくつかのTamarinプロジェクトで既に実現されていて、個人でも手軽に試すことができます(see also: <a href="http://www.youtube.com/watch?v=0Nk8XmQZEtY">programming socket with redtamarin</a>)。</p>
<p>しかし、AS3SXはそれをクラウドでホスティングしている所が時代を感じさせます。<strong>AS3版PaaS</strong>ですね。制作サイドの人にやさしいのは良いことです。</p>
<p>以下数行は細かい部分なので読み飛ばしてもらってOKです。<br />
&#8212;&#8212;&#8212;-<br />
技術的な面では、実は最初に書いたようなTamarinプロジェクトの成果を貪欲に取り込んだものではなく、サーバプロセス本体をAS3で書けるわけではない。パケットを覗いてみたところ、まずクライアント起動後にサーバとの接続確立後(ESTABLISHED)、アプリケーションIDとGUIDを送る。そのあとは普通のソケット通信。パケットのデータ部は　<code>DataService?data={データ}&#038;type={送るデータのクラス名}</code>　というクエリ文字列の形でそのままASCIIデータを送っていたので、HTTPクライアントも後で使えるようになるのかもしれない(厳密には<code>DataService?...</code>の前に数バイトのバイナリデータが付いている)。サーバ側はアプリケーションIDとGUIDを受け取った時にプレイヤーを蹴り起こしているようだけど、その時点から新規リクエストがない場合、最大60分間はプレイヤーを立ち上げたままにするようだ。アプリケーションの設定項目に「Shutdown Time」というのが設けてあり、現在は最小2分、最大60分の制限が掛けられている。<br />
&#8212;&#8212;&#8212;-</p>
<p>* <strong>スケーラビリティ</strong><br />
初回接続時の遅延から考えて、複数のサーバに分散させたSWF群にTCPコネクションを貼り回っているのか、クラスタのメタ情報を管理するSWFがサーバSWFを選び取ってきて抱え込んでいるのかどちらでもいいですが、この仕組みでどうやって上手くスケールするようなインフラ構成を考えているのでしょう。オートスケールには対応してくれるのか。あとWindows Serverだとそれなりにお金もかかっちゃうので、採算がとれる課金体系にも注目したいところ。</p>
<p>* <strong>データベース</strong><br />
スキーマレスなデータベースが使えるようです。下の例のようにドキュメント指向でオブジェクトをそのまま保存することができます。</p>
<pre>
{
    "___type" : "com.as3sx.persitency::PersistentObject",
     "_id" : "4e6b3254ebd1d65dd80000a7",
     "name" : ryo
}
</pre>
<p>&#8220;Collection&#8221;と呼んでいるので実体はMongoDBでしょうか。AS3ドライバもコミュニティサポートで公開されてますし。でもライブラリ(AS3SX.swc)の使い方がよくわからなくて、インデックスを貼ったり他のCollectionと関係性を持たせたり(embed)とかはどうやって操作するんでしょう。。</p>
<p>* <strong>レイテンシ</strong><br />
テンポ良く検証できないなぁと思っておもむろにRTTを測ってみたのですが、東京からだと平均300msくらい。おっそい。リージョンがヨーロッパ(ドイツ)のようなのでこれはどうしようもないことですが、今のところ日本からまともに利用するのは厳しいです。せめてUS西海岸あたりにリージョンを作ってもらえれば。。</p>
<p>ちなみに、DNSレコードを覗いてみると tobiheidi.de なドメインを目にしたので、会社情報を調べてみたらすぐに見つかりました(フリーランス?)。AS3SXのWebサイトもASP.NET製ですし技術スペックもピンポイントなので、たぶんAS3SXの開発にはここも加わっているんでしょうね。<br />
<a href="http://www.twago.de/p/TobiHeidi/1704/skills" title="TobiHeidi: Expertise, Sprachen, Zertifikate | twago">TobiHeidi: Expertise, Sprachen, Zertifikate | twago</a><br />
インフラをどこが作っているのかというのは、PaaSを利用する上で重要な判断材料になるかと思います。</p>
<p>* <strong>感想</strong><br />
システム構成の物珍しさも相まって個人的には使っていてとても面白かったです。デプロイやデバッグはめんどうなんですが、FDTにデプロイ支援やアプリケーションのステータス確認等ができるようなプラグインが提供されると嬉しいですね。僕はFDTもってないけど(´・ω・`)</p>
<p>DotCloudやfluxflexのようにプログラミング言語やミドルウェアが自由に選べるPaaSが注目されている今、かなりターゲットを絞った戦略で乗り込んできたわけですが、これで多くのユーザーを獲得できるのか。まぁ採算はしばらく別のところで取るつもりかもしれません。</p>
<p>ライブラリの使い方などのドキュメントもこれから充実してくるはずですし、ユーザーの意見や感想もたくさん上がってくると思いますのでそれを楽しみにしておきます。</p>
<p>AS3SXは巨大雲の夢を見るか</p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/rest-term?a=ggtfT-INMsU:0HXlDSLy4HY:spdCosxkSQE"><img src="http://feeds.feedburner.com/~ff/rest-term?d=spdCosxkSQE" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/rest-term?a=ggtfT-INMsU:0HXlDSLy4HY:OAQBO0PjnPA"><img src="http://feeds.feedburner.com/~ff/rest-term?d=OAQBO0PjnPA" border="0"></img></a>
</div>]]></content:encoded>
			<wfw:commentRss>http://rest-term.com/archives/2935/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://rest-term.com/archives/2935/</feedburner:origLink></item>
		<item>
		<title>Google Maps API for Flash が 廃止</title>
		<link>http://feedproxy.google.com/~r/rest-term/~3/X8HvhH1aXPo/</link>
		<comments>http://rest-term.com/archives/2934/#comments</comments>
		<pubDate>Sun, 04 Sep 2011 05:35:46 +0000</pubDate>
		<dc:creator>wellflat</dc:creator>
				<category><![CDATA[tech/study]]></category>
		<category><![CDATA[flash]]></category>

		<guid isPermaLink="false">http://rest-term.com/?p=2934</guid>
		<description><![CDATA[Google Geo Developers Blog: Maps API for Flash deprecation announcement リリースされたのが2008年なので、3年も様子を見た上での決定ということにな [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://googlegeodevelopers.blogspot.com/2011/09/maps-api-for-flash-deprecation.html" title="Google Geo Developers Blog: Maps API for Flash deprecation announcement">Google Geo Developers Blog: Maps API for Flash deprecation announcement</a></p>
<p>リリースされたのが2008年なので、3年も様子を見た上での決定ということになりますが、Googleさんにしては辛抱強かったなと思います。半年くらい前に乃木坂だか六本木あたりでいっしょにハイボール飲んでた時に「Flashからは全然使われてないはず」と彼らも言ってたし、そんなに長くはないだろうなぁとは僕も思ってましたが、ついにその時が来たようです。</p>
<p>原文は無機質ですが要は「ActionScript Developerの需要に応えて提供開始したのに、ぜんぜん使ってないじゃん！」というなんだか含みのある内容で、残念ながら需要喚起にもならなかったということですかね。どうやらブラウザのJavaScript実装の進化はパフォーマンスなどの点においても代替するには十分だと判断したようです。</p>
<p>ここ最近で活発になってきている<a href="http://techwave.jp/archives/51676154.html">O2O(Online to Offline)</a>なビジネスにおいて「地図」の重要性はますます高くなってきています。Google Mapsのようなその分野のキラーアプリケーションにおいて、Flashという1つの大きなプラットフォームを打ち切る事実はいろいろ考えさせられますね。</p>
<p>今後3年間はまだサービス提供されるとのことですが、JavaScript APIへの切り替え作業は忘れないうちに手を付けておいた方がいいかと思います。</p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/rest-term?a=X8HvhH1aXPo:w-Qem9RDR6g:spdCosxkSQE"><img src="http://feeds.feedburner.com/~ff/rest-term?d=spdCosxkSQE" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/rest-term?a=X8HvhH1aXPo:w-Qem9RDR6g:OAQBO0PjnPA"><img src="http://feeds.feedburner.com/~ff/rest-term?d=OAQBO0PjnPA" border="0"></img></a>
</div>]]></content:encoded>
			<wfw:commentRss>http://rest-term.com/archives/2934/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://rest-term.com/archives/2934/</feedburner:origLink></item>
		<item>
		<title>Perlの画像処理モジュールメモ</title>
		<link>http://feedproxy.google.com/~r/rest-term/~3/4ZEXjxZuc60/</link>
		<comments>http://rest-term.com/archives/2933/#comments</comments>
		<pubDate>Sat, 03 Sep 2011 16:40:34 +0000</pubDate>
		<dc:creator>wellflat</dc:creator>
				<category><![CDATA[tech/study]]></category>
		<category><![CDATA[cv/im]]></category>
		<category><![CDATA[perl]]></category>

		<guid isPermaLink="false">http://rest-term.com/?p=2933</guid>
		<description><![CDATA[僕はぜんぜん詳しくないのですが、Perlの場合はどれが一番モダンで使いやすいんですかね。 このブログを見てくださっている方はわかるかと思いますが、ブラウザ上で動く画像処理のサンプルを書く時はActionScriptを好ん [...]]]></description>
			<content:encoded><![CDATA[<p>僕はぜんぜん詳しくないのですが、Perlの場合はどれが一番モダンで使いやすいんですかね。</p>
<p>このブログを見てくださっている方はわかるかと思いますが、ブラウザ上で動く画像処理のサンプルを書く時はActionScriptを好んで使っています。その理由は簡単で、Flashは描画系が優れているから。また、最近だとChrome Developer Toolsの使い勝手がとても良いので、JavaScript(+ Canvas)でサンプルを書くことも増えてきました。</p>
<p>ただ、この2つの言語は仕事で使うわけでもないし得意でもありません。普段の業務だとシステムツールやデーモンなどはPerlで書くことが多いのですが、仕事以外でも使い慣れた道具で遊びたいです。でも僕はPerlで画像処理のコードなど1byteも書いたことがないので勝手が全然わかりません＞＜</p>
<p>画像を加工するようなライトな用途ではなく、解析処理などが行いやすい自由度の高い操作ができるPerlのモジュールを探して試そうと思います。</p>
<p>なんかオススメのあったら教えてください。</p>
<p>&#8212;&#8212;&#8212;&#8212;&#8212;<br />
(調べたことをここに追記していく予定)</p>
<p>* 画像処理用CPANモジュールのメモ<br />
Image::Magick / <a href="http://search.cpan.org/~tonyc/Imager-0.85/Imager.pm">Imager</a> / <a href="http://search.cpan.org/~lbrocard/Image-Imlib2-2.03/lib/Image/Imlib2.pm">Image::Imlib2</a></p>
<p>個人的には低レベルなAPIが使いやすい Imager が画像解析には一番使いやすそうに感じた。ブラーなどの画像加工程度の用途なら Image::Imlib2 で確かに十分っぽいか。</p>
<p>* その他参考資料<br />
<a href="http://www.slideshare.net/kazeburo/perl-2003830" title="大規模画像配信とPerl">大規模画像配信とPerl</a></p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/rest-term?a=4ZEXjxZuc60:8sg9UieBVVE:spdCosxkSQE"><img src="http://feeds.feedburner.com/~ff/rest-term?d=spdCosxkSQE" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/rest-term?a=4ZEXjxZuc60:8sg9UieBVVE:OAQBO0PjnPA"><img src="http://feeds.feedburner.com/~ff/rest-term?d=OAQBO0PjnPA" border="0"></img></a>
</div>]]></content:encoded>
			<wfw:commentRss>http://rest-term.com/archives/2933/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://rest-term.com/archives/2933/</feedburner:origLink></item>
		<item>
		<title>JavaScriptで決定木</title>
		<link>http://feedproxy.google.com/~r/rest-term/~3/YwADe3lkNQQ/</link>
		<comments>http://rest-term.com/archives/2928/#comments</comments>
		<pubDate>Sun, 28 Aug 2011 10:03:06 +0000</pubDate>
		<dc:creator>wellflat</dc:creator>
				<category><![CDATA[tech/study]]></category>
		<category><![CDATA[javascript]]></category>

		<guid isPermaLink="false">http://rest-term.com/?p=2928</guid>
		<description><![CDATA[前回のAS3でNaive Bayesによる文書分類に関連して機械学習による分類問題を扱います。 今回は決定木(Decision Tree)をCanvasに描いてみます。前回のナイーブベイズよりも決定木の方が解釈が簡単で、 [...]]]></description>
			<content:encoded><![CDATA[<p>前回の<a href="http://rest-term.com/archives/2925/" title="AS3でNaive Bayesによる文書分類" >AS3でNaive Bayesによる文書分類</a>に関連して機械学習による分類問題を扱います。</p>
<p><img src="http://rest-term.com/wp-content/uploads/2011/08/tree.jpg" alt="" title="tree" width="400" height="243" class="alignnone size-full wp-image-2929" /></p>
<p>今回は<a href="http://ja.wikipedia.org/wiki/%E6%B1%BA%E5%AE%9A%E6%9C%A8">決定木(Decision Tree)</a>をCanvasに描いてみます。前回のナイーブベイズよりも決定木の方が解釈が簡単で、分類過程が見た目にも分かりやすいので手軽に試すことができました。今回も理論的な部分はWebや書籍の方を参考にしてもらって、ここでは試したことだけ書きます。</p>
<p>今回扱う問題は<a href="http://www.amazon.co.jp/gp/product/4873113644/ref=as_li_qf_sp_asin_tl?ie=UTF8&#038;tag=rest-term-22&#038;linkCode=as2&#038;camp=247&#038;creative=1211&#038;creativeASIN=4873113644">集合知プログラミング</a><img src="http://www.assoc-amazon.jp/e/ir?t=rest-term-22&#038;l=as2&#038;o=9&#038;a=4873113644" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" />にある「サインアップを予測する」を取り上げます。これは、あるユーザーがあるWebサービスの無料会員になるか有料会員になるかを事前に集めたデータから予測する分類問題になっています。決定木は文章で説明するよりも、決定木そのものを見た方が意味が分かりやすいのでサンプルを載せます。</p>
<p>Demo: <a href="http://rest-term.com/labs/html5/dtree.html" title="Decision Tree on Canvas"><img src="http://rest-term.com/wp-content/uploads/2011/08/dtree.png" alt="" title="dtree" width="338" height="209" class="alignnone size-full wp-image-2930" /><br />
Decision Tree on Canvas</a></p>
<p>この決定木の見方ですが、例えばリファラーが&#8221;google&#8221;で、さらに見たページ数が&#8221;21&#8243;ページ以上の人は有料会員(&#8220;Premium&#8221;)になったという結果を表しています。こういった分類結果は、例えばマーケティングの世界だと顧客プロファイリングに利用されたりしています。</p>
<p>ソースコードは少し長いのでGitHubに上げておきます。決定木学習の部分では外部ライブラリ等を使っていないので、JavaScriptの知識があれば読めると思います。</p>
<p>* GitHub<br />
<a href="https://github.com/wellflat/jslib/blob/master/dtree.js" title="dtree.js at master from wellflat/jslib - GitHub">dtree.js at master from wellflat/jslib &#8211; GitHub</a></p>
<p>ちなみにこの決定木の応用先は画像処理分野にも及んでいて、もちろんOpenCVにも既に実装されています。<br />
<a href="http://opencv.jp/opencv-2svn/cpp/decision_trees.html">決定木 &mdash; opencv 2.2 documentation</a> (see also: <a href="http://ja.wikipedia.org/wiki/Random_forest">Random Forest</a>)</p>
<p>* 参考<br />
<a href="http://lecture.ecc.u-tokyo.ac.jp/~yamaguch/keisan-kiko-ron-1/2008/decision.html">computing machinery</a><br />
<a href="http://www.teradata-j.com/library/ma/ins_1314a.html">Teradata｜第14回：決定木</a></p>
<p>&#8212;&#8212;&#8212;&#8212;&#8212;<br />
Chrome Developer Toolsが予想以上に便利だった。なにこれすごい。</p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/rest-term?a=YwADe3lkNQQ:y3nAK22-MX8:spdCosxkSQE"><img src="http://feeds.feedburner.com/~ff/rest-term?d=spdCosxkSQE" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/rest-term?a=YwADe3lkNQQ:y3nAK22-MX8:OAQBO0PjnPA"><img src="http://feeds.feedburner.com/~ff/rest-term?d=OAQBO0PjnPA" border="0"></img></a>
</div>]]></content:encoded>
			<wfw:commentRss>http://rest-term.com/archives/2928/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://rest-term.com/archives/2928/</feedburner:origLink></item>
		<item>
		<title>AS3でNaive Bayesによる文書分類</title>
		<link>http://feedproxy.google.com/~r/rest-term/~3/M4snr5GywfQ/</link>
		<comments>http://rest-term.com/archives/2925/#comments</comments>
		<pubDate>Mon, 15 Aug 2011 02:36:22 +0000</pubDate>
		<dc:creator>wellflat</dc:creator>
				<category><![CDATA[tech/study]]></category>
		<category><![CDATA[actionscript]]></category>
		<category><![CDATA[NLP]]></category>

		<guid isPermaLink="false">http://rest-term.com/?p=2925</guid>
		<description><![CDATA[自然言語処理は専門外なんですが、職種的(?にこの分野に精通している人がたくさん社内にいるので僕も少し影響を受け始めています。。 今回は単純ベイズ分類器(Naive Bayes:ナイーブベイズ)による文書分類を基礎の部分だ [...]]]></description>
			<content:encoded><![CDATA[<p><img src="http://rest-term.com/wp-content/uploads/2011/08/Bayes_Theorem.jpg" alt="" title="Bayes&#039;_Theorem" width="300" height="192" class="alignnone size-full wp-image-2927" /></p>
<p>自然言語処理は専門外なんですが、職種的(?にこの分野に精通している人がたくさん社内にいるので僕も少し影響を受け始めています。。</p>
<p>今回は<a href="http://ja.wikipedia.org/wiki/%E5%8D%98%E7%B4%94%E3%83%99%E3%82%A4%E3%82%BA%E5%88%86%E9%A1%9E%E5%99%A8" title="単純ベイズ分類器">単純ベイズ分類器(Naive Bayes:ナイーブベイズ)</a>による文書分類を基礎の部分だけやってみます。単純(Naive)と呼ばれているのは、文書の出現確率を単語の出現確率の&#8221;積&#8221;で近似し、語順や単語間の相関関係を考慮しないためにそう呼ばれています。ベイズ理論については書籍がたくさん出ているのでそちらを参考にしてください。ここで説明するにはとても大変なので(というより正しく説明できる自信がない)。この理論の実社会における適用分野としてはスパムフィルタなどが有名です。<br />
<span id="more-2925"></span><br />
ナイーブベイズは他の分類器と比べるとマシンリソースをあまり必要としないので、Flash(ActionScript)でもある程度の文書量までならPlayerがタイムアウトせずに学習/分類が可能です。ちなみにWeb上だとPythonでのサンプルが多く見つかりますが、確かにPythonなら実装が楽そうですね。</p>
<p>ここでは文書はBug-of-words(単語の集合)として扱い、良い文書(good)と悪い文書(bad)という2つのカテゴリーに分類します。また、良い文書が悪い文書に分類されるのを防ぐためにしきい値を設定しています。これは例えば、スパムメールがたまに受信フォルダに入り込むのは我慢できるけど、重要なメールがスパムメールと判定されるのは大変困ってしまいます。なので悪い文書に分類される確率が良い文書に分類される確率よりもしきい値以上高い場合にのみ悪い文書に分類し、しきい値以下の場合はunknownに分類するようにしています。<br />
<script type="text/javascript" src="http://wonderfl.net/blogparts/vT8x/js"></script>
<p class="ttlBpWonderfl" style="width: 465px; margin: 0; text-align: right; font-size: 11px;"><a href="http://wonderfl.net/c/vT8x" title="Naive Bayes Classifier">Naive Bayes Classifier &#8211; wonderfl build flash online</a></p>
<p>補足)<br />
&#8216;HTML5&#8242; と &#8216;Flash&#8217; という2つの単語を含む文書は悪い文書(bad)である確率が高いが、しきい値(悪い文書である確率が良い文書である確率の3.0倍)以下なため分類は保留。再度学習を重ねてから再試行すると、良い文書である確率が下がってしきい値を超えるため悪い文書に分類される。</p>
<p>* GitHub<br />
<a href="https://github.com/wellflat/aslib/tree/master/naive_bayes" title="naive_bayes at master from wellflat/aslib - GitHub">naive_bayes at master from wellflat/aslib &#8211; GitHub</a></p>
<p>* 分類テスト<br />
[as]<br />
package {<br />
  import flash.display.Sprite;<br />
  import flash.text.TextField;<br />
  import flash.text.TextFormat;<br />
  import flash.utils.Dictionary;</p>
<p>  [SWF(width="465", height="465", backgroundColor="#000000")]</p>
<p>  public class Main extends Sprite {<br />
    private var nb:NaiveBayes;<br />
    private var tf:TextField;</p>
<p>    public function Main():void {<br />
      p(&#8220;*** Document Classification using Naive Bayes ***\n&#8221;);<br />
      nb = new NaiveBayes(getFeatures);<br />
      sampleTrain(nb); // train<br />
      p(&#8220;P(quick fox|good) = &#8221; + nb.getProb(&#8220;quick fox&#8221;, &#8220;good&#8221;).toString());<br />
      p(&#8220;P(quick fox|bad) = &#8221; + nb.getProb(&#8220;quick fox&#8221;, &#8220;bad&#8221;).toString());<br />
      p(&#8220;class &#8216;quick fox&#8217;: &#8221; + nb.classify(&#8220;quick fox&#8221;));<br />
      p(&#8220;&#8221;);<br />
      p(&#8220;P(quick money|good) = &#8221; + nb.getProb(&#8220;quick money&#8221;, &#8220;good&#8221;).toString());<br />
      p(&#8220;P(quick money|bad) = &#8221; + nb.getProb(&#8220;quick money&#8221;, &#8220;bad&#8221;).toString());<br />
      p(&#8220;class &#8216;quick money&#8217;: &#8221; + nb.classify(&#8220;quick money&#8221;));<br />
      p(&#8220;&#8221;);<br />
      nb.setThreshold(&#8220;bad&#8221;, 3.0); // set classification threshold<br />
      p(&#8220;P(HTML5 Flash|good) = &#8221; + nb.getProb(&#8220;HTML5 Flash&#8221;, &#8220;good&#8221;).toString());<br />
      p(&#8220;P(HTML5 Flash|bad) = &#8221; + nb.getProb(&#8220;HTML5 Flash&#8221;, &#8220;bad&#8221;).toString());<br />
      p(&#8220;class &#8216;HTML5 Flash&#8217;: &#8221; + nb.classify(&#8220;HTML5 Flash&#8221;));<br />
      p(&#8220;&#8221;);<br />
      for(var i:int=0; i<10; i++) { // more training<br />
        sampleTrain(nb);<br />
      }<br />
      p("P(HTML5 Flash|good) = " + nb.getProb("HTML5 Flash", "good").toString());<br />
      p("P(HTML5 Flash|bad) = " + nb.getProb("HTML5 Flash", "bad").toString());<br />
      p("class 'HTML5 Flash': " + nb.classify("HTML5 Flash"));<br />
    }<br />
    // sample training set below<br />
    private function sampleTrain(classifier:Classifier):void {<br />
      classifier.train("Hello how are you?", "good");<br />
      classifier.train("make quick money to live a good life", "bad");<br />
      classifier.train("the quick brown fox jumps", "good");<br />
      classifier.train("Dive into HTML5", "good");<br />
      classifier.train("HTML5 vs. Flash comparison", "bad");<br />
    }<br />
    // make features(bug-of-words) from document<br />
    private function getFeatures(document:String):Dictionary {<br />
      var delimiter:RegExp = /\W+/;<br />
      var words:Array = [];<br />
      var features:Dictionary = new Dictionary();<br />
      for each(var s:String in document.split(delimiter)) {<br />
        if(s.length > 2) {<br />
          words.push(s.toLowerCase());<br />
        }<br />
      }<br />
      for each(var w:String in words) {<br />
        features[w] = 1;<br />
      }<br />
      return features;<br />
    }<br />
    private function p(str:String):void {<br />
      if(!tf) {<br />
        tf = new TextField();<br />
        tf.x = 10;<br />
        tf.y = 20;<br />
        tf.width = tf.height = 465;<br />
        tf.defaultTextFormat = new TextFormat(&#8216;Courier New&#8217;, 12, 0x00ff66, true);<br />
        addChild(tf);<br />
      }<br />
      tf.appendText(str + &#8220;\n&#8221;);<br />
    }<br />
  }<br />
}<br />
[/as]</p>
<p>* Naive Bayesクラス (ベースとしてClassfierクラスを作成)<br />
[as]<br />
// Naive Bayes<br />
package {<br />
  import flash.utils.Dictionary;</p>
<p>  public class NaiveBayes extends Classifier {<br />
    private var thresholds:Dictionary;</p>
<p>    public function NaiveBayes(getFeatures:Function) {<br />
      super(getFeatures);<br />
      thresholds = new Dictionary();<br />
    }<br />
    public function getThreshold(category:String):Number {<br />
      for(var c:String in thresholds) {<br />
        if(c == category) {<br />
          return thresholds[category];<br />
        }<br />
      }<br />
      return 1.0;<br />
    }<br />
    public function setThreshold(category:String, threshold:Number):void {<br />
      thresholds[category] = threshold;<br />
    }<br />
    // classify document into category<br />
    public function classify(document:String):String {<br />
      var probs:Dictionary = new Dictionary();<br />
      var max:Number = 0.0;<br />
      var best:String = null;<br />
      getCategories().forEach(function(c:String, index:int, arr:Array):void {<br />
        probs[c] = getProb(document, c);<br />
        if(probs[c] > max) {<br />
          max = probs[c];<br />
          best = c;<br />
        }<br />
      });<br />
      for(var c:String in probs) {<br />
        var second:Number = probs[c]*getThreshold(best);<br />
        if(c === best) continue;<br />
        if(second > probs[best]) return &#8220;unknown&#8221;;<br />
      }<br />
      return best;<br />
    }<br />
    // Bayes&#8217; theorem<br />
    // P(category|document) = P(document|category)P(category)/P(document)<br />
    //<br />
    public function getProb(document:String, category:String):Number {<br />
      // P(category)<br />
      var categoryProb:Number = getCategoryCount(category)/getTotalCount();<br />
      // P(document|category)<br />
      var documentProb:Number = getDocumentProb(document, category);<br />
      return documentProb*categoryProb;  // ignore P(document)<br />
    }<br />
    // P(document|category)<br />
    private function getDocumentProb(document:String, category:String):Number {<br />
      var features:Dictionary = getFeatures(document);<br />
      var prob:Number = 1.0;<br />
      for(var f:String in features) {<br />
        prob *= getWeightedFeatureProb(f, category);<br />
      }<br />
      return prob;<br />
    }<br />
  }<br />
}<br />
// &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br />
// Base Classfier<br />
package {<br />
  import flash.utils.Dictionary;</p>
<p>  public class Classifier {<br />
    private var featureCount:Dictionary;<br />
    private var categoryCount:Dictionary;<br />
    protected var getFeatures:Function;</p>
<p>    public function Classifier(getFeatures:Function) {<br />
      this.featureCount = new Dictionary();<br />
      this.categoryCount = new Dictionary();<br />
      this.getFeatures = getFeatures;<br />
    }<br />
    public function train(document:String, category:String):void {<br />
      var features:Dictionary = getFeatures(document);<br />
      for(var f:String in features) {<br />
        incrFeatureCount(f, category);<br />
      }<br />
      incrCategoryCount(category);<br />
    }<br />
    protected function getCategoryCount(category:String):Number {<br />
      if(categoryCount[category]) {<br />
        return categoryCount[category];<br />
      }<br />
      return 0.0;<br />
    }<br />
    protected function getFeatureCount(feature:String, category:String):Number {<br />
      if(!featureCount[feature][category]) {<br />
        return 0.0;<br />
      }<br />
      return Number(featureCount[feature][category]);<br />
    }<br />
    protected function getTotalCount():uint {<br />
      var cnt:uint = 0;<br />
      for each(var i:uint in categoryCount) {<br />
        cnt += i;<br />
      }<br />
      return cnt;<br />
    }<br />
    protected function getCategories():Array {<br />
      var categories:Array = [];<br />
      for(var c:String in categoryCount) {<br />
        categories.push(c);<br />
      }<br />
      return categories;<br />
    }<br />
    // P(feature|category)<br />
    protected function getFeatureProb(feature:String, category:String):Number {<br />
      if(getCategoryCount(category) == 0) {<br />
        return 0.0;<br />
      }<br />
      return getFeatureCount(feature, category)/getCategoryCount(category);<br />
    }<br />
    protected function getWeightedFeatureProb(feature:String, category:String,<br />
                                              weight:Number = 1.0, aprob:Number = 0.5):Number {<br />
      var basicProb:Number = getFeatureProb(feature, category);<br />
      var totals:Number = 0.0;<br />
      getCategories().forEach(function(c:String, index:int, arr:Array):void {<br />
        totals += getFeatureCount(feature, c);<br />
      });<br />
      return ((weight*aprob) + (totals*basicProb))/(weight + totals);<br />
    }<br />
    private function incrFeatureCount(feature:String, category:String):void {<br />
      if(!featureCount[feature]) {<br />
        featureCount[feature] = new Dictionary();<br />
      }<br />
      if(!featureCount[feature][category]) {<br />
        featureCount[feature][category] = 0;<br />
      }<br />
      featureCount[feature][category]++;<br />
    }<br />
    private function incrCategoryCount(category:String):void {<br />
      if(!categoryCount[category]) {<br />
        categoryCount[category] = 0;<br />
      }<br />
      categoryCount[category]++;<br />
    }<br />
  }<br />
}<br />
[/as]<br />
ナイーブベイズ以外の分類器も後で追加しやすいようにクラスを分けて書いたのですが、AS3のDictionaryクラスが使いづらいので複雑になってしまいました。画像処理だと便利に使えるAS3ですが、描画系以外だとクラスライブラリやデータ構造が貧弱なのでやはり不利です。</p>
<p>このベイズ理論ですが、いきなりこれを勉強しても難しいと思うので、条件付き確率などの確率統計の基礎を固めてから勉強することを強くオススメします。僕は学部時代の教科書をひっぱりだして復習から始めましたがやっぱり難しいです。。この理論は確率的画像処理にも応用されているので、もう少し勉強してからチャレンジしてみたいと思います。</p>
<p>* 参考<br />
<a href="http://www.amazon.co.jp/gp/product/4489020368/ref=as_li_tf_tl?ie=UTF8&#038;tag=rest-term-22&#038;linkCode=as2&#038;camp=247&#038;creative=1211&#038;creativeASIN=4489020368">入門ベイズ統計―意思決定の理論と発展</a><img src="http://www.assoc-amazon.jp/e/ir?t=rest-term-22&#038;l=as2&#038;o=9&#038;a=4489020368" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /><br />
<a href="http://www.amazon.co.jp/gp/product/4873113644/ref=as_li_qf_sp_asin_tl?ie=UTF8&#038;tag=rest-term-22&#038;linkCode=as2&#038;camp=247&#038;creative=1211&#038;creativeASIN=4873113644">集合知プログラミング</a><img src="http://www.assoc-amazon.jp/e/ir?t=rest-term-22&#038;l=as2&#038;o=9&#038;a=4873113644" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /></p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/rest-term?a=M4snr5GywfQ:7ZZNPqrFhmI:spdCosxkSQE"><img src="http://feeds.feedburner.com/~ff/rest-term?d=spdCosxkSQE" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/rest-term?a=M4snr5GywfQ:7ZZNPqrFhmI:OAQBO0PjnPA"><img src="http://feeds.feedburner.com/~ff/rest-term?d=OAQBO0PjnPA" border="0"></img></a>
</div>]]></content:encoded>
			<wfw:commentRss>http://rest-term.com/archives/2925/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://rest-term.com/archives/2925/</feedburner:origLink></item>
		<item>
		<title>CanvasとBitmapとよもやま話</title>
		<link>http://feedproxy.google.com/~r/rest-term/~3/HeEbMs7phpg/</link>
		<comments>http://rest-term.com/archives/2917/#comments</comments>
		<pubDate>Sun, 07 Aug 2011 06:00:11 +0000</pubDate>
		<dc:creator>wellflat</dc:creator>
				<category><![CDATA[tech/study]]></category>
		<category><![CDATA[actionscript]]></category>
		<category><![CDATA[cv/im]]></category>
		<category><![CDATA[javascript]]></category>

		<guid isPermaLink="false">http://rest-term.com/?p=2917</guid>
		<description><![CDATA[久しぶりにフロントエンド寄りのこと。 ウチの会社の制作本部の人達と久しぶりに飲みに行ったので、彼/彼女らの仕事の進め方みたいなものを聞いてきた。一般的なWebサイト制作はもちろん、FlashやJavaScriptなどの技 [...]]]></description>
			<content:encoded><![CDATA[<p>久しぶりにフロントエンド寄りのこと。</p>
<p><a href="http://rest-term.com/labs/html5/jitter.html"><img src="http://rest-term.com/wp-content/uploads/2011/08/jitterfilter.jpg" alt="" title="jitterfilter" width="350" height="293" class="alignnone size-full wp-image-2919" /></a></p>
<p>ウチの会社の制作本部の人達と久しぶりに飲みに行ったので、彼/彼女らの仕事の進め方みたいなものを聞いてきた。一般的なWebサイト制作はもちろん、FlashやJavaScriptなどの技術力も僕より数倍上な人達だ。大きな組織の内でプロのデザイナーとしてたくさんの人と競争し続けてきたわけだから優秀で当たり前なのかもしれない。趣味程度にやってる僕なんかとは違う。</p>
<p>* <strong>CanvasとBitmapについて</strong><br />
彼らはしばらくCanvasを使ったアニメーションやゲームなどの案件に注力することになるらしいが、どれだけ正確に作業工数の見積もりができるかが大切だと言っていた。大組織ならではのお金が見え隠れする面白い話も聞いたけど詳しくは書けない。。ただ工数に関して言えば、例えばアニメーションで利用する機能として、HTML5のCanvasもFlashのGraphicsも moveTo() や lineTo() など同じ名前/機能のAPIが提供されている。コードレベルでのFlashからCanvasへの移植作業など彼らにとってはただのタイピングに過ぎないようだが、クロスブラウザ対応の方はやはりある程度まとまった時間を取られてしまうらしい。</p>
<p>大事なのはそのクロスブラウザ対応にかける工数の見積もり。彼らはそれぞれ独自に考えた方法で工数を計算しているらしいが、共通しているのはCanvasを使った案件でもクロスブラウザ対応に工数をたくさんかけるものとそうでないものがあるということ。Canvas上に描いた絵を動かすアニメーションなどには多く工数をかけるが、イメージエフェクト系のものにはあまりかける必要がない。普通に考えたら当たり前なんだけど、画像データ自体を操作する部分ではクロスブラウザとか関係ないわけだ。そもそもCanvasにはイメージエフェクト系のAPIは存在しない。<a href="http://www.w3.org/TR/2010/WD-2dcontext-20100304/#pixel-manipulation">pixel manipulation</a>の項目にあるAPIは以下の4つだけ。<br />
[javascript]<br />
// pixel manipulation<br />
ImageData createImageData(in float sw, in float sh);<br />
ImageData createImageData(in ImageData imagedata);<br />
ImageData getImageData(in float sx, in float sy, in float sw, in float sh);<br />
void putImageData(in ImageData imagedata, in float dx, in float dy, in optional float dirtyX, in float dirtyY, in float dirtyWidth, in float dirtyHeight);<br />
[/javascript]<br />
BitmapDataと比べると大きな違いがあることがわかる。<br />
<a href="http://livedocs.adobe.com/flash/9.0_jp/ActionScriptLangRefV3/flash/display/BitmapData.html" title="BitmapData">BitmapData &#8211; ActionScript 3.0 コンポーネントリファレンスガイド</a><br />
実質的にCanvasでは BitmapData#getPixels と BitmapData#setPixels に該当する機能しか備えていないことになる。多くの人は機能が貧弱だと言っているが、画像処理は実質的にこの2つの機能さえあればなんでもできる。Canvasに colorTransform() や copyChannel() などがなくても実装すればいいだけなので問題にはならない。JavaScriptは必要最低限の機能だけを提供して自分は小さくシンプルなままでいることに努め、リッチな機能はjQueryなどの外部ライブラリに任せている。一方、ActionScriptは自分にどんどん機能を追加して太らせていく方針を採った。(追加する機能の種類が偏っている気はするけど、。)</p>
<p>難しいのはパフォーマンスの確保。JavaScriptには<a href="http://www.w3.org/TR/workers/">Web Workers</a>という強力な機能が備っているが、いつどういった時に使うかが難しい。画像処理であれば、採用するアルゴリズムとサービスが許容する最大画像サイズから計算量を見積もってWeb Workersを使うかどうかを決める。画像処理部分の計算量が少ない場合、workerとのメッセージングのコストの方が高く付くため、常にWeb Workersを使うという選択は賢明ではない。両者の実装コストの違いに関しては、やはり彼女らにとってはただのタイピングに過ぎないだろうから聞くのは止めた。まぁ確かに、Flashで計算をフレーム分散する面倒くささと比べて、Web Workersのshared nothingな仕組みだと実装は楽だと思う。</p>
<p>せっかくなので、昔Flashで作ったものを工数を意識しながらCanvas(JavaScript)で書き直してみる。<br />
2009年に書いた<a href="http://rest-term.com/archives/2115/" title="Jitter Filter">Jitter Filter</a> (Photoshopの [フィルタ] > [表現技法] > [拡散] と同じ効果フィルタ) をCanvasに移植。これはVectorを使って実装されているので、コンテナ内の実データの配置がCanvasの getImageData() で得られるものとは全く違うところに注意、計算量は少ないのでWeb Workersは使わずに実装する。</p>
<p>Demo: <a href="http://rest-term.com/labs/html5/jitter.html">Jitter Filter implementation on Canvas</a></p>
<p>特にクロスブラウザ対応をしなくても主要なモダンブラウザで動作することは確認。画素値が255を超える部分と画像データを指すインデックス値がコンテナサイズを超える部分、つまり境界値テストさえしっかりやればイメージエフェクト系のテストは大丈夫。QAテストをパスするのに必要なテスト項目を如何に速く列挙できるかが大切らしい。</p>
<p>* <strong>HTML5の良いところ</strong><br />
彼女らの意見は僕と全く同じ意見だった。HTML5の良さは文書の高度な構造化方法が提供されたこと。WWWにおけるリソース、つまりHyperText文書としての表現方法の幅が大きく広がったのだから喜んで受け入れよう。</p>
<p>* <strong>僕が聞きたかったこと</strong><br />
正直、FlashとかHTML5とかはどうでもよくて、この人達のような制作サイドのエース達が僕らインフラエンジニアについてどう思っているのかが一番知りたかった。「キミたちのおかげで私たちがいっぱいサイト作れるんですよー」みたいなのは本心なのだろうか。デザイナーの人が&#8221;テンプレート&#8221;とか華麗に使いこなすのを僕は知ってるから、たぶんこれもテンプレートだとは思うんだけど、。</p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/rest-term?a=HeEbMs7phpg:Ceg69meyF7Y:spdCosxkSQE"><img src="http://feeds.feedburner.com/~ff/rest-term?d=spdCosxkSQE" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/rest-term?a=HeEbMs7phpg:Ceg69meyF7Y:OAQBO0PjnPA"><img src="http://feeds.feedburner.com/~ff/rest-term?d=OAQBO0PjnPA" border="0"></img></a>
</div>]]></content:encoded>
			<wfw:commentRss>http://rest-term.com/archives/2917/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://rest-term.com/archives/2917/</feedburner:origLink></item>
		<item>
		<title>DotCloudでのMySQL,Redis,MongoDBの使い方</title>
		<link>http://feedproxy.google.com/~r/rest-term/~3/dJ-lC0Utemg/</link>
		<comments>http://rest-term.com/archives/2915/#comments</comments>
		<pubDate>Sun, 31 Jul 2011 11:31:19 +0000</pubDate>
		<dc:creator>wellflat</dc:creator>
				<category><![CDATA[tech/study]]></category>
		<category><![CDATA[database]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[ruby]]></category>
		<category><![CDATA[service]]></category>

		<guid isPermaLink="false">http://rest-term.com/?p=2915</guid>
		<description><![CDATA[前回のエントリーDotCloudが素晴らしいに引き続きDotCloudで遊びます。 今回はDotCloudの「プログラミング言語やミドルウェアを自由に選べる」という特徴を活かしていろんな組み合わせを試してみたいと思います [...]]]></description>
			<content:encoded><![CDATA[<p>前回のエントリー<a href="http://rest-term.com/archives/2912/">DotCloudが素晴らしい</a>に引き続きDotCloudで遊びます。</p>
<p>今回はDotCloudの「<strong>プログラミング言語やミドルウェアを自由に選べる</strong>」という特徴を活かしていろんな組み合わせを試してみたいと思います。</p>
<p>* メニュー<br />
　<strong>PHP + MySQL</strong><br />
　<strong>Ruby + Redis</strong> (Sinatraを利用)<br />
　<strong>Python + MongoDB</strong> (Flaskを利用)<br />
　</p>
<h3 class="term">PHP + MySQL</h3>
<p>この組み合わせでのシステム開発はなんだかんだ言って仕事で慣れている方も多いのではと思います。もちろんDotCloudでもPHP + MySQLの環境をサポートしています。アプリケーションのデプロイまでの簡単なフローは<a href="http://rest-term.com/archives/2912/">前回のエントリー</a>に書いたのでそちらを参照してください。</p>
<p>* 環境<br />
　PHP 5.3.2<br />
　MySQL 5.1.41<br />
[shell]<br />
$ mkdir phpmysql<br />
$ cd phpmysql<br />
$ dotcloud create phpmysql<br />
Created application &#8220;phpmysql&#8221;<br />
[/shell]<br />
* dotcloud.yml<br />
[shell]<br />
www:<br />
  type: php<br />
data:<br />
  type: mysql<br />
[/shell]<br />
ここまでで一度pushして、MySQLの各種情報を確認します。<br />
[shell]<br />
$ dotcloud push phpmysql  ## dotcloud.ymlを書いてpush<br />
$ dotcloud info phpmysql.data  ## MySQLの情報を確認<br />
cluster: wolverine<br />
config:<br />
    mysql_password: {ここにパスワードが表示される}<br />
created_at: 1312032855.118799<br />
ports:<br />
-   name: ssh<br />
    url: ssh://mysql@89e51352.dotcloud.com:12096<br />
-   name: mysql<br />
    url: mysql://root:TCU3JM8kQripzTg9OG75@89e51352.dotcloud.com:12097<br />
state: running<br />
type: mysql<br />
[/shell]<br />
ウルヴァリンだって。サイクロップスとかローグって名前のクラスタもあるんでしょうか。</p>
<p>次にMySQLに接続します。普通にシェルから操作できます。<br />
[shell]<br />
$ dotcloud run phpmysql.data &#8212; mysql -uroot -p<br />
# mysql -uroot -p<br />
Enter password: {パスワードを入力}<br />
Welcome to the MySQL monitor.  Commands end with ; or \g.<br />
Your MySQL connection id is 34<br />
Server version: 5.1.41-3ubuntu12.10-log (Ubuntu)</p>
<p>Type &#8216;help;&#8217; or &#8216;\h&#8217; for help. Type &#8216;\c&#8217; to clear the current input statement.</p>
<p>mysql> show databases;<br />
+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;+<br />
| Database           |<br />
+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;+<br />
| information_schema |<br />
| mysql              |<br />
+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;+<br />
2 rows in set (0.00 sec)</p>
<p>mysql> create database testdb;<br />
Query OK, 1 row affected (0.00 sec)<br />
[/shell]<br />
testdb という名前でデータベースを1つ作りました。</p>
<p>次はPHPからアクセスしてみます。MySQLへの接続に必要なデータはホームディレクトリの <strong>environment.json</strong> というファイルに記述されているのでそこから取ってきます。<br />
[php]<br />
<?php<br />
try {<br />
  // environment.json ファイルから接続に必要な情報を取得<br />
  $filepath = $_SERVER['HOME'].'/environment.json';<br />
  $env = json_decode(file_get_contents($filepath), true);<br />
  // "DOTCLOUD_DATA_MYSQL_xxx" というキーでMySQL関連のデータが入っている<br />
  $host = $env['DOTCLOUD_DATA_MYSQL_HOST'];<br />
  $port = $env['DOTCLOUD_DATA_MYSQL_PORT'];<br />
  $dbname = 'testdb';<br />
  $dsn = 'mysql:host='.$host.';port='.$port.';dbname='.$dbname;<br />
  $user = $env['DOTCLOUD_DATA_MYSQL_LOGIN'];<br />
  $pass = $env['DOTCLOUD_DATA_MYSQL_PASSWORD'];<br />
  $dbh = new PDO($dsn, $user, $pass); // ここでは PDO を利用<br />
  $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);<br />
  $sql = &#8216;SELECT NOW()&#8217;;  // 日付,時刻を取得<br />
  $stmt = $dbh->prepare($sql);<br />
  $stmt->execute();<br />
  $row = $stmt->fetch(PDO::FETCH_ASSOC);<br />
  echo &#8216;NOW(): &#8216;,$row['NOW()'];<br />
  $dbh = null;<br />
}catch(PDOException $e) {<br />
  echo $e->getMessage();<br />
}<br />
?><br />
[/php]<br />
[shell]<br />
$ dotcloud push phpmysql  ## アプリケーションのデプロイ<br />
[/shell]<br />
後はエントリーポイントにアクセスして日付と時刻が表示されたら成功です。PHPを書いたのは半年ぶりくらいですがここまで10分かかりませんでした。とても簡単。今回はPDOを使いましたが、もちろんmysqliも使えます。</p>
<h3 class="term">Ruby + Redis</h3>
<p>この組み合わせに特に意味はないです。なんとなく字面が良かったので、。Redisは最近注目され始めているNoSQLデータベースの1つで、githubやdigg、stackoverflowなど大規模サービスのバックエンドでも利用されています。Redisは文字列型の他にリストやセット、ハッシュなどの様々な型があり、それぞれの型に対するコマンドがatomicに動作するという良い特徴を持っています。また、Tokyo Cabinet/Tokyo Tyrantのようにデータの永続化やレプリケーションも可能です。Redisについては別エントリーの<a href="http://rest-term.com/archives/2898/" title="インメモリKVSのRedisについて « Rest Term">インメモリKVSのRedisについて</a>で少し詳しく調べているのでもし興味があればそちらを参照してください。Ruby + Redisでの開発フローもPHP + MySQLの例とほとんど同じです。ここではWebアプリケーションフレームワークの<a href="http://www.sinatrarb.com/">Sinatra</a>も利用します。</p>
<p>* 環境<br />
　Ruby 1.9.2 (Rack 1.3.2)<br />
　Redis 2.2.2<br />
　Sinatra 1.2.6<br />
[shell]<br />
$ mkdir rubyredis<br />
$ cd rubyredis<br />
$ dotcloud create rubyredis<br />
Created application &#8220;rubyredis&#8221;<br />
[/shell]<br />
* dotcloud.yml<br />
[shell]<br />
www:<br />
  type: ruby<br />
data:<br />
  type: redis<br />
[/shell]<br />
ここまでで一度pushして、Redisの各種情報を確認します。<br />
[shell]<br />
$ dotcloud push rubyredis  ## dotcloud.ymlを書いてpush<br />
$ dotcloud info rubyredis.data  ## Redisの情報を確認<br />
cluster: wolverine<br />
config:<br />
    redis_password: {ここにパスワードが表示される}<br />
created_at: 1312036663.3893609<br />
ports:<br />
-   name: ssh<br />
    url: ssh://redis@6c235a17.dotcloud.com:11761<br />
-   name: redis<br />
    url: redis://redis:i5b8tq7d4UiG1cvlyQ9j@6c235a17.dotcloud.com:12096<br />
state: running<br />
type: redis<br />
$ dotcloud run rubyredis.data redis-cli  ## シェルからRedisの接続確認<br />
# redis-cli<br />
redis> auth {ここにパスワードを入力}<br />
OK<br />
redis> rpush message &#8216;hello&#8217;  ## 簡単な操作確認<br />
(integer) 1<br />
redis> rpush message &#8216;world&#8217;<br />
(integer) 2<br />
redis> lrange message 0 -1<br />
1) &#8220;&#8216;hello&#8217;&#8221;<br />
2) &#8220;&#8216;world&#8217;&#8221;<br />
redis><br />
[/shell]<br />
ここまでは特に問題はないかと。次にRuby(Rack)アプリケーションからRedisにアクセスしてみます。接続に必要な情報はMySQLの時と同様に environment.json ファイルから取ってきます。また、Rubyのバージョンは特に指定をしなければ1.9.2になるようなので $LOAD_PATH 等の細かい所に注意してください。</p>
<p>* Gemfile<br />
[ruby]<br />
source :rubygems<br />
require &#8216;json&#8217;<br />
require &#8216;rack&#8217;<br />
require &#8216;redis&#8217;<br />
require &#8216;sinatra&#8217;<br />
[/ruby]<br />
* config.ru<br />
[ruby]<br />
$:.unshift File.dirname(__FILE__)<br />
require &#8216;redistest&#8217;<br />
run Sinatra::Application<br />
[/ruby]<br />
* redistest.rb<br />
[ruby]<br />
require &#8216;rubygems&#8217;<br />
require &#8216;json&#8217;<br />
require &#8216;redis&#8217;<br />
require &#8216;sinatra&#8217;</p>
<p>## 汚い書き方ですが接続テストということで;;<br />
get &#8216;/&#8217; do<br />
  filepath = &#8216;/home/dotcloud/environment.json&#8217;<br />
  env = JSON.parse(File.open(filepath).read)<br />
  host = env['DOTCLOUD_DATA_REDIS_HOST']<br />
  port = env['DOTCLOUD_DATA_REDIS_PORT']<br />
  pass = env['DOTCLOUD_DATA_REDIS_PASSWORD']<br />
  redis = Redis.new(:host => host, :port => port, :password => pass)<br />
  redis.rpush(:data, &#8216;redis&#8217;)  ## リストの末尾(右)からpush<br />
  redis.lpush(:data, &#8216;hello, &#8216;)  ## リストの先頭(左)からpush<br />
  p redis.lrange(:data, 0, -1)  ## リストの先頭から末尾までの値を取得<br />
end<br />
[/ruby]<br />
[shell]<br />
$ dotcloud push rubyredis<br />
[/shell]<br />
エントリーポイントにアクセスして hello, redis と表示されたら成功です。やはり簡単。Rubyのバージョンに関しては、dotcloud.ymlで指定すれば1.8.x系も使えます。<br />
[shell]<br />
www:<br />
  type: ruby<br />
  config:<br />
    ruby-version: ree  ## Ruby Enterprise Edition (1.8.x系)<br />
[/shell]<br />
HerokuもアドオンでRedisは使えるようですが、FREEプランだと「容量5MB, 1データベース, 永続化なし, レプリケーションなし」になっています。DotCloudではどれくらい使えるのかがドキュメントには書いていませんが、もし容量5MBだったらつらいなぁという感じです。アプリケーションのデプロイまでのステップはDotCloudの方が若干少ないので楽だと思います。Redisはalphaではなく既にbetaコンポーネントとして提供されているのでドキュメントもこれから充実してくるはず。</p>
<h3 class="term">Python + MongoDB</h3>
<p>この組み合わせは最近よく見かけるようになってきました。自然言語処理(NLP)屋さんがPythonを好むのはまぁわかるのですが、MongoDBも好きな人が多いのは何か理由があるんですかね。やっぱりドキュメント(文書)としてデータを扱うからでしょうか。とにかくNLPな人達はLLにもミドルウェアにも強くなれて羨ましい限り。一方、画像処理(CV/IM)な世界はPythonを広めるのに必要な土台は十分なのに、NLPな世界と比べるとあまり流行ってない上にデータストアに詳しい人も少ない気がします。CVな人達は動画像ファイルのような巨大データの&#8221;管理&#8221;ってどうやってるんでしょう、。</p>
<p>すみません、愚痴で話しがそれました。NLPな人達がときどき羨ましくなります(´・ω・`)<br />
Python + MongoDBでの開発フローもこれまでの例と同じで特筆することはありません。ここではWebアプリケーションフレームワークの<a href="http://flask.pocoo.org/">Flask</a>も利用します。</p>
<p>* 環境<br />
　Python 2.6.5<br />
　MongoDB 1.8.2<br />
　Flask 0.7.2<br />
[shell]<br />
$ mkdir pythonmongo<br />
$ cd pythonmongo<br />
$ dotcloud create pythonmongo<br />
Created application &#8220;pythonmongo&#8221;<br />
[/shell]<br />
* dotcloud.yml<br />
[shell]<br />
www:<br />
  type: python<br />
data:<br />
  type: mongodb<br />
[/shell]<br />
[shell]<br />
$ dotcloud push pythonmongo  ## dotcloud.ymlを書いてpush<br />
$ dotcloud info pythonmongo.data  ## MongoDBの情報を確認<br />
cluster: wolverine<br />
config:<br />
    mongodb_password: {ここにパスワードが表示される}<br />
created_at: 1312085176.141191<br />
ports:<br />
-   name: ssh<br />
    url: ssh://mongodb@8107c568.dotcloud.com:11761<br />
-   name: mongodb<br />
    url: mongodb://root:EFe0oQokRCeWh6q6Cpbc@8107c568.dotcloud.com:12096<br />
state: running<br />
type: mongodb<br />
$ dotcloud run pythonmongo.data mongo  ## シェルからMongoDBの接続確認<br />
# mongo<br />
MongoDB shell version: 1.8.2<br />
connecting to: test<br />
> use admin<br />
switched to db admin<br />
> db.auth(&#8220;root&#8221;, &#8220;{パスワードを入力}&#8221;);<br />
1<br />
> use test<br />
switched to db test<br />
> db.messages.save({&#8220;message&#8221;:&#8221;hello, &#8220;})  ## データを入れておく<br />
> db.messages.save({&#8220;message&#8221;:&#8221;mongodb&#8221;})<br />
> db.messages.find()<br />
{ &#8220;_id&#8221; : ObjectId(&#8220;4e350736e720613220dcc5f2&#8243;), &#8220;message&#8221; : &#8220;hello, &#8221; }<br />
{ &#8220;_id&#8221; : ObjectId(&#8220;4e35073de720613220dcc5f3&#8243;), &#8220;message&#8221; : &#8220;mongodb&#8221; }<br />
> quit()<br />
[/shell]<br />
次にPython(WSGI)アプリケーションからMongoDBにアクセスします。普通の構成でファイル作ってpushするだけ。</p>
<p>* requirements.txt (RackアプリでいうGemfileのようなもの)<br />
[shell]<br />
Flask==0.7.2<br />
pymongo==1.11<br />
[/shell]<br />
* wsgi.py<br />
[python]<br />
import json<br />
from flask import Flask, g<br />
from pymongo import Connection</p>
<p>application = Flask(__name__)<br />
app = application</p>
<p>@app.before_request<br />
def before_request():<br />
    filepath = &#8216;/home/dotcloud/environment.json&#8217;<br />
    f = open(filepath)<br />
    env = json.load(f)<br />
    f.close()<br />
    conn = Connection(env['DOTCLOUD_DATA_MONGODB_HOST'],<br />
                      int(env['DOTCLOUD_DATA_MONGODB_PORT']))</p>
<p>    conn.admin.authenticate(env['DOTCLOUD_DATA_MONGODB_LOGIN'],<br />
                            env['DOTCLOUD_DATA_MONGODB_PASSWORD'])<br />
    g.db = conn</p>
<p>@app.teardown_request<br />
def teardown_request(exception):<br />
    g.db.disconnect()</p>
<p>@app.route(&#8216;/&#8217;)<br />
def hello():<br />
    message = &#8221;<br />
    for item in g.db.test.messages.find():<br />
        message += item['message'] + &#8220;\n&#8221;<br />
    return message</p>
<p>if __name__ == &#8216;__main__&#8217;:<br />
    app.run()<br />
[/python]<br />
[shell]<br />
$ dotcloud push pythonmongo<br />
[/shell]<br />
エントリーポイントにアクセスして hello, mongodb と表示されたら成功です。</p>
<p>今回はPerlでの利用例は省略しますが、普通にPSGIアプリケーションを作って、使いたいCPANモジュールを Makefile.PL に書いてpushするだけで動きました。特に難しいところはないかと思います。</p>
<p>DotCloudでWebアプリケーションを開発する手順を整理すると以下のようになります。</p>
<div style="border:1px solid #999; padding:2px;">
　1. dotcloud create でプロジェクトを作る<br />
　2. dotcloud.yml を書く<br />
　4. dotcloud push をしてから dotcloud info でミドルウェアの情報を確認する<br />
　5. シェルから接続確認<br />
　6. アプリケーションから接続確認 (environment.jsonから情報を取得)
</div>
<p>また、SSHでDotCloudのサーバに接続するのは簡単です。<br />
[shell]<br />
アプリケーション名は pythonmongo とする<br />
$ dotcloud ssh pythonmongo.www<br />
# $SHELL<br />
dotcloud@pythonmongo-default-www-0:~$ ls -aF<br />
./   .bash_history  .bashrc  code@     env/              .pip-cache/  revisions/            .ssh/<br />
../  .bash_logout   .cache/  current@  environment.json  .profile     rsync-1312103158.72/<br />
[/shell]<br />
ちなみに、nginxなどのサービス監視は<a href="http://mmonit.com/monit/">monit</a>で行っているようです。僕もさくらVPSに入れて使っていますが、設定ファイルの書式が綺麗で書きやすいので好きです。</p>
<p>DotCloudと似たようなPaaSでは日本人の方が作った<a href="https://www.fluxflex.com/">fluxflex</a>がありますが、そちらはマウスでぽちぽち作業できるのでポップな感じがしますね(公式サイトの作りも)。当然ながら日本語ドキュメントが整備されているので英語が苦手な人はfluxflexの方が使いやすいかもしれません。現時点でのスペック比較はあまり参考にならない気もするので、公式のPVとか見て好きな方を選んだらいいんじゃないかと思います。僕はDotCloudの音の響きで選んだだけなので、。あと、コンポーネントのロードマップが公式サイトに載ってるので印象は良いです。</p>
<p>僕はインフラ寄りのエンジニアなのでアプリケーション開発には詳しくないのですが、これを機にアプリ側の勉強もしてみようと思います。</p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/rest-term?a=dJ-lC0Utemg:4usvTf5IVnw:spdCosxkSQE"><img src="http://feeds.feedburner.com/~ff/rest-term?d=spdCosxkSQE" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/rest-term?a=dJ-lC0Utemg:4usvTf5IVnw:OAQBO0PjnPA"><img src="http://feeds.feedburner.com/~ff/rest-term?d=OAQBO0PjnPA" border="0"></img></a>
</div>]]></content:encoded>
			<wfw:commentRss>http://rest-term.com/archives/2915/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://rest-term.com/archives/2915/</feedburner:origLink></item>
		<item>
		<title>DotCloudが素晴らしい</title>
		<link>http://feedproxy.google.com/~r/rest-term/~3/hSwvOQpLx8o/</link>
		<comments>http://rest-term.com/archives/2912/#comments</comments>
		<pubDate>Sun, 24 Jul 2011 04:14:23 +0000</pubDate>
		<dc:creator>wellflat</dc:creator>
				<category><![CDATA[tech/study]]></category>
		<category><![CDATA[service]]></category>

		<guid isPermaLink="false">http://rest-term.com/?p=2912</guid>
		<description><![CDATA[これからのPaaS注目株であるDotCloudを遅ればせながら少し試してみました。つい最近ベータ期間を終了し、正式サービスを開始したとのことです。ヒゲのおじさまカッコイイ。 * [追記] 各ミドルウェアも使ってみました。 [...]]]></description>
			<content:encoded><![CDATA[<p>これからのPaaS注目株である<a href="https://www.dotcloud.com/" title="DotCloud - Deployment made simple">DotCloud</a>を遅ればせながら少し試してみました。つい最近ベータ期間を終了し、正式サービスを開始したとのことです。ヒゲのおじさまカッコイイ。</p>
<p>* [追記] 各ミドルウェアも使ってみました。続きの記事はこちら。<br />
<a href="http://rest-term.com/archives/2915/" title="DotCloudでのMySQL,Redis,MongoDBの使い方">DotCloudでのMySQL,Redis,MongoDBの使い方</a></p>
<p><img src="http://rest-term.com/wp-content/uploads/2011/07/logo.png" alt="" title="logo" width="255" height="59" class="alignnone size-full wp-image-2914" /><br />
<iframe width="640" height="390" src="https://www.youtube.com/embed/QSR7kb9W3kw" frameborder="0" allowfullscreen></iframe></p>
<p>DotCloudの特徴としては上のビデオや下の図を見て分かるように、プログラミング言語やミドルウェアを選択できるという点です。つまりユーザーはこれらの技術を自由に組み合わせてシステムを構築することができます。これはGAEやHerokuなど利用技術に制限のあるPaaSとの大きな違いです。<a href="http://docs.dotcloud.com/services/roadmap/" title="DotCloud Documentation">DotCloud &#8211; Components Roadmap</a>を眺めていると、今後さらにたくさんのソフトウェアがサポートされることが期待できます。</p>
<p><img src="http://rest-term.com/wp-content/uploads/2011/07/sometechs.png" alt="" title="sometechs" width="537" height="139" class="alignnone size-full wp-image-2913" /></p>
<p>公式のチュートリアルを読めば使い方はすぐわかるのですが、一応ここでもデプロイまでの手順を簡単に追ってみます。先日のRubyKaigiの興奮冷めやらぬ今、利用言語はRubyで。</p>
<p>と言いつつ、DotCloudのコマンドラインツールはPythonで書かれているのでまずそれをインストールします。アカウントは先に作成しておいてください。<a href="https://www.dotcloud.com/accounts/register/" title="DotCloud - Deployment made simple">DotCloud &#8211; Sign Up Free</a><br />
[shell]<br />
## pip導入済みの場合は sudo pip install dotcloud のみでOK<br />
$ sudo easy_install pip &#038;&#038; sudo pip install dotcloud</p>
<p>## 初めて実行したときにapi keyを入力する<br />
## http://www.dotcloud.com/account/settings で確認して入力<br />
$ dotcloud<br />
Enter your api key (You can find it at http://www.dotcloud.com/account/settings):</p>
<p>## ヘルプ表示<br />
$ dotcloud -h<br />
Command line tool to interact with dotcloud</p>
<p>positional arguments:<br />
  {info,status,run,logs,versions,url,setup,list,rollback,alias,ssh,push,destroy,create,restart}<br />
    setup               setup your api key<br />
    create              create an application<br />
    push                push your application<br />
    list                list your applications<br />
    versions            list the versions of your application<br />
    run                 run a remote command<br />
    logs                read logs<br />
    alias               bind a custom domain name to a service<br />
    destroy             destroy your application<br />
    url                 display URL(s) of your application<br />
    info                get information about your application or service<br />
    status              check the status<br />
    rollback            rollback your service to the previous pushed version<br />
    ssh                 open an SSH session<br />
    restart             restart your service</p>
<p>optional arguments:<br />
  -h, &#8211;help            show this help message and exit<br />
[/shell]<br />
ここではRackアプリケーションをデプロイするところまで。<br />
[shell]<br />
$ mkdir helloworld<br />
$ cd helloworld<br />
$ dotcloud create helloworld<br />
Created application &#8220;helloworld&#8221;<br />
[/shell]<br />
dotcloud.yml という設定ファイルに利用言語などを記述します。環境を選べるDotCloudの特徴的なところですね。必要に応じてデータベースやサービスのエントリーポイントなどの指定もこのファイルに記述します。</p>
<p>* dotcloud.yml<br />
[shell]<br />
www:<br />
  type: ruby<br />
[/shell]<br />
ここでは言語を選んでいるだけなのでこれだけです。次にRackアプリ本体をRubyで普通に書きます。実際にアプリ開発するときはSinatraなどのフレームワークを利用することになると思います。</p>
<p>* config.ru<br />
[ruby]<br />
run proc{|env| [200, {'Content-Type'=>'text/plain'}, 'Hello World!']}<br />
[/ruby]<br />
残るはデプロイ。これも簡単。<br />
[shell]<br />
## 必要なファイルを確認<br />
$ ls<br />
config.ru  dotcloud.yml</p>
<p>## 作成したアプリケーションをpush<br />
$ dotcloud push helloworld</p>
<p>Pseudo-terminal will not be allocated because stdin is not a terminal.<br />
building file list &#8230; done<br />
./<br />
config.ru<br />
dotcloud.yml</p>
<p>sent 295 bytes  received 70 bytes  42.94 bytes/sec<br />
total size is 88  speedup is 0.24<br />
Deployment for &#8220;helloworld&#8221; triggered. Will be available in a few seconds.<br />
2011-07-24 03:36:36 [api] Waiting for the build. (It may take a few minutes)<br />
2011-07-24 03:36:36 [www.0] Deploying&#8230;<br />
2011-07-24 03:37:05 [www.0] Service booted<br />
2011-07-24 03:37:05 [www.0] The build started<br />
2011-07-24 03:37:06 [www.0] Fetched code revision rsync-1311478595.13<br />
2011-07-24 03:37:16 [www.0] nginx start/running, process 1412<br />
2011-07-24 03:37:17 [www.0] The build finished successfully<br />
2011-07-24 03:37:17 [api] Deploy finished</p>
<p>Deployment finished. Your application is available at the following URLs<br />
www: http://9c79afdc.dotcloud.com/   ## URLが表示されるのでここにアクセス  </p>
<p>## サービスの情報を確認<br />
$ dotcloud info helloworld<br />
www:<br />
    config:<br />
        rack-env: production<br />
        ruby-version: 1.9.2<br />
    instances: 1<br />
    type: ruby<br />
    url: http://9c79afdc.dotcloud.com/<br />
[/shell]<br />
あとはブラウザから表示されたURLにアクセスして Hello World! と表示されたら成功です。</p>
<p><strong>Deployment made simple</strong>を謳うとおり本当に簡単でした。ここまで5分かからない。コマンドラインツールの使い方も dotcloud.yml の書き方も簡単なのですぐに覚えられます。他の言語やミドルウェア(MongoDB, Redis)もいくつか試してみたのですが、特につまづくところはなかったです。ドキュメントもこれから充実してくるはず。また、料金ですが2サービスまで無料とのことです。<a href="https://www.dotcloud.com/pricing/" title="DotCloud - Deployment made simple">DotCloud &#8211; Pricing</a></p>
<p>GAEなどの従来のPaaSを利用していて窮屈に感じていた人はDotCloudを新しい遊び場に選ぶといいかもしれません。<br />
雲は縛られない。</p>
<p>* 参考<br />
<a href="http://www.publickey1.jp/blog/11/paasdotcloud.html" title="プログラミング言語やデータベースが選べる新世代PaaS「DotCloud」が正式サービス開始 － Publickey">プログラミング言語やデータベースが選べる新世代PaaS「DotCloud」が正式サービス開始 － Publickey</a></p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/rest-term?a=hSwvOQpLx8o:4jxKn3KZ5jw:spdCosxkSQE"><img src="http://feeds.feedburner.com/~ff/rest-term?d=spdCosxkSQE" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/rest-term?a=hSwvOQpLx8o:4jxKn3KZ5jw:OAQBO0PjnPA"><img src="http://feeds.feedburner.com/~ff/rest-term?d=OAQBO0PjnPA" border="0"></img></a>
</div>]]></content:encoded>
			<wfw:commentRss>http://rest-term.com/archives/2912/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://rest-term.com/archives/2912/</feedburner:origLink></item>
		<item>
		<title>RubyKaigi 2011に参加してきた</title>
		<link>http://feedproxy.google.com/~r/rest-term/~3/8ts9zVK_T-U/</link>
		<comments>http://rest-term.com/archives/2907/#comments</comments>
		<pubDate>Mon, 18 Jul 2011 15:13:50 +0000</pubDate>
		<dc:creator>wellflat</dc:creator>
				<category><![CDATA[diary/work]]></category>
		<category><![CDATA[ruby]]></category>

		<guid isPermaLink="false">http://rest-term.com/?p=2907</guid>
		<description><![CDATA[7月16日から18日までの3日間、RubyKaigi 2011に参加してきました。 最後のRubyKaigiということで、特に最終日はスピリチュアルな熱いセッションばかり。 如何に楽しくモノつくりができるか、それがなによ [...]]]></description>
			<content:encoded><![CDATA[<p>7月16日から18日までの3日間、RubyKaigi 2011に参加してきました。</p>
<p><img src="http://rest-term.com/wp-content/uploads/2011/07/rubykaigi2011.jpg" alt="" title="rubykaigi2011" width="500" height="277" class="alignnone size-full wp-image-2909" /></p>
<p>最後のRubyKaigiということで、特に最終日はスピリチュアルな熱いセッションばかり。<br />
如何に<strong>楽しく</strong>モノつくりができるか、それがなにより大切であると。<br />
数々の熱いセッションを聞いているうちに情熱が湧き上がってくるのがわかりました。<br />
僕自身は普段あまりRubyを書く機会に恵まれないのですが、それでもRubyKaigiに今年も楽しく参加できたのは、<br />
Ruby言語の素晴らしさ故だけではなく、Rubyコミュニティとその文化に大きな魅力があったからに他なりません。<br />
なんと人間くさくて楽しい世界なんだろうといつの間にか感動している自分がいるのです。</p>
<p>ラストはスタンディングオべーション。いつまでも拍手が鳴り止まず、みんな感極まってた＞＜</p>
<p><img src="http://rest-term.com/wp-content/uploads/2011/07/rubykaigi_final.jpg" alt="" title="rubykaigi_final" width="450" height="450" class="alignnone size-full wp-image-2908" /></p>
<p>いつかまたどこかで、RubyKaigiのような素晴らしいRubyのイベントが開かれることを願っています。</p>
<p>　<br />
&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br />
ノベルティもいっぱいもらったよｖ</p>
<p><img src="http://rest-term.com/wp-content/uploads/2011/07/rubykaigi_goods.jpg" alt="" title="rubykaigi_goods" width="500" height="338" class="alignnone size-full wp-image-2910" /></p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/rest-term?a=8ts9zVK_T-U:FYKNNq3P-Xw:spdCosxkSQE"><img src="http://feeds.feedburner.com/~ff/rest-term?d=spdCosxkSQE" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/rest-term?a=8ts9zVK_T-U:FYKNNq3P-Xw:OAQBO0PjnPA"><img src="http://feeds.feedburner.com/~ff/rest-term?d=OAQBO0PjnPA" border="0"></img></a>
</div>]]></content:encoded>
			<wfw:commentRss>http://rest-term.com/archives/2907/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://rest-term.com/archives/2907/</feedburner:origLink></item>
		<item>
		<title>libmemcached c++ interface</title>
		<link>http://feedproxy.google.com/~r/rest-term/~3/D0KEnsjdC5Q/</link>
		<comments>http://rest-term.com/archives/2906/#comments</comments>
		<pubDate>Sun, 10 Jul 2011 07:01:48 +0000</pubDate>
		<dc:creator>wellflat</dc:creator>
				<category><![CDATA[tech/study]]></category>
		<category><![CDATA[c/c++]]></category>
		<category><![CDATA[database]]></category>

		<guid isPermaLink="false">http://rest-term.com/?p=2906</guid>
		<description><![CDATA[業務で libmemcached を利用した小さなモジュールを作ることになった。このライブラリにはC++インタフェースも用意されているのでCとC++の両方で試してみる。 環境は CentOS 5.6 (x86_64) , [...]]]></description>
			<content:encoded><![CDATA[<p>業務で <a href="http://libmemcached.org/libMemcached.html" title="libMemcached">libmemcached</a> を利用した小さなモジュールを作ることになった。このライブラリにはC++インタフェースも用意されているのでCとC++の両方で試してみる。</p>
<p>環境は CentOS 5.6 (x86_64) , gcc4.4,  libmemcached-devel</p>
<p>* インストール<br />
remiレポジトリから新しいバージョンを入れる</p>
<pre>
$ sudo yum install libmemcached-devel.x86_64 --enablerepo=remi
</pre>
<p>* 各基本操作(set, get, delete)を行う C版<br />
[cpp]<br />
// libmemcached_test.c</p>
<p>#include <stdio.h><br />
#include <string.h><br />
#include <unistd.h><br />
#include
<libmemcached/memcached.h>
<p>int main(void) {<br />
  memcached_st *memc;<br />
  memcached_server_st *servers = NULL;<br />
  memcached_return rc;<br />
  const char* server_list = &#8220;localhost:11211&#8243;;<br />
  const char* key= &#8220;key&#8221;;<br />
  const char* value= &#8220;value&#8221;;<br />
  char* retval = NULL;<br />
  size_t len = 0;<br />
  time_t expire = 0;<br />
  uint32_t flags = 0;</p>
<p>  // 初期化<br />
  memc = memcached_create(NULL);<br />
  servers = memcached_servers_parse(server_list);<br />
  rc = memcached_server_push(memc, servers);<br />
  memcached_server_list_free(servers);  // すぐに解放してOK<br />
  if(rc == MEMCACHED_SUCCESS) {<br />
    printf(&#8220;added server successfully\n&#8221;);<br />
  } else {<br />
    printf(&#8220;couldn&#8217;t add server: %s\n&#8221;, memcached_strerror(memc, rc));<br />
  }</p>
<p>  // set<br />
  rc = memcached_set(memc, key, strlen(key), value, strlen(value), expire, flags);<br />
  if(rc == MEMCACHED_SUCCESS) {<br />
    printf(&#8220;key stored successfully\n&#8221;);<br />
  } else {<br />
    printf(&#8220;couldn&#8217;t store key: %s\n&#8221;, memcached_strerror(memc, rc));<br />
  }</p>
<p>  // get<br />
  retval = memcached_get(memc, key, strlen(key), &#038;len, &#038;flags, &#038;rc);<br />
  if(rc == MEMCACHED_SUCCESS) {<br />
    printf(&#8220;key got successfully\n&#8221;);<br />
    printf(&#8220;value: %s\n&#8221;, retval);<br />
  } else {<br />
    printf(&#8220;couldn&#8217;t get key: %s\n&#8221;, memcached_strerror(memc, rc));<br />
  }<br />
  free(retval);  // 忘れないように!!</p>
<p>  // delete<br />
  rc = memcached_delete(memc, key, strlen(key), expire);<br />
  if(rc == MEMCACHED_SUCCESS) {<br />
    printf(&#8220;key deleted successfully\n&#8221;);<br />
  } else {<br />
    printf(&#8220;couldn&#8217;t delete key: %s\n&#8221;, memcached_strerror(memc, rc));<br />
  }</p>
<p>  // get<br />
  retval = memcached_get(memc, key, strlen(key), &#038;len, &#038;flags, &#038;rc);<br />
  if(rc == MEMCACHED_SUCCESS) {<br />
    printf(&#8220;key got successfully\n&#8221;);<br />
    printf(&#8220;value: %s\n&#8221;, retval);<br />
  } else if(rc == MEMCACHED_NOTFOUND) {  // keyが存在しない<br />
    printf(&#8220;couldn&#8217;t get key (not found): %s\n&#8221;, memcached_strerror(memc, rc));<br />
  } else if(rc == MEMCACHED_FAILURE) {  // memcachedに接続できない<br />
    printf(&#8220;couldn&#8217;t get key (failure): %s\n&#8221;, memcached_strerror(memc, rc));<br />
  }<br />
  free(retval);</p>
<p>  // 解放<br />
  memcached_free(memc);<br />
  return 0;<br />
}<br />
[/cpp]<br />
* コンパイル, 実行 (+ valgrindでメモリリークのチェック)</p>
<pre>
$ gcc44 -o -libmemcached_test libmemcached.c -L/usr/lib64 -lmemcached
$ valgrind ./libmemcached_test
==10510== Memcheck, a memory error detector
==10510== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==10510== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
==10510== Command: ./libmemcached_test
==10510==
added server successfully
key stored successfully
key got successfully
value: value
key deleted successfully
couldn't get key (not found): NOT FOUND
==10510==
==10510== HEAP SUMMARY:
==10510==     in use at exit: 0 bytes in 0 blocks
==10510==   total heap usage: 54 allocs, 54 frees, 42,214 bytes allocated
==10510==
==10510== All heap blocks were freed -- no leaks are possible
==10510==
==10510== For counts of detected and suppressed errors, rerun with: -v
==10510== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 4 from 4)
</pre>
<p>memcached_get 内で malloc が行われるので free で解放するのを忘れずに。また、key が存在しない場合は NOT FOUND が、memcachedに接続出来ない場合などは FAILURE が返る。</p>
<p>あと、libmemcachedは関係ないけど以下のような警告が出た場合は文字列の扱いを見直す必要がある。</p>
<pre>
deprecated conversion from string constant to ‘char*’
</pre>
<p>どうやら gcc4.2 から(? 文字列の扱いに厳しくなっていて、const を付けるべき所はちゃんと付けないとだめらしい。見かけない警告だったので少し焦ったけど、そういえば会社の開発環境は gcc4.1 だったかもしれない。</p>
<p>次はC++インタフェースを使う。APIの使い方は libmmecached/memcached.hpp を読んで確認。</p>
<p>* 各基本操作(set, get, delete)を行う C++版<br />
[cpp]<br />
#include <iostream><br />
// ヘッダは memcached.h ではなく memcached.hpp を include<br />
#include
<libmemcached/memcached.hpp>
<p>int main(void) {<br />
  using namespace std;<br />
  try {<br />
    // Memcache インスタンス生成<br />
    // 内部で memcached_create などが呼ばれて初期化処理が行われる<br />
    memcache::Memcache memc(&#8220;localhost:11211&#8243;);<br />
    string key = &#8220;mykey&#8221;;<br />
    string value_str = &#8220;myvalue&#8221;;<br />
    // value は std::string ではなく std::vector<char> で扱う<br />
    vector<char> value(value_str.begin(), value_str.end());<br />
    vector<char> retval;<br />
    time_t expire = 0;<br />
    uint32_t flags = 0;</p>
<p>    // set<br />
    //   bool set(const std::string &#038;key, const std::vector<char> &#038;value,<br />
    //            time_t expiration, uint32_t flags)<br />
    if(memc.set(key, value, expire, flags)) {<br />
      cout<<"key stored successfully"<<endl;<br />
    } else {<br />
      cerr<<"couldn't store key"<<endl;<br />
    }</p>
<p>    // get<br />
    //   bool get(const std::string &#038;key, std::vector<char> &#038;ret_val)<br />
    if(memc.get(key, retval)) {<br />
      cout<<&#8220;key got successfully&#8221;<<endl;<br />
      cout<<&#038;retval[0]<<endl;<br />
    } else {<br />
      cerr<<"couldn't get key"<<endl;<br />
    }</p>
<p>    // delete<br />
    //   bool remove(const std::string &#038;key)<br />
    if(memc.remove(key)) {<br />
      cout<<"key deleted successfully"<<endl;<br />
    } else {<br />
      cerr<<"couldn't delete key"<<endl;<br />
    }<br />
  } catch(const memcache::Error&#038; e) {<br />
    cerr<<"catch exception"<<endl;<br />
    cerr<<e.what()<<endl;<br />
    cerr<<e.getErrno()<<endl;<br />
  }<br />
  return 0;<br />
}<br />
[/cpp]<br />
注意する点は、value は std::string ではなく std::vector&lt;char&gt; で扱うところ。 </p>
<p>ほとんどのC++ APIは内部でC APIを呼んでいるだけの薄いラッパーになっていて、それらのC APIの結果が MEMCACHED_SUCCESS の場合は true を返す仕様になっている (一部のAPIは memcached_return_t を返す)。ただ、異常系処理において memcache::Error はあまり役に立たず、APIの戻り値チェックがいちいち必要なところは変わらない。。</p>
<p>C APIがどれくらいカバーされているかまだ調べきれてないけど、使うのは簡単なのでつまづくところは少なそう。</p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/rest-term?a=D0KEnsjdC5Q:M2RmhTgA1Ew:spdCosxkSQE"><img src="http://feeds.feedburner.com/~ff/rest-term?d=spdCosxkSQE" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/rest-term?a=D0KEnsjdC5Q:M2RmhTgA1Ew:OAQBO0PjnPA"><img src="http://feeds.feedburner.com/~ff/rest-term?d=OAQBO0PjnPA" border="0"></img></a>
</div>]]></content:encoded>
			<wfw:commentRss>http://rest-term.com/archives/2906/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://rest-term.com/archives/2906/</feedburner:origLink></item>
	</channel>
</rss>

