<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	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/"
	>

<channel>
	<title>橋本商会 &#187; 画像処理</title>
	<atom:link href="http://shokai.org/blog/archives/tag/%e7%94%bb%e5%83%8f%e5%87%a6%e7%90%86/feed" rel="self" type="application/rss+xml" />
	<link>http://shokai.org/blog</link>
	<description>なんか作ったりした記録を忘れないうちに書くblog</description>
	<lastBuildDate>Sun, 20 May 2012 10:50:20 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.2</generator>
<atom:link rel="hub" href="http://pubsubhubbub.appspot.com/" />
			<item>
		<title>canvasで画像処理</title>
		<link>http://shokai.org/blog/archives/5668</link>
		<comments>http://shokai.org/blog/archives/5668#comments</comments>
		<pubDate>Tue, 09 Aug 2011 04:49:15 +0000</pubDate>
		<dc:creator>shokai</dc:creator>
				<category><![CDATA[未分類]]></category>
		<category><![CDATA[canvas]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Tech]]></category>
		<category><![CDATA[画像処理]]></category>

		<guid isPermaLink="false">http://shokai.org/blog/?p=5668</guid>
		<description><![CDATA[html5のcanvasのピクセル単位の処理を試してみた。ここで動かせる → http://dev.shokai.org/test/canvas/cv/ ソースはここ https://github.com/shokai/ [...]]]></description>
			<content:encoded><![CDATA[<p>
html5のcanvasのピクセル単位の処理を試してみた。ここで動かせる → <a href="http://dev.shokai.org/test/canvas/cv/">http://dev.shokai.org/test/canvas/cv/</a><br />
<br />
ソースはここ <a href="https://github.com/shokai/js-canvas-test/tree/master/cv">https://github.com/shokai/js-canvas-test/tree/master/cv</a><br />
<br />
<br />
輪郭抽出とかしてみた<br />
<a href="http://www.flickr.com/photos/shokai/6024175915/" title="html5 canvas edge detect by shokai, on Flickr"><img src="http://farm7.static.flickr.com/6075/6024175915_ac6253602e_z.jpg" width="640" height="465" alt="html5 canvas edge detect"></a><br />
<br />
<br />
■canvasでのピクセル処理方法<br />
canvasはgetContext(&#8217;2d&#8217;)してからgetImageDataするとImageDataオブジェクトが手に入る。<br />
あとの処理はこんな感じ（画像をグレースケールにする例、<a href="https://github.com/shokai/js-canvas-test/blob/master/cv/main.js#L39">cv/main.jsの39行目あたり</a>）<br />
<pre class="prettyprint">
var canvas = $('canvas#img');<br />
var ctx = canvas[0].getContext('2d');<br />
<br />
var img = ctx.getImageData(0, 0, canvas[0].width, canvas[0].height);<br />
for(var i = 0; i &lt; img.data.length; i+=4){<br />
    var r = img.data[i]&amp;0xFF;<br />
    var g = img.data[i+1]&amp;0xFF;<br />
    var b = img.data[i+2]&amp;0xFF;<br />
    // var a = img.data[i+3]&amp;0xFF;<br />
    var gray = (r+g+b)/3;<br />
    img.data[i] = gray;<br />
    img.data[i+1] = gray;<br />
    img.data[i+2] = gray;<br />
}<br />
ctx.putImageData(img, 0, 0);<br />
</pre>
データが1次元配列になっていて、RGBAの順に値が格納されている。なのでImageData.dataの要素数はピクセル数の4倍になる。<br />
x,y座標でデータを取れる関数が無いので、自分で色々試して作ってみたけどどうしても3〜6倍ぐらい遅くなってしまうので1次元配列のまま扱う事にした。<br />
JavaScriptで多値を扱うには配列やオブジェクトを使わなければならないんだけどどうやらそれらの生成コストがかなり高いらしく、1ピクセルずつ処理したらひどく遅くなった。<br />
<br />
<br />
■クロスドメイン<br />
別ドメインの画像をcanvasに読み込んで、getImageDataはできない。chromeだと &#8220;Uncaught Error: SECURITY_ERR: DOM Exception 18&#8243; というエラーが開発パネルに出る。<br />
ローカルでの開発時にローカルのhtmlからローカルの画像を読み込んでgetImageDataしてもこのセキュリティエラーが出るので、<a href="https://gist.github.com/1090762">rubyで簡単なhttpサーバー</a>を作っておいたのでコレ使うといい。<br />
実行するとそのディレクトリでhttpサーバーが起動する。<br />
<br />
<br />
■実行速度<br />
端末によって実行速度が違う。<br />
<br />
下の画像は、今年の2月に買った11インチMacbook Airのchromeで、1000&#215;1000の画像を4段階量子化したところ。<br />
だいたい50ミリ秒で量子化できた。遅いパソコンでも毎秒数回は実行できそう。<br />
iPodTouch 4Gや初代iPadはMacbook Airより30倍ぐらい遅くて2秒弱かかる。Nexus Sが意外なことに更に遅くて25秒ぐらいかかる。Macの500倍遅い。<br />
<a href="http://www.flickr.com/photos/shokai/6024176027/" title="html5 canvas qunatize by shokai, on Flickr"><img src="http://farm7.static.flickr.com/6080/6024176027_be371f6078_z.jpg" width="640" height="465" alt="html5 canvas qunatize"></a><br />
</p>
]]></content:encoded>
			<wfw:commentRss>http://shokai.org/blog/archives/5668/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>scansnapで自炊した本をkindleで読めるように補正する(2)</title>
		<link>http://shokai.org/blog/archives/5337</link>
		<comments>http://shokai.org/blog/archives/5337#comments</comments>
		<pubDate>Tue, 14 Sep 2010 12:16:33 +0000</pubDate>
		<dc:creator>shokai</dc:creator>
				<category><![CDATA[未分類]]></category>
		<category><![CDATA[cpp]]></category>
		<category><![CDATA[Kindle]]></category>
		<category><![CDATA[kindlize_images]]></category>
		<category><![CDATA[OpenCV]]></category>
		<category><![CDATA[scansnap]]></category>
		<category><![CDATA[Tech]]></category>
		<category><![CDATA[画像処理]]></category>

		<guid isPermaLink="false">http://shokai.org/blog/?p=5337</guid>
		<description><![CDATA[橋本商会 scansnapで自炊した本をkindleで読めるように補正するをOpenCV使ってC++で書き直したら60倍速くなった。635ページのオライリーの本が約1分半で処理できたし、画質も前のJRuby版より少し読み [...]]]></description>
			<content:encoded><![CDATA[<p>
<a href="http://shokai.org/blog/archives/5327">橋本商会  scansnapで自炊した本をkindleで読めるように補正する</a>をOpenCV使ってC++で書き直したら60倍速くなった。635ページのオライリーの本が約1分半で処理できたし、画質も前のJRuby版より少し読みやすくなった気がする。<br />
<br />
<br />
こういう事をする。<br />
</p>
<blockquote><p>
■やっている事<br />
scansnapで自炊した本をそのままkindleで表示するには色々問題がある。<br />
<ul>
  <li>kindleの解像度は800×600だけど、画面内にページ位置等のUIが入るし画面を縦横回転させたり拡大縮小できるので、表示時にkindle上でリサイズされる。この時、線が細くて薄い部分が消滅してしまって、文字がかすれて読めなくなってしまう。</li>
  <li>scansnapで取り込んだままのPDFファイルでは、コントラストが弱くてとても読みづらい。</li>
  <li>ページの上下左右の余白が邪魔で、kindleに全画面表示した時に小さくなってしまう</li>
</ul>
<br />
kindle上でリサイズされる事を前提として、文字が消えてしまうような細い部分を太くしてやるしかない。<br />
しょうがないので、コントラストを上げたり、拡大縮小されるのを前提として先にアンチエイリアス？的な処理をしたり、余白を切り取ったり、リサイズしたりするようにした。<br />
上下左右を裁ち落とし→リサイズ→グレースケール化→2値化→黒の周りを#999999で太らせるという処理をしている。<br />
</p></blockquote>
<p>
<br />
上下左右裁ち落とし→グレースケール化→2値化→黒の周りを#000000で太らせる→リサイズ<br />
という処理に変えた。JRuby+javax.imageioでピクセル単位に処理すると、解像度が大きい画像を処理するのが苦しかったので先にリサイズするようにしていた。<br />
OpenCVは十分速いので、高解像度のまま処理して最後に縮小するように変更した。これでサイズが大きい本でも綺麗に変換できる。<br />
<br />
<br />
■使う<br />
OpenCV1.00以上と、boostのboost::system, boost::filesystem, boost::program_optionsのインストールが必要。少なくともMacとUbuntuでは動く。<br />
その辺は<a href="http://github.com/shokai/scansnap_adjust_images_kindle">github/shokai/scansnap_adjust_images_kindle</a>に書いた。<br />
<br />
<br />
gitリポジトリを持ってきてコンパイルする<br />
<pre class="prettyprint">
git clone git://github.com/shokai/scansnap_adjust_images_kindle.git<br />
cd scansnap_adjust_images_kindle/kindlize_images<br />
<br />
# for Mac<br />
make -f Makefile.macosx<br />
</pre>
<br />
./configureの作り方がよくわからないのでとりあえずMac用のMakefileだけ置いてある。<br />
Linuxとかでも、opencv、boost::filesystem、boost::progoram_optionsをインストールしてあればコンパイルはできる。(この組み合わせは色々なOSでよく使ってる)<br />
<br />
<br />
オライリーの判型の本をkindle用に補正する。上下左右の余白を削除して白黒になって文字が太くなる。イラストは見づらくなるかもしれない。<br />
<pre class="prettyprint">
mkdir ~/tmp/mybook_kindle<br />
./kindlize_images --help<br />
./kindlize_images -i ~/tmp/mybook/ -o ~/tmp/mybook_kindle/ -t 190 -w 1200 -h 1600 --cleft 120 --cright 120 --ctop 150 --cbottom 150<br />
</pre>
<br />
<br />
はじめにPDFから連番のJPEG画像に書き出して、それからこのツールを使って変換して、最後に連番画像をPDFにまとめるという使い方を想定している。<br />
PDFから連番画像を書き出す方法は、前の記事やgithubのREADME.mdに書いた。<br />
連番画像を1つのPDFへ結合するのは、gitリポジトリの中に一緒にimages2pdf.appというautomatorアプリを入れて置いたのでそれを使うと良い。<br />
<br />
<br />
■ソースコード<br />
100行切った。boost::filesystemのおかげでディレクトリ内一括処理が書きやすかった。<br />
JRuby使って書くよりもコードが35行短くなったけど、最初に試行錯誤するにはJRuby+javax.imageioでやる方が手軽だったな。<br />
<br />
<a href="http://github.com/shokai/scansnap_adjust_images_kindle/blob/master/kindlize_images/kindlize_images.cpp">kindlize_images/kindlize_images.cpp at master from shokai&apos;s scansnap_adjust_images_kindle &#8211; GitHub</a><br />
<pre class="prettyprint">
#include "cv.h"<br />
#include "highgui.h"<br />
#include &lt;boost/program_options.hpp&gt;<br />
#include &lt;boost/filesystem/operations.hpp&gt;<br />
#include &lt;boost/filesystem/path.hpp&gt;<br />
#include &lt;boost/filesystem/fstream.hpp&gt;<br />
#include &lt;iostream&gt;<br />
using namespace boost;<br />
using namespace std;<br />
namespace fs = boost::filesystem;<br />
<br />
IplImage *adjust_image(IplImage *img, program_options::variables_map argmap){<br />
  int cleft = argmap["cleft"].as&lt;int&gt;();<br />
  int cright = argmap["cright"].as&lt;int&gt;();<br />
  int ctop = argmap["ctop"].as&lt;int&gt;();<br />
  int cbottom = argmap["cbottom"].as&lt;int&gt;();<br />
<br />
  const int w = img-&gt;width - cleft - cright;<br />
  const int h = img-&gt;height - ctop - cbottom;<br />
  <br />
  cvSetImageROI(img, cvRect(cleft, ctop, w, h));<br />
<br />
  IplImage *img_gray = cvCreateImage(cvSize(w,h), IPL_DEPTH_8U, 1);<br />
  cvCvtColor(img, img_gray, CV_BGR2GRAY);<br />
<br />
  // 2値化<br />
  IplImage *img_bin = cvCreateImage(cvSize(w,h), IPL_DEPTH_8U, 1);<br />
  cvThreshold(img_gray, img_bin, argmap["threshold"].as&lt;int&gt;(), 255, CV_THRESH_BINARY);<br />
<br />
  // 文字を太らせる<br />
  cvErode(img_bin, img_bin, NULL, 1);<br />
<br />
  // リサイズ<br />
  int width = argmap["width"].as&lt;int&gt;();<br />
  int height = argmap["height"].as&lt;int&gt;();<br />
  IplImage *img_resized;<br />
  double scale = ((double)h)/w;<br />
  if(((double)height)/width &lt; scale){ // 縦長<br />
    img_resized = cvCreateImage(cvSize((int)(height/scale), height), IPL_DEPTH_8U, 1);<br />
  }<br />
  else{ // 横長<br />
    img_resized = cvCreateImage(cvSize(width, (int)(scale*width)), IPL_DEPTH_8U, 1);<br />
  }<br />
  cvResize(img_bin, img_resized, CV_INTER_LINEAR);<br />
<br />
  cvReleaseImage(&amp;img_bin);<br />
  cvReleaseImage(&amp;img_gray);<br />
  return img_resized;<br />
}<br />
<br />
int main(int argc, char* argv[]) {<br />
  program_options::options_description opts("options");<br />
  opts.add_options()<br />
    ("help", "ヘルプを表示")<br />
    ("width,w", program_options::value&lt;int&gt;(), "output width")<br />
    ("height,h", program_options::value&lt;int&gt;(), "output height")<br />
    ("threshold,t", program_options::value&lt;int&gt;(), "binarize threshold")<br />
    ("input,i", program_options::value&lt;string&gt;(), "input directory name")<br />
    ("output,o", program_options::value&lt;string&gt;(), "output directory name")<br />
    ("cleft", program_options::value&lt;int&gt;(), "crop left (pixel)")<br />
    ("cright", program_options::value&lt;int&gt;(), "crop right (pixel)")<br />
    ("ctop", program_options::value&lt;int&gt;(), "crop top (pixel)")<br />
    ("cbottom", program_options::value&lt;int&gt;(), "crop bottom (pixel)");<br />
  program_options::variables_map argmap;<br />
  program_options::store(parse_command_line(argc, argv, opts), argmap);<br />
  program_options::notify(argmap);<br />
  if (argmap.count("help") || !argmap.count("input") || !argmap.count("output") ||<br />
      !argmap.count("threshold")) {<br />
    cerr &lt;&lt; "[input, output] required" &lt;&lt; endl;<br />
    cerr &lt;&lt; opts &lt;&lt; endl;<br />
    return 1;<br />
  }<br />
<br />
  string in_dir = argmap["input"].as&lt;string&gt;();<br />
  fs::path path = complete(fs::path(in_dir, fs::native));<br />
  fs::directory_iterator end;<br />
  for (fs::directory_iterator i(path); i!=end; i++){<br />
    string img_fullname = in_dir + i-&gt;leaf();<br />
    cout &lt;&lt; img_fullname &lt;&lt; endl;<br />
    IplImage *img, *img_result;<br />
    img = cvLoadImage(img_fullname.c_str());<br />
    if(!img){<br />
      cerr &lt;&lt; "image file load error" &lt;&lt; endl;<br />
    }<br />
    else{<br />
      img_result = adjust_image(img, argmap);<br />
      string out_filename = argmap["output"].as&lt;string&gt;() + "/" + i-&gt;leaf();<br />
      cvSaveImage(out_filename.c_str(), img_result);<br />
      cvReleaseImage(&amp;img);<br />
      cvReleaseImage(&amp;img_result);<br />
    }    <br />
  }<br />
}<br />
</pre>
</p>
]]></content:encoded>
			<wfw:commentRss>http://shokai.org/blog/archives/5337/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>scansnapで自炊した本をkindleで読めるように補正する</title>
		<link>http://shokai.org/blog/archives/5327</link>
		<comments>http://shokai.org/blog/archives/5327#comments</comments>
		<pubDate>Tue, 07 Sep 2010 01:51:51 +0000</pubDate>
		<dc:creator>shokai</dc:creator>
				<category><![CDATA[未分類]]></category>
		<category><![CDATA[Automator]]></category>
		<category><![CDATA[javax.imageio]]></category>
		<category><![CDATA[JRuby]]></category>
		<category><![CDATA[Kindle]]></category>
		<category><![CDATA[kindlize_images]]></category>
		<category><![CDATA[Tech]]></category>
		<category><![CDATA[画像処理]]></category>

		<guid isPermaLink="false">http://shokai.org/blog/?p=5327</guid>
		<description><![CDATA[最近新しく発売されたkindle黒が届いたが、scansnapで作ったPDFを入れてみたら文字がかすれて読めなくなって悲しかったので、なんとかするスクリプトをJRubyで作った。 http://github.com/sh [...]]]></description>
			<content:encoded><![CDATA[<p>
最近新しく発売されたkindle黒が届いたが、scansnapで作ったPDFを入れてみたら文字がかすれて読めなくなって悲しかったので、なんとかするスクリプトをJRubyで作った。<br />
<a href="http://github.com/shokai/scansnap_adjust_images_kindle">http://github.com/shokai/scansnap_adjust_images_kindle</a>に連番画像の補正プログラムと、連番画像をPDFにまとめるautomatorアプリを置いておいた。<br />
JRuby1.5+Java1.5以上なら動くと思う。<br />
<br />
scansnapの設定は<a href="http://shokai.org/blog/archives/4999">橋本商会  scansnapと裁断機を買って本を電子化しまくる</a>に書いたのと同じ。白黒ページはスーパーファインのグレースケールで取り込んでいる。<br />
<br />
文庫本を補正した場合こうなる。<br />
日本語の文庫本を読めるようにする事を目的に作ってるので、他の判型だとちょっとダメかも（理由は後述）<br />
<a href="http://www.flickr.com/photos/shokai/4963909034/" title="R0015336.JPG by shokai, on Flickr"><img src="http://farm5.static.flickr.com/4110/4963909034_c84d464e01.jpg" width="375" height="500" alt="R0015336.JPG" /></a><br />
<br />
<br />
詳解OpenCVという本を、補正をかけずに表示したところ。<br />
文字の中でも線の細い部分だけが、kindle上でリサイズした時に消滅するので字が読めない<br />
<a href="http://www.flickr.com/photos/shokai/4963472917/" title="R0015339.JPG by shokai, on Flickr"><img src="http://farm5.static.flickr.com/4145/4963472917_ed20d70e4a.jpg" width="500" height="375" alt="R0015339.JPG" /></a><br />
<br />
<br />
詳解OpenCVの同じページ補正するとこうなる。<br />
それなりに読めるが、線の密度とか計算せずに全て太らせているのでアルファベットや記号がところどころ融合して読みづらい。<br />
<a href="http://www.flickr.com/photos/shokai/4963472211/" title="R0015337.JPG by shokai, on Flickr"><img src="http://farm5.static.flickr.com/4150/4963472211_ddb26d3139.jpg" width="500" height="375" alt="R0015337.JPG" /></a><br />
<br />
<br />
■やっている事<br />
scansnapで自炊した本をそのままkindleで表示するには色々問題がある。<br />
<ul>
  <li>kindleの解像度は800&#215;600だけど、画面内にページ位置等のUIが入るし画面を縦横回転させたり拡大縮小できるので、表示時にkindle上でリサイズされる。この時、線が細くて薄い部分が消滅してしまって、文字がかすれて読めなくなってしまう。</li>
  <li>scansnapで取り込んだままのPDFファイルでは、コントラストが弱くてとても読みづらい。</li>
  <li>ページの上下左右の余白が邪魔で、kindleに全画面表示した時に小さくなってしまう</li>
</ul>
<br />
kindle上でリサイズされる事を前提として、文字が消えてしまうような細い部分を太くしてやるしかない。<br />
しょうがないので、コントラストを上げたり、拡大縮小されるのを前提として先にアンチエイリアス？的な処理をしたり、余白を切り取ったり、リサイズしたりするようにした。<br />
上下左右を裁ち落とし→リサイズ→グレースケール化→2値化→黒の周りを#999999で太らせるという処理をしている。<br />
<br />
<br />
<br />
■画像を補整する<br />
<br />
まずPDFから連番画像に戻す。MacやLinuxなら、pdfimagesというツールを使うといい。<br />
<pre>
# for Mac<br />
% sudo port install pdfX<br />
<br />
# for Ubuntu<br />
% sudo apt-get install xpdf-utils<br />
<br />
# extract images<br />
% mkdir -p ~/tmp/mybook<br />
% pdfimages -j ~/Documents/book/mybook.pdf ~/tmp/mybook/<br />
</pre>
もしくはAcrobat Proを買って「書き出し」でもできる。<br />
<br />
<br />
補正する。githubから俺の作ったツールを持ってくる。<br />
<pre>
% git clone git://github.com/shokai/scansnap_adjust_images_kindle.git<br />
% cd scansnap_adjust_images_kindle<br />
</pre>
<br />
<br />
で、先ほど作った連番画像のディレクトリを元にして、補正した連番画像を作る<br />
<pre>
% jruby kindlize_images.rb -help<br />
% jruby kindlize_images.rb -i ~/tmp/mybook/ -o ~/tmp/mybook_kindle/ -w 1200 -h 1600 -cl 150 -cr 150 -ct 120 -cb 180 -t 240<br />
</pre>
出力ディレクトリは無ければ自動的に作成される。<br />
<br />
上はオライリーの本の場合の裁ち落とし。<br />
文庫の場合はこんなパラメータ<br />
<pre>
jruby kindlize_images.rb -i ~/tmp/mybook/ -o ~/tmp/mybook_kindle/ -w 1200 -h 1600 -cl 30 -cr 30 -ct 80 -cb 115 -t 220<br />
</pre>
<br />
<br />
作った連番画像をPDFに戻すには、cloneしたgitリポジトリ内のimages2pdf.appを使えばいい。MacのAutomator.appで作ったアプリ。<br />
Preview.appでもPDFのページを並べ替えたりできるが、連番ページを順番に入れる事はできなかったので作った。<br />
<br />
<br />
<br />
■問題点<br />
文字を太らせる処理は、1 pixelずつグレーで周りを塗っているだけなので、文字のサイズや密度を考慮していない。<br />
処理速度も遅い。300ページの本でも1時間近くかかる。まあ読む速度より遅くはないからいいや。もっとたくさんkindleに入れたくなったらC++でOpenCV使って書き直す。<br />
<br />
<br />
■ソース<br />
Javaのjavax.imageioをRubyから使うのが簡単な画像処理に便利なので、JRubyを使ってます<br />
<br />
<a href="http://github.com/shokai/scansnap_adjust_images_kindle/blob/master/kindlize_images.rb">kindlize_images.rb</a><br />
<pre class="prettyprint">
#!/usr/bin/env jruby<br />
# -*- coding: utf-8 -*-<br />
# ディレクトリ内の画像を全てkindleで読みやすいように2値化、アンチエイリアス、リサイズする<br />
# イラストも2値化されるので、小説などの文字ページ専用。<br />
require 'rubygems'<br />
require 'ArgsParser'<br />
require 'java'<br />
import 'java.lang.System'<br />
import 'javax.imageio.ImageIO'<br />
import 'java.awt.image.BufferedImage'<br />
import 'java.awt.image.WritableRaster'<br />
import 'java.awt.image.AffineTransformOp'<br />
import 'java.awt.geom.AffineTransform'<br />
import 'java.awt.Color'<br />
$KCODE = 'u'<br />
<br />
parser = ArgsParser.parser<br />
parser.bind(:width, :w, 'width')<br />
parser.bind(:height, :h, 'height')<br />
parser.bind(:input, :i, 'input')<br />
parser.bind(:output, <img src='http://shokai.org/blog/wp-includes/images/smilies/icon_surprised.gif' alt=':o' class='wp-smiley' /> , 'output')<br />
parser.bind(:cleft, :cl, 'crop left (pixel)')<br />
parser.bind(:cright, :cr, 'crop right (pixel)')<br />
parser.bind(:ctop, :ct, 'crop top (pixel)')<br />
parser.bind(:cbottom, :cb, 'crop bottom (pixel)')<br />
parser.bind(:threshold, :t, 'threshold of binarize (0-255)')<br />
first, params = parser.parse(ARGV)<br />
<br />
if !parser.has_params([:width, :height, :input, <img src='http://shokai.org/blog/wp-includes/images/smilies/icon_surprised.gif' alt=':o' class='wp-smiley' /> utput, :threshold]) or<br />
    parser.has_option(:help)<br />
  puts parser.help<br />
  puts 'e.g. jruby kindlize_images.rb -i /path/to/in_dir/ -o /path/to/out_dir/ -w 1200 -h 1600 -t 240'<br />
  puts 'e.g. jruby kindlize_images.rb -i /path/to/in_dir/ -o /path/to/out_dir/ -w 1200 -h 1600 -cleft 50 -cright 50 -ctop 80 -cbottom 100 -t 240'<br />
  exit 1<br />
end<br />
  <br />
p params<br />
WIDTH = params[:width].to_i<br />
HEIGHT = params[:height].to_i<br />
Dir.mkdir(params[:output]) unless File.exists? params[:output]<br />
params[:output] += '/' unless params[:output] =~ /\/$/<br />
<br />
Dir.glob(params[:input]+'*').each{|i|<br />
  puts i<br />
  begin<br />
    img = ImageIO.read(java.io.File.new(i))<br />
  rescue =&gt; e<br />
    STDERR.puts 'image load error'<br />
    STDERR.puts e<br />
    next<br />
  end<br />
  puts "size : #{img.width}x#{img.height}"<br />
  <br />
  if params[:cleft] or params[:cright] or params[:ctop] or params[:cbottom]<br />
    params[:cleft] = 0 unless params[:cleft]<br />
    params[:cright] = 0 unless params[:cright]<br />
    params[:ctop] = 0 unless params[:ctop]<br />
    params[:cbottom] = 0 unless params[:cbottom]<br />
    img = img.get_subimage(params[:cleft].to_i, params[:ctop].to_i,<br />
                           img.width-params[:cleft].to_i-params[:cright].to_i, <br />
                           img.height-params[:ctop].to_i-params[:cbottom].to_i)<br />
    puts "crop : #{img.width}x#{img.height}"<br />
  end<br />
<br />
  # リサイズ<br />
  if img.width.to_f/img.height &gt; WIDTH.to_f/HEIGHT # 指定されたWIDTH,HEIGHTよりも横長の画像<br />
    scale = WIDTH.to_f/img.width<br />
    img_resized = BufferedImage.new(WIDTH, (scale*img.height).to_i, img.type)<br />
  else # 縦長<br />
    scale = HEIGHT.to_f/img.height<br />
    img_resized = BufferedImage.new((scale*img.width).to_i, HEIGHT, img.type)<br />
  end<br />
  puts "scale : #{scale}"<br />
  AffineTransformOp.new(AffineTransform.getScaleInstance(scale, scale), nil).filter(img, img_resized)<br />
  puts "resized : #{img_resized.width}x#{img_resized.height}"<br />
<br />
  # 固定サイズ画像にはめこむ<br />
  img_frame = BufferedImage.new(WIDTH, HEIGHT, img.type)<br />
  graph = img_frame.graphics<br />
  graph.color = Color.new(255,255,255)<br />
  graph.fillRect(0, 0, WIDTH, HEIGHT)<br />
  if WIDTH &gt; img_resized.width<br />
    graph.drawImage(img_resized, (WIDTH-img_resized.width)/2, 0, nil)<br />
  else<br />
    graph.drawImage(img_resized, 0, (HEIGHT-img_resized.height)/2, nil)<br />
  end<br />
  puts "set in frame : #{img_frame.width}x#{img_frame.height}"<br />
  img = img_frame<br />
<br />
  # 2値化<br />
  for y in 0...img.height do<br />
    for x in 0...img.width do<br />
      pix = img.get_rgb(x, y)<br />
      r = pix &gt;&gt; 16 &amp; 0xFF<br />
      g = pix &gt;&gt; 8 &amp; 0xFF<br />
      b = pix &amp; 0xFF<br />
      gray = (r+g+b)/3<br />
      if gray &gt; params[:threshold].to_i<br />
        pix = 0xFFFFFF<br />
      else<br />
        pix = 0x000000<br />
      end<br />
      img.set_rgb(x, y, pix)<br />
    end<br />
  end<br />
  puts "binarize"<br />
<br />
  # 膨張<br />
  img_dilated = BufferedImage.new(img.width, img.height, img.type)<br />
  for y in 1...img.height-1 do<br />
    for x in 1...img.width-1 do<br />
      if img.get_rgb(x, y)&amp;0xFF == 0<br />
        img_dilated.set_rgb(x, y, 0x000000)<br />
      elsif img.get_rgb(x-1, y)&amp;0xFF == 0 or img.get_rgb(x+1, y)&amp;0xFF == 0 or<br />
          img.get_rgb(x, y-1)&amp;0xFF == 0 or img.get_rgb(x,y+1)&amp;0xFF == 0<br />
        img_dilated.set_rgb(x, y, 0x999999)<br />
      else<br />
        img_dilated.set_rgb(x, y, 0xFFFFFF)<br />
      end<br />
    end<br />
  end<br />
  img = img_dilated<br />
  puts "dilate"<br />
<br />
  out_name = i.split(/\//).last<br />
  out_type = 'bmp'<br />
  begin<br />
    ImageIO.write(img, out_type, java.io.File.new(params[:output]+out_name))<br />
    puts 'saved! =&gt; '+params[:output]+out_name<br />
  rescue =&gt; e<br />
    STDERR.puts 'image save error'<br />
    STDERR.puts e<br />
    next<br />
  end<br />
}<br />
</pre>
</p>
]]></content:encoded>
			<wfw:commentRss>http://shokai.org/blog/archives/5327/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>ZeroMQでOpenCV cvOpticalFlowのデータを配信する</title>
		<link>http://shokai.org/blog/archives/5311</link>
		<comments>http://shokai.org/blog/archives/5311#comments</comments>
		<pubDate>Wed, 11 Aug 2010 10:39:22 +0000</pubDate>
		<dc:creator>shokai</dc:creator>
				<category><![CDATA[未分類]]></category>
		<category><![CDATA[cpp]]></category>
		<category><![CDATA[OpenCV]]></category>
		<category><![CDATA[OpticalFlow]]></category>
		<category><![CDATA[Ruby]]></category>
		<category><![CDATA[Tech]]></category>
		<category><![CDATA[ZeroMQ]]></category>
		<category><![CDATA[画像処理]]></category>

		<guid isPermaLink="false">http://shokai.org/blog/?p=5311</guid>
		<description><![CDATA[1VQ9がZeroMQで遊んでたので、俺も橋本商会 cvCalcOpticalFlowBMをZeroMQでpubしてみた。ZeroMQはなんか面倒な事を適当にやってくれるmessaging libraryで、色々な言語の [...]]]></description>
			<content:encoded><![CDATA[<p>
1VQ9がZeroMQで遊んでたので、俺も<a href="http://shokai.org/blog/archives/4820">橋本商会  cvCalcOpticalFlowBM</a>をZeroMQでpubしてみた。<a href="http://www.zeromq.org/">ZeroMQ</a>はなんか面倒な事を適当にやってくれるmessaging libraryで、色々な言語のバインディングが出ている。<br />
<br />
ZeroMQのpubはセンサーのデータとかを垂れ流しにするのに都合がよさそう。<br />
clientが何台いるかどうかを考えないで良いし、pub/subどちらが先に起動していても適当に接続処理をしてくれる。cookbookを見てるとmulticastやthread間通信にも使ってる。とりあえずセンサーデータ垂れ流しという用途に俺はよく使いそう。<br />
<br />
<br />
ソースコードは<a href="http://github.com/shokai/zeromq-study">githubに置いた</a>。<br />
他にも<a href="http://github.com/shokai/zeromq-study/tree/7ac74e64a0343ea87f55b94fb26baa29cf652668/count">単純なカウントアップのpub/sub両方をC++/C/Rubyで書いた（6種）</a>のと、<a href="http://github.com/shokai/zeromq-study/tree/7ac74e64a0343ea87f55b94fb26baa29cf652668/twitter-stream">twitterのstream APIをZMQ_PUBで中継する</a>のを作ってみた（解説：<a href="http://d.hatena.ne.jp/shokai/20100810/1281455530">zeromqインストール、twitter stream APIを中継 &#8211; 橋本詳解</a>）。特にstream APIのHUB的存在は便利。<br />
<br />
あと、<a href="http://mongrel2.org/">mongrel2</a>が<a href="http://nichol.as/zeromq-an-introduction">WebSocketやXMLSocketとZeroMQの接続をしてくれるようになる</a>らしくて期待してる。<br />
<br />
<br />
<br />
受信側<br />
<a href="http://github.com/shokai/zeromq-study/blob/d2f3e4f69881a517a9c0445372b7b366e0a74f7b/opticalflow/opticalflow_sub.rb">opticalflow_sub.rb</a><br />
<pre class="prettyprint">
#!/usr/bin/env ruby<br />
require 'rubygems'<br />
require 'zmq'<br />
<br />
ctx = ZMQ::Context.new<br />
sock= ctx.socket(ZMQ::SUB)<br />
sock.connect('tcp://127.0.0.1:5000')<br />
sock.setsockopt(ZMQ::SUBSCRIBE, 'opticalflow')<br />
<br />
loop do<br />
  puts sock.recv()<br />
end<br />
</pre>
<br />
<br />
送信側。これを適当なパソンコにUSBカメラ刺して動かしておけば、別のマシンから動きが取れる！!<br />
<a href="http://github.com/shokai/zeromq-study/blob/d2f3e4f69881a517a9c0445372b7b366e0a74f7b/opticalflow/opticalflow_pub.cpp">opticalflow_pub.cpp</a><br />
<pre class="prettyprint">
// http://opencv.jp/sample/optical_flow.html<br />
#include &lt;cv.h&gt;<br />
#include &lt;highgui.h&gt;<br />
#include &lt;cxcore.h&gt;<br />
#include &lt;ctype.h&gt;<br />
#include &lt;stdio.h&gt;<br />
#include &lt;iostream&gt;<br />
#include &lt;boost/format.hpp&gt;<br />
#include &lt;zmq.hpp&gt;<br />
<br />
using namespace std;<br />
using namespace boost;<br />
<br />
void detect_flow(IplImage *img, IplImage *img_p, IplImage *dst);<br />
zmq::context_t ctx(1);<br />
zmq::socket_t sock(ctx, ZMQ_PUB);<br />
<br />
int main(int argc, char* argv[]) {<br />
  IplImage *img = NULL;<br />
  CvCapture *capture = NULL;<br />
  capture = cvCreateCameraCapture(0);<br />
  //capture = cvCaptureFromAVI("test.mov");<br />
  if(capture == NULL){<br />
    cerr &lt;&lt; "capture device not found!!" &lt;&lt; endl;<br />
    return -1;<br />
  }<br />
<br />
  sock.bind("tcp://127.0.0.1:5000");<br />
<br />
  CvSize size = cvSize(320, 240);<br />
  IplImage *img_resized = cvCreateImage(size, IPL_DEPTH_8U, 3);<br />
  IplImage *img_gray = cvCreateImage(size, IPL_DEPTH_8U, 1);<br />
  IplImage *img_gray_p = cvCreateImage(size, IPL_DEPTH_8U, 1);<br />
  IplImage *img_dst = cvCreateImage(size, IPL_DEPTH_8U, 3);<br />
<br />
  char winNameCapture[] = "Capture";<br />
  cvNamedWindow(winNameCapture, CV_WINDOW_AUTOSIZE);<br />
  <br />
  while (1) {<br />
    img = cvQueryFrame(capture);<br />
    cvResize(img, img_resized);<br />
    cvCvtColor(img_resized, img_gray, CV_BGR2GRAY);<br />
    cvCopy(img_resized, img_dst);<br />
    detect_flow(img_gray, img_gray_p, img_dst);<br />
    cvShowImage(winNameCapture, img_dst);<br />
    cvCopy(img_gray, img_gray_p);<br />
    if (cvWaitKey(10) == 'q') break;<br />
  }<br />
  <br />
  cvReleaseCapture(&amp;capture);<br />
  cvDestroyWindow(winNameCapture);<br />
  <br />
  return 0;<br />
}<br />
<br />
void detect_flow(IplImage *src_img1, IplImage *src_img2, IplImage *dst_img){<br />
  int i, j, dx, dy, rows, cols;<br />
  int block_size = 24;<br />
  int shift_size = 10;<br />
  CvMat *velx, *vely;<br />
  CvSize block = cvSize(block_size, block_size);<br />
  CvSize shift = cvSize(shift_size, shift_size);<br />
  CvSize max_range = cvSize(50, 50);<br />
<br />
  rows = int(ceil (double (src_img1-&gt;height) / block_size));<br />
  cols = int(ceil (double (src_img1-&gt;width) / block_size));<br />
  velx = cvCreateMat(rows, cols, CV_32FC1);<br />
  vely = cvCreateMat(rows, cols, CV_32FC1);<br />
  cvSetZero(velx);<br />
  cvSetZero(vely);<br />
<br />
  cvCalcOpticalFlowBM(src_img1, src_img2, block, shift, max_range, 0, velx, vely);<br />
  string result_str = string("");<br />
  for (i = 0; i &lt; velx-&gt;width; i++) {<br />
    for (j = 0; j &lt; vely-&gt;height; j++) {<br />
      dx = (int)cvGetReal2D(velx, j, i);<br />
      dy = (int)cvGetReal2D(vely, j, i);<br />
      cvLine(dst_img, cvPoint(i * block_size, j * block_size),<br />
              cvPoint(i * block_size + dx, j * block_size + dy), CV_RGB(255, 0, 0), 1, CV_AA, 0);<br />
      if(dx != 0 || dy != 0){<br />
	result_str += str(format("[%d,%d,%d,%d]") % (i*block_size) % (j*block_size) % dx % dy);<br />
      }<br />
    }<br />
  }<br />
  if(result_str.size() &gt; 0){<br />
    result_str = str(format("opticalflow %s") % result_str);<br />
    cout &lt;&lt; result_str &lt;&lt; endl;<br />
    zmq::message_t msg(result_str.size()+1); // ZeroMQ<br />
    memcpy(msg.data(), result_str.c_str(), result_str.size()+1);<br />
    sock.send(msg);<br />
  }<br />
}<br />
</pre>
<br />
<br />
<pre>
g++ -O opticalflow_pub.cpp -o opticalflow_pub.bin -I/opt/local/include/opencv -lcv -lcvaux -lcxcore -lhighgui  -I/usr/local/include /usr/local/lib/libzmq.a<br />
</pre>
<br />
<br />
これで動いた座標とその方向 [x,y,dx,dy] が連続で送られてくる。<br />
<pre>
opticalflow [48,216,4,-29][72,216,0,-29][96,216,0,-29][264,216,-9,-29]<br />
opticalflow [48,216,4,-29][96,216,0,-29][120,216,0,-29][264,216,-9,-29]<br />
opticalflow [48,216,4,-29][96,168,0,10][96,192,-10,-20][96,216,0,-29][120,192,0,10][120,216,0,-29][144,216,0,-29][168,216,0,-29][192,48,-10,0][192,216,0,-29][216,216,0,-29][264,216,-9,-29]<br />
opticalflow [96,168,0,10][96,192,-10,-10][96,216,0,-29][120,168,0,10][120,192,0,10][120,216,0,-29][144,216,0,-29][168,48,0,10][168,96,0,10][168,216,0,-29][192,72,0,40][192,96,0,-30][192,216,0,-29][264,216,-9,-29]<br />
opticalflow [48,216,4,-29][96,168,0,10][96,216,0,-29][120,168,0,10][120,192,0,10][120,216,0,-29][144,216,0,-29][168,48,10,0][168,96,0,10][168,216,0,-29][192,96,0,-30][192,216,0,-29][264,216,-9,-29]<br />
</pre>
</p>
]]></content:encoded>
			<wfw:commentRss>http://shokai.org/blog/archives/5311/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>JRubyでglitch iconを作る(2)</title>
		<link>http://shokai.org/blog/archives/5291</link>
		<comments>http://shokai.org/blog/archives/5291#comments</comments>
		<pubDate>Thu, 05 Aug 2010 23:46:05 +0000</pubDate>
		<dc:creator>shokai</dc:creator>
				<category><![CDATA[未分類]]></category>
		<category><![CDATA[glitch]]></category>
		<category><![CDATA[glitchicon]]></category>
		<category><![CDATA[javax.imageio]]></category>
		<category><![CDATA[JRuby]]></category>
		<category><![CDATA[Tech]]></category>
		<category><![CDATA[Twitter]]></category>
		<category><![CDATA[画像処理]]></category>

		<guid isPermaLink="false">http://shokai.org/blog/?p=5291</guid>
		<description><![CDATA[プラグイン機構を採用し、ランダムにいろいろ作れるようにしてみた。 普通はjpegとかのバイナリを直接いじるみたいだけどよく分からないのでJRubyでjavax.imageioを使ってやっている。 ランダムに96個作ってみ [...]]]></description>
			<content:encoded><![CDATA[<p>
プラグイン機構を採用し、ランダムにいろいろ作れるようにしてみた。<br />
普通はjpegとかのバイナリを直接いじるみたいだけどよく分からないのでJRubyでjavax.imageioを使ってやっている。<br />
<br />
ランダムに96個作ってみて8&#215;12に敷き詰めてみたのがこれ。<br />
で、定期的にランダムに作って<a href="http://twitter.com/shokai">twitterのアイコンとしてアップロードしてる</a>。<br />
<a href="http://www.flickr.com/photos/shokai/4861585419/" title="montage96 by shokai, on Flickr"><img src="http://farm5.static.flickr.com/4123/4861585419_d3310346ec_b.jpg" width="694" height="1024" alt="montage96" /></a><br />
敷き詰めるのはImageMagickと一緒にインストールされるmontageコマンドでできる <a href="http://d.hatena.ne.jp/shokai/20100803/1280785691">montageコマンド &#8211; 橋本詳解</a><br />
<br />
ソースコードは全て<a href="http://github.com/shokai/glitchicon">githubに置いた</a>。<br />
<br />
<br />
■仕組み<br />
単機能モジュールをランダムに連結する事である程度ランダムな画像を生成できる。<br />
javax.imageio.BufferedImageのインスタンスを受け取り、少し加工して返すというJRubyのmoduleをプラグインとし、それらを<a href="http://github.com/shokai/glitchicon/blob/master/glitch.rb">Glitch</a>というclassがランダムに呼び出す。<br />
<br />
<br />
今のところプラグインは23種類ある。単体だとそれほど派手にはならない。<br />
<a href="http://www.flickr.com/photos/shokai/4862180974/" title="glitchicon montage by shokai, on Flickr"><img src="http://farm5.static.flickr.com/4118/4862180974_296b734bd0.jpg" width="500" height="492" alt="glitchicon montage" /></a><br />
<br />
<br />
<br />
■プラグイン<br />
<a href="http://github.com/shokai/glitchicon/tree/master/plugins/">http://github.com/shokai/glitchicon/tree/master/plugins/</a>にある。<br />
pluginが動くルールはこれ。<br />
<ul>
  <li><a href="http://github.com/shokai/glitchicon/tree/master/plugins/">pluginsディレクトリ</a>の中に置いておく</li>
  <li>ファイル名末尾が.rbである</li>
  <li>JRubyのmoduleである</li>
  <li>ファイル名の先頭1文字を大文字にしたmodule名である</li>
  <li>BufferedImageを受け取って、同じサイズのBufferedImageを返す glitch(BufferedImage) というstatic methodを持つ</li>
  <li>受け取ったBufferedImageに対しては破壊的に処理しても、そうでなくてもいい</li>
</ul>
<br />
プラグインはBufferedImageの処理に集中できるように、他の事は<a href="http://github.com/shokai/glitchicon/blob/master/glitch.rb">glitch.rb</a>がやる。<br />
<br />
<br />
どのプラグインも簡単にできている。30行ぐらい。<br />
<br />
<a href="http://github.com/shokai/glitchicon/blob/master/plugins/drumroll_vertical.rb">drumroll_vertical</a>プラグインの場合（画像では一番左、下から2番目）<br />
<pre class="prettyprint">
#!/usr/bin/env&nbsp;jruby<br />
require&nbsp;'java'<br />
import&nbsp;'java.lang.System'<br />
import&nbsp;'javax.imageio.ImageIO'<br />
import&nbsp;'java.awt.image.BufferedImage'<br />
<br />
<br />
module&nbsp;Drumroll_vertical<br />
<br />
&nbsp;&nbsp;def&nbsp;Drumroll_vertical.glitch(img)<br />
&nbsp;&nbsp;&nbsp;&nbsp;img_result&nbsp;=&nbsp;BufferedImage.new(img.width,&nbsp;img.height,&nbsp;img.type)<br />
&nbsp;&nbsp;&nbsp;&nbsp;roll&nbsp;=&nbsp;0<br />
&nbsp;&nbsp;&nbsp;&nbsp;for&nbsp;x&nbsp;in&nbsp;0...img.width&nbsp;do<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;roll&nbsp;=&nbsp;rand(img.height)&nbsp;if&nbsp;rand&nbsp;&gt;&nbsp;0.95<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;roll&nbsp;=&nbsp;0&nbsp;if&nbsp;rand&nbsp;&gt;&nbsp;0.95<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for&nbsp;y&nbsp;in&nbsp;0...img.height&nbsp;do<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pix&nbsp;=&nbsp;img.get_rgb(x,&nbsp;y)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y2&nbsp;=&nbsp;y+roll<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y2&nbsp;-=&nbsp;img.height&nbsp;if&nbsp;y2&nbsp;&gt;&nbsp;img.height-1<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;img_result.set_rgb(x,&nbsp;y2,&nbsp;pix)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end<br />
&nbsp;&nbsp;&nbsp;&nbsp;end<br />
&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;img_result<br />
&nbsp;&nbsp;end<br />
<br />
end<br />
</pre>
ランダムに縦に区切って、ランダムにスロットのドラムみたいにずらす。<br />
<br />
<br />
色を反転させる<a href="http://github.com/shokai/glitchicon/blob/master/plugins/color_reverse.rb">color_reverse</a>プラグイン<br />
<pre class="prettyprint">
#!/usr/bin/env&nbsp;jruby<br />
require&nbsp;'java'<br />
import&nbsp;'java.lang.System'<br />
import&nbsp;'javax.imageio.ImageIO'<br />
import&nbsp;'java.awt.image.BufferedImage'<br />
<br />
<br />
module&nbsp;Color_reverse<br />
<br />
&nbsp;&nbsp;def&nbsp;Color_reverse.glitch(img)<br />
&nbsp;&nbsp;&nbsp;&nbsp;for&nbsp;y&nbsp;in&nbsp;0...img.height&nbsp;do<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for&nbsp;x&nbsp;in&nbsp;0...img.width&nbsp;do<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pix&nbsp;=&nbsp;img.get_rgb(x,&nbsp;y)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;r&nbsp;=&nbsp;pix&nbsp;&gt;&gt;&nbsp;16&nbsp;&amp;&nbsp;0xFF<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;g&nbsp;=&nbsp;pix&nbsp;&gt;&gt;&nbsp;8&nbsp;&amp;&nbsp;0xFF<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;b&nbsp;=&nbsp;pix&nbsp;&amp;&nbsp;0xFF<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;r&nbsp;=&nbsp;256-r<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;g&nbsp;=&nbsp;256-g<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;b&nbsp;=&nbsp;256-b<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pix&nbsp;=&nbsp;((r&nbsp;&lt;&lt;&nbsp;16)&amp;0xFF0000&nbsp;|&nbsp;(g&nbsp;&lt;&lt;&nbsp;8)&amp;0xFF00&nbsp;|&nbsp;b)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;img.set_rgb(x,y,&nbsp;pix)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end<br />
&nbsp;&nbsp;&nbsp;&nbsp;end<br />
&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;img<br />
&nbsp;&nbsp;end<br />
<br />
end<br />
</pre>
<br />
<br />
量子化した後に輪郭抽出する<a href="http://github.com/shokai/glitchicon/blob/master/plugins/quantize_contour.rb">quantize_contour</a><br />
<pre class="prettyprint">
#!/usr/bin/env&nbsp;jruby<br />
require&nbsp;'java'<br />
import&nbsp;'java.lang.System'<br />
import&nbsp;'javax.imageio.ImageIO'<br />
import&nbsp;'java.awt.image.BufferedImage'<br />
<br />
<br />
module&nbsp;Quantize_contour<br />
<br />
&nbsp;&nbsp;def&nbsp;Quantize_contour.glitch(img)<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;for&nbsp;y&nbsp;in&nbsp;0...img.height&nbsp;do<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for&nbsp;x&nbsp;in&nbsp;0...img.width&nbsp;do<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pix&nbsp;=&nbsp;img.get_rgb(x,&nbsp;y)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;r&nbsp;=&nbsp;pix&nbsp;&gt;&gt;&nbsp;16&nbsp;&amp;&nbsp;0xFF<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;g&nbsp;=&nbsp;pix&nbsp;&gt;&gt;&nbsp;8&nbsp;&amp;&nbsp;0xFF<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;b&nbsp;=&nbsp;pix&nbsp;&amp;&nbsp;0xFF<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;gray&nbsp;=&nbsp;(r+g+b)/3<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;quant&nbsp;=&nbsp;gray&nbsp;&amp;&nbsp;0xC0<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pix&nbsp;=&nbsp;((quant&nbsp;&lt;&lt;&nbsp;16)&amp;0xFF0000&nbsp;|&nbsp;(quant&nbsp;&lt;&lt;&nbsp;8)&amp;0xFF00&nbsp;|&nbsp;quant)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;img.set_rgb(x,&nbsp;y,&nbsp;pix)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end<br />
&nbsp;&nbsp;&nbsp;&nbsp;end<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;img_result&nbsp;=&nbsp;BufferedImage.new(img.width,&nbsp;img.height,&nbsp;img.type)<br />
&nbsp;&nbsp;&nbsp;&nbsp;for&nbsp;y&nbsp;in&nbsp;1...img.height-1&nbsp;do<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for&nbsp;x&nbsp;in&nbsp;1...img.width-1&nbsp;do<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pix&nbsp;=&nbsp;img.get_rgb(x,&nbsp;y)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;around&nbsp;=&nbsp;(img.get_rgb(x-1,y)+img.get_rgb(x+1,y)+img.get_rgb(x,y-1)+img.get_rgb(x,y+1))/4<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;around&nbsp;&lt;&nbsp;pix<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pix&nbsp;=&nbsp;0<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pix&nbsp;=&nbsp;0xFFFFFF<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;img_result.set_rgb(x,&nbsp;y,&nbsp;pix)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end<br />
&nbsp;&nbsp;&nbsp;&nbsp;end<br />
&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;img_result<br />
&nbsp;&nbsp;end<br />
<br />
end<br />
</pre>
<br />
<br />
そんなかんじ。画像をいじるけど、元の画像を知っている人なら元画像を思い浮かべられる程度にglitchしたい。<br />
</p>
]]></content:encoded>
			<wfw:commentRss>http://shokai.org/blog/archives/5291/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>JRubyでglitch iconを作る</title>
		<link>http://shokai.org/blog/archives/5283</link>
		<comments>http://shokai.org/blog/archives/5283#comments</comments>
		<pubDate>Sun, 01 Aug 2010 19:44:15 +0000</pubDate>
		<dc:creator>shokai</dc:creator>
				<category><![CDATA[未分類]]></category>
		<category><![CDATA[glitch]]></category>
		<category><![CDATA[javax.imageio]]></category>
		<category><![CDATA[JRuby]]></category>
		<category><![CDATA[Tech]]></category>
		<category><![CDATA[画像処理]]></category>

		<guid isPermaLink="false">http://shokai.org/blog/?p=5283</guid>
		<description><![CDATA[こういうのをtwitter iconにしたかった Twitter のアイコンの目を光らせるライフハック &#8211; 地獄の猫日記を参考にアニメgifを作った。末尾も3C2Cにした。 でもちょうど今日からtwitter [...]]]></description>
			<content:encoded><![CDATA[<p>
こういうのをtwitter iconにしたかった<br />
<img src="http://shokai.org/tmp/glitchicon.gif" /><img src="http://shokai.org/tmp/glitchicon2.gif" />
<br />
<br />
<a href="http://d.hatena.ne.jp/nokturnalmortum/20100702/1277999623">Twitter のアイコンの目を光らせるライフハック &#8211; 地獄の猫日記</a>を参考にアニメgifを作った。末尾も3C2Cにした。<br />
<br />
でもちょうど今日からtwitterがGIFアニメアイコンのチェックを厳しくしたらしく、最初の1フレームだけの静止アイコンになってしまう。<br />
既にアップされている他の人のアイコンを見ると、3C00002Cとかになってるのもあったけど色々ためしたけどやっぱりアップロードすると静止画像になる。<br />
<br />
<br />
画像を生成するスクリプト<br />
jrubyでjavax.imageioを使うと簡単。<br />
最後のあたりのコメントアウトしているconvertかffmpegどちらでも、アニメーションgifを作れる。<br />
glitchicon.rb<br />
<pre class="prettyprint">
#!/usr/bin/env&nbsp;jruby<br />
require&nbsp;'java'<br />
import&nbsp;'java.lang.System'<br />
import&nbsp;'javax.imageio.ImageIO'<br />
import&nbsp;'java.awt.image.BufferedImage'<br />
<br />
if&nbsp;ARGV.size&nbsp;&lt;&nbsp;2<br />
&nbsp;&nbsp;STDERR.puts&nbsp;'require&nbsp;:&nbsp;input&nbsp;file&nbsp;and&nbsp;output&nbsp;dir'<br />
&nbsp;&nbsp;STDERR.puts&nbsp;'jruby&nbsp;glitchicon.rb&nbsp;/path/to/img.jpg&nbsp;/path/to/out/'<br />
&nbsp;&nbsp;exit&nbsp;1<br />
end<br />
<br />
img_in&nbsp;=&nbsp;ImageIO.read(java.io.File.new(ARGV.shift))<br />
out_dir&nbsp;=&nbsp;ARGV.shift<br />
out_dir&nbsp;+=&nbsp;'/'&nbsp;unless&nbsp;out_dir&nbsp;=~&nbsp;/\/$/<br />
<br />
puts&nbsp;"#{img_in.width}x#{img_in.height}"<br />
<br />
for&nbsp;i&nbsp;in&nbsp;1..10<br />
&nbsp;&nbsp;img&nbsp;=&nbsp;BufferedImage.new(img_in.width,&nbsp;img_in.height,&nbsp;img_in.type);<br />
&nbsp;&nbsp;img.graphics.drawImage(img_in,&nbsp;0,&nbsp;0,&nbsp;nil)<br />
&nbsp;&nbsp;shifts&nbsp;=&nbsp;[0,0,0]<br />
&nbsp;&nbsp;for&nbsp;y&nbsp;in&nbsp;0...img.height&nbsp;do<br />
&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;rand&nbsp;&gt;&nbsp;0.85<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;shifts&nbsp;=&nbsp;shifts.map{|j|<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;j&nbsp;=&nbsp;rand(255)-128<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;end<br />
&nbsp;&nbsp;&nbsp;&nbsp;for&nbsp;x&nbsp;in&nbsp;0...img.width&nbsp;do<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pix&nbsp;=&nbsp;img.get_rgb(x,&nbsp;y)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;r&nbsp;=&nbsp;pix&nbsp;&gt;&gt;&nbsp;16&nbsp;&amp;&nbsp;0xFF<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;g&nbsp;=&nbsp;pix&nbsp;&gt;&gt;&nbsp;8&nbsp;&amp;&nbsp;0xFF<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;b&nbsp;=&nbsp;pix&nbsp;&amp;&nbsp;0xFF<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;r&nbsp;+=&nbsp;shifts[0]<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;g&nbsp;+=&nbsp;shifts[1]<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;b&nbsp;+=&nbsp;shifts[2]<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;r&nbsp;=&nbsp;255&nbsp;if&nbsp;r&nbsp;&gt;&nbsp;255<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;g&nbsp;=&nbsp;255&nbsp;if&nbsp;g&nbsp;&gt;&nbsp;255<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;b&nbsp;=&nbsp;255&nbsp;if&nbsp;b&nbsp;&gt;&nbsp;255<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;r&nbsp;=&nbsp;0&nbsp;if&nbsp;r&nbsp;&lt;&nbsp;0<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;g&nbsp;=&nbsp;0&nbsp;if&nbsp;g&nbsp;&lt;&nbsp;0<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;b&nbsp;=&nbsp;0&nbsp;if&nbsp;b&nbsp;&lt;&nbsp;0<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pix&nbsp;=&nbsp;((r&nbsp;&lt;&lt;&nbsp;16)&amp;0xFF0000&nbsp;|&nbsp;(g&nbsp;&lt;&lt;&nbsp;8)&amp;0xFF00&nbsp;|&nbsp;b)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"#{r},#{g},#{b}"<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;img.set_rgb(x,y,&nbsp;pix)<br />
&nbsp;&nbsp;&nbsp;&nbsp;end<br />
&nbsp;&nbsp;end<br />
&nbsp;&nbsp;out_name&nbsp;=&nbsp;"#{out_dir}#{i}.bmp"<br />
&nbsp;&nbsp;ImageIO.write(img,&nbsp;'bmp',&nbsp;java.io.File.new(out_name))<br />
&nbsp;&nbsp;puts&nbsp;out_name<br />
end<br />
<br />
puts&nbsp;`convert&nbsp;#{out_dir}*.bmp&nbsp;#{out_dir}out.gif`<br />
#puts&nbsp;`ffmpeg&nbsp;-y&nbsp;-i&nbsp;#{out_dir}%d.bmp&nbsp;-s&nbsp;100x100&nbsp;-pix_fmt&nbsp;rgb24&nbsp;-loop_output&nbsp;0&nbsp;-sameq&nbsp;#{out_dir}out.gif`<br />
<br />
open("#{out_dir}out.gif"){|gif|<br />
&nbsp;&nbsp;bytes&nbsp;=&nbsp;gif.read.unpack('H*')<br />
&nbsp;&nbsp;bytes&nbsp;=&nbsp;[bytes.first.gsub!(/3b$/,'3c2c')]<br />
&nbsp;&nbsp;open("#{out_dir}glitchicon.gif",'w+'){|out|<br />
&nbsp;&nbsp;&nbsp;&nbsp;out.write(bytes.pack('H*'))<br />
&nbsp;&nbsp;}<br />
}<br />
</pre>
</p>
]]></content:encoded>
			<wfw:commentRss>http://shokai.org/blog/archives/5283/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>gemだけで画像をリサイズできるImageResizeを作った</title>
		<link>http://shokai.org/blog/archives/5213</link>
		<comments>http://shokai.org/blog/archives/5213#comments</comments>
		<pubDate>Sun, 27 Jun 2010 12:16:42 +0000</pubDate>
		<dc:creator>shokai</dc:creator>
				<category><![CDATA[未分類]]></category>
		<category><![CDATA[gem]]></category>
		<category><![CDATA[ImageResize]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Ruby]]></category>
		<category><![CDATA[Tech]]></category>
		<category><![CDATA[画像処理]]></category>

		<guid isPermaLink="false">http://shokai.org/blog/?p=5213</guid>
		<description><![CDATA[ImageResizeはImageMagickなどの外部プログラムに依存せずに、単体で画像をリサイズできる。ただしJavaの実行環境が必要。 SFCの「革新的ネットサービスの構築」という授業のTAをやっていて、昨日と今日 [...]]]></description>
			<content:encoded><![CDATA[<p>
ImageResizeはImageMagickなどの外部プログラムに依存せずに、単体で画像をリサイズできる。ただしJavaの実行環境が必要。<br />
<br />
<br />
SFCの「革新的ネットサービスの構築」という授業のTAをやっていて、昨日と今日niftyに行って開発合宿をしていた。<br />
そこでいろいろあって、ImageMagickなしで画像のサムネイルを作る方法が無いのか模索していたら、<a href="http://rubygems.org/gems/ImageResize">ImageResize</a>というgemができた。<br />
<br />
■インストール<br />
<pre>
gem install ImageResize<br />
</pre>
<br />
<br />
■使う<br />
<pre class="prettyprint">
require 'rubygems'<br />
require 'ImageResize'<br />
<br />
# input, output, width, height<br />
Image.resize('big.jpg', 'small.jpg', 40, 40)<br />
</pre>
これで40&#215;40ピクセルに縮小される。<br />
縦横のアスペクト比が1:1ではない画像の場合、アスペクト比を保ったまま長辺の方にあわせて縮小する。<br />
<br />
<br />
■ソースコード<br />
githubに置いた。<br />
<a href="http://github.com/shokai/ImageResize-ruby">http://github.com/shokai/ImageResize-ruby</a><br />
<br />
<br />
■実装<br />
本体はJava。<br />
標準実行環境にJPEGやGIFやBITMAPなど様々な画像フォーマットの読み書き機能を含んでいる環境というと、Javaと.NETとopenFrameworksしか思いつかなかった。<br />
その中でいちばん色々なマシンにインストールされていそうで、配布が容易な物を選んだらJavaになった。<br />
<br />
<br />
Javaでの画像の扱いは.NET並に簡単。java.awtとjavax.imageioの下に色々充実しているのでそれらを使えばいい。<br />
<a href="http://github.com/shokai/ImageResize-ruby/blob/master/java/ImageResize.java">ImageResize.java</a><br />
<br />
<pre class="prettyprint">
import&nbsp;javax.imageio.*;<br />
import&nbsp;java.io.*;<br />
import&nbsp;java.awt.image.*;<br />
import&nbsp;java.awt.geom.*;<br />
import&nbsp;java.awt.image.*;<br />
import&nbsp;java.util.regex.*;<br />
<br />
class&nbsp;ImageResize{<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;ImageResize(){<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;static&nbsp;void&nbsp;main(String&nbsp;args[]){<br />
	if(args.length&nbsp;&lt;&nbsp;4){<br />
	&nbsp;&nbsp;&nbsp;&nbsp;System.out.println("ImageResize&nbsp;in.jpg&nbsp;out.jpg&nbsp;320&nbsp;320");<br />
	&nbsp;&nbsp;&nbsp;&nbsp;System.exit(1);<br />
	}<br />
	ImageResize&nbsp;app&nbsp;=&nbsp;new&nbsp;ImageResize();<br />
	if(app.resize(args[0],&nbsp;args[1],&nbsp;Integer.parseInt(args[2]),&nbsp;Integer.parseInt(args[3]))){<br />
	&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(args[1]);<br />
	}<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;boolean&nbsp;resize(String&nbsp;fname_in,&nbsp;String&nbsp;fname_out,&nbsp;int&nbsp;max_width,&nbsp;int&nbsp;max_height){<br />
	System.out.println(fname_in);<br />
	BufferedImage&nbsp;img&nbsp;=&nbsp;null;<br />
	try&nbsp;{<br />
	&nbsp;&nbsp;&nbsp;&nbsp;img&nbsp;=&nbsp;ImageIO.read(new&nbsp;File(fname_in));<br />
	}<br />
	catch&nbsp;(Exception&nbsp;e)&nbsp;{<br />
	&nbsp;&nbsp;&nbsp;&nbsp;e.printStackTrace();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;img&nbsp;=&nbsp;null;<br />
	}<br />
	<br />
	int&nbsp;width,&nbsp;height;<br />
	if(img.getWidth()&nbsp;&lt;&nbsp;img.getHeight()){<br />
	&nbsp;&nbsp;&nbsp;&nbsp;height&nbsp;=&nbsp;max_height;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;width&nbsp;=&nbsp;img.getWidth()&nbsp;*&nbsp;max_height&nbsp;/&nbsp;img.getHeight();<br />
	}<br />
	else{<br />
	&nbsp;&nbsp;&nbsp;&nbsp;width&nbsp;=&nbsp;max_width;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;height&nbsp;=&nbsp;img.getHeight()&nbsp;*&nbsp;max_width&nbsp;/&nbsp;img.getWidth();<br />
	}<br />
	System.out.println(img.getWidth()&nbsp;+&nbsp;"x"&nbsp;+img.getHeight()&nbsp;+&nbsp;"&nbsp;=&gt;&nbsp;"&nbsp;+&nbsp;width&nbsp;+&nbsp;"x"&nbsp;+&nbsp;height);<br />
	<br />
	BufferedImage&nbsp;img_resized&nbsp;=&nbsp;new&nbsp;BufferedImage(width,&nbsp;height,&nbsp;img.getType());<br />
	AffineTransformOp&nbsp;ato&nbsp;=&nbsp;new&nbsp;AffineTransformOp(AffineTransform.getScaleInstance((double)width&nbsp;/&nbsp;img.getWidth(),&nbsp;<br />
										&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(double)height&nbsp;/&nbsp;img.getHeight()),&nbsp;<br />
						&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;null);<br />
	ato.filter(img,&nbsp;img_resized);<br />
	<br />
<br />
	String&nbsp;format&nbsp;=&nbsp;Pattern.compile("^.+\\.(.+)$").matcher(fname_out).replaceAll("$1");<br />
	boolean&nbsp;result&nbsp;=&nbsp;false;<br />
	try&nbsp;{<br />
	&nbsp;&nbsp;&nbsp;&nbsp;result&nbsp;=&nbsp;ImageIO.write(img_resized,&nbsp;format,&nbsp;new&nbsp;File(fname_out));<br />
	}<br />
	catch&nbsp;(Exception&nbsp;e)&nbsp;{<br />
	&nbsp;&nbsp;&nbsp;&nbsp;e.printStackTrace();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;result&nbsp;=&nbsp;false;<br />
	}<br />
	return&nbsp;result;<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
}<br />
</pre>
<br />
<br />
■参考<br />
<ul>
  <li><a href="http://d.hatena.ne.jp/toshyon/20060609/p1">Java で画像のリサイズ &#8211; toshyonのメモ書き</a></li>
  <li><a href="http://www.javadrive.jp/java2d/bufferedImage/index2.html">ImageIOクラスで画像ファイルを読み書きする &#8211; BufferedImageの使い方色々 &#8211; Java2D</a></li>
</ul>
<br />
<br />
今回は使わなかったが、<a href="http://java.sun.com/javase/technologies/desktop/media/jai/">Java Advanced Imaging (JAI) API</a>というのが標準実行環境には含まれていないけどSunが作っていて、かなり充実しているらしい。<br />
Javaランタイムも速くなってきているらしいしScalaで画像処理とかしてみたい。<br />
</p>
]]></content:encoded>
			<wfw:commentRss>http://shokai.org/blog/archives/5213/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>OpenCVをはじめた</title>
		<link>http://shokai.org/blog/archives/4141</link>
		<comments>http://shokai.org/blog/archives/4141#comments</comments>
		<pubDate>Sun, 08 Feb 2009 15:49:26 +0000</pubDate>
		<dc:creator>shokai</dc:creator>
				<category><![CDATA[未分類]]></category>
		<category><![CDATA[cpp]]></category>
		<category><![CDATA[OpenCV]]></category>
		<category><![CDATA[Tech]]></category>
		<category><![CDATA[画像処理]]></category>

		<guid isPermaLink="false">http://shokai.org/blog/?p=4141</guid>
		<description><![CDATA[openFrameworksをやっていて詰まったのでとりあえずOpenCVをやってみることにした。 openFrameworksはOpenCV・OpenGL・シリアルポート関連・音声関連のC++ライブラリをまとめたWin [...]]]></description>
			<content:encoded><![CDATA[<p>
openFrameworksをやっていて詰まったのでとりあえずOpenCVをやってみることにした。<br />
<br />
openFrameworksはOpenCV・OpenGL・シリアルポート関連・音声関連のC++ライブラリをまとめたWin/Mac/Ubuntuで動作するクロスプラットフォームラッパーで、processingの影響を受けているらしいのだが、その中のOpenCVとOpenGL自体がまず巨大なライブラリなのでこいつらを扱う作法を知っていないとopenFrameworksが使いこなせない。<br />
<br />
<br />
今回はOpenCVを単体で扱ってみてどんなもんか探ってみた。<br />
<br />
<br />
■OpenCVのセットアップ、サンプル実行、コンパイル環境と対応カメラの確認<br />
<br />
マシンに3つOSが入っているので実行環境も3つ揃えてみた。<br />
MacとUbuntuはgccで、WinはVS2008でコンパイル。Ubuntuは色々ためしたけどiSightを認識しなかったが古い外付けUSBカメラを認識して使えた。<br />
この内Macだけが駄目。描画が異常に重い。ググったらhighguiとCarbonの組み合わせが悪さしている？ような情報もあったがよくわからなかった。でもちゃんと動いていると報告してる人がたくさんいる。悔しい。<br />
<br />
<ul>
<li><a href="http://d.hatena.ne.jp/shokai/20090129/1233216221">MacにMacportsでインストール、サンプルをgccでコンパイルしi-Sightで実行</a></li>
<li><a href="http://d.hatena.ne.jp/shokai/20090129/1233232796">VMWare+Ubuntuにaptでインストール、サンプルをgccでコンパイルしLogicoolの古いカメラQV-40で実行</a></li>
<li><a href="http://d.hatena.ne.jp/shokai/20090129/1233241388">Windowsにインストーラからいれて、VisualStudio2008でコンパイルする設定、iSightでキャプチャ</a></li>
<li><a href="http://d.hatena.ne.jp/shokai/20090129/1233238820">VMWare+Ubuntuで単純なカメラキャプチャプログラムとMakefileを1から書く</a></li>
<li>おまけ：<a href="http://d.hatena.ne.jp/shokai/20090129/1233252859">Macにソースからインストールしなおし</a>（意味無かった、相変わらず重い）</a></li>
</ul>
最近は全部パッケージ管理システムから入るんですね<br />
<br />
<br />
本当はMacかUbuntuで動かしたかったけど、WindowsでVS2008でやる事にした。<br />
俺が電車で移動中プログラムを書く事が多いので、Windowsからは内蔵i-Sightを使えて一番都合が良い。<br />
<br />
cvWaitKey(msec)の待ち時間を短くしすぎなければ（30ぐらいなら大丈夫）どの環境でも動く事が確認できたので、後述するプログラムはどの環境でも動くと思う。少なくともWindowsとUbuntuでは動いている。<br />
<br />
<br />
<br />
■画像処理の勉強<br />
<br />
この本でやった。プログラムの途中重要な定数宣言部分が省略されまくったりしていて困ったけど、理論的な所は詳しく解説してくれているのでこの本と<a href="http://opencv.jp/opencv-1.0.0/document/">翻訳版ドキュメント</a>と<a href="http://opencv.jp/sample/">サンプル集</a>を見ればなんとかなった。<br />
<br />
<br />
<div class="amazlet-box" style="margin-bottom:0px;"><div class="amazlet-image" style="float:left;"><a href="http://www.amazon.co.jp/exec/obidos/ASIN/483992354X/shokai-22/ref=nosim/" name="amazletlink" target="_blank"><img src="http://ecx.images-amazon.com/images/I/416qhEJCUGL._SL160_.jpg" alt="OpenCV プログラミングブック" style="border: none;" /></a></div><div class="amazlet-info" style="float:left;margin-left:15px;line-height:120%"><div class="amazlet-name" style="margin-bottom:10px;line-height:120%"><a href="http://www.amazon.co.jp/exec/obidos/ASIN/483992354X/shokai-22/ref=nosim/" name="amazletlink" target="_blank">OpenCV プログラミングブック</a><div class="amazlet-powered-date" style="font-size:7pt;margin-top:5px;font-family:verdana;line-height:120%">posted with <a href="http://www.amazlet.com/browse/ASIN/483992354X/shokai-22/ref=nosim/" title="OpenCV プログラミングブック" target="_blank">amazlet</a> at 09.02.08</div></div><div class="amazlet-detail">奈良先端科学技術大学院大学 OpenCVプログラミングブック制作チーム <br />毎日コミュニケーションズ <br />売り上げランキング: 58513<br /></div><div class="amazlet-review" style="margin-top:10px; margin-bottom:10px"><div class="amazlet-review-average" style="margin-bottom:5px">おすすめ度の平均: <img src="http://images-jp.amazon.com/images/G/09/x-locale/common/customer-reviews/stars-3-0.gif" alt="3.0" /></div><img src="http://images-jp.amazon.com/images/G/09/x-locale/common/customer-reviews/stars-3-0.gif" alt="3" /> とっかかりとしてはよい本<br /><img src="http://images-jp.amazon.com/images/G/09/x-locale/common/customer-reviews/stars-4-0.gif" alt="4" /> OpenCVを日本語でまとまって解説した初めての本<br /><img src="http://images-jp.amazon.com/images/G/09/x-locale/common/customer-reviews/stars-2-0.gif" alt="2" /> 肩すかし<br /></div><div class="amazlet-link" style="margin-top: 5px"><a href="http://www.amazon.co.jp/exec/obidos/ASIN/483992354X/shokai-22/ref=nosim/" name="amazletlink" target="_blank">Amazon.co.jp で詳細を見る</a></div></div><div class="amazlet-footer" style="clear: left"></div></div>
<br />
<br />
OpenCVというとまっさきに「顔認識」を思い浮かべるけど、機械学習を用いた分類器については巻末のリファレンスに少し書かれている程度で、この本では基本的なOpenCVの組み込み関数を使った2値化やノイズ除去や表色系の変換、輪郭、エッジ抽出、テンプレートマッチング、物体追跡、背景差分法などが書かれている。書かれている内容は<a href="http://www.amazon.co.jp/exec/obidos/ASIN/4861005582/shokai-22">built with processing</a>や<a href="http://www.amazon.co.jp/exec/obidos/ASIN/4789837076/shokai-22">ディジタル画像処理の基礎と応用</a>なんかに書かれている内容と被っている部分が多いけど、OpenCVの関数を使っているから滅茶苦茶処理が速い。やばい。もっと早くOpenCVに移れば良かった。<br />
<br />
<br />
<span id="more-4141"></span><br />
<br />
<br />
サンプルを自分なりに改変しながら写経していった。<br />
<ul>
<li><a href="http://d.hatena.ne.jp/shokai/20090131/1233416585">背景差分</a></li>
<li><a href="http://d.hatena.ne.jp/shokai/20090202/1233561850">aviファイルの読み込み</a><br />
カメラとaviファイルの入力を切り替えて、同じアルゴリズムで試せるので便利。再現性を見たい時などに。</li>
<li><a href="http://d.hatena.ne.jp/shokai/20090202/1233563393">肌色領域検出</a></li>
</ul>
<a href="http://www.flickr.com/photos/shokai/3246255983/" title="肌色領域検出 by shokai, on Flickr"><img src="http://farm4.static.flickr.com/3478/3246255983_dcb7f0a9ff.jpg" width="500" height="408" alt="肌色領域検出" /></a><br />
<br />
<br />
肌色領域検出でわかったのは、OpenCVでの画像処理で一番特徴的な「お作法」は画像データ（IplImageオブジェクト）同士を集合の様に扱って、論理積や差などを取るためのcvAndやcvSubなどの関数を使うこと。今までProcessing等でピクセルを1つずつ走査していって数値として計算していたが、そうではなく集合同士をガッと合わせて処理させてしまえばほとんどメモリの中でのビット演算で済むので、めちゃくちゃ速い。<br />
OpenCVのIplImageを扱う関数は全ピクセルに同じ条件でまとめて処理を行う関数ばかりなので、ちょっと考え方を改めるのに頭が疲れた。<br />
<br />
<br />
ここから、サンプルで「2値画像をいじる処理」が良く出てくるようになったので、自分で赤色領域を検出するプログラムを作ってその2値画像に対するアルゴリズムだけ適応していってみた。<br />
<br />
<a href="http://d.hatena.ne.jp/shokai/20090202/1233589674">赤色領域を検出(1)</a><br />
RGB要素をそれぞれ調べて、赤いところを抜く。これはRGBそれぞれcvThresholdで2値化して、論理和を求める。<br />
ついでにcvSetMouseCallback()マウスイベントを取り、マウス位置のピクセルのRGB要素の値を調べてコンソール出力もした。これでdebugしやすい。<br />
<a href="http://www.flickr.com/photos/shokai/3247888926/" title="detect red by shokai, on Flickr"><img src="http://farm4.static.flickr.com/3110/3247888926_7b76125b12.jpg" width="500" height="206" alt="detect red" /></a><br />
<br />
<br />
<a href="http://d.hatena.ne.jp/shokai/20090202/1233591298">赤色領域を検出(2)</a><br />
(1)のアルゴリズムだと暗い場所では色が変わって認識できないので、青・緑の要素より相対的に赤がある比率以上の場所を抜き出すようにした。cvDivで赤と青、赤と緑の比率を計算しそれぞれcvThresholdで一定以上の比率の場所を抜き出し、論理和を取る。<br />
<a href="http://www.flickr.com/photos/shokai/3247036747/" title="detect red by shokai, on Flickr"><img src="http://farm4.static.flickr.com/3338/3247036747_696139d2fc.jpg" width="500" height="257" alt="detect red" /></a><br />
<br />
<br />
<a href="http://d.hatena.ne.jp/shokai/20090202/1233602502">赤色検出</a><br />
同じプログラムをVMWare+Ubuntuでも動かしてみた。あっさり動いた。<br />
<a href="http://www.flickr.com/photos/shokai/3247665335/" title="detect red by shokai, on Flickr"><img src="http://farm4.static.flickr.com/3327/3247665335_efac220a73.jpg" width="500" height="273" alt="detect red" /></a><br />
<br />
<br />
<a href="http://d.hatena.ne.jp/shokai/20090203/1233608481">収縮・膨張で砂嵐ノイズフィルタ</a><br />
砂嵐ノイズを消す方法。速い。<br />
<a href="http://www.flickr.com/photos/shokai/3248706616/" title="背景差分 by shokai, on Flickr"><img src="http://farm4.static.flickr.com/3129/3248706616_36cce3ea17_m.jpg" width="240" height="187" alt="背景差分" /></a><a href="http://www.flickr.com/photos/shokai/3248755394/" title="背景差分+ノイズカット by shokai, on Flickr"><img src="http://farm4.static.flickr.com/3536/3248755394_5a20af66a6_m.jpg" width="240" height="187" alt="背景差分+ノイズカット" /></a><br />
<br />
<br />
<a href="http://d.hatena.ne.jp/shokai/20090203/1233609981">赤色領域を検出(3) 重心も計算</a><br />
2値画像から一発で重心を求められるcvMomentsで計算した。<br />
<a href="http://www.flickr.com/photos/shokai/3248775462/" title="赤色検出+重心計算 by shokai, on Flickr"><img src="http://farm4.static.flickr.com/3517/3248775462_b61ef0a15c.jpg" width="500" height="281" alt="赤色検出+重心計算" /></a><br />
<br />
<br />
<a href="http://d.hatena.ne.jp/shokai/20090204/1233775786">赤色領域を検出(4) 輪郭を囲む</a><br />
2値画像から一発で輪郭を探索してくれるcvFindContourを使って、青い線で囲む。何個あるかもわかってしまう。<br />
<a href="http://www.flickr.com/photos/shokai/3254002232/" title="detect contour of red by shokai, on Flickr"><img src="http://farm4.static.flickr.com/3396/3254002232_d3844b664c.jpg" width="500" height="215" alt="detect contour of red" /></a><br />
<br />
<br />
<br />
C++だけど、OpenCVで実装したプログラム自体は高度なプログラミングテクニックが使われているわけではない。<br />
webでいろいろな人が公開しているサンプルを見ても、上から処理が降りてきて画像処理用のメモリ領域を確保し、while(1)で毎フレームカメラを処理して、cvWaitKey()でキーボード割り込みで終了できるようにしつつ、ループの中で画面表示を更新し続けるだけという単純なものだった。まるでマイコンそっくりの構成。<br />
<br />
逆に言えばOpenCV単体ではカメラ入力をいじった結果をフルスクリーンで表示する位しかできないので、それ以上の事をやるにはopenFrameworksを使うとかした方がいいんだろうと思う。<br />
一応highguiというGUI部品も同梱されているが、基本的なボタンやスライダーがあるだけ。<br />
<br />
<br />
で、本を一応最後まで読んだので、あとは<a href="http://opencv.jp/sample/">opencv.jp &#8211; OpenCV サンプルコード -</a>を見たりして応用的なアルゴリズムや関数を見たり、機械学習ライブラリの使い方を（使う予定が無いけど）見ておくぐらいか。<br />
いよいよ実戦投入のために自分のプロジェクトのコードを書かなければ。<br />
</p>
]]></content:encoded>
			<wfw:commentRss>http://shokai.org/blog/archives/4141/feed</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>AS3 &#8211; あらためてラベリング処理</title>
		<link>http://shokai.org/blog/archives/1490</link>
		<comments>http://shokai.org/blog/archives/1490#comments</comments>
		<pubDate>Wed, 30 Jan 2008 17:03:11 +0000</pubDate>
		<dc:creator>shokai</dc:creator>
				<category><![CDATA[未分類]]></category>
		<category><![CDATA[AS3]]></category>
		<category><![CDATA[Flash]]></category>
		<category><![CDATA[Tech]]></category>
		<category><![CDATA[画像処理]]></category>

		<guid isPermaLink="false">http://shlog.sakura.ne.jp/blog/?p=1490</guid>
		<description><![CDATA[火曜に、imglのうっちーとあらためてラベリング処理ってどうやんのよという話をしていて、あらためてAS3で書き直した。 ただし、ラベルの衝突処理はまだやっていない。 ラベリング処理について→s.h.log: Proce5 [...]]]></description>
			<content:encoded><![CDATA[<p>
<p>火曜に、imglのうっちーとあらためてラベリング処理ってどうやんのよという話をしていて、あらためてAS3で書き直した。<br /><br />
ただし、ラベルの衝突処理はまだやっていない。</p>
<p>ラベリング処理について→<a href="http://web.sfc.keio.ac.jp/~shokai/archives/2007/06/proce55ing-binarize-labeling.html">s.h.log: Proce55ing &#8211; 2値化画像を4近傍ラベリング処理してパーツ抜き出し</a></p>
<p>今まで、ラベルそのものを2次元のint配列で管理してたけど、今回はBitmapDataオブジェクトとして保存する様にしてみた。BitmapDataならx,y座標で要素にアクセスできるし、ピクセルはuint型なので都合がいい。</p>
<p>■できたもの<br /><br />
download => <a href="http://shokai.org/archive/as3/080129_labeling/">fla, as, swf (FlashDevelop3.0.0 Beta5 + FlashCS3)</a></p>
<p><a href="http://web.sfc.keio.ac.jp/~shokai/archives/2008/01/post_726.html">年末年始作ったカメラから画像処理のサンプル</a>を元に改造した。<br /><br />
webcamから撮影してBitmapDataにして、1ピクセルずつ走査して真っ白（0x00FFFFFF）のピクセルを検出。<br /><br />
さらにラベリング処理をして、「ピクセルのかたまり」毎に番号をつける。</p>
<p>（0x00FFFFFFは蛍光灯を撮影すると良いです）<br /><br />
<a href="http://www.flickr.com/photos/shokai/2231166650/" title="labeling by shokai, on Flickr"><img src="http://farm3.static.flickr.com/2160/2231166650_daa29aed6b_o.png" width="422" height="250" alt="labeling" /></a><a href="http://www.flickr.com/photos/shokai/2230373719/" title="labeling by shokai, on Flickr"><img src="http://farm3.static.flickr.com/2176/2230373719_b3653fa3ca_o.png" width="422" height="250" alt="labeling" /></a><a href="http://www.flickr.com/photos/shokai/2230374593/" title="labeling by shokai, on Flickr"><img src="http://farm3.static.flickr.com/2118/2230374593_11a7b53936_o.png" width="422" height="250" alt="labeling" /></a></p>
<p>今回はノイズを飛ばしたりしていないので、右の本棚の小さな白にも反応してしまって28個とか検出される<br /><br />
<a href="http://www.flickr.com/photos/shokai/2230377983/" title="labeling by shokai, on Flickr"><img src="http://farm3.static.flickr.com/2357/2230377983_a63a9d3bc2_o.png" width="422" height="250" alt="labeling" /></a></p>
<p>ラベルの衝突時の処理もしていないので、左上が空いた弧の様な形の時に割れてしまう<br /><br />
<a href="http://www.flickr.com/photos/shokai/2230391701/" title="labeling by shokai, on Flickr"><img src="http://farm3.static.flickr.com/2154/2230391701_8ae1deeb31_o.png" width="422" height="250" alt="labeling" /></a><br /><br />
この辺はまたの機会に。</p>
<p><span id="more-1490"></span><br /><br />
■動きます（USBカメラが必要）<br /><br />
<object width="410" height="200"><param name="movie" value="http://shokai.org/archive/as3/080129_labeling/ColorPicker.swf"></param><embed src="http://shokai.org/archive/as3/080129_labeling/ColorPicker.swf" type="application/x-shockwave-flash" width="410" height="200"></embed></object></p>
<p>■コード<br /><br />
<a href="http://shokai.org/archive/as3/080129_labeling/ColorPicker.as">ColorPicker.as</a><br /><br />
</p>
<blockquote><p>package{<br /><br />
importfl.controls.Button;<br /><br />
importfl.controls.NumericStepper;<br /><br />
importflash.events.*;<br /><br />
importflash.media.*;<br /><br />
importflash.geom.Point;<br /><br />
importflash.utils.*;<br /><br />
importflash.display.*;<br /><br />
importflash.filters.*;<br /><br />
publicclassColorPickerextendsSprite{<br /><br />
privatevarcam:Camera;//カメラ<br /><br />
privatevarvideo:Video;//カメラからの画像を表示<br /><br />
privatevarbmp:Bitmap;//表示<br /><br />
privatevarbmd:BitmapData;//中身のデータ<br /><br />
privatevarlabels:BitmapData;//bmdと同サイズのラベル<br /><br />
privatevarcolor:int=0x00ffffff;//選択する色(白)<br /><br />
<br /><br />
privatevar_numericStepperLabelNum:NumericStepper;<br /><br />
privatevar_buttonSnapshot:Button;<br /><br />
<br /><br />
/*IDEが生成したオブジェクトを関連づけ*/<br /><br />
publicfunctionlink_ide_obj(){<br /><br />
this._numericStepperLabelNum=numericStepperLabelNum;<br /><br />
this._buttonSnapshot=buttonSnapshot;<br /><br />
}<br /><br />
<br /><br />
publicfunctionColorPicker(){<br /><br />
stage.scaleMode=StageScaleMode.EXACT_FIT;//伸縮する<br /><br />
//stage.scaleMode=StageScaleMode.NO_SCALE;//伸縮しない<br /><br />
stage.align=StageAlign.TOP_LEFT;//左上から<br /><br />
<br /><br />
link_ide_obj();//IDEが生成したオブジェクトを関連づけ<br /><br />
<br /><br />
cam=Camera.getCamera();<br /><br />
cam.setMode(800,600,24,true);//24FPS<br /><br />
cam.setQuality(0,100);<br /><br />
<br /><br />
video=newVideo(200,150);//bitmap計算用の小さいビデオ<br /><br />
video.attachCamera(cam);<br /><br />
this.addChild(video);//videoを表示<br /><br />
<br /><br />
_buttonSnapshot.addEventListener(MouseEvent.CLICK,snapShot);//撮影の関数登録<br /><br />
bmd=newBitmapData(video.width,video.height);<br /><br />
bmp=newBitmap(bmd);//BitmapDataをBitampに登録<br /><br />
this.addChild(bmp);//Bitmapを表示<br /><br />
bmp.x=video.width+10;<br /><br />
<br /><br />
//ラベル番号の選択GUI<br /><br />
_numericStepperLabelNum.enabled=false;<br /><br />
_numericStepperLabelNum.addEventListener(Event.CHANGE,function(){<br /><br />
showSelectedLabel(_numericStepperLabelNum.value);<br /><br />
});<br /><br />
}<br /><br />
<br /><br />
/*撮影*/<br /><br />
publicfunctionsnapShot(e:Event){<br /><br />
bmd.draw(video);//カメラから制止画をキャプチャ<br /><br />
labeling();<br /><br />
showSelectedLabel(_numericStepperLabelNum.value);<br /><br />
_numericStepperLabelNum.enabled=true;<br /><br />
}<br /><br />
<br /><br />
/*4近傍ラベリング処理*/<br /><br />
publicfunctionlabeling(){<br /><br />
labels=newBitmapData(bmd.width,bmd.height,false,0);//transparent=false<br /><br />
varlabel:int=0;<br /><br />
varx:int,y:int;<br /><br />
bmd.lock();//表示の更新を止める。これやらないとえらい事になる<br /><br />
for(y=0;y<bmd.height;y++){<br /><br />
for(x=0;x<bmd.width;x++){<br /><br />
if(bmd.getPixel(x,y)==color){//選択した色の時（今回は白）<br /><br />
if(x>0&#038;&#038;labels.getPixel(x-1,y)>0){//左チェック<br /><br />
labels.setPixel(x,y,labels.getPixel(x-1,y));//ラベルをコピー<br /><br />
}<br /><br />
elseif(x>0&#038;&#038;y>0&#038;&#038;labels.getPixel(x-1,y-1)>0){//左上<br /><br />
labels.setPixel(x,y,labels.getPixel(x-1,y-1));<br /><br />
}<br /><br />
elseif(y>0&#038;&#038;labels.getPixel(x,y-1)>0){//上<br /><br />
labels.setPixel(x,y,labels.getPixel(x,y-1));<br /><br />
}<br /><br />
elseif(y>0&#038;&#038;x<labels.width&#038;&#038;labels.getPixel(x+1,y-1)>0){//右上<br /><br />
labels.setPixel(x,y,labels.getPixel(x+1,y-1));<br /><br />
}<br /><br />
else{//4方向いずれも隣接していない時<br /><br />
labels.setPixel(x,y,++label);//新しいラベル<br /><br />
}<br /><br />
}<br /><br />
//else{<br /><br />
//labels.setPixel(x,y,0);//ラベル無し<br /><br />
//}<br /><br />
}<br /><br />
}<br /><br />
bmd.unlock();//表示を更新する<br /><br />
<br /><br />
_numericStepperLabelNum.maximum=label;<br /><br />
_numericStepperLabelNum.minimum=1;<br /><br />
_numericStepperLabelNum.value=1;<br /><br />
}<br /><br />
<br /><br />
/*選択したラベルの箇所を青く塗る。他は赤くする*/<br /><br />
publicfunctionshowSelectedLabel(labelNum:int){<br /><br />
bmd.lock();<br /><br />
varx:int,y:int;<br /><br />
for(y=0;y<bmd.height;y++){<br /><br />
for(x=0;x<bmd.width;x++){<br /><br />
varcurrentLabel:int=labels.getPixel(x,y);<br /><br />
if(currentLabel==labelNum){<br /><br />
bmd.setPixel(x,y,0x000000FF);//青<br /><br />
}<br /><br />
elseif(currentLabel>0){<br /><br />
bmd.setPixel(x,y,0x00FF0000);//赤<br /><br />
}<br /><br />
}<br /><br />
}<br /><br />
bmd.unlock();<br /><br />
}<br /><br />
}<br /><br />
}</p></p></blockquote>
<p>
</p>
]]></content:encoded>
			<wfw:commentRss>http://shokai.org/blog/archives/1490/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>AS3 &#8211; BitmapMirror.asでYouTubeを鏡写しにする</title>
		<link>http://shokai.org/blog/archives/1433</link>
		<comments>http://shokai.org/blog/archives/1433#comments</comments>
		<pubDate>Sun, 30 Sep 2007 11:49:11 +0000</pubDate>
		<dc:creator>moblog</dc:creator>
				<category><![CDATA[未分類]]></category>
		<category><![CDATA[AS3]]></category>
		<category><![CDATA[Flash]]></category>
		<category><![CDATA[flv]]></category>
		<category><![CDATA[Tech]]></category>
		<category><![CDATA[YouTube]]></category>
		<category><![CDATA[画像処理]]></category>

		<guid isPermaLink="false">http://shlog.sakura.ne.jp/blog/?p=1433</guid>
		<description><![CDATA[右下の「Load」を押して再生。 → Source Code (Flash CS3 / AS3) 土善開発合宿(ゆーすけべー日記: 奥出研&#8217;07秋合宿@土善旅館に部外者(OB)の開発合宿も兼ねて参入してきた) [...]]]></description>
			<content:encoded><![CDATA[<p>
<p>右下の「Load」を押して再生。</p>
<p><object width="640" height="278"><param name="movie" value="http://shokai.org/archive/as3/0709_dozen/KagamiVideo/Kagami.swf"></param><embed src="http://shokai.org/archive/as3/0709_dozen/KagamiVideo/Kagami.swf" type="application/x-shockwave-flash" width="640" height="278"></embed></object></p>
<p>→ <a href="http://shokai.org/archive/as3/0709_dozen/KagamiVideo/">Source Code (Flash CS3 / AS3)</a></p>
<p>土善開発合宿(<a href="http://yusukebe.com/archives/07/09/23/152051.html">ゆーすけべー日記: 奥出研&#8217;07秋合宿@土善旅館に部外者(OB)の開発合宿も兼ねて参入してきた</a>)で作ってた<a href="http://shokai.org/archive/as3/0709_dozen/KagamiVideo/BitmapMirror.as">BitmapMirror.as</a>を公開しておきます。</p>
<p>宴会直前に到着したのであんまり時間がなかったけど、今回やったことは3つ<br /><br />
１．YouTubeのビデオ(flv)をGoogleVideo経由で取得して<br /><br />
２．AS3のBitmapDataとして扱って<br /><br />
３．鏡面反射させる</p>
<p>ビデオはこれを読み込んでいます。ひゃあ゛ああああうまい゛いいいいい<br /><br />
<object width="425" height="353"><param name="movie" value="http://www.youtube.com/v/YRY0M5cgDG8&#038;rel=1"></param><param name="wmode" value="transparent"></param><embed src="http://www.youtube.com/v/YRY0M5cgDG8&#038;rel=1" type="application/x-shockwave-flash" wmode="transparent" width="425" height="353"></embed></object></p>
<p>■１．YouTubeのビデオをGoogleVideo経由で取得する<br /><br />
数ヶ月前にGoogleに買収されて以来、<br /><br />
http://www.youtube.com/watch?v=YRY0M5cgDG8<br /><br />
のビデオは<br /><br />
http://cache.googlevideo.com/get_video?video_id=YRY0M5cgDG8<br /><br />
から取れる様になりました。<br /><br />
以前は<a href="http://search.cpan.org/~yoshida/WebService-YouTube/lib/WebService/YouTube.pm">WebService::YouTube</a>を<a href="http://web.sfc.keio.ac.jp/~shokai/archives/2006/12/tagtube.html">使って<br /><br />
</a>スクレイピングしなければならなかったけど、素直になった。</p>
<p><span id="more-1433"></span><br /><br />
■２．flvからBitmapDataオブジェクトを作る<br /><br />
Flash Player9から、flvファイルや画像を外部から読み込んでBitmapDataオブジェクトを作るとクロスドメイン警告が出る様になった。<br /><br />
適切なcrossdomain.xmlが置いてあるサイトなら（例えばfarm1.static.flickr.comなど）<br /><br />
fl.Loader系でloadする時に</p>
</p>
<blockquote><p><p>uiLoader.load(new URLRequest(textUrl.text), new LoaderContext(true,null, null));</p></p></blockquote>
<p>
<p>などして、<a href="http://livedocs.adobe.com/flex/2_jp/langref/flash/system/LoaderContext.html">LoaderContext</a>オブジェクトを渡してやれば<br /><br />
crossdomain.xmlを読んでくれるんだが<br /><br />
なんとかする方法を見つけた。</p>
<p>BitmapData.draw()メソッドでビットマップデータオブジェクトを外部のドメインのデータから作ろうとするとした時に警告が出る、ということは、<br /><br />
draw()する瞬間だけ外部ドメインじゃなければいい。</p>
<p>こうする。</p>
</p>
<blockquote><p><p>video.attachNetStream(null);<br /><br />
bmd.draw(video);//ビットマップデータを取得<br /><br />
video.attachNetStream(stream);</p></p></blockquote>
<p>
<p>ビットマップ取る時だけnullをattachしても、videoの表示は消えない（一瞬止<br /><br />
まる）だけなので目には見えない。</p>
<p>ここまで情報元：<a href="http://saqoosha.net/2007/07/22/609/">[Saq.] YouTube の FLV を抜き出すのが簡単になっている件</a></p>
<p>■３．<a href="http://shokai.org/archive/as3/0709_dozen/KagamiVideo/BitmapMirror.as">BitmapMirror.as</a>で鏡面反射させる<br /><br />
使い方。<br /><br />
コンストラクタでBitmapDataを渡してあげて、process()する。getBitmapData()で処理後のBitmapDataを取得できるので結果表示用のBitmapに渡してやれば表示できる。<br /><br />
thresholdプロパティで左端から鏡までの距離を指定できる。</p>
<p>myBitmapData:BitmapDataがカメラやストリームやMovieCilpなり何なりから作ってあるとして、</p>
</p>
<blockquote><p><p>var mirror:BitmapMirror = new BitmapMirror(myBitmapData);</p>
<p>// 鏡処理(左右反転)したbitmapdataを受け取る<br /><br />
mirror.getBitmapData();</p>
<p>// 反転処理を反映させる<br /><br />
mirror.process();</p></p></blockquote>
<p>
<p>普通にあらかじめ</p>
</p>
<blockquote><p><p>var bmp_result:Bitmap = new Bitmap(mirror.getBitmapData());<br /><br />
this.addChild(bmp_result);</p></p></blockquote>
<p>
<p>しておけば、毎フレーム毎に mirror.process(); すれば更新されます。</p>
<p>thresholdプロパティは、「左端から何ピクセル目に鏡をあてているか」です。<br /><br />
mirror.threshold = 30だと30ピクセル目になる。<br /><br />
今回はスライダーに合わせてるので、ソースが横幅320pixelだから</p>
</p>
<blockquote><p><p>slider.maximum = 320;<br /><br />
mirror.threshold = slider.value;<br /><br />
mirror.process();</p></p></blockquote>
<p>
<p>ってやってる。</p>
<p><a href="http://shokai.org/archive/as3/0709_dozen/KagamiVideo/Kagami.fla">Kagami.fla</a> 1frame目（<a href="http://shokai.org/archive/as3/0709_dozen/KagamiVideo/BitmapMirror.as">BitmapMirror.as</a>を同じディレクトリに入れてやる必要あり）<br /><br />
</p>
<blockquote><p>importfl.events.*;</p>
<p>//入力用<br /><br />
varvideo:Video=newVideo(320,240);<br /><br />
varconn:NetConnection=newNetConnection();<br /><br />
conn.connect(null);<br /><br />
varstream:NetStream=newNetStream(conn);<br /><br />
stream.client={onMetaData:function():void{}};</p>
<p>varbmd:BitmapData=newBitmapData(320,240);</p>
<p>//ミラー<br /><br />
varmirror:BitmapMirror=newBitmapMirror(bmd);<br /><br />
varbmp_result:Bitmap=newBitmap(mirror.getBitmapData());</p>
<p>//結果表示用<br /><br />
bmp_result.x=0;<br /><br />
bmp_result.width=640;<br /><br />
bmp_result.height=240;<br /><br />
this.addChild(bmp_result);</p>
<p>varplayFlug:Boolean=false;</p>
<p>buttonLoad.addEventListener(MouseEvent.CLICK,loadVideo);<br /><br />
functionloadVideo(e:Event){<br /><br />
trace(&#8220;loadVideo()&#8221;);<br /><br />
stream.play(textUrl.text);<br /><br />
playFlug=true;<br /><br />
}</p>
<p>this.addEventListener(Event.ENTER_FRAME,reloadMirror);<br /><br />
slider.addEventListener(SliderEvent.THUMB_DRAG,reloadMirror);</p>
<p>functionreloadMirror(e:Event){<br /><br />
if(!playFlug)return;<br /><br />
<br /><br />
video.attachNetStream(null);<br /><br />
bmd.draw(video);//ビットマップデータを取得<br /><br />
video.attachNetStream(stream);<br /><br />
mirror.threshold=slider.value;<br /><br />
mirror.process();//鏡写しのビットマップデータ作成<br /><br />
}<br />
</p></p></blockquote>
<p>
</p>
]]></content:encoded>
			<wfw:commentRss>http://shokai.org/blog/archives/1433/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

