0

BeagleBoard(Ubuntu9.04)とArduinoやmoxaを接続し、シリアル通信でやりとりする

BeagleBoardはTIの作ったオープンソースハードウェア。こっそり春頃からいじっていたんだけど書いてなかった。
スペックに難はあるが、ディスプレイとサウンド出力つきの組み込み環境が俺でも作れるのは魅力。作品のレベルをプロダクトに近い所まで引き上げられる。

最近CRESTでのプロジェクト用にARM版Ubuntu9.04をbuildしてインストールして、Arduinoと接続する所までやったので書いておこう。

今はApache2とruby1.8.7(いずれもaptでインストールした)を使って、外付けしたArduinoとmoxaからデータを読み取っている。とりあえず大学院棟の明るさをCdSで取得してwebに公開している → http://shokai-b.mag.keio.ac.jp/light


R0012112


■準備
BeagleBoardと、インストール時にパソコンとBeagleBoard間でシリアルコンソールを使うのに必要なBeagleケーブルはdigikeyで購入できる。他の部品は国内で全て入手可能。
BeargleBoard 周辺機器


■Ubuntu9.04をBeagleBoardにインストール
OSはSDカードにインストールする。
最初デスクトップ版をインストールしてみたけど重すぎた。AngstromやHandheld Mojoならそれなりに動くけど、フルのUbuntuデスクトップはウィンドウマネージャをtwmにしても無理だった。
今回の用途にはCUIがあれば十分だったので、最低限のbuildツールを含んだARM用イメージを作って起動させた。

なお作業はext3のファイルシステムが読み書きできる必要があるので全て別のUbuntu Desktopで行った。

  1. BeagleBoardにUbuntu Desktop版インストール – 橋本詳解
  2. BeagleBoardにUbuntuセットアップ(GUIなし)、sshdを入れてリモートログインしてみる – 橋本詳解
  3. いろいろインストールして環境を整える – 橋本詳解
  4. Rubyまわりをインストール – 橋本詳解

これで、ApacheやRubyやsshdはもちろん、emacsと各種elisp、gemとeasy_install、rails2やsinatra、mongrel_clusterとapache2でmod_proxy_balancerなどがaptとgemでさくっとインストールできた。
このへんのサーバサイドアプリ開発環境のセットアップの容易さはubuntuさすが。

処理速度は体感でDebian化した白箱より速い。SDカードなのであまりR/Wしたくないがたぶん外付けUSB-HDDも認識できるんじゃないだろうか。そうすると、クローラ作ったり簡単なbotを動かす程度の俺は自宅サーバ環境はBeagleBoardで十分という可能性もある。



処理速度とストレージ読み書きに不安はあるが小型かつバッテリーでも動かせるUbuntu Linux環境ができた。


■Arduino / moxaとの通信
BeagleBoard基板上のシリアルコンソールに使ったポートをArduinoやmoxaとの通信を使った。

本当はUSBで接続したかったけどできなかった。
FTDIチップのドライバはaptでインストールできるが、USBに接続しても認識されない(/dev/ttyUSB0に現れない)
同じバージョンのUbuntuデスクトップを別のAMD64マシンで用意して、同じようにセットアップをしたがこちらは認識した。ARM版Ubuntuが何かおかしいのかもしれない。

64bitAMDマシンではlsmodした時にFTDIチップを監視してるプロセスがいるんだけど、BeagleBoardではlsmodしてもいない。
誰かLinux詳しい人教えてくれるとうれしいです・・・


しかたないのでセットアップに使ったBeagleケーブルのオス版を作って通信に使う。
シリアルポートの9つのピンにはよく見ると番号が振ってあり、それをBeagleBoardの基板上の1~9ピン(10は無視する)と接続する。
シリアルポート側は

12345
6789

のようになるが、BeagleBoard上のコネクタは

13579
2468

となっているのでとても捻れる。気合いではんだづけする。基板上のシルク印刷をよく見てね。


R0012102
R0012104
R0012106
R0012107


これで、Arduinoの外側にあるRX / TXピンにMAX232(ADM3202)を接続すればBeagleBoardとシリアル通信できる。
R0012110

ArduinoにCdS(明るさのセンサ)なんかを接続して数値をBeagleBoardに送り続ければいい。


/etc/event.d/ttyS2 を削除して再起動すると、/dev/ttyS2がシリアルコンソールではなくシリアルポートとして開ける。

screen /dev/ttyS2

でモニタできる。
UNIX系なので、シリアルポートもFileとして簡単に読み出せた。
Arduino + BeagleBoard(Ubuntu) シリアルポートを読んでたまにファイルに書き出す – 橋本詳解

通信できる手段が整えばあとは好きにできる。

0

buzztterでtimelineを作るYahoo Pipes

buzztter.comでtwitter上の最近の頻出単語の統計を出してくれている。
頻出語の上位5単語で検索してタイムラインを作るYahoo Pipesを作った。

Pipes: buzztter timeline

1週間ぐらい見ているが、だいたいみんなテレビとかゲームとか選挙とか芸能人の話題、眠気や月曜日出勤したくないなどの体調の問題を喋っている。ワイドショー的。
上位3語や10語も試したけど、5語ぐらいが話題がバラけすぎもせず偏りすぎもせずちょうどよかった。


前作「twitter上で親しい人のタイムラインを動的に生成するYahoo Pipes」は自分に近い人がでてくるけど、今度のはtwitterの中心近くで騒いでいる人たちが見える。

0

Playstation EyeをOpenCVで使う

プレステ3のカメラをWindowsのVisualStudio2008環境のOpenCVで使う方法をまとめる。

ゲーム用の画像解析用のカメラなので

  • 解像度は640×480もしくは320×240と高くは無いが、320×240の時は最大120FPSでキャプチャできる
  • レンズのモードを切り替える事が出来て、かなり広角になる
  • オートフォーカスが無いので画像処理しやすい
  • ゲインやホワイトバランスの調整が高速
  • 4000円前後で手に入る。性能の割に安い
などの利点がある。また、後述するWindows用のドライバを使うとゲインやホワイトバランスを任意の値にしたり、自動調整をオフにしたりできるので画像処理には便利。

この記事で使ったプログラムは全部OpenCV勉強リポジトリにアップロードした。


PLAYSTATION Eye(CEJH-15001)
PLAYSTATION Eye(CEJH-15001)
posted with amazlet at 09.07.07
ソニー・コンピュータエンタテインメント (2008-07-24)
売り上げランキング: 1418
おすすめ度の平均: 4.5
3 GAMEはあんまり・・・
3 まぁ、及第点
4 確実に使用できるWebカメラ
4 気になる点を
5 お得!WindowsXP, VISTAでも問題なく使用できましたよ!



■準備
詳しくはVS2008 OpenCVでPlaystation3 eyeを使うにまとめた。

Macではmacamを通せば普通のwebcamとして使えるし、LinuxでもgpscaにOV534というPS3Eyeが使っているチップのドライバを入れれば動くらしい

Windowsの場合、Alexさんという方が作ったドライバを入れれば使える。ただし普通のwebcamとして認識は出来ないのでFlashなどからは使えない。
ドライバと一緒にIPS3EyeLib.hが付いてきて、これを通すとDirectShowのFilterとして使えるようになる。
Visual Studio2008のC++でOpenCVを使いたいので、これもプロジェクトに読み込むようにした。

Visual Studio2008でのOpenCVのセットアップ自体はVC++2008にOpenCV環境セットアップ、カメラでキャプチャに書いた。


■キャプチャしてみる
IPS3EyeLib.hなどへのパスを通したら、プログラムを書いてみる。
dandelion’s log ? PS3EyeLibで遊んでみたに書いてあるコードが参考になった。

作ったのはこれ → PlayStation3 eyeでキャプチャして表示
このプログラムは、普通のwebcamでキャプチャして表示するだけプログラムのカメラ取得と画像取得データ部分をPS3Eye用に置き換えたもの。比較するとわかりやすいと思う。

違いは、カメラを取得するのが
cvCreateCameraCapture(0);
の代わりに
IPS3EyeLib::Create();
でカメラを取得するのと、
image = cvQueryFrame(capture);
で画像を取ってこれる所が
PBYTE capBuffer = new BYTE[(WIDTH * HEIGHT * DEPTH) / 8];
capture->GetFrame(capBuffer, DEPTH, false)
memcpy(image->imageData, capBuffer, image->imageSize);
という風に一度バッファに渡さないとならない。面倒。



■画像処理してみる
Teleshadowでも使っている、cvRunningAvgを使って背景統計を取り背景差分するのコードをPS3Eyeで動かしてみる。
背景差分して影を作る。写真の通り、かなり近づいてカメラの外側にいるのに画面にちゃんと体が写っていることから広角っぷりがわかる。
R0012068

コードはOpenCV Study: dcd37cb7a048 BgSubAvgPs3eye/bgsubAvgPs3eye.cppにアップロードした。

PS3Eyeはゲインやホワイトバランスの調整やオートゲイン調整のon/offができるんだけど、一度オートをoffにしてプログラムを終了させてから再度起動すると前にoffにした設定が引き継がれてしまって真っ暗になる。起動時に全部onにしなければならない。
今回は最初だけオートにしつつゲインだけ固定して、安定したら全部オートをoffにするようにした。これが一番安定して影が作れる。


bgsubAvgPs3eye.cpp
#include <stdio.h>
#include <highgui.h>
#include <cv.h>
#include <highgui.h>
#include <ctype.h>
#include <iostream>
#include <cstdlib>
#include <IPS3EyeLib.h>

#define WIDTH  320
#define HEIGHT 240
#define FPS    30
#define DEPTH  24

#define INIT_TIME 200
#define BG_RATIO 0.02 // 背景領域更新レート
#define OBJ_RATIO 0.005 // 物体領域更新レート
#define Zeta 10.0

int main(int argc, char** argv)
{
        bool isStop = false;
        IplImage *img = cvCreateImage(cvSize(WIDTH, HEIGHT), IPL_DEPTH_8U, 3);
        IPS3EyeLib *capture = IPS3EyeLib::Create();
        capture->AutoAGC(false); // ゲイン固定
        capture->SetGain(1);
        capture->AutoAWB(true);
        capture->AutoAEC(true);

        if(capture == NULL){
                printf("capture device not found!");
                return -1;
        }

        IplImage *imgAverage = cvCreateImage(cvSize(WIDTH, HEIGHT), IPL_DEPTH_32F, 3);
        IplImage *imgSgm = cvCreateImage(cvSize(WIDTH, HEIGHT), IPL_DEPTH_32F, 3);
        IplImage *imgTmp = cvCreateImage(cvSize(WIDTH, HEIGHT), IPL_DEPTH_32F, 3);
        IplImage *img_lower = cvCreateImage(cvSize(WIDTH, HEIGHT), IPL_DEPTH_32F, 3);
        IplImage *img_upper = cvCreateImage(cvSize(WIDTH, HEIGHT), IPL_DEPTH_32F, 3);
        IplImage *imgSilhouette = cvCreateImage(cvSize(WIDTH, HEIGHT), IPL_DEPTH_8U, 1);
        IplImage *imgSilhouetteInv = cvCreateImage(cvSize(WIDTH, HEIGHT), IPL_DEPTH_8U, 1);
        IplImage *imgResult = cvCreateImage(cvSize(WIDTH, HEIGHT), IPL_DEPTH_8U, 3);

        capture->SetFormat(IPS3EyeLib::GetFormatIndex(WIDTH, HEIGHT, FPS));
        PBYTE capBuffer = new BYTE[(WIDTH * HEIGHT * DEPTH) / 8];
        capture->StartCapture();

        printf("背景初期化中...¥n");
        cvSetZero(imgAverage);
        for(int i = 0; i < INIT_TIME; i++){
                if(capture->GetFrame(capBuffer, DEPTH, false)){
                        memcpy(img->imageData, capBuffer, img->imageSize);
                }
                cvAcc(img, imgAverage);
                printf("輝度平均 %d/%d¥n", i, INIT_TIME);
        }
        cvConvertScale(imgAverage, imgAverage, 1.0 / INIT_TIME);
        cvSetZero(imgSgm);
        for(int i = 0; i < INIT_TIME; i++){
                if(capture->GetFrame(capBuffer, DEPTH, false)){
                        memcpy(img->imageData, capBuffer, img->imageSize);
                }
                cvConvert(img, imgTmp);
                cvSub(imgTmp, imgAverage, imgTmp);
                cvPow(imgTmp, imgTmp, 2.0);
                cvConvertScale(imgTmp, imgTmp, 2.0);
                cvPow(imgTmp, imgTmp, 0.5);
                cvAcc(imgTmp, imgSgm);
                printf("輝度振幅 %d/%d¥n", i, INIT_TIME);
        }
        cvConvertScale(imgSgm, imgSgm, 1.0 / INIT_TIME);
        printf("背景初期化完了¥n");

        capture->AutoAWB(false); // ホワイトバランス固定
        capture->AutoAEC(false);

        char winNameCapture[] = "Capture";
        char winNameSilhouette[] = "Silhouette";
        cvNamedWindow(winNameCapture, 0);
        cvNamedWindow(winNameSilhouette, 0);

        int waitKey = 0;
        while(1){
                if(!isStop){
                        if(capture->GetFrame(capBuffer, DEPTH, false)){
                                memcpy(img->imageData, capBuffer, img->imageSize);
                        }
                        if(img == NULL) break;
                        cvConvert(img, imgTmp);

                        // 輝度範囲
                        cvSub(imgAverage, imgSgm, img_lower);
                        cvSubS(img_lower, cvScalarAll(Zeta), img_lower);
                        cvAdd(imgAverage, imgSgm, img_upper);
                        cvAddS(img_upper, cvScalarAll(Zeta), img_upper);
                        cvInRange(imgTmp, img_lower, img_upper, imgSilhouette);

                        // 輝度振幅
                        cvSub(imgTmp, imgAverage, imgTmp);
                        cvPow(imgTmp, imgTmp, 2.0);
                        cvConvertScale(imgTmp, imgTmp, 2.0);
                        cvPow(imgTmp, imgTmp, 0.5);

                        // 背景領域を更新
                        cvRunningAvg(img, imgAverage, BG_RATIO, imgSilhouette);
                        cvRunningAvg(imgTmp, imgSgm, BG_RATIO, imgSilhouette);

                        // 物体領域を更新
                        cvNot(imgSilhouette, imgSilhouetteInv);
                        cvRunningAvg(imgTmp, imgSgm, OBJ_RATIO, imgSilhouetteInv);

                        cvErode(imgSilhouette, imgSilhouette, NULL, 1); // 収縮
                        cvDilate(imgSilhouette, imgSilhouette, NULL, 2); // 膨張
                        cvErode(imgSilhouette, imgSilhouette, NULL, 1); // 収縮

                        cvMerge(imgSilhouette, imgSilhouette, imgSilhouette, NULL, imgResult);
                        cvShowImage(winNameCapture, img);
                        cvShowImage(winNameSilhouette, imgResult);
                }
                waitKey = cvWaitKey(33);
                if(waitKey == 'q') break;
                if(waitKey == ' '){
                        isStop = !isStop;
                        if(isStop) printf("stop¥n");
                        else printf("start¥n");
                }
        }

        delete [] capBuffer;
        capture->StopCapture();
        cvDestroyWindow(winNameCapture);
        cvDestroyWindow(winNameSilhouette);
        return 0;
}


今回は320×240で30FPSで動かしたが、
dandelion’s log PS3EyeLibで遊んでみたより

320×240@15fps
320×240@30fps
320×240@60fps
320×240@75fps
320×240@100fps
320×240@125fps
640×480@15fps
640×480@30fps
640×480@40fps
640×480@50fps
640×480@60fps
640×480@75fps

で動くらしい。

0

Mac版Arduino IDEがlibrxtxSerial.jnilibが見つからないエラーで起動しないのを対処

起動時にこんなエラーが出た。環境はMacbook pro 17インチ OSX LeopardでXCodeなどもたぶん全部入れてあるメイン開発環境。

java.lang.UnsatisfiedLinkError: /Applications/arduino/Arduino.app/Contents/Resources/Java/librxtxSerial.jnilib: no suitable image found.



arduino error


原因は64bit環境で実行されようとしているかららしい。
対処法は2つ。


■Arduinoを32bit版で起動させる
Arduino実行ファイルを右クリックして「情報を見る」
「32ビットモードで開く」にチェック入れる
(hitoriblogさんより)


■64bit版rxtxSerial.jnilibをインストールする
http://iharder.sourceforge.net/current/java/librxtxSerial.jnilib
/Arduino 16.app/Contents/Resources/Java/
の中にある librxtxSerial.jnilib と入れ替える。

これはTechnobabble: Arduino: Problems With librxtxSerial.jnilibに書いてあった方法。


両方ともIDEの起動までは試した。
Arduinoの実機が今ないので動作は試していない。まあどっちかで動くだろう。



他にもフォーラムでも同様の質問が出ていて、こちらでは
アプリケーション/ユーティリティ/Java Preferences
でJavaアプリケーションの方でJ2SE5.0の32bit版が優先されるように上の方にもってこいという指示が出ていた。質問者はこれで解決したらしいけど俺の環境では駄目だった。
これはArduinoがJ2SE6向けに作られていないため。

0

twitter上で親しい人のタイムラインを動的に生成するYahoo Pipes

修論を出した後のテンションでまたYahooPipesを作った。

Pipes: twitter dynamic timeline


親しい人で動的にタイムラインを作るpipes。
しくみは単純で、最近favを付けたり付けられたり、replyしたりされたりした相手でタイムラインを作る。
自分の発言内容やそれに対する相手の反応によってメンバーが動的に入れ替わる。

これの面白いのは、キーワードでの振り分けと異なりごく普通にtwitterを使っているだけでタイムラインが親しいメンツに変わっていく。
一見流動性が無いように見えるが、自分をふぁぼったりreplyしてきた人は入ってくるし、その人にreplyしたりfavし返せばしばらくはdynamic timelineに入り続ける。相手をfollowする必要すらない。

忙しくてtwitter見れない時は、これだけ見ておけば親しい人のpostはだいたいチェックできるかもしれない。


RSSにはusericons.relucks.orgを使ってアイコンを表示した。ユーザ名やURLもリンクになるようにしてある。
twitter dynamic timeline


pipes全体図
twitter dynamic timeline


一番上ですぐ分岐させて、左から順に

という具合。

YahooPipesは処理時間が長くなりすぎるか、pipesから相手へのアクセスが多くなりすぎるとエラーが出てしまう。

処理時間が長くならないようにするには並列化。pipeを縦につなげないようにする。
重要なのはpipesから外部サイトへのHTTPリクエストからレスポンスまでの時間だと思う。米yahooの強力なサーバーなので正規表現でページ解析する処理時間はたぶん無視していい。

今回上の方で4つに分けているように、早めにsplitモジュールで分けてそれぞれでHTTPリクエストを発行させてレスポンス待ちにすると速い。splitで分けられたそれぞれと、Loopモジュール内でFetch PageやFetch Feedモジュールは自動的にpipes内で非同期処理になってそうな気がする。
※この時どれかで取得した結果を他のsplit先に使おうとすると、結局縦に1本につなげているのと変わらなくなってしまうので注意しましょう

pipesから他のサイトへのアクセスについては、twitterとyahooが地理的に遠くないからなのか制限が緩くなっているのかよくわからないが、3秒で100アクセスぐらいしてるのにエラーはでない。