INDEX(各項目ごとの目次)

[HOME]  [Processing関係]  [Arduino関係]  [マテリアル関係]  [秋葉原工作マップ]

2008年用ですが、部分的に内容を更新しています(2010/06/14)。
また、[建築農業工作ゼミ2009-2010]とも連動していますので、そちらにも幾つかサンプルがあります。
:

検索キーワード「processing」に一致する投稿を日付順に表示しています。 関連性の高い順 すべての投稿を表示
検索キーワード「processing」に一致する投稿を日付順に表示しています。 関連性の高い順 すべての投稿を表示

10/18/2008

Arduino-Processing シリアル通信6


【変更】以下はArduino1.0まで対応したプログラム内容です。
特にシリアル通信においては、Arduino2.0使用の際、バイト送信する場合、
Serial.print(value,BYTE);
のかわりに、
Serial.write(value);
を使用してください。


これまでのシリアル通信では、ProcessingとArduinoの一対一の通信を行ってきましたが、今回はProcessingで二つのシリアルポートを使い、二つのArduinoとシリアル通信を行う実験をしてみます。それぞれのArduino基盤には可変抵抗器をとりつけて入力値をProcessingへ別々に送信することにします。Processingの画面では、二つの入力信号を個別に読み取ってそれぞれの状態を描画することにします。

具体的なサンプルとして、「Pong」(下画像)のように二つのコントローラによって対戦するプログラムにします。それぞれのArduino基盤がコントローラとしてコンピュータに接続され、画面上でそれぞれのラケットを動かすことになります(得点のプログラムは含まれていません)。


(上画像:Processingの画面「Pong」)

Arduino基盤と可変抵抗器の接続は以下のようになります(二つ必要です)。



Arduinoのプログラム:
void setup(){
  Serial.begin(9600);
}

void loop(){
  //可変抵抗器の読み取り
  int val=analogRead(0);
  //シリアル通信処理
  if(Serial.available()>0){//合図用データが一つ来たら
    //合図用データを読み込んでバッファを空にする
    Serial.read();
    //読取値を4で割り、バイトで送信
    Serial.print(val/4,BYTE);
  }
}

Arduino側のプログラムでは、可変抵抗器からの読取り値を4で割ってスケールダウンした値(0~255)をシリアル通信でProcessing側へ送信しています(同期通信させるために合図用データを用いるシリアル通信の方法については「Arduino-Processing シリアル通信2」を参照して下さい)。二つのArduino基盤とも同じ内容になります。

Processingのプログラム:
//シリアル通信ライブラリを取り込む
import processing.serial.*;
//二つのポートのインスタンス
Serial portA;
Serial portB;
//二つの読取値の変数
int valA=100,valB=100;
//ボ−ル座標用変数
int x=100,y=100;
//ボールの動きの向きの変数(1:正の向き、-1:負の向き)
int dirX=1,dirY=1;

void setup(){
  //画面サイズ設定
  size(300,256);
  //二つのシリアルポート設定
  portA = new Serial(this, "/dev/tty.usbserial-A50019vD", 9600);
  portB = new Serial(this, "/dev/tty.usbserial-A40014iU", 9600);
  //図形外形線なし
  noStroke();
  //塗り色(白)
  fill(255);
  //矩形描画位置を中央に設定
  rectMode(CENTER);
}

void draw(){
  //背景(黒)
  background(0);
  //左ラケット描画(valAをY座標に代入)
  rect(20,valA,10,30);
  //右ラケット描画(valBをY座標に代入)
  rect(280,valB,10,30);
  //ボール描画
  rect(x,y,10,10);

  //ボールX座標の動き
  x+=dirX;//X軸方向に+1または-1ずつ進める

  //ラケットAに当たった時のはね返り
  if(x==30 && y>valA-15 && y<valA+15){
    dirX*=-1;//向きを反転する
  }
  //ラケットBに当たった時のはね返り
  if(x==270 && y>valB-15 && y<valB+15){
    dirX*=-1;//向きを反転する
  }
  //画面左端からはみ出た場合
  if(x<0){
    x=270;//右側に戻る
  }
  //画面右端からはみ出た場合
  if(x>width){
    x=30;//画面左側に戻る
  }

  //ボールY座標の動き
  y+=dirY;//Y軸方向に+1または-1ずつ進める

  //画面上下位置でのはね返り
  if(y<5 || y>251){
    dirY*=-1;//向きを反転する
  }  
}

//キーを押した場合
void keyPressed(){
  //「s」キーでシリアル通信開始
  if(key=='s'){
    //二つのポートへ開始用データ送信
    portA.write(65);
    portB.write(65);
  }
}

//シリアル通信処理
void serialEvent(Serial p){
  //portAの場合
  if(p==portA){
    if(p.available()>0){
      //値を読み込みvalAに代入
      valA=p.read();
      //合図用データ送信
      portA.write(65);
    }
  }
  //portBの場合
  if(p==portB){
    if(p.available()>0){
      //値を読み込みvalBに代入
      valB=p.read();
      //合図用データ送信
      portB.write(65);
    }
  }
}

Processing側のプログラムでは、二つのシリアルポートを用意し、それぞれportA、portB(名前は任意)にしておきます。serialEvent(Serial p){...}の括弧内のpは、ポート名に対応しています(今回の場合は、pはportAまたはportBに対応します)。serialEvent()は、Processingがデータを受信した際に作動するので、if()文を使ってどちらのポートなのかを条件分けして判別し、ポートに応じてそれぞれの読込み値を変数に代入します。同期通信させるために、Arduinoから送信されたデータをp.available()で確認しデータを読み込んだ後に、合図用データ(0~255の数値あるいは'A'や'a'などの一つの文字/1バイト分のデータ)を送信しています。
Processingのプログラムを開始したら、「s」キーを押すことでシリアル通信を開始することにしました(プログラム開始から数秒経った後に「s」キーを押さないと、シリアル通信が開始されないことがあります)。

関連:
Arduino-Processing シリアル通信1」(一つの値を送る/非同期通信)
Arduino-Processing シリアル通信2」(複数の値をバイトで送る/同期通信)
Arduino-Processing シリアル通信3」(大きな値を複数送る)
Processing-Arduino シリアル通信4」(ProcessingからArduinoを制御する)
Arduino-Processing シリアル通信5」(複数の値を文字列で送信する)

10/17/2008

Arduino+Xbee Shield/Processing+XBee Explorer USB

Arduinoにはワイヤレス通信するためのXbee Shieldがあります。今回はArduinoのXbee Shieldのサイトを参考に簡単な通信実験から始めたいと思います。Xbee Shiledを装着したArduino基板が2個必要になります。


(Xbee Shiledを装着したArduino基板)

注意しなければいけないことは、Arduino基板にXbee Shiledを装着したままプログラムをアップロードする際、Xbee Shiled上にある二つのジャンパピン(二つの間には「XBEE/USB」と表示されています)を「USB」側に差し替えなければいけないことです(3本のピンのうち、USB側のピンと中央のピンの2本のピンに差し込まれている状態になります)。
尚、アップロード後は「XBEE」側のピンと中央のピンの2本のピンに差し込まれている状態に戻してください。


「二つのArduino間での通信」
以下では、Xbee Shieldのサイトに従って、LEDの点滅実験を行います。一方のXbee Shiledを装着したArduino基板から、一秒おきに'H'か'L'の文字を送信し、受信したもう一方のXbee Shiledを装着したArduino基板の13番ピンに接続されたLEDが点滅する内容です。基本的には、通常のシリアル通信のプログラムと同じような内容になります。

送信側Arduinoのプログラム:
void setup(){
  //シリアル通信開始
  Serial.begin(9600);
}

void loop(){
  Serial.print('H');//「H」を送信(点灯)
  delay(1000);      //1秒待つ
  Serial.print('L');//「L」を送信(消灯)
  delay(1000);      //1秒待つ
}

送信側は一方的に'H'と'L'を1秒間隔で送信するだけです。受信側は、送信側からのデータが届いたら読み込みをし、データが「H」であれば13番ピンをHIGHで出力し、そうでなければLOWで出力するプログラムになります。

受信側Arduinoのプログラム:
int val;//受信データ用の変数を用意

void setup(){
  //シリアル通信開始
  Serial.begin(9600);
  //13ピンをデジタル出力に設定
  pinMode(13,OUTPUT);
}

void loop(){
  if(Serial.available()>0){ //データが来たら
    val=Serial.read();    //データを読み込み、valへ代入
  }
  if(val=='H'){           //valが「H」の場合
    digitalWrite(13,HIGH);//点灯
  }else{                  //valが「L」の場合
    digitalWrite(13,LOW); //消灯
  }
}

乾電池などの外部電源でArduinoを作動させれば、それぞれスタンドアロンで通信し合います。Decimilaなどの旧型のArduino基板の場合、外部電源を使うにはArduino基板についている「USB/EXT」のジャンパピンを「EXT」側に差し替える必要があります(プログラムをアップロードする際には、パソコンとUSB接続し、ジャンパピンも「USB」側に差し替えて下さい)。



「コンピュータに接続したXbeeと通信」:
コンピュータにXbeeを接続し、Arduinoとワイヤレス通信するには以下のようになります。
Sparkfunで販売されている「XBee Explorer USB」を使うと簡単にコンピュータとUSB接続でき、Processingからワイヤレスにシリアル通信が可能になります。


(左:「XBee Explorer USB」、右:Xbeeを装着した状態)

以下では、先ほどの受信用のXbee Shieldを装着したArduino基板に対して、Xbeeモジュールを装着した「XBee Explorer USB」を通して、Processingからワイヤレスでシリアル通信してみます。
まず、「XBee Explorer USB」のシリアルポートを調べてみます。

import processing.serial.*;
println(Serial.list());

「XBee Explorer USB」をコンピュータに接続し、上記の二行のプログラムをランさせれば、「XBee Explorer USB」のシリアルポートが出力されるはずです。


「Processingのプログラム」
受信側となるArduinoのプログラムは先ほどと同じものを使い、送信側となるProcessingだけのプログラムを以下に書きます。マウスボタンを押したら点灯、放したら消灯する内容とします。

//シリアル通信ライブラリを取り入れる
import processing.serial.*;
//ポートのインスタンス
Serial port;

void setup(){
  //「XBee Explorer USB」のシリアルポート設定
  port=new Serial(this,"/dev/tty.usbserial-A8003VXd",9600);
}
void draw(){
  //特になし  
}

void mousePressed(){//マウスボタンを押したら
  port.write('H');  //「H」を送信(点灯)
}

void mouseReleased(){//マウスボタンを放したら
  port.write('L');   //「L」を送信(消灯)
}

プログラムの内容はこれまでのシリアル通信と同じなので、ポートの設定、接続やジャンパピンの差し替えなどを間違わなければ特に問題はないと思います。



「Xbeeモジュールの設定/ATコマンド」
XbeeにATコマンドを送ることで、Xbeeモジュール自体の設定を確認したり変更することができます。ATコマンドをXbeeモジュールに送信するには、MacOSXなら「ZTerm」など、Windowsなら「ハイパーターミナル」や「Tera Term」などのターミナルアプリケーションで入力すると便利です(「ZTerm」と「ハイパーターミナル」の設定や使い方については、「Arduino-Processing BlueTooth通信+曲げセンサ」にも、説明があります)。

コンフィグレーションモードに入るには、
+++

プラスを3回入力し待機します(リターンキーは押さない)。そうすれば、
OK

という返事が返ってきます(ATコマンドモードでは、「+++」を押したあと1秒後に「OK」が返答され、さらに10秒以内に次のコマンドを送信しなければコマンドモードが自動的に終了してしまうので、返答がない場合は再度「+++」を押す必要があります)。さらに続けて、
ATID(リターンキーを押して送信)

と押せば、デフォルトの状態であれば、
3332

という数値が返ってきます。「3332」という数値は、そのモジュールのネットワークIDであり、このネットワークIDを変更すれば同じネットワークIDを共有しているXbeeモジュール間だけでの通信が可能になります(ひとつのモジュールだけでなく、相手になるモジュールも変える必要があります)。
設定されている通信速度を確認するには、
ATBD(リターンキーを押して送信)

と押せば、デフォルトであれば、
3

という数値が返ってきます。通信速度はそれぞれ
0:1200 bps
1:2400 bps
2:4800 bps
3:9600 bps
4:19200 bps
5:38400 bps
6:57600 bps
7:115200 bps

なので、デフォルトの「3」は9600 bpsということになります。
ATBD4(リターンキーを押して送信)

と打てば(「ATBD」の後に「4」を付け加える)、通信速度は「4」(19200)に変更されます(「OK」という返答がきます)。再度、
ATBD(リターンキーを押して送信)

と打てば、変更した内容を確認できます(この場合「4」という数値が返ってきます)。ただし、この場合電源が切れると設定内容は消えてしまいます。電源を切っても設定内容が戻らないようにするには、
ATWR(リターンキーを押して送信)

を変更後すぐに送信します(「OK」という返答がきます)。あるいは、
ATBD4,WR(リターンキーを押して送信)

という感じで、コマンドを複数合成して送信することもできます(「OK OK」という二つ分の返答がきます)。デフォルトの状態に戻すには、
ATRE(リターンキーを押して送信)

になるのですが、「WR」を付け加えていないので再度電源を入れたときには前回の状態に戻ってしまいます。
ATRE,WR(リターンキーを押して送信)

とすれば、電源を入れ直してもデフォルト状態は保持されます。
尚、コマンドモードから出るには、
ATCN(リターンキーを押して送信)

になります。
デフォルトでは、
ID:3332(ネットワークID)
CH:0x0C(チャンネル)
MY:0(そのモジュールのアドレス)
SH: (シリアルナンバー上位32ビット/モジュールごとに異なる)
SL: (シリアルナンバー下位32ビット/モジュールごとに異なる)
DH:0(送信先アドレス上位32ビット)
DL:0(送信先アドレス下位32ビット)
BD:3(通信速度:9600 bps)

に設定されており、ネットワーク、チャンネル、モジュールのアドレス、送信先アドレスがそれぞれ同じであるため、どのモジュール間でも通信可能です。逆に、ネットワークIDを変更してしまえば、他のモジュール群から干渉を受けずに通信し合うことも可能になります。あるいは、二つのモジュール間で送信先を互いに設定してしまえば、そのアドレスのモジュールだけとの通信が可能になります。そのためには、IDとCHは共有しておき、それぞれのモジュールアドレスを個別に設定しておきます。
ATMY1111,WR(リターンキーを押して送信)
OK OK(Xbeeからの返信)

とすれば、このモジュールの「MY」は「1111」に設定されたことになります(「WR」を付け加えたので、電源を落としても変更内容は記憶されます)。さらに送信先のアドレスを「2222」に設定するには、「DL」を「2222」に「DH」を「0」にします。
ATDL2222,DH0,WR(リターンキーを押して送信)
OK OK OK(Xbeeからの返信)

もう一方のほうも設定する必要があるので、「MY」を「2222」、「DL」を「1111」、「DH」を「0」にします。
ATMY2222,DL1111,DH0,WR(リターンキーを押して送信)
OK OK OK OK(Xbeeからの返信)

とします。こうすることで、「1111」のモジュールと「2222」のモジュールが互いに送信先を特定して通信し合うことができます。

Arduino Xbee Shieldサイトの説明によれば、「DH」を「0」、「DL」を「FFFF」に設定すれば、そのモジュールからの通信は、その他のすべてのモジュールによって受信可能になります。
また、送信先アドレス(上位ビットDHと下位ビットDL)が「FFFF」より大きい値(つまり「DH」が「0」以外の数値に設定したとき)、その「DH」と「DL」が相手モジュールの「SH」と「SL」に等しければ、相手モジュールのみに受信させることが可能になります。ただし、この場合も、ネットワークIDとチャンネルは同じでなければなりません。


「Ztermの設定」
以下は、MacOSXで「ZTerm」を使ったときの画面です。


ATコマンドを打つ画面。



メニューバー>Dial>Directory...をクリックすれば、上画面が現れます。「New」を押せば、新たな接続先を追加する画面(下画像)がでてきます。



「Service Name:」の欄に適当な名前を入れます。「Local Echo」にはチェックをいれておきます。チェックを外すと、自分の打った文字は画面に現れないので、チェックを入れておいた方がいいでしょう。Xbeeがデフォルト状態であれば、その他の項目は上画面のようになります。メニューバー>Setting>Connection...をクリックすることで、再度この画面が現れます。



メニューバー>Setting>Terminal...をクリックすれば、上画面が現れます。ここでは「Auto Line Feed」にチェックをいれておきます。チェックを外すと、ATコマンドが改行されなくなるので、チェックをいれておいたほうがいいでしょう。恐らく、MacOSXの改行コードは、「CR(キャリッジリターン:行頭に戻る)」だけなので、「LF(ラインフィード:次の行に移る)」も付け加えないと改行されなくなるからでしょう。

Zigbee開発ハンドブック (実践入門ネットワーク)
鄭 立
リックテレコム
売り上げランキング: 9866

DESIGNING ZIGBEE NETWORKS AND TRANSCEIVERS: The Complete Guide for Rf/Wireless Engineers
Shanin Farahani
Newnes
売り上げランキング: 21994

10/12/2008

Processing FileChooser2

以前「Processing FileChooser/ファイル選択画面の表示」でJava Swingを用いましたが、Processing 146以降からselectInput()によって、ファイル選択画面を通して任意の場所にあるファイルを読み込むことが簡単にできるようになりました。
以下は、「f」キーを押すとselectInput()で、ファイル選択画面を表示し、コンピュータの任意の場所にある画像を表示するサンプルです。表示された画像は、ドラッグすることで位置を変えられるようにしてあります。

//画像用インスタンス用意
PImage img;

//現在選択中のファイルパスの変数
String currentPath=null;
//画像配置座標の変数
int x,y;
//画像配置座標とクリック座標の差分の変数
int dx,dy;

void setup(){
  //画面サイズ設定
  size(600,400);
}

void draw(){
  //背景描画(黒)
  background(0);
  //現在選択中のファイルパスが空ではないとき
  if(currentPath!=null){
    //画像描画
    image(img,x,y);
  }
}

//クリックしたら
void mousePressed(){
  //画像配置座標とクリック座標の差分を求めておく
  dx=x-mouseX;
  dy=y-mouseY;
}

//ドラッグ中
void mouseDragged(){
  //マウス座標に差分座標を加えた値を画像配置座標とする
  x=mouseX+dx;
  y=mouseY+dy;
}

//キーを押した場合
void keyPressed(){
  //「f」キーなら
  if(key=='f'){
    //ファイル選択画面を表示し選択したファイルパス取得
    String loadPath = selectInput();
    //ファイルパスが空の場合
    if (loadPath == null) {
      //「ファイルが選ばれてない」メッセージを出力
      println("No file was selected...");
      //ファイルパスを前回のファイルパスにする
      loadPath=currentPath;
    } 
    else {//ファイルパスが選択された場合
      //ファイルパスのドット以降の文字列を取得(拡張子名を取得)
      String ext = loadPath.substring(loadPath.indexOf('.') + 1);
      //拡張子が「jpg」または「png」なら
      if(ext.equals("jpg") || ext.equals("png")){
        //選択ファイルパスの画像を取り込み
        img = loadImage(loadPath);
        //現在選択中のファイルパスを更新
        currentPath=loadPath;
        //現在選択中のファイルパスを出力
        println(currentPath);
      }else{//拡張子が「jpg」または「png」ではないとき
        //「画像ファイルではない」と出力
        println("Not image file.");
      }      
    }
  }
}


ファイル選択画面上で選択したファイルのパスを取得したら、substring()によってパスの文字列末尾に含まれる拡張子を調べます。その際、indexOf()を使うことで、パスの文字列に含まれる「.」を手掛かりに、パスの文字列末尾の拡張子を抜き出します(indexOf()は、括弧内に入れた文字が文字列中の何番目にあるかを教えてくれます)。
今回は、拡張子が「jpg」か「png」であれば、loadImage()で画像を取り込み、次回のためにファイルパスを記憶させておきます。それ以外の拡張子の場合は、"Not image file."だけを出力します。

selectInput()以外に、フォルダを選択するためのselectFolder()や、保存先を指定するselectOutput()も加えられています。

----------------------------------
以下は頂いたコメントに対するサンプルです。
import java.awt.*;

void setup() {
  size(200,200);
}

void draw(){  
}

void fileSelected(File selection) {
  if (selection == null) {
    println("Window was closed or the user hit cancel.");
  } else {
    println("User selected " + selection.getAbsolutePath());
  }
}

void mousePressed(){
  FileDialog fd = new FileDialog(new Frame(),"Choose a file");
  fd.setDirectory("/Users/username/Desktop");//ここのディレクトリを任意に変えてください。
  fd.setVisible(true);
  if(fd.getFile()==null){
    println("Canceled");
  }else{
    println("FILE NAME:"+fd.getFile());
    println("DIRECTORY:"+fd.getDirectory());
  }
}

10/11/2008

Processing Webカメラ/定点記録画像

身体の動作などを連続写真として記録するために、Webカメラを用いインデックス番号をつけて画像保存する方法です。設定したフレームレートで撮影画像を順番に保存していきます。以下は、フレームレート2の速度(0.5秒/フレーム)で処理するサンプルです。sキーで連続写真の画像を0.5秒おきに保存し、eキーで保存を終了、cキーでカメラセッティング画面に切り替わります。
videoライブラリの基本的な使い方は「Processing Video(Webカメラ)」を参照して下さい。

*Windowsの場合、そのままの設定ではこのVideoライブラリを使用することができません。WinVDIG 1.0.1をインストールする必要があります。


//ライブラリの取り込み
import processing.video.*;
Capture myCapture;

//記録開始用のフラグ
boolean start=false;
//記録画像インデックス用変数
int num=0;

void setup() {
//画面サイズ設定
size(320, 240);
//キャプチャする映像の設定(2フレーム/秒)
myCapture = new Capture(this, width, height, 2);
//ループのフレームレート(2フレーム/秒)
frameRate(2);
}

void draw() {
//映像を画面に配置
image(myCapture, 0, 0);
if(start){
//記録中の目印表示
rect(0,0,10,10);
//記録画像インデックス名(jpgで保存)
String s="image_"+num+".jpg";
//画像を保存
save(s);
//インデックス番号を更新
num++;
}
}

//映像の読み込み
void captureEvent(Capture myCapture) {
myCapture.read();
}

void keyPressed(){
//sキーで画像記録開始
if(key=='s'){
start=true;
}
//eキーで記録終了
if(key=='e'){
start=false;
}
//cキーでカメラセッティング
if(key=='c'){
myCapture.settings();
}
}


連続写真の画像は、撮影された数だけスケッチフォルダのなかに保存されます。

(スケッチフォルダの中にインデックス番号を含んだ保存名で保存される/MacOSXの場合)

上図の場合、「image_0.jpg」から「image_10.jpg」までの合計11枚の画像が保存されています。フレームレートは2なので、約5.5秒間撮影(連続保存)したことになります。

関連:
Processing Video (Webカメラ)」--Webカメラの使い方/映像にフィルタをかけて表示。
Arduino+Processing マトリクスLED+Webカメラ」--Webカメラ映像をマトリクスLEDに映す。
Processing Webカメラを光センサとして使う」--点光源で画面内に線を描く。
Processing Webカメラ/カラートラッキング」--Webカメラを使い、色を手がかりに物体を追いかける。
Processing Webカメラ/モーショントラッキング」--Webカメラを使って動体検知する。

10/04/2008

Processing QRコード/2次元コード

Processingのライブラリには、「QRCode」というものがあります。QRコードとは、以下のような2次元的なマトリクスを利用したコードです。バーコードよりは情報量が多く、様々な場面に用いられています。


このブログのURL情報が含まれたQRコード(上画像:qrcode.png)

Processingの「QRCode」ライブラリを用いることで、QRコードに含まれた情報を解読することができます。逆に、任意の情報のQRコードを生成するには以下のようなサイトで行うことができます。

http://qrcode.kaywa.com
http://qr.quel.jp

QRCode」ライブラリでは、上記サイトなどで直接生成したQRコード以外にも、Webカメラやデジタルカメラで撮影したQRコード(紙上に印刷したQRコード)を認識/解読することができます。尚、このライブラリを使うには「QRCode」ライブラリサイトからライブラリをダウンロード+インストールする必要があります。サイトの説明によれば、以下のようなコードでQRコードを解読することができます。予めQRコードの画像を用意して、スケッチフォルダ内dataフォルダに入れておいて下さい。
以下のサンプルは、上にあるQRコード(qrcode.png)を使って解読するコードです。無事QRコードが解読されれば、link()によって、自動的にこのブログのURLへジャンプするようになっています。


//ライブラリを取り入れる
import pqrcode.*;

//インスタンス名
Decoder decoder;

void setup() {
//オブジェクトの生成
decoder = new Decoder(this);
//イメージのロード
PImage img = loadImage("qrcode.png");
//イメージの解読
decoder.decodeImage(img);
}

void decoderEvent(Decoder decoder) {
//解読結果をテキストとして取り出す
String statusMsg = decoder.getDecodedString();
//テキストを画面に出力
println(statusMsg);
//テキストに書かれているURLへ移動
link(statusMsg, "_new");
}



QRCode」ライブラリのサイトには、Webカメラから撮影したQRコードを読み込んで解読するサンプルがあります。

http://www.shiffman.net/p5/pqrcode_files/Pqrcode_example.zip

このサンプルでは、「スペース」キーでカメラからのQRコードを画像として読み込み解読します。解読されたテキストは画面に文字として表示されます。「f」キーで、既に用意されているテスト用の画像を読み込んで解読します(「http://www.shiffman.net」と表示されるはずです)。「s」キーで、カメラセッティングの画面に切り替わります。
カメラでQRコードを撮影するときに、ピントがずれていると認識できないこともあるので、カメラを調整する必要があるかもしれません。

以下では、デスクトップ上あるいはその他の場所に保存してあるQRコードをファイルチューザーで選び、解読する実験をしてみます。ファイルチューザーについては、前回のブログ「Processing FileChooser/ファイル選択画面の表示」を参照して下さい。
マウスを押したら、ファイルチューザーのダイアログ画面が現れ、任意のQRコードを選択し、解読結果をProcessingのコンソールに表示します。

//JavaのSwingを取り込む
import javax.swing.*;

import pqrcode.*;

//解読用インスタンスを用意
Decoder decoder;

//画像インスタンスを用意
PImage pimage;

//選択ファイル名を用意し
//ファイル名を空にしておく
String getFile = null;

void setup(){
//とりあえず表示画面を400角に設定
size(400,400);
//解読用オブジェクトの生成
decoder = new Decoder(this);
//背景(黒)
background(0);
}

void draw(){
//選択ファイル名が空でないとき
if(getFile != null){
//ファイルを取り込む
fileLoader();
}
}

//マウスを押したら
void mousePressed(){
//選択ファイル取得処理
getFile = getFileName();
}

//ファイルを取り込むファンクション
void fileLoader(){
//選択ファイル名のドット以降の文字列を取得
String ext = getFile.substring(getFile.lastIndexOf('.') + 1);
//その文字列を小文字にする
ext.toLowerCase();
//文字列末尾がjpg,png,gif,tgaのいずれかであれば
if(ext.equals("jpg") || ext.equals("png") || ext.equals("gif") || ext.equals("tga")){
//選択ファイル名のイメージを取り込む
pimage = loadImage(getFile);
//背景(黒)
background(0);
//イメージ表示
image(pimage, 0, 0, pimage.width, pimage.height);
//イメージの解読
decoder.decodeImage(pimage);
}
//選択ファイルパスを空に戻す
getFile = null;
}

//ファイル選択画面、選択ファイル名取得の処理
String getFileName(){
//処理タイミングの設定
SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
//ファイル選択画面表示
JFileChooser fc = new JFileChooser();
int returnVal = fc.showOpenDialog(null);
//「開く」ボタンが押された場合
if (returnVal == JFileChooser.APPROVE_OPTION) {
//選択ファイル取得
File file = fc.getSelectedFile();
//選択ファイルのパス取得
getFile = file.getPath();
}
}
//上記以外の場合
catch (Exception e) {
//エラー出力
e.printStackTrace();
}
}
}
);
//選択ファイルパス取得
return getFile;
}

void decoderEvent(Decoder decoder) {
//解読結果をテキストとして取り出す
String statusMsg = decoder.getDecodedString();
//テキストを画面に出力
println(statusMsg);
}



以下は、授業内で実験した内容です。
数値の情報(100など)を含んだQRコードを生成し、プログラムによって解読された文字列としての数値を整数型の数値に変換します。その数値をArduinoへシリアル通信で送信し、analogWrite()でLEDの輝度やモータの速度あるいはサーボの回転角度などに反映させる内容です。いくつかのQRコードを作成し、ファイルチューザーで選択したQRコードを入れ替わりで送ります。

//シリアル通信ライブラリを取り込む
import processing.serial.*;
//シリアル通信インスタンス
Serial port;

//JavaのSwingを取り込む
import javax.swing.*;
import pqrcode.*;
//解読用インスタンスを用意
Decoder decoder;
//画像インスタンスを用意
PImage pimage;
String getFile = null;

void setup(){
//とりあえず表示画面を400角に設定
size(400,400);
//解読用オブジェクトの生成
decoder = new Decoder(this);
background(0);

//シリアルポートの設定
port = new Serial(this, "/dev/tty.usbserial-A4001Kjl", 9600);
}

void draw(){
//選択ファイル名が空でないとき
if(getFile != null){
//ファイルを取り込む
fileLoader();
}
}

void mousePressed(){
getFile = getFileName();
}

void fileLoader(){
//選択ファイル名のドット以降の文字列を取得
String ext = getFile.substring(getFile.lastIndexOf('.') + 1);
//その文字列を小文字にする
ext.toLowerCase();
//文字列末尾がjpg,png,gif,tgaのいずれかであれば
if(ext.equals("jpg") || ext.equals("png") || ext.equals("gif") || ext.equals("tga")){
//選択ファイル名のイメージを取り込む
pimage = loadImage(getFile);
//背景(黒)
background(0);
//イメージ表示
image(pimage, 0, 0, pimage.width, pimage.height);
//イメージの解読
decoder.decodeImage(pimage);
}
//選択ファイルパスを空に戻す
getFile = null;
}

//ファイル選択画面、選択ファイル名取得の処理
String getFileName(){
//処理タイミングの設定
SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
//ファイル選択画面表示
JFileChooser fc = new JFileChooser();
int returnVal = fc.showOpenDialog(null);
//「開く」ボタンが押された場合
if (returnVal == JFileChooser.APPROVE_OPTION) {
//選択ファイル取得
File file = fc.getSelectedFile();
//選択ファイルのパス取得
getFile = file.getPath();
}
}
//上記以外の場合
catch (Exception e) {
//エラー出力
e.printStackTrace();
}
}
}
);
//選択ファイルパス取得
return getFile;
}

void decoderEvent(Decoder decoder) {
//解読結果をテキストとして取り出す
String statusMsg = decoder.getDecodedString();
//テキストを画面に出力
println(statusMsg);

//解読した文字列を整数値に変換
int val=int(statusMsg);
//シリアル通信で送信
port.write(val);
}



Arduino側は、11番ピン(PWMピン)にLEDを接続し、輝度が変化する内容であれば以下のようになります。


int val;

void setup(){
//シリアル通信開始
Serial.begin(9600);
}

void loop(){
//受信データがひとつ届いたら
if(Serial.available()>0){
//受信データ読み込み
val=Serial.read();
}
//11番ピンをアナログ出力する
analogWrite(11,val);
}



QRコードで、「100,20,35,180」のように、幾つかの数値をコンマ(デリミタ)で区切り連続した数値の文字列を作成し、解読した結果として送信すれば、複数の数値データをArduinoへ送信することもできます。
この場合、Processingのプログラムの最後の部分にあるvoid decoderEvent(){...}の部分を以下のようにします。

void decoderEvent(Decoder decoder) {
String statusMsg = decoder.getDecodedString();
int[] data=int(split(statusMsg,',');
for(int i=0;i<data.length;i++){
port.write(val);
}
}

解読される文字列は

statusMsg = "100,20,35,180"

なので、
この文字列statusMsgをsplit()に代入し「,」コンマを区切り記号として複数の文字列を含んだ配列に変換します。同時にその文字列群をint()で括ることで、文字列データを整数値データに変換し、

data = {100,20,35,180}

という整数値の配列になります。data.lengthによって配列dataに何個のデータが含まれているか確認し(この場合4個、data[0]からdata[3]まで)、for()文でデータ数の分だけport.write()で繰り返しシリアル通信で送信します(4回分送信)。Arduino側で、順番にこれらの数値を受け取ることで、LEDの輝度調整のプログラムであれば、連続した値をもとに変化する輝度調整が可能になります。

応用的な使い方として、LEDの点灯やサーボの回転角度などの連続する動きのデータをQRコードで幾つか作成しておき、Webカメラを通して解読させる度に異なる動作をさせたり、マトリクスLEDなどに文字や模様として表示させたりできます。

10/03/2008

Processing FileChooser/ファイル選択画面の表示

通常Processingでは、プログラム上で使われる画像データや音源データなどは、スケッチフォルダ内のdataフォルダ内に入れておく必要があります。ProcessingはJavaでつくられているため、JavaのGUIライブラリであるSwingを使うことで、ファイルチューザーのダイアログ画面(ファイル選択画面)を表示し、パソコン上にある任意のファイルを選択し開くことができます(尚、この方法はProcessing/Hacks/filechooserで紹介されています)。
関連:「Processing FileChooser2」(Processing 146以降/selectInput()の使い方)


(Windows XPのファイルチューザー画面/ファイル選択ダイアログ)

dataフォルダ内のデータだけでなく、デスクトップ上にある画像データなどを読み込むことができるので、入れ替わりで画像表示させるときなどに便利です。
以下は、クリックするとファイルチューザー(ファイル選択画面)というダイアログ画面が現れ、コンピュータ上の任意の場所にある画像ファイル(jpeg、png、gif、tga)を取り込んで表示するサンプルです。


//JavaのSwingを取り込む
import javax.swing.*;

//画像インスタンスを用意
PImage pimage;

//選択ファイルを用意し
//ファイルを空にしておく
String getFile = null;

void setup(){
//とりあえず表示画面を400角に設定
size(400,400);
}

void draw(){
//選択ファイル名が空でないとき
if(getFile != null){
//ファイルを取り込む
fileLoader();
}
}

//マウスを押したら
void mousePressed(){
//選択ファイル取得処理
getFile = getFileName();
}

//ファイルを取り込むファンクション
void fileLoader(){
//選択ファイルパスのドット以降の文字列を取得
String ext = getFile.substring(getFile.lastIndexOf('.') + 1);
//その文字列を小文字にする
ext.toLowerCase();
//文字列末尾がjpg,png,gif,tgaのいずれかであれば
if(ext.equals("jpg") || ext.equals("png") || ext.equals("gif") || ext.equals("tga")){
//選択ファイルパスの画像を取り込む
pimage = loadImage(getFile);
//イメージ表示
image(pimage, 0, 0, pimage.width, pimage.height);
}
//選択ファイルパスを空に戻す
getFile = null;
}

//ファイル選択画面、選択ファイルパス取得の処理
String getFileName(){
//処理タイミングの設定
SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
//ファイル選択画面表示
JFileChooser fc = new JFileChooser();
int returnVal = fc.showOpenDialog(null);
//「開く」ボタンが押された場合
if (returnVal == JFileChooser.APPROVE_OPTION) {
//選択ファイル取得
File file = fc.getSelectedFile();
//選択ファイルのパス取得
getFile = file.getPath();
}
}
//上記以外の場合
catch (Exception e) {
//エラー出力
e.printStackTrace();
}
}
}
);
//選択ファイルパス取得
return getFile;
}


選択ファイルが画像ファイルとして相応しいものであるかどうかを

if(ext.equals("jpg") || ext.equals("png") || ext.equals("gif") || ext.equals("tga")){...}

によって判別しています。選択したファイル名の「.」ドット以降が、「jpg」、「png」、「gif」、「tga」であれば、読み込み可能な画像フォーマットとして処理されます。以下の

String getFileName(){...}

以降はJavaのSwingによるファイルチューザーを呼び出して選択ファイルまでのパスを得るコードなので、そのままコピー&ペーストしても構わないでしょう。
PImageでは、jpg、png、gif、tgaの4種類が読み込み可能ですが、この部分を音源のファイルフォーマットに指定し、PImageのかわりにサウンドライブラリを使えば、音源の選択/読み込み/再生も可能になります。
ファイルチューザー画面を用いて、Processingのサウンドライブラリである「Ess」による音源再生のプログラムを以下に書きます。画面中央の白い正方形をクリックすれば、音源選択の画面が現れます。今回利用できる音源のフォーマットは、「wav」だけとします。dataフォルダの中にある音源だけでなく、iTuneなどの音楽ライブラリの中から曲を選ぶこともできるはずです。白い正方形以外の周辺の場所をクリックすれば、再度音源が再生されます。尚、「Ess」ライブラリをダウンロード+インストールしておく必要があります。また、曲などの大きな音源データの場合は、ProcessingのメニューバーからPreferencesあるいは環境設定で、メモリーを増やしておく必要があります(「Increase maximum available memory to [ ]MB」という欄にチェックを入れ、データ量に相当するメモリー数を記入して下さい)。


//Essサウンドライブラリの取り込み
import krister.Ess.*;
//音源インスタンスの用意
AudioChannel mySample;

//JavaのSwingライブラリの取り込み
import javax.swing.*;

String getFile = null;

void setup(){
size(200,200);
//Ess使用開始
Ess.start(this);
background(100,100,30);
rectMode(CORNER);
}

void draw(){
//画面中央白い正方形の描画
rect(width/2-25,height/2-25,50,50);
if(getFile != null){
fileLoader();
}
}

void mousePressed(){
//クリックの箇所が白い正方形以内なら
if(mouseX>width/2-25 && mouseX<width/2-25+50 && mouseY>height/2-25 && mouseY<height/2-25+50){
//選択ファイル所得処理
getFile = getFileName();
}
else{//白い正方形以外の箇所をクリックしたら
//音源ファイルが空ではないとき
if(mySample!=null){
//音源再生
mySample.play();
}
}
}

void fileLoader(){
String ext = getFile.substring(getFile.lastIndexOf('.') + 1);
ext.toLowerCase();
//選択したファイルが「wav」フォーマットなら
if(ext.equals("wav") ){
//音源ファイルの指定
mySample=new AudioChannel(getFile);
//音源再生
mySample.play();
}
getFile = null;
}

//ファイル選択画面、選択ファイルパス取得の処理
String getFileName(){
SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
JFileChooser fc = new JFileChooser();
int returnVal = fc.showOpenDialog(null);
if (returnVal == JFileChooser.APPROVE_OPTION) {
File file = fc.getSelectedFile();
getFile = file.getPath();
}
}
catch (Exception e) {
e.printStackTrace();
}
}
}
);
return getFile;
}

//Ess使用停止
public void stop(){
Ess.stop();
super.stop();
}

9/26/2008

Processing Webカメラを光センサとして使う

今回は、パソコンに接続したWebカメラを光センサとして使う応用実験を行います。点光源(LEDなど)を空間内で動かし、その軌跡をProcessingの画面上に描画してみたいと思います(身体にLEDなどの点光源をつけて、腕を動かしたり歩いたりすれば、身体の動きを連続的に描画/記録することができます)。
Processingでは、videoライブラリを用いてWebカメラを通してキャプチャし、キャプチャした画面のピクセルをひとつずつ読み込んで、設定した明るさ以上のピクセルを選択します。選択したピクセルのみを別の色で表示するプログラムになります。
以下のプログラムでは、カメラからキャプチャした画像の各ピクセルの明るさ(0~255)を調べ、そのピクセルの明るさが254以上であれば、画面上に赤で表示する内容になります。クリックすれば、黒で塗りつぶして画面をリセットすることにします。
videoライブラリの基本的な使い方は「Processing Video(Webカメラ)」を参照して下さい。

*Windowsの場合、そのままの設定ではこのVideoライブラリを使用することができません。WinVDIG 1.0.1をインストールする必要があります。

//ライブラリを取り込む
import processing.video.*;

//オブジェクトの用意
Capture video;

//画面サイズの変数と値
int w=320;
int h=240;

void setup() {
  size(w, h);
  video = new Capture(this, w, h, 30);
  //背景を黒にしておく
  background(0);
  video.start();//Processing2.0以上の場合はこの行が必要
}

void draw() {
  //画面のピクセルをロードしておく
  loadPixels();

  //カメラ画像のピクセルをひとつずつ調べる
  for(int i=0;i<w*h;i++){
    //ピクセルが254以上の明るさの場合
    if(brightness(video.pixels[i])>=254){
      //選択されたピクセルを赤にする
      pixels[i]=color(255,0,0);
    }   
  }

  //ピクセル表示更新
  updatePixels();
}

//キャプチャ画面の読み込み
void captureEvent(Capture video) {
  video.read();
}

//マウスボタンを押したら
void mousePressed(){
  //背景を黒にする
  background(0);
}


LEDなどの点光源をカメラに対して動かせば、以下のような画像ができあがります。赤いピクセル部分が、動かした点光源の軌跡です。




webカメラからの映像と合成(オーバーレイ)するには、以下のようになります。ここでは、webカメラからの映像を左右鏡像反転しています(カメラによっては、セッティング画面で鏡像にできるものもあります)。カメラからの映像を鏡像にすることで、点光源を右に動かせば画面内の点光源も右に動くようになります。マウスボタンを押せば画面を黒にリセット、「s」キーを押すと画面内の映像をjpeg画像で保存、「t」キーを押すとカメラセッティング画面になります。
import processing.video.*;
Capture video;
int w=320;
int h=240;

//軌跡用の配列を用意しておく
int[] pix=new int[w*h];

void setup() {
  size(w, h);
  video = new Capture(this, w, h, 30);
  //軌跡用配列の値をすべてゼロにしておく
  for(int i=0;i<w*h;i++){
    pix[i]=0;
  }
  video.start();//Processing2.0以上の場合はこの行が必要
}

void draw() {
  loadPixels();
  for(int i=0;i<w*h;i++){
    if(brightness(video.pixels[i])>=254){
      //ピクセルを鏡像反転するための計算
      //配列に値を記憶しておく
      int a=i/w;
      pix[w-i%w+a*w-1]=255;//選択されたピクセルだけを255にする
    }
    //配列からの値を画面ピクセルへ代入
    //選択されたピクセルを緑で画面表示    
    pixels[i]=color(0,pix[i],0);
  }
  updatePixels();

  //合成するカメラ映像の処理
  tint(255,128);//透明度128(50%)
  scale(-1.0, 1.0);//左右反転(鏡像)
  image(video, -w, 0);//映像出力
}

//カメラ映像読み込み
void captureEvent(Capture video) {
  video.read();
}

//マウスボタンを押すとリセット(黒へ)
void mousePressed(){
  for(int i=0;i<w*h;i++){
    pix[i]=0;
  }
}

int num=0;//保存画像インデックスの変数

void keyPressed(){
  //sキーを押すと(jpeg画像で保存)
  if(key=='s'){
    String s="image_" + num + ".jpg";//保存ファイル名
    save(s);//画像保存
    num++;//保存画像インデックスを+1しておく
  }
  //tキーを押すと
  if(key=='t'){
    video.settings();//カメラセッティング画面表示
  }
}

「s」キーを押すことで画面をjpegフォーマットで保存できるようにsave()を用います。画像は、save()の括弧内の指定したファイル名でスケッチフォルダ内に保存されます。インデックス用の変数numを用意し、image_0.jpg、image_1.jpg、image_2.jpg...というように、保存名にはインデックス番号がつくようにします。
「t」キーを押せばsettings()によって、カメラのセッティング画面が現れます。カメラの露出やコントラストなどの設定が「オート/自動」になっている場合があるので、できれば「マニュアル/手動」に切り替えて、それぞれを固定値にしたほうが、選択するピクセルの明るさが変化せずに済むのでいいでしょう。


上画像:合成/重ね合わせられた映像
映像内の手にはボタン電池に接続されたLEDが点灯しています。
この画像は、プログラム中にある「s」キーを押して画像保存したものです。


ドライバなしですぐにコンピュータに接続可能(UVC対応)なWebカメラとして以下のようなものがあります。
Macintosh/Windows兼用です。
      

関連:
Processing Video (Webカメラ)」--Webカメラの使い方/映像にフィルタをかけて表示。
Arduino+Processing マトリクスLED+Webカメラ」--Webカメラ映像をマトリクスLEDに映す。
Processing Webカメラ/定点記録画像」--Webカメラ映像を0.5秒おきに画像保存(JPEG)する。
Processing Webカメラ/カラートラッキング」--Webカメラを使い、色を手がかりに物体を追いかける。
Processing Webカメラ/モーショントラッキング」--Webカメラを使って動体検知する。

9/23/2008

次回(9/27)授業について

9/27の授業では、以下のものを持参して下さい。

・パソコン
・Webカメラ(パソコン内蔵カメラでも可)
・Arduino基盤
・LED

Webカメラをパソコンに接続し、Processingのプログラムによって身体計測の実験を行います。
Webカメラの使い方については、「Processing Video(Webカメラ)」などを参考にして下さい。

尚、上記機材等を持参できない場合は、学校の備品を使うことになります(ただし、数が限られています)。

9/14/2008

Processing 緊急モーションセンサー(Mac)

Appleの2005年以降のPowerBook/iBook/MacBook(ノート型)には、緊急モーションセンサーが内蔵されています。緊急モーションセンサーは、ハードディスクを保護するために、あやまってコンピュータを落としたときの衝撃を感知します。
Processingには、この緊急モーションセンサーから値を得る「Sudden Motion Sensorライブラリ」があります。緊急モーションセンサーでは、以前扱った「加速度センサ」のように、コンピュータ本体を傾けたり衝撃を与えたりすることによって変化する値(XYZ軸に対する3つの値)が得られます。



Sudden Motion Sensorライブラリのサイトに従えば、3つの値は以下の方法で読み込むことが可能になります。尚、ライブラリをダウンロード+インストールする必要があります。

//ライブラリを取り込む
import sms.*;

void setup() {
//画面サイズをとりあえず200角
size(200,200);
}

void draw() {
//3つの値を読み込み、配列に代入
int[] vals = Unimotion.getSMSArray();
//3つの値を出力
println(vals[0] + " " + vals[1] + " " + vals[2]);
}

上記プログラムによって出力された値は以下のようになりました(機種や状況によって多少誤差が含まれるかもしれません)。

水平時 x: 0, y: -3, z:56
左90度 x: 52, y: -3, z: 6
右90度 x:-51, y: -3, z: 5
前90度 x: 1, y: 49, z: 6
後90度 x: 1, y:-54, z: 5

xの振り幅:103(-51から52までの±51.5)
yの振り幅:103(-54から49までの±51.5)

になります。それぞれの振り幅からxとyの中点(水平時の値)を新たに求めると、

xの中点:(-51+52)/2=0.5
yの中点:(-54+49)/2=-2.5

になります。これら中点の値をオフセット値として用いることにします。つまり、計測された値からオフセット値を差し引いて角度の計算をすることになります。以前の「Arduino 加速度センサ」のときと同様に、出力値からそれぞれの角度を求める式を用意します。それぞれの角度をradX、radY、オフセット値をoffsetX、offsetY、読み取り値をx、yとすると、

//atan2()で求める場合
radX=atan2((x-offsetX),sqrt(51.5*51.5-(x-offsetX)*(x-offsetX)))
radY=atan2((y-offsetY),sqrt(51.5*51.5-(y-offsetY)*(y-offsetY)))

//またはacos()、asin()で求める場合
radX=asin((x-offsetX)/51.5)
radY=acos((y-offsetY)/51.5)

になります。式中の51.5は加速度1G(重力)の時の値です(機種によっては256くらいのときもあります)。

以上の式を使って、コンピュータ本体を傾けることでProcessing画面上の3Dモデルを動かしてみます。
3Dモデルに関しては、前回の記事「Processing 3Dモデル/OBJ Loader」のものを使うことにします。コンピュータを傾けた方向に、画面内の3Dモデルも同様に傾く内容とします。
スケッチフォルダ内にdataフォルダを作成し、3Dモデルのデータを入れておいて下さい(3Dデータは、ここからダウンロードできます/.objファイルと.mtlファイルの二つが必要です)。


(コンピュータを傾けた時の3Dモデル/前回ブログの3Dモデルを使用)


//ライブラリのインポート
import sms.*;
import saito.objloader.*;

//モデルのオブジェクトを用意
OBJModel model;

void setup() {
//3D画面サイズ設定
size(400,400,P3D);
//モデルのオブジェクトを生成
model=new OBJModel(this);
//3Dデータ読み込み
model.load("macbook.obj");
//ワイヤーフレームなし
noStroke();
}

void draw() {
//3つの値を読み込み、配列に代入
int[] vals = Unimotion.getSMSArray();
//println(vals[0] + " " + vals[1] + " " + vals[2]);

//背景描画
background(50);
//直線光の設定
directionalLight(200, 200, 200, -1, 1, -1);
//環境光の設定
ambientLight(200, 200, 200);

//3Dモデルの位置座標設定
translate(width/2,height*2/3,0);

//オフセット値
float offsetX=-2.5;
float offsetY=0.5;
//角度の計算:atan2()で求める場合
float radX=-atan2(vals[1]-offsetX,sqrt(51.5*51.5-(vals[1]-offsetX)*(vals[1]-offsetX)));
float radY=-atan2(vals[0]-offsetY,sqrt(51.5*51.5-(vals[0]-offsetY)*(vals[0]-offsetY)));
//またはacos()、asin()で求める場合
//float radX=asin((vals[1]-offsetX)/51.5);
//float radY=acos((vals[0]-offsetY)/51.5);

//回転角度
rotateX(radX+PI/2);
rotateY(radY);

//三角形分割で面を生成する
model.drawMode(TRIANGLES);
//スケール(200倍)
scale(200);
//モデル描画
model.draw();
}

画面内の3D座標は、
左:-X
右:+X
上:-Y
下:+Y
後:-Z
前:+Z
という関係になります。
緊急モーションセンサーのX軸は、Processing上のY軸に対応しているので、vals[0]の値を3DモデルのY軸回転角度へ代入し、val[1]の値は3DモデルのX軸回転角度へ代入します。表示上90度X軸に対してずれていたので、rotateX(radX+PI/2)というようにradXにPI/2(90度)足しておきました。
directionalLight()は太陽光のような直線光であり、括弧内の数値については、最初の3つがRGBで光の色を指定、最後の3つが(0,0,0)の原点を基準に光の向きを設定することになります。
ambientLight()は環境光であり、光の向きはなく、空間全体を明るくしたり暗くしたりし(あるいは色を変える)、RGBの3つの数値で指定します。


もう一つのサンプルとして、コンピュータ自体をコントローラとして傾けて、水平面上のボールを転がすプログラムをしてみます。今回は、setup(){...}内で、一度緊急モーションセンサーから値を読み込み、それらをオフセット値として使うことにします。こうすることで、コンピュータを平らな場所においてプログラムを開始したときの状態(オフセット値)を記憶させておくことができます。ellipse()で擬似的な影を地面に落とすことで立体感がでるようにします。


(コンピュータの傾きに合わせてボールが転がる)


//ライブラリのインポート
import sms.*;

//ボールの座標用変数
float xPos,yPos;

//オフセット用変数
float xOffset;
float yOffset;

void setup() {
//3D画面設定
size(400,400,P3D);
//ワイヤーフレームなし
noStroke();
//水平状態の読み込み
int[] vals = Unimotion.getSMSArray();
//読み込み値をオフセット値に設定する
xOffset=vals[0];
yOffset=vals[1];
}

void draw() {
//モーションセンサーからの読み込み
int[] vals = Unimotion.getSMSArray();
//背景色
background(220);
//画面上半分の塗色
fill(50);
//画面上半分の矩形
rect(0,0,width,height/2);

//ボールの速度の計算
float xSpeed=vals[0]-xOffset;
float ySpeed=vals[1]-yOffset;
//ボール移動量の計算
xPos+=-xSpeed;
yPos+=ySpeed;
//ボール位置の設定
translate(width/2+xPos,height*4/5,-200+yPos);

//影の塗色
fill(50);
//影の座標と大きさ
ellipse(-25,30,80,80/5);

//直線光の設定
directionalLight(255, 255, 255, -1, 1, 0);
//ボールの塗色
fill(255,100,50);
//ボール描画
sphere(30);
}

緊急モーションセンサーの読み取り値からオフセット値を差し引いた値を、そのままボールのスピードに反映させています。読み取られたY方向の値は、画面内のZ座標(前後の軸)に対応するので、yPosをtranslate()のZ軸に代入してあります。実際に動かしてみて、向きが逆であったり、座標軸がきちんと対応していない場合は、値にマイナスを掛けたり、代入先を入れ替えたりして調整してみて下さい。
ボールの位置設定のためのtranslate()は、まずX座標をwidth/2で左右中央、Y座標をheight*4/5で画面上下4/5の位置、Z座標を-200奥とした座標を基準とし、その基準の座標に変化する値となるxPosとyPosを追加して、最終的な位置を決定しています。
疑似の影として用いたellipse()は2D用の図形でありX座標とY座標しか設定できませんが、translate()以後に挿入してあるので、translate()のZ軸の値に合わせて、前後に動きつつ大きさも変化します(2D図形は、3D空間上ではZ座標値が0の位置に配置されており、translate()以後に書いた2D図形は、Z軸の値を変化させれば、見た目の大きさや位置も影響を受けて変化します)。同様に、directionalLight()に対しても、2D図形をdirectionalLight()以前に書けば、光の影響を受けませんが、directionalLight()以後に2D図形を書くと、光の影響を受けて図形自体に陰影がつきます。そのため、directionalLight()は、プログラムの冒頭の方に書かずに、ボール描画の直前(2D図形描画以降)に書いておきます。

9/11/2008

Processing 3Dモデル/OBJ Loader

Processingのライブラリには、3Dモデリングソフトで制作した3Dモデルを読み込むOBJ Loaderライブラリがあります。OBJ Loaderでは、拡張子が「.obj」の3Dモデルを扱うことができます。3Dモデリングソフトがあれば、制作した3Dモデルを「.obj」フォーマットで書き出して、3DデータをOBJ Loaderで読み込みます。読み込みに使用する3Dデータは、スケッチフォルダ内に入れておきます。
また、モデリングが面倒であれば、インターネットからフリーの3Dデータを検索しダウンロードして利用する方法もあります。この場合「.obj」フォーマットのデータでなければならないのですが、それ以外のフォーマットであっても、一旦3Dモデリングソフトで読み込んで「.obj」フォーマットにして書き出せば利用可能です。最近はフリーの3Dモデリングソフトも多く存在するので、ダウンロード/インストールしてすぐに使うことができるはずです(ブログページ右側にもフリーのモデリングソフトのリストがあります)。

参考3Dモデリングソフト(フリーウェア):
Blender (Win,Mac)
Maya 2010体験版(30日)(Win,Mac)
Rhino (Mac用ベータ版/要登録)
Metasequoia/LE (Win)
DoGA (Win)
CB Model Pro (Win,Mac)
SketchUp (Win,Mac)
SketchyPhysics (Win):SketchUp物理演算プラグイン
trueSpace7 (Win)
CoCreate (Win/要登録)

今回はインターネット上から無料の3Dモデルのデータをダウンロードし、Processing上に表示してみたいと思います。Turbo Squidという3Dモデルのデータライブラリのサイトから無料の3Dモデルを探し出し、以下の3Dモデルを利用してみたいと思います(データをダウンロードするにはTurbo Squidに登録する必要があります)。

http://www.turbosquid.com/3d-models/max-apple-macbook/391534

このモデルは拡張子が「.3DS」なので3ds Max用のデータです。フリーウェアのBlenderという3Dモデリングソフトで、この「.3DS」フォーマットのデータを読み込み(File>Import>.3ds)、「.obj」フォーマットで書き出して(File>Export.obj)利用してみたいと思います(「.obj」に変換したファイルのリンクはこの記事の最後にあります)。

はじめてのBlender (I・O BOOKS)
山崎 聡
工学社
売り上げランキング: 6131


そのまま読み込むと、各パーツの位置が少しずれていたので、修正して「.obj」フォーマットで書き出すことにしました。「.obj」フォーマットを選択して書き出すと、「~.obj」と「~.mtl」という二つのファイルが出来上がります。「~.obj」はポリゴンの座標についてのデータであり、「~.mtl」は色や材質、テクスチャなどのデータが含まれています。以下のプログラムで、3Dデータを読み込んでみます(「~.obj」ファイルと「~.mtl」ファイルは、スケッチフォルダ内に入れておいてください)。

//ライブラリのインポート
import saito.objloader.*;

//モデルのオブジェクトを用意
OBJModel model;

void setup() {
//3D用の画面設定
size(400,400,P3D);
//モデルのオブジェクトを生成
model=new OBJModel(this);
//objファイルの読み込み
model.load("macbook.obj");
//ワイヤーフレームなし
noStroke();
}

void draw(){
//背景描画
background(50);
//直線光の設定
directionalLight(200, 200, 200, -1, 1, -1);
//環境光の設定
ambientLight(200, 200, 200);

//3Dモデルの位置設定
translate(width/2,height/2,0);
//スケール設定(200倍)
scale(200);

//マウス入力で回転させる
rotateX(PI*mouseY/height);

//三角形分割で面を生成する
model.drawMode(TRIANGLES);
//3Dモデルの描画
model.draw();
}


Blenderでは、細かな出力設定はせずに、そのまま「.3DS」データを読み込み(File>Import>.3ds)、「.obj」フォーマットで書き出す(File>Export>.obj)ことにします(書き出す際には、予め画面上で3DモデルをSelectしておく必要があります)。表示されるスケールが小さすぎたので、scale()で200倍の大きさに変換しています。そのまま表示させると、3Dモデルの色が真っ黒のままだったので、多少の修正が必要になります。



色などの内容を確かめるために「.mtl」ファイルをテキストエディタなどで開くと、以下のような内容が記述されていることが分かります。

# Blender3D MTL File: macbook.blend
# Material Count: 2
newmtl Screen
Ns 96.078431
Ka 0.000000 0.000000 0.000000
Kd 0.084706 0.084706 0.084706
Ks 0.449020 0.449020 0.449020
Ni 1.000000
d 1.000000
illum 2

newmtl Plastic
Ns 96.078431
Ka 0.000000 0.000000 0.000000
Kd 0.800000 0.800000 0.800000
Ks 0.449020 0.449020 0.449020
Ni 1.000000
d 1.000000
illum 2

このデータには、ScreenとPlasticという名前の二つの材質についての数値が含まれており、以下のような内訳になります。
# コメント
newmtl 材料名
Ns 輝度
Ka 環境色
Kd 拡散色
Ks 反射色
Ni 光の屈折率
d  アルファ値
illum 0:照明なし、1:反射ハイライトなし、2:Ksの値で反射ハイライトあり

恐らくKaの値がそれぞれ0.000000, 0.000000, 0.000000なので真っ黒な状態になったのでしょう。それぞれの値を以下のように変えます。

ScreenのKaの値を
Ka 0.1 0.01 0.01

PlasticのKaの値を
Ka 0.7 0.7 0.7

上記三つの値はRGB(赤,緑,青)に対応しています。
それぞれの値を変更すると、ScreenのKaは少し赤みがかった黒、PlasticのKaは白に近いグレーになります。
先ほどのプログラムで再度描画させれば、以下のようにかたちが認識できるようになるはずです。


マウス上下で3Dモデルも上下に回転します。
見えない場合はこちらへ


Processingでは、box()sphere()vertex()などの基本的な3D描画のコマンドはありますが、複雑な3Dモデルを制作する際には、モデリングソフトを利用して取り込んだ方がいいでしょう。あるいは、3Dモデルのデータもインターネット上に数多く存在しているので、検索すれば相応しいものが見つかるかもしれません。
尚、上記プログラムで使用したmacbook.objとmacbook.mtlのファイルは、ここからダウンロードできます。


3Dキャラクタアニメーション Blender(DVD付)
トニー・マレン
アスキー
売り上げランキング: 118200

8/30/2008

Arduino タッチパネル(4線式)4-wire touch panel

*Some are written in English at the moment(sorry not all of them...)
This example shows how to use a touch panel/screen with an Arduino board plus a serial communication to a Processing program, which draws where to touch on the touch panel.
This is not about a multi-touch function, only single point on the touch panel can be detected.

今回は、4線式のタッチパネルをArduino基盤に接続し操作実験してみたいと思います。
タッチパネルには、4線式や5線式という比較的簡単な構造になっているものがあります。今回使うタッチパネルは、指先やペン先で触れた一点の位置(X座標値とY座標値)を検出可能にするものです(複数の点を同時検出可能なマルチタッチではありません)。
基本的には、X座標に2線、Y座標に2線あり、合計4線あります。手順としては、まずX座標を検出、そしてY座標を検出というように別々(交互)に行います。タッチパネル自体がX座標用とY座標用に対応した2層の抵抗になっており、X座標(横方向)だけで考えれば、タッチパネルの左端に0V、右端に5Vを接続しておいて、指先で触れた箇所で分圧される仕組みになっています。つまり、X座標の左に行くほど0Vに近く、中心に触れれば約2.5V、右に行くほど5Vに近い電圧が読み取れることになります。X座標を読み取る際には、使用していないY座標の2線のうちの1本を使います。X座標を検出したら、検出対象をY座標に切り替えて同様の方法で検出を続けます。下の図では、タッチパネルの2層あるうちのX座標用の層を押せば、下にあるY座標用の層と接触し、その地点での分圧された電圧の値をY座標用の層から読み込むことができます。
There are two layers of conductive films on a 4-wire touch panel, the one for x-coordinate and the other for y-coordinate. Each layer is connected to GND and 5V on the egdes. Pressing the x-coordinate layer with your finger, then the x-coordinate layer will contact to the other layer(y-coordinate layer) underneath. At this moment the voltage is devided at the point where the two layers are touching, and the devided valtage can be read from the edge of the y-coordinate layer(the y-coordinate layer is working as a conductive film for the x-coordinate film at this time).



Arduino基盤との接続は、以下のようにアナログ入力の0〜3番ピンに接続することにします(X座標用に0番ピンと1番ピン、Y座標用に2番ピンと3番ピンを使用)。通常タッチパネルなどの薄型の機器にはFPC(フレキシブルプリント基板)/FFC(フレキシブルフラットケーブル)の端子がついています。Arduino基盤からのワイヤーと接続するためにはFFC用コネクタを介して接続します(直接ハンダ付けできないので)。
X座標を計測中にはyLowの端子からX座標の分圧された電圧を読み取るので(Y座標を計測中にはxLowの端子で読み取る)、タッチパネル上に何も触れていない時は、xLow端子に0Vが接続されるようにするため、プルダウン抵抗を接続しておきます(xLowやxHighとyHighにもプルダウン抵抗をつけておきます)。


Needs a pull-down resistor for each wire from the touch panel.

処理の手順は、まずX座標(横方向)の検出を行う際には、アナログ入力の「0番ピンと1番ピン」を「14番ピンと15番ピン」としてデジタル出力に切り替え、14番ピンを0V(LOW)、15番ピンを5V(HIGH)で出力しておきます。そして、Y座標用のアナログ入力2番ピンを通してanalogRead()で値を読み込みます。読み込まれた値は、X座標の値になります。このとき、Y座標用のアナログ入力「2番ピン」がデジタル出力にならないように「16番ピン」として予めデジタル入力にしておきます。「3番ピン」も同様に「17番ピン」としてデジタル入力にしておきます。
Y座標(縦方向)の検出の際には、アナログ入力の「2番ピンと3番ピン」を「16番ピンと17番ピン」としてデジタル出力に切り替え、16番ピンを0V(LOW)、17番ピンを5V(HIGH)で出力しておきます。X座標用であった「14番ピンと15番ピン」をデジタル入力に切り替えておいてから、アナログ入力「0番ピン」を通してY座標の値をanalogRead()で読み込みます。このように1ループのなかで、X座標とY座標の検出処理を順番に行い、それぞれの座標値を得ます。
Basically using the analog pins 0,1,2,3 on an Arduino board, those pins can be both analogRead pins and digitalWrite pins(in this case:pin numbers are 14,15,16,17) depending on setting.
First, to read an x-coordinate value, set the pin14 as 0V(LOW) and the pin15 as 5V(HIGH), then the rest of the pins(either of 16 and 17) can be analogRead pins. Next, to read a y-coordinate value, set the pin16 as 0V(LOW) and the pin16 as 5V(HIGH), then read the value from either of the pin14 and the pin15. Reading the both value one after another then send them through a serial communication to Processing.

//デジタル出力用ピン番号の定義:the digital output pins
#define xLow  14
#define xHigh 15
#define yLow  16
#define yHigh 17

void setup(){
  //シリアル通信開始:start serial communication
  Serial.begin(9600);
}

void loop(){
  //X座標用端子をデジタル出力に設定し、それぞれをLOWとHIGHで出力しておく
  //set the both x-coordinate pins as digital output:one is Low the other is HIGH  
  pinMode(xLow,OUTPUT);
  pinMode(xHigh,OUTPUT);
  digitalWrite(xLow,LOW);
  digitalWrite(xHigh,HIGH);

  //Y座標用端子をLOWにしておく:the both y-coordinate pins are set to be LOW
  digitalWrite(yLow,LOW);
  digitalWrite(yHigh,LOW);

  //Y座標用端子をデジタル入力に設定:change the y-coordinate pins as digital input
  pinMode(yLow,INPUT);
  pinMode(yHigh,INPUT);
  delay(10);

  //アナログ入力2番ピン(yLowピン)で読み込み
  //read analog pin2(yLow pin) to get an x-coordinate value
  int x=analogRead(2);
  
  //Y座標用端子をデジタル出力に設定し、それぞれをLOWとHIGHで出力しておく
  //set the both y-coordinate pins as digital output:one is Low the other is HIGH 
  pinMode(yLow,OUTPUT);
  pinMode(yHigh,OUTPUT);
  digitalWrite(yLow,LOW);
  digitalWrite(yHigh,HIGH);

  //X座標用端子をLOWにしておく:the both x-coordinate pins are set to be LOW
  digitalWrite(xLow,LOW);
  digitalWrite(xHigh,LOW);

  //X座標用端子をデジタル入力に設定:change the x-coordinate pins as digital input
  pinMode(xLow,INPUT);
  pinMode(xHigh,INPUT);
  delay(10);

  //アナログ入力0番ピン(xLowピン)で読み込み
  //read analog pin0(xLow pin) to get an y-coordinate value
  int y=analogRead(0);

  if(Serial.available()>0){
    //文字列でシリアル通信:send the values as a DEC format with a delimiter
    Serial.print(x,DEC);   //X座標:x-coordinate
    Serial.print(",");     //デリミタ:delimiter
    Serial.println(y,DEC); //Y座標:y-coordinate

    //合図用信号読み込みでバッファを空にする
    //read a handshake signal from Processing and clear the buffer
    Serial.read();
  }
}

今回実験で用いたタッチパネルは、12.1インチのサイズ(横:縦=4:3)であり、指先で触れた位置が
パネル左端: 70 :a minimum value when touching the left edge of the touch panel
パネル右端:781 :a maxmum value when touching the right edge of the touch panel
パネル上端: 81 :a minimum value when touching the upper edge of the touch panel
パネル下端:822 :a maxmum value when touching the lower edge of the touch panel
の値として検出されました。パネルに触れないときには、プルダウン抵抗により0が出力されます(以下Processingのプログラムでは、XとYの読み取り値が10以上のときタッチしていることとして判別しています)。
In this example, I used a 12.1-inch(width:height=4:3) touch panel.
When touching each edge of the touch panel, the values are like the above.
When not touching the touch panel, you can read zero value from the analog pins because of the pull-down resistors.

それでは、座標値をProcessingへシリアル通信し、Processingの画面上に描画することにします。Processingへは、座標値を文字列として送信することにします(文字列のシリアル通信については「Arduino-Processing シリアル通信5」を参照して下さい。Processing側では、タッチパネル上の指先の動きに合わせて円が動くようにします。タッチパネルに触れている時といない時では円の色が変化するようにします。「s」キーを押してシリアル通信開始です。
The below Processing code is that:
a circle on the screen moves and follows where to touch on the touch panel,
the color of the circle changes depending on touching or not touching.
*Press 's' key to start the serial communication with the Arduino.

//シリアルライブラリを取り込む
import processing.serial.*;
//シリアル通信用インスタンスportを用意
Serial port;

//読み込み値の変数を用意:variables for data from the Arduino
int x,y;
//座標用変数を用意:variables for xy-coordinates to draw a circle on the screen
float xPos,yPos;

void setup(){
  //画面サイズ設定
  size(800,600);
  smooth();
  //シリアルポート設定
  port = new Serial(this,"/dev/tty.usbserial-A4001Kjl",9600);
  //「10」(ラインフィード)が来る度に
  //serialEvent()を作動させる
  port.bufferUntil(10);
  background(0);
  stroke(255); 
}

void draw(){
  //背景描画(黒)
  background(0);

  if(x>10 && y>10){//タッチしている時:when touching
    //塗り色を白にする
    fill(255);
    //読み取り値を座標にマッピングする
    xPos=map(x,70,781,0,width);
    yPos=map(y,81,822,0,height);
  }else{//タッチしていない時:when not touching
    //塗り色を黒にする
    fill(0);
  }

  //マッピングした座標を代入し円を描写:draw a circle
  ellipse(xPos,yPos,20,20);
}

//シリアル通信:serial communication
void serialEvent(Serial p){
  //文字列の変数stringDataを用意し「10」(ラインフィード)が来るまで読み込む
  String stringData=port.readStringUntil(10);

  //文字列データが空ではないとき
  if(stringData!=null){
    //文字列データに含まれる改行記号を取り除く
    stringData=trim(stringData);

    //整数型の配列data[]を用意し、
    //コンマ記号をもとに文字列データを区切って
    //配列data[]に整数化して入れておく
    int data[]=int(split(stringData,','));

    //配列data[]内のデータが2つなら、
    if(data.length==2){
      //最初のデータをxに代入
      x=data[0];
      //次のデータをyに代入
      y=data[1];

      //合図用データ送信:send a handshake signal
      port.write(65);
    }
  }  
}

//キー「s」が押されたら通信開始
void keyPressed(){
  if(key=='s'){
    //開始用データ送信:send a first handshake signal
    port.write(65);
  }
}

Arduinoから送られてくるXY座標値をmap()をつかってProcessingの画面上のXY座標値に対応させます。今回の場合、読み取りの最小値(70,81)を画面上の(0,0)に、読み取りの最大値(781,822)を(width=800,height=600)に対応させます。

通常タッチパネルはモニターと一体型になっていますが、タッチパネルだけを用いて透明で平面的な入力デバイスとして利用することも考えられます。仕組みはそれほど複雑ではないので、中古やジャンクのタッチスクリーンなどを分解して手に入れてもいいかもしれません。

以下のサイトでは、タッチパネルの構造や仕組みについて説明してあります。Other sites explaining about touch panels/touch screens.
グンゼ/タッチパネル製造
DMC/タッチパネル製造

また、以下のようなタッチパネル製品/部品などもあります。Shops selling touchpanel parts.
aitendo/タッチパネル部品販売
ストロベリーリナックス/PSP用タッチパネル
スイッチサイエンス/NintendoDSタッチスクリーン
Liquidware/Arduino TouchShield
Sparkfun/OLED+Touchscreen
iPodパーツショップ/iPhone 3G タッチスクリーン(部品)



Logitec USB対応15型タッチパネル LTP-15U
ロジテック (2004-10-31)
売り上げランキング: 13452

8/18/2008

Arduino デジタルコンパス/HMC6352

デジタルコンパス(方位センサ)によって、物体の方位を磁気的に調べることができます。Arduinoで制御可能あるいは入手しやすいデジタルコンパスとして幾つか以下に挙げておきます。

デジタルコンパスの種類:
・「RDCM-802」(秋月電子:¥3400
・「HMC1052L」(Sparkfun:$24.95ストロベリーリナックス:¥3480
・「HMC6352」(Sparkfun:$59.95ストロベリーリナックス:¥7350スイッチサイエンス:¥6980
・「HM55B(日立製)」(Parallax:$29.99日本マイクロボット教育社:¥4450
・「CMPS03」(RoboticsConnection:$55)

それぞれの性能など:
「RDCM-802」は、3ビットの信号で8方位の解像度しかないので、大体の向きを調べるくらいしかできません。
「HMC1052L」は、ArduinoのanalogRead()で入力すれば10ビット(1024段階)の解像度が得られます。
「HMC6352」は、I2C通信で0.0〜359.9度(0.1度ずつ)の分解能があります。
「HM55B」は、シリアル通信で11ビット(2048段階)の解像度が得られます。
「CMPS03」は、I2C通信で3600の分解能。

サンプルソースについて:
「HMC6352」については、WiringのExamplesのサイトにサンプルソース(Standbyモード)があり、I2C通信のライブラリを用いればArduinoでも使用可能になります。ArduinoのPlaygroundにもサンプルがあります。
「HM55B」については、ArduinoのPlaygroundのサイトにサンプルソースがあります。
「CMPS03」については、Arduino用のライブラリがあります。

HMC6352の使い方:
今回は、「HMC6352」デジタルコンパスモジュールの実験をしてみます。
「HMC6352」は、I2C通信で方位データを得るので、I2C通信のライブラリを用います。ライブラリを取り入れるには、メニューバーのSketch>Import Library>Wireを選択することで「#include <Wire.h>」の一文が自動的にプログラムを書く欄に挿入されます。ArduinoでのI2C通信は、SDA端子(データ用端子)はアナログ入力の4番ピン、SCK端子(クロック用端子)はアナログ入力の5番ピンに限定されます。「HMC6352」は、5V電源でも許容範囲ですが今回は3.3Vを使用することにします。接続方法は以下の様になります。



アドレスについて:
I2C通信では、マスターデバイスとスレーブデバイスという関係性があります。マスターデバイスには複数のスレーブデバイスが接続できるメリットがあり、複数のスレーブデバイスはマスターデバイスから制御されることになります。今回の場合は、Arduino基盤がマスターデバイス、HMC6325がスレーブデバイスになります。複数のスレーブデバイスが接続可能であるため、スレーブデバイス側にはどのデバイスであるかを識別するためのアドレスが必要となります。データシートによれば、HMC6325のデフォルトのアドレスは16進数の「0x42」であり、今回はこのアドレスを使うことにします。ただし、識別されるアドレスは8ビット中の上位7ビットであり、「0x42」を1ビット右にシフト「>>」した値になります(16進数の「0x42」は、二進数の「01000010」であり、1ビット右にシフトするということは、二進数の「01000010」の桁を右に一桁ずらすことになるので、「00100001」(=0x21)になるということです/ビットシフトに関しては「Arduino-Processing シリアル通信3」を参照)。

モード切替について:
HMC6325には、マスターデバイスから「A」を送信することで方位を計測開始しデータを読み取る「Standbyモード」と、一旦「A」を送信後データを読み取るごとに自動的に計測/出力する「Queryモード」、そして設定した周期(1Hz、5Hz、10Hz、20Hz、)で連続的に計測/出力した結果を読み取る「Continuousモード」があります。
今回は「Continuousモード(20Hz)」を使うことにします。この設定をするためには、RAM書き込み用コマンド「G」、書き込み先(レジスタ)の「0x74」、そして設定内容の8ビット「0111010」あるいは16進数の「0x72」を送信します。ちなみに、設定内容については、

0x50:Standbyモード(デフォルト)
0x51:Queryモード
0x52:Continuousモード(10Hz,Periodic Set/Reset=ON)
0x72:Continuousモード(20Hz,Periodic Set/Reset=ON)

となります(詳細はデータシートを参照して下さい)。
シリアルモニターを使って読み取り値を出力することにします。


//I2C通信ライブラリを取り込む
#include <Wire.h>

//デジタルコンパスモジュールのアドレス設定
int compassAddress = 0x42 >> 1; //=0x21
//読み込み値(角度)の変数を用意
int reading = 0;

void setup() {
//I2C通信開始
Wire.begin();
//角度表示のためのシリアル通信開始
//Serial.begin(9600);

//Continuous Modeに設定する
Wire.beginTransmission(compassAddress);
//RAM書き込み用コマンド
Wire.send('G');
//書き込み先指定
Wire.send(0x74);
//モード設定
Wire.send(0x72);
//通信終了
Wire.endTransmission();
//処理時間
delayMicroseconds(70);
}

void loop() {
//デバイスに2バイト分のデータを要求する
Wire.requestFrom(compassAddress, 2);
//要求したデータが2バイト分来たら
if(Wire.available()>1){
//1バイト分のデータの読み込み
reading = Wire.receive();
//読み込んだデータを8ビット左シフトしておく
reading = reading << 8;
//次の1バイト分のデータを読み込み
//一つ目のデータと合成(2バイト)
reading += Wire.receive();
//2バイト分のデータを10で割る
reading /= 10;
Serial.println(reading);
}
//処理のために少し待つ(20Hz)
delay(50);
}


まずは、Wire.begin()でI2C通信を開始します。「HMC6325」の初期設定では、「Standbyモード」になっているため、setup(){...}内で「Continuousモード」に切り替えます。設定内容を「HMC6325」のRAMに書き込むために「G」というコマンド、書き込み先となる専用レジスタの「0x74」、設定内容となる8ビットの「0x72」をWire.send()を使って送信します。
方位角度の読み込みは、Wire.requestFrom()でアドレスと何バイト分のデータかを指定して要求します。計測結果は0〜3599までの値(360度を10倍した値)を2バイト(16ビット)で返してきます。Wire.available()によって2バイト分読み込み可能なデータをカウントしたら、Wire.receive()によって、2バイトの値を2回に分けて読み込みます。一度に読み込むことができる値は1バイト(8ビット、0〜255)までなので、最初の1バイトを読み込んだら、その値を8ビット左にビットシフト(8桁左にビットシフトするということは、256倍することと等しくなります)、その値に次の1バイト分のデータを加算します。値は10倍されているので、最終的に10で割って0〜359度の角度として取り込みます(float型を使えば0.0〜359.9で出力します)。20Hzごとに計測する「Continuousモード」に設定したので、delay(50)を挿入してループの周波数も20Hzにしておきました。

測定結果をProcessingへシリアル通信するのであれば、2回に分けて読み取った値(2バイト分)をそのまま1バイトずつ2回送信(合計2バイト)し、Processing側で受け取ってから二つの値を合成すればいいでしょう。値を合成するには、一個目の値を左に8ビットシフト(または256倍)してから二個目の値を足せば0〜3599の値として得ることができるはずです。BYTEフォーマットなら、そのまま2回で送信します。文字列でデリミタ(区切り記号)を挿入して送信する場合は、2回目の送信時にSerial.println()で改行記号を用いて送信します(Processing側での受信については、「Arduino-Processing シリアル通信3(複数の値を送信する場合)」あるいは「Arduino-Processing シリアル通信5(文字列で送信する場合)」を参照して下さい。

BYTEフォーマットで送信する場合:

void loop() {
Wire.requestFrom(compassAddress, 2);
if(Wire.available()>1){
//2回に分けて読み取った値を
//BYTEフォーマットで2個送信する
Serial.print(Wire.receive(),BYTE);//1個目
Serial.print(Wire.receive(),BYTE);//2個目
}
delay(50);
}

あるいは、文字列として送信する場合:

void loop() {
Wire.requestFrom(compassAddress, 2);
if(Wire.available()>1){
//2回に分けて読み取った値を
//DECフォーマットで2個送信する
Serial.print(Wire.receive(),DEC);
Serial.print(",");//デリミタを挿入送信
Serial.println(Wire.receive(),DEC);//改行記号つき
}
delay(50);
}


また、「C」を送信することでキャリブレーションモードに入り、周囲の磁気の影響によるモジュール内素子のゆがみをなくし正常な状態に調整することが出来ます。モジュールを平らな場所に置いてから6秒から3分以内に、ニ周程度回転させ、「E」を送信することで終了します。20秒以上かけて2回転させれば、正確なキャリブレーションになります。

//キャリブレーション開始コマンド送信
Wire.beginTransmission(compassAddress);
Wire.send('C');
Wire.endTransmission();

//30秒ほど待つ(6秒〜3分まで)
//この間に数回モジュールを回転させる
delay(30000);

//キャリブレーション終了コマンド送信
Wire.beginTransmission(compassAddress);
Wire.send('E');
Wire.endTransmission();


デジタルコンパスをモータなどの磁気を発するものの近くに設置すると磁気的影響を受けるので、少し離れた場所に設置したほうがいいでしょう。

8/11/2008

Arduino+Processing マトリクスLED+Webカメラ

今回は、Webカメラから取り込んだ映像をArduinoに接続した8×8マトリクスLEDに映す実験を行います。まず、ProcessingでWebカメラからの映像を8×8ピクセルで取り込み、合計64個のピクセルの明るさの値(0〜255)を調べてから、その個々の値をシリアル通信でArduinoに送ります。Arduino側では、受け取った64個分の値をマトリクスLEDの個々の明るさに反映させます。Arduino基盤とマトリクスLEDとは、ICを使わず直結することにします(接続方法は「Arduino マトリクスLED1」を参照)。
Processingの画面では、マトリクスLEDの表示シミュレーション(モニタリング)を同時に行うことにします(前回行ったモザイク処理のような方法で赤い円を64個映し出すことにします)。


「Processingの画面(モニタリング)」

Processingのプログラム:

//ビデオライブラリを取り込む
import processing.video.*;
//キャプチャ用オブジェクトを用意
Capture video;

//シリアル通信ライブラリを取り込む
import processing.serial.*;
//シリアル通信オブジェクトを用意
Serial port;

//64個分のピクセル色の配列を用意
int[] pixelValue=new int[64];

//シリアル通信開始用フラグ
boolean start=false;

void setup(){
//画面を160角に設定
size(160,160);
//描画を滑らかにする
smooth();

//映像キャプチャの設定(幅8,高さ8ピクセル,フレームレート30)
video = new Capture(this, 8, 8, 30);

//ポートの設定
port=new Serial(this,"/dev/tty.usbserial-A40014iU",9600);

//外形線なし
noStroke();
}

void draw(){
//背景を黒で塗る
background(0);

//64個分のピクセルの処理
for(int i=0;i<64;i++){
//映像の各ピクセルの色の値を
//明るさの値に変換する
pixelValue[i]=int(brightness(video.pixels[i]));

//円の塗色(赤の値)に代入
fill(pixelValue[i],0,0);
//円を描画
ellipse((i%8)*20+10,(i/8)*20+10,15,15);

//値を送信
if(start){
port.write(pixelValue[i]);
}
}
}

//キャプチャ映像読み込み
void captureEvent(Capture video) {
video.read();
}

クリックでシリアル通信開始
void mousePressed(){
start=true;
}


Processingの方では、VideoライブラリSerialライブラリの二つを取り込む必要があります。マトリクスLEDが8×8の解像度なので、Webカメラから取り込む映像の解像度も8×8にしておきます(カメラ映像の横縦比は4:3なので、少し縦長の映像になってしまいます)。
*Windowsの場合、そのままの設定ではこのVideoライブラリを使用することができません。WinVDIG 1.0.1をインストールする必要があります。
「pixelValue[i]=int(brightness(video.pixels[i]))」では、まず映像の各ピクセルの色をpixels[]で読み込みます。pixels[]は、RGBの三色の値(三つの値)を含んでおり、brightness()で括ると明るさの値(一つの値)に変換されます(0〜255)。変換された値はfloat(小数)なのでint()で括って整数に変換しておきます。この値を、fill()の赤の値に代入し(緑と青は0)、LEDのような赤い円をellipse()を使って64個描画します。8×8を160×160の画面で表示しているので、20×20ピクセルのグリッド状に配置されます。ellipse()の直径はとりあえず15にしておきました。ellipse()のXとY座標は、「%」と「/」を使って計算します(X座標となる「(i%8)*20+10」は、iを8(横幅)で割った余りに20ピクセル掛けて、さらに10ピクセル足すことでellipse()が20×20のグリッドの中心に来るように位置調整しています)。最後に「port.write(pixelValue[i])」で、Arduinoへ各ピクセルの明るさの値を送信します。今回は画面をクリックしたらシリアル通信が開始されるようにしています。

次にArduinoの方に移ります。冒頭で書いたように、今回はマトリクスLEDを、ICを使わず直結します。個々のLEDはダイナミック点灯しているので、点灯時間の長さによって明るさを調整することになります。点灯時間が短ければ暗くなり、長くなれば明るくなります。つまり、Processingから送られて来た明るさの値(0〜255)を、個々のLEDの点灯時間に反映させるプログラムになるということです。マトリクスLEDとの接続方法や詳細については「Arduino マトリクスLED1」を参照してください。

Arduinoのプログラム:

//8x8の二次元配列を用意
byte matrix[8][8];

void setup(){
//出力ピンの設定、すべてオフにする
for(int i=2;i<=17;i++){
pinMode(i,OUTPUT);
digitalWrite(i,LOW);
}
//シリアル通信開始
Serial.begin(9600);
}

void loop(){
//シリアル通信(64個分のデータ)
if(Serial.available()>63){
for(int k=0;k<8;k++){
for(int l=0;l<8;l++){
//読み込んだ値を配列に代入
matrix[k][l]=Serial.read();
}
}
}

//各LEDの点灯制御
for(int i=2;i<=9;i++){
//列の点灯
digitalWrite(i,HIGH);

for(int j=10;j<=17;j++){
//行の点灯
digitalWrite(j,LOW);
//行の点灯継続時間
delayMicroseconds(1+matrix[i-2][j-10]);
//行の消灯
digitalWrite(j,HIGH);
//行の消灯継続時間
delayMicroseconds(256-matrix[i-2][j-10]);
}
//列の消灯
digitalWrite(i,LOW);
}
}


以前の「Arduino マトリクスLED1」とほぼプログラム内容は同じです。異なる部分は、シリアル通信と各LEDを点灯/消灯させる継続時間の部分です。用意する二次元配列は、boolean型ではなくbyte型(0~255の値なので)にしています。
シリアル通信上で干渉しないようにするため、Arduino基盤の0番ピンと1番ピンには何も接続しないことにしています(2〜17番ピンを使用)。シリアル通信では、Processingから送られてくる64個のデータをSerial.available()でカウントして、それぞれの値を予め用意しておいた二次元配列matrixに保存しておきます。送られてくるデータは0〜255(明るさの値)となります。そしてダイナミック点灯していく際に、明るさの値をdelayMicroseconds()に代入して、点灯継続時間と消灯継続時間に割り当てます。delayMicroseconds(0)としてしまうと、0マイクロ秒としては扱ってくれないので、delayMicroseconds()の括弧内に入れられる最小値は1にしてあります。最小1マイクロ秒の点灯時間かつ最大256マイクロ秒の消灯時間のときが最も暗くなるときです。その逆で、最大256マイクロ秒の点灯時間かつ最小1マイクロ秒の消灯時間のときが最も明るくなります。個々のLEDは257マイクロ秒ごとにダイナミック点灯していることになります。個々のLEDの点滅を300マイクロ秒程度にすると点滅しているようには見えないので、今回の257マイクロ秒周期で点滅させれば、ほぼ問題ないでしょう。

関連:
・「シリアル通信1〜5
・「Arduino マトリクスLED1
・「Processing Video(Webカメラ)

ロジクールストア(ウェブカメラカテゴリ)

8/09/2008

Processing Video (Webカメラ)

ProcessingのVideoライブラリを使用することで、パソコンに接続した(あるいは内蔵された)Webカメラからの映像を取り込むことができます。このライブラリを使用するには、パソコンにQuickTimeがインストールされている必要があるので、もしインストールされていない場合は、アップルのQuickTimeのサイトからダウンロード/インストールして下さい。
USB接続する外付けのWebカメラの場合は必要なドライバをメーカーのサイトなどからダウンロード/インストールして使用可能な状態にしておいて下さい。Webカメラがパソコンに内蔵されている場合は、そのまま使うことができるはずです。まずは、簡単な映像の取り込みかたから始めます。

*Windowsの場合、そのままの設定ではこのVideoライブラリを使用することができないことがあります。その場合、WinVDIG 1.0.1をインストールする必要があります。


//ライブラリの取り込み
import processing.video.*;
//キャプチャする映像のオブジェクトを用意
Capture myCapture;

void setup() {
size(200, 200);
//キャプチャする映像の設定
myCapture = new Capture(this, width, height, 30);
}

void draw() {
//映像を画面に配置
image(myCapture, 0, 0);
}

//映像の読み込み
void captureEvent(Capture myCapture) {
myCapture.read();
}

//クリックでカメラセッティング画面になる
void mousePressed(){
myCapture.settings();
}


上記プログラムでは、画面サイズが200×200の正方形であるため、Webカメラでキャプチャされた映像は縦長になってしまうかもしれません。通常画面の比率は4:3(横:縦)であるため、もし比率を合わせるのであれば、320×240(4:3)などに合わせてください。
Capture()によって、親オブジェクト(thisのままでよい)と映像の幅と高さ、フレームレートを設定できます。映像が滑らかに動かない場合は、フレームレートを下げるか、画像サイズを縮小して調整して下さい。
キャプチャされた映像は、毎フレームごとにimage()によって画面内に配置されます。image()の括弧内の数値は、配置するX座標、Y座標となります。上記プログラムでは0,0に設定されているので、左上角の座標を基準に映像が画面内に配置されます。
captureEvent()は、Webカメラから信号が送られてくる度に作動します。read()によって、毎回送られてくる映像信号を読み込んでいます。
今回は最後にクリックするとsettings()によって、カメラのセッティング画面(Webカメラ用の設定アプリケーション画面)が現れるようにしてあります。ここで、カメラの解像度やコントラストなどの設定を行うことができるはずです。

次に映像にフィルタをかけてみるプログラムをしてみます。
画像用のfilter()というコマンドがあり、
THRESHOLD 白黒ニ値化:0.0~1.0(黒〜白)/初期設定0.5
GRAY    グレースケール:パラメータなし
INVERT   反転色:パラメータなし
POSTERIZE ポスタライズ:2~255(各チャンネルの色数の限定)
BLUR    ぼかし:指定半径範囲でぼかす
それぞれをfilter()の括弧内に入れれば、映像にフィルタをかけることができます。フィルタの種類の後にパラメータを入れることで、フィルタのかかり具合を調整することができます。
複数のフィルタを重ねて使うこともできます。ただ、フィルタの種類(特にBLUR)によっては処理速度に影響がでる場合があるので、適宜フレームレートや画素数などを調整する必要がでてくるときがあります。
以下は、キャプチャした映像にぼかしをいれノイズを取払い、その後白黒でニ値化し、白黒の単純な映像に変換するサンプルです。尚、映像の比率は4:3にすることにします。


「フィルタ(ぼかし+白黒ニ値化)をかけた映像(上画像)」


//ライブラリの取り込み
import processing.video.*;
//キャプチャする映像のオブジェクトを用意
Capture myCapture;

void setup() {
size(320, 240);//比率4:3
//キャプチャする映像の設定
myCapture = new Capture(this, width, height, 30);
}

void draw() {
//映像を画面に配置
image(myCapture, 0, 0);
filter(BLUR,1.8);//ぼかし
filter(THRESHOLD,0.7);//白黒ニ値化
}

//映像の読み込み
void captureEvent(Capture myCapture) {
myCapture.read();
}


次に、映像のモザイク化のプログラムをしてみます。画面サイズを320×240とし、横を32分割、縦を24分割し、10×10のモザイク処理をすることにします。まずloadPixels()によって映像の全ピクセルを読み込み、pixels[]によって選択したピクセルの色を取得します。取得した色をもとにrect()でモザイクの矩形を描くことにします。モザイク化されるには、必要なキャプチャ映像は結果的に32×24の解像度があれば済むので、予め32×24のサイズでキャプチャすることにします。


「モザイク映像(上画像)」


//ライブラリの取り込み
import processing.video.*;
//キャプチャする映像のオブジェクトを用意
Capture myCapture;

void setup() {
size(320, 240);//比率4:3
//キャプチャする映像を32×24に設定
myCapture = new Capture(this, 32, 24, 30);
//noStroke(); //モザイクの黒枠をなしにする場合
}

void draw() {
//キャプチャ画像の各ピクセルの色を
//塗色に割当て、矩形を描く
for(int i=0;i<32*24;i++){
fill(myCapture.pixels[i]);
rect((i%32)*10,(i/32)*10,10,10);
}
}

//映像の読み込み
void captureEvent(Capture myCapture) {
myCapture.read();
}


最終的に32×24のモザイクになるので、キャプチャする画像も同様の解像度にしておけば、処理速度が遅くならなくて済みます。pixels[]によってキャプチャされた映像の各ピクセルの色を取得します。32×24なので合計768ピクセル分となります。rect()による矩形も768個必要になるので、まずfill()で塗色を指定してから描きます。pixel[i]のiは画面左上から数えていき最後は右下のピクセルの番号になります。pixel[i]のiは0~767(合計768個)までの連続した数値なので、「%」と「/」を使ってXとYの座標値に変換します。変換したXとYの座標値をrect()に代入することで、320×240の画面に10ピクセル単位で横方向と縦方向に32×24のモザイク矩形として配置されます。今回はnoStroke()を使っていないので、黒い枠のついたモザイク矩形にしています。


ドライバなしですぐにコンピュータに接続可能(UVC対応)なWebカメラとして以下のようなものがあります。
Macintosh/Windows兼用です。
      

関連:
Arduino+Processing マトリクスLED+Webカメラ」--Webカメラ映像をマトリクスLEDに映す。
Processing Webカメラを光センサとして使う」--点光源で画面内に線を描く。
Processing Webカメラ/定点記録画像」--Webカメラ映像を0.5秒おきに画像保存(JPEG)する。
Processing Webカメラ/カラートラッキング」--Webカメラを使い、色を手がかりに物体を追いかける。
Processing Webカメラ/モーショントラッキング」--Webカメラを使って動体検知する。


[目次:Processing関係]  [HOMEへ戻る]  [目次:Arduino関係]