TetsuakiBaba

workshop/kisozemi/main

基礎ゼミサポートページ

ここは基礎ゼミのサポートページです.制作,ワークショップなどの情報がこのページと同期しています.授業中もこのページを開きながら作業することをおすすめします. 課題などの提出物はkibacoを利用しますので,ご注意ください.4月25日の授業では,1.2のArduinoの起動確認までしておいてください.

Arduinoを使ってみる

Arduinoとはマイクロコントローラーと周辺機器がセットになった小型コンピュータのなかで,一般開発ユーザにとって最もメジャーな開発基板の一つです. この授業ではArduinoを利用して,実際にハードウェア,ソフトウェアプログラミングを体験し,PCインタフェースを創ることを目標としています.ではまず Arduinoを実際に使ってみましょう.実はArduinoにはたくさんの種類があり,この授業ではArduino Microと呼ばれるものを使います.

Arduino(ソフトウェア)のインストール

Arduino公式ウェブサイトのソフトウェアページから自身の環境に 合わせたアプリケーションをダウンロードしてください.

  • Windowsの場合:Windows Installerをダウンロード後,ダブルクリックでインストールを開始してください.
    インストールの途中,したの画像のように,USB Driverの項目にチェックが入っていることを確認し,チェックが入っていなければ必ずチェックを入れた上でインストールを行って下さい.Windows app形式やzipファイルでダウンロードすることはしないでください.
  • Mac OSXの場合:Mac OS X 10.7 Lion or newerをダウンロード後,展開されたArduino.appをアプリケーション等に移動し,起動を確認してください.

ダウンロードに関する注意事項
この記事の執筆時点(平成29年4月19日)では最新バージョンは1.8.2になっています.ダウンロードする際,「Support the Arduino Software」なるページで,寄付支援をするかどうかを聞かれます.Arduinoを支援したい方は金額を選び,Contributeしてください.今回はみなさん初めてArduinoを使うと思いますので,今回は下記の画像で赤丸でくくった箇所の「Just Download」を選択して,アプリケーションをダウンロードしてください.

Arduinoの起動確認

実際にインストール手続きが終わったら,Arduinoを起動して,下記のようなエディタ画面が表示されるかどうかを確認してください.ここまででArduinoが起動しない場合は,もう一度インストール手続きを行ってみて下さい.念のためofficialのインストールマニュアルへのリンクも確認してください.

Arduinoボードを使ってみる

それではソフトウェアの準備ができたので,Arduinoのボードを使ってみます.この授業ではArduino Microと呼ばれるものを使います.まずは下記に従って,テストプログラムを動かしてみます.

  1. Arduino MicroとPCをUSBケーブルで接続する
  2. Arduino ソフトウェアを起動する
  3. 「Arduinoソフトウェアメニュー」→「ツール」→「ボード:Arduino/Genuino Micro」を選択
  4. 「Arduinoソフトウェアメニュー」→「ツール」→「シリアルポート:」の中にある括弧書きでArduino Microと書かれた行を選択.
  5. Arduinoソフトウェアメニュー→「ファイル」→「スケッチブックの例」→「01.Basic」→「Blink」を選択
  6. Arduinoソフトウェアメニュー→「スケッチ」→「マイコンボードに書き込む」を選択
以上の手続きを終えた後,Arduino Micro上のLEDが1秒おきに点滅を始めればOKです.

プログラム基礎

Hello LED!!

すでにLEDの点滅に関して上記で確認していますが,改めてプログラムを記述してみます.LEDを一秒おきに点滅させるプログラムは下記のようになります.

Hello LED!! (LEDを点滅させるプログラム)

void setup()
{
  pinMode(13, OUTPUT);
}

void loop()
{
  digitalWrite(13, HIGH);
  delay(1000);
  digitalWrite(13, LOW);
  delay(1000);
}

ArduinoではC言語と呼ばれるプログラム記述方法を利用します.またC言語の一つ上の機能であるC++にも一部対応しているため,ある程度の複雑なプログラムを記述することも可能です.上記の例では2つの関数(setup, loop)の中に幾つかの命令文を記述しています.関数とは一定量の命令記述をまとめたものです.今回記述されているものは全て関数になっています.setup, loopは関数の定義を記述しており,pinMode, digitalWRite, delayに関してはすでに定義済みの関数を呼び出して命令を実行しています.それぞれの関数についてはArduinoのReferenceを参考にすることができます.下記にリンクを貼っておきます.これから新しい関数が登場した場合は,Arduino Referenceページでまずは確認してください.

なお,setup, loopの定義済み関数(the function)についてですが,こちらもArduino Referenceページに説明があります.自身で確認してください.簡単にいうと下記のとおりです.

  • setup: Arduinoの起動時に一回だけ呼び出される関数
  • loop: setupの後に呼び出される関数で,関数内の記述を実行し終わると,再度呼び出されて再帰的にループを実現している

変数を使う,確認する

上記では関数を利用していましたが,次に変数を利用してみます.まずは下記のプログラムを実行してみます.

変数を1ずつ増やし(インクリメント),PCで値を確認する

void setup()
{
  Serial.begin(9600);
}

int count = 0;

void loop()
{
  Serial.println(count);
  count = count + 1;
  delay(1000);
}
  

int count = 0; というのが変数を宣言している箇所です.ここでは count という名前の整数型の変数を一つ準備しました.初期値を0としています.これをloopごとに1ずつ増やしています.Serialというものが出てきましたが,これはクラスになります.クラスとはC++における機能で,変数や関数をひとまとめにして,使いやすくするための機能です.この場合Serialという定義済みクラスを利用して,PCへcountの変数を送信しています.実際に上記プログラムを書き込んでもなにも変化が起きません.そこでArduinoメニューから「ツール」→「シリアルモニタ」を選択してください.するとcountの変数が1秒おきに表示されます.SerialとはPCにおける通信規格で,今回はArduino側から通信用のクラスを利用して,PCに文字列や変数などのデータを送信するプログラムになっています.

加速度センサの値を読み込む

みなさんにお配りしたArduino Microにはチップ(32u4)の上に加速度センサ(ADXL335)が載っていて,それぞれ下記のように接続されています.

  • x軸:A4
  • y軸:A3
  • z軸:A2
  • 加速度センサ電源:3pin
  • 加速度センサグランド:23pin
そこで,各加速度値を読み取り,Arduino のシリアルモニタにて値を確認してみましょう.3pinをHigh,23pinをLOWにすることで,加速度センサの電源供給を行い,analogReadを利用して値を読み取っています.

加速度センサの値を読み,各値をシリアルモニタに出力するプログラム

void setup()
{
  Serial.begin(9600);
  pinMode(3, OUTPUT);
  pinMode(23, OUTPUT);
  digitalWrite(3, HIGH);
  digitalWrite(23, LOW);
}

int count = 0;

void loop()
{
  int x,y,z;
  x = analogRead(4);
  y = analogRead(3);
  z = analogRead(2);

  Serial.print(x);
  Serial.print(",");
  Serial.print(y);
  Serial.print(",");
  Serial.println(z);
  delay(100);
}

加速度センサの情報からクリック操作を行う

この回では,加速度センサだけでまずはクリックを実装してみます.arduinoを手で持ち,クリックするように叩くと,各x,y,z軸の値が変化をします.これをうまく処理して,クリックとして扱いたいと思います.すでに加速度センサの値は観測してみたとおり,1023/2を中心として正方向の加速度はプラス(1023/2 - 1023),負の方向にはマイナス(0-1023/2)に値が変化します.つまりタップ動作が行われた場合も値は下がったり,上がったりしてしまいます.そこでこれらの状況を無視でき,且つどの軸に加速度が加わってもその変化量だけを取得する計算をしたいと思います.そこで3次元のユークリッド距離を考えてみます.x,y,zのそれぞれの値はセンサの更新時間(単位時間)ごとに値が変化する3次元ベクトルであり,各単位時間でどれだけベクトル値が変化したかどうかをみる一つの目安として,各ベクトルの距離を計算すればよいです.

時間をt,センサ更新速度を単位時間として,それぞれ,\( dx, dy,dz, dt \)を考えると.下記の式で距離計算ができます.

\[ a = \sqrt{(\frac{dx}{dt})^2+(\frac{dy}{dt})^2+(\frac{dz}{dt})^2} \]

では実際に上記計算式でプログラムを記述してみます.今回はクリックの感度(しきい値)を100,500[ms]をクリック反応後のキャンセリング時間としています.

加速度センサの値を処理し,各軸の単位時間あたりの変化量を値とした3次元ユークリッド距離を計算し,その値が閾値を超えたときにクリック動作を行うプログラム

#include "Mouse.h"

void setup()
{
  Serial.begin(9600);
  pinMode(3, OUTPUT);
  pinMode(23, OUTPUT);
  digitalWrite(3, HIGH);
  digitalWrite(23, LOW);

  Mouse.begin();
}

int count = 0;
int x_prev, y_prev, z_prev;

void loop()
{
  int x,y,z;
  x = analogRead(4);
  y = analogRead(3);
  z = analogRead(2);

  int a;
  a = pow(x-x_prev, 2)+pow(y-y_prev, 2)+pow(z-z_prev,2);
  a = sqrt(a);

  if( a > 100 ){
   Serial.println("Click!!");
   Mouse.click(MOUSE_LEFT);
   delay(500);
  }

  x_prev = x;
  y_prev = y;
  z_prev = z;
  delay(10);
}

加速度センサの情報からマウス移動を行う

マウスのクリックができるようになったので,次はポインタの移動に挑戦します.マウスクリックは Mouse.press(Mouse_LEFT); で実現できました.ポインタの移動の場合は同じようなやり方で,Mouse.move(x,y,w);を利用します.一般的なパソコン画面上ではx,yだけですが,マウスのスクロールを機能させるために,w(Wheel)のパラメータが用意されています.スクロール操作を行いたい場合は,wのパラメータを指定してください.今回は利用しないので0をいれておきます.

Arduino Mircoを傾けるとマウスポインタが上下左右に1ずつ移動するプログラム

#include "Mouse.h"

void setup()
{
  Serial.begin(9600);
  pinMode(3, OUTPUT);
  pinMode(23, OUTPUT);
  digitalWrite(3, HIGH);
  digitalWrite(23, LOW);

  Mouse.begin();
}

int count = 0;
int x_prev, y_prev, z_prev;

void loop()
{
  int x,y,z;
  x = analogRead(4);
  y = analogRead(3);
  z = analogRead(2);

  int a;
  a = pow(x-x_prev, 2)+pow(y-y_prev, 2)+pow(z-z_prev,2);
  a = sqrt(a);

  // Click
  if( a > 100 ){
   Serial.println("Click!!");
   Mouse.click(MOUSE_LEFT);
   delay(500);
  }

  // Move
  if( x > 550 ){
    Mouse.move(-1, 0, 0);
  }
  else if( x < 450 ){
    Mouse.move(1, 0, 0);    
  }

  if( y > 550 ){
    Mouse.move(0, 1, 0 );
  }
  else if( y < 450 ){
    Mouse.move(0, -1, 0);
  }
  Serial.print(x);
  Serial.print(",");
  Serial.print(y);
  Serial.print(",");
  Serial.println(z);

  x_prev = x;
  y_prev = y;
  z_prev = z;
  delay(10);
}

上記例を応用して,よりポインティング作業がよくなるようなプログラムを作成せよ

#include "Mouse.h"

void setup()
{
  Serial.begin(9600);
  pinMode(3, OUTPUT);
  pinMode(23, OUTPUT);
  digitalWrite(3, HIGH);
  digitalWrite(23, LOW);

  Mouse.begin();
}

int count = 0;
int x_prev, y_prev, z_prev;

void loop()
{
  int x,y,z;
  x = analogRead(4);
  y = analogRead(3);
  z = analogRead(2);

  int a;
  a = pow(x-x_prev, 2)+pow(y-y_prev, 2)+pow(z-z_prev,2);
  a = sqrt(a);

  // Click
  if( a > 100 ){
   Serial.println("Click!!");
   Mouse.click(MOUSE_LEFT);
   delay(500);
  }

  // Move
  Mouse.move((505-x)/5, (y-509)/5, 0);

  Serial.print(x);
  Serial.print(",");
  Serial.print(y);
  Serial.print(",");
  Serial.println(z);

  x_prev = x;
  y_prev = y;
  z_prev = z;
  delay(10);
}

PONGで高得点を目指す

Pong Game(ブラウザ上でPongが楽しめます)を自作した加速度マウスポインタを利用して高得点が取れるようにプログラムを修正してみましょう.

Processingとの連携

ここまで主にArduinoを対象に説明を行ってきました。一方でArduinoから得られたデータをコーンピュータに送信し、ゲームをしたり、映像、音を操作することもよくあります。プロトタイピングをする際、なにか一つのデバイスを制作するとします。例として、デバイスを振ると音がなるデバイスを考えてみましょう。Arduinoと複数の音声再生モジュールやスピーカを組み合わせることで、デバイス単体でこのような機能を再現することは可能ですが、ハードウェアに知識や必要なモジュールを買い揃える必要があります。プロトタイプの段階ではまず動作させ、体験価値を作り出すことが重要です。そこでよく用いられる手法はセンシング部分だけはArduinoを利用し、その他の処理は全てPCで行います。例えばArduinoからは加速度センサの値だけを出力し、Processing側でその値を処理し、音声再生をおこなうプログラムを作成することで、ハードウェアにかける時間を最低限にし、ソフトウェア場で基本的な体験が可能になるわけです。

ArduinoとPCを連携させるのに手軽なソフトウェアとしてProcessingがあります。ここではProcessingを通じてPCとArduinoを連携させてみます。Processingはプログラムを学習する上で効率的なプログラミング環境を構築しています。ぜひ下記で自習してみてください。

加速度センサの値をPC上で取得する

プログラムの基礎、Processingの可能性を体験したところで、Processingを使ってArduino上の加速度センサの値を読めるようにします。ArduinoとProcessingのデータやりとりにはシリアル通信を利用します。シリアル通信を使う場合、まずArduino上で次のプログラムを記述しておきます。

加速度センサの値を読み,x値だけをシリアル送信するプログラム

void setup()
{
  Serial.begin(9600);
  pinMode(3, OUTPUT);
  pinMode(23, OUTPUT);
  digitalWrite(3, HIGH);
  digitalWrite(23, LOW);
}

int count = 0;

void loop()
{
  int x,y,z;
  x = analogRead(4);
  y = analogRead(3);
  z = analogRead(2);

	x = map(x, 0, 1023, 0,99); // x値の範囲を 0-1023から0-99へ変更
  Serial.write(x);
  delay(30);
}

では次はProcessing側の準備です。次の例はシリアルデータを読み取り、画面上にその値を表示し、値に応じて円の直径を変化させてみます。

import processing.serial.*;

Serial myPort;  // Create object from Serial class
int val;      // Data received from the serial port

void setup()
{
  size(500,500);
  String portName = Serial.list()[0];
  println(Serial.list());
  // Change the portname as your PC
  myPort = new Serial(this, "/dev/tty.usbmodem14321", 9600);
}

void draw(){
  background(255);
  
   if ( myPort.available() > 0) {  // If data is available,
    val = myPort.read();         // read it and store it in val
  }

	text(val, 20,20);
  noStroke();
  fill(34,150,90);
  ellipse(width/2, height/2, val,val);
}

x,y,zの送信

さあ、これでArduinoからのデータを受け取ることができました。しかしまだxの値のみです。そこでy, zの値も同様にしてPCに送信したいと思います。しかし実際にこのやり方では問題が生じます。ArduinoからPCへ値を送信した場合に、そのデータがx,y,zのどの値なのかが分からなければなりません。そこでデータの送信形式(フォーマット)を事前に決めておく必要があります。

どのようにすれば3つの値を読み込むことができるか、まずは下記の解答を見ずに、自分で考えプログラムを工夫してみましょう

Arduinoのプログラム

void setup()
{
  Serial.begin(9600);
  pinMode(3, OUTPUT);
  pinMode(23, OUTPUT);
  digitalWrite(3, HIGH);
  digitalWrite(23, LOW);
}

int count = 0;

void loop()
{
  int x,y,z;
  x = analogRead(4);
  y = analogRead(3);
  z = analogRead(2);

  x = map(x, 0, 1023, 0,99); // x値の範囲を 0-1023から0-99へ変更
  y = map(y, 0, 1023, 0,99);
  z = map(z, 0, 1023, 0,99);
  Serial.write(100);
  Serial.write(x);
  Serial.write(y);
  Serial.write(z);
  delay(30);
}

Processingのプログラム

import processing.serial.*;

Serial myPort;  // Create object from Serial class
int val;      // Data received from the serial port

void setup()
{
  size(500,500);
  String portName = Serial.list()[0];
  println(Serial.list());
  // Change the portname as your PC
  myPort = new Serial(this, "/dev/tty.usbmodem14321", 9600);
}

int x,y,z;
void draw(){
  background(255);
  
   while( myPort.available() > 4) {  // If data is available,
    val = myPort.read();
    if( val == 100 ){
      x = myPort.read();
      y = myPort.read();
      z = myPort.read();
    }
  }
  
  text(x, 20,20);
  text(y, 20,40);
  text(z, 20,60);

  noStroke();
  fill(34,150,90);
  ellipse(width/2, height/2, val,val);
}

Arduinoで実装した時系列3次元ベクトルのユークリッド距離計算による、加速度変化計算

では次にすでにArduino上で実装したユークリッド距離計算をProcessing上に実装し直して見ます。計算式は下記の通りでした。すでにArduino上でコードを記述したので、そちらを参照して自分でProcessing上に記述してください。

\[ a = \sqrt{(\frac{dx}{dt})^2+(\frac{dy}{dt})^2+(\frac{dz}{dt})^2} \]

一定の強さでデバイスを振ると丸の色が変わるプログラム

import processing.serial.*;

Serial myPort;  // Create object from Serial class
int val;      // Data received from the serial port

void setup()
{
  size(500, 500);
  String portName = Serial.list()[0];
  println(Serial.list());
  // Change the portname as your PC
  myPort = new Serial(this, "/dev/tty.usbmodem14621", 9600);
}

int x, y, z;
int x_prev, y_prev, z_prev;

void draw() {
  background(255);

  while ( myPort.available() > 4) {  // If data is available,
    val = myPort.read();
    if ( val == 100 ) {
      x = myPort.read();
      y = myPort.read();
      z = myPort.read();
    }
  }

  text(x, 20, 20);
  text(y, 20, 40);
  text(z, 20, 60);

  float d = pow((x-x_prev), 2)+pow((y-y_prev), 2)+pow((z-z_prev), 2);
  d = sqrt(d);
  if ( d > 20.0) {
    fill(250, 150, 90);
  } else {
    fill(34, 150, 90);
  }
  noStroke();
  ellipse(width/2, height/2, val, val);

  x_prev = x;
  y_prev = y;
  z_prev = z;
}

センサと連動した簡易ゲームの作成

ここまでの知識をもとに,簡単なゲームを作ってみます.加速度センサを利用し,画面上の丸を転がすゲームです.まずは単純なところからスタートしていきます.すこしプログラムで難しいところもでてきますが,現時点では全部わからなくても,とにかく動かすことに焦点をあてて進めて行きます

クラスを利用して準備をすすめる

まずは丸を描くプログラムを作成します.これまでやった内容で, ellipse(x,y,w,h); を利用すると簡単に円を描くことができますが,この丸は画面上を移動するため,位置などの情報が同時に存在します.このように一つのオブジェクトが複数のパラメータや関数によって定義できる場合,これらをクラスで記述するのが一般的です.クラスの作成手順は下記のようにします.

  1. Processingの新規ファイルを作成する
  2. プログラム画面タブの横にある展開アイコンをクリックし,「新規タブ」を選択
  3. ファイル名を聞かれるので「circle」(任意)と入力する

main

  import processing.serial.*;

Serial myPort;  // Create object from Serial class
tama[] t = new tama[1000];

int val;      // Data received from the serial port

void setup()
{
  size(500, 500);
  String portName = Serial.list()[0];
  println(Serial.list());
  // Change the portname as your PC
  myPort = new Serial(this, "/dev/tty.usbmodem14621", 9600);
  for ( int i = 0; i < t.length; i++ ) {
    t[i] = new tama();
  }
}

int x, y, z;
void draw() {
  background(255);

  while ( myPort.available() > 4) {  // If data is available,
    val = myPort.read();
    if ( val == 100 ) {
      x = myPort.read();
      y = myPort.read();
      z = myPort.read();
    }
  }

  text(x, 20, 20);
  text(y, 20, 40);
  text(z, 20, 60);

  noStroke();
  fill(34, 150, 90);

  for ( int i = 0; i < t.length; i++ ) {
    t[i].setGravity(x, y);
    t[i].update();
    t[i].draw();
  }
}

  

tama.pde

class tama {
  float x, y;
  float v_x, v_y;
  float a_x, a_y;
  float w;
  float h;
  tama() {
    x = y = v_x = v_y = a_x = a_y = 0.0;
    w = 10.0;
    h = 10.0;
    x = random(255);
    y = random(255);
  }

  void setGravity(float g_x, float g_y) {
    a_x = g_x;
    a_y = g_y;
  }
  void update() {
    v_x = v_x + (-1)*(a_x-50)/100.0;
    v_y = v_y +      (a_y-50)/100.0;
    x = x + v_x;
    y = y + v_y;

    float k = 0.7;
    if ( x > width ) {
      v_x = (-1)*k*v_x;
      v_y = k*v_y;
      x = width;
    }
    if ( x < 0 ) {
      v_x = (-1)*k*v_x;
      v_y = k*v_y;
      x = 0;
    }
    
    if( y > height ){
      v_y = (-1)*k*v_y;
      v_x = k*v_x;
      y = height;
    }
    if( y < 0 ){
      v_y = (-1)*k*v_y;
      v_x = k*v_x;
      y = 0;
    }
  }

  void draw() {
    ellipse(x, y, w, h);
  }
}

Box2Dを利用する

次はBox2Dと呼ばれる物理演算ライブラリを利用して,実際に同じようなことをしてみます.とは言っても物体同士の衝突を検知するなど,Box2Dを利用すると,細かな設定ができるようになります.まずは下のコードを使ってプログラムを動作させてみます.

    付属ファイル
  • ball.wav
main
import shiffman.box2d.*;
import org.jbox2d.collision.shapes.*;
import org.jbox2d.common.*;
import org.jbox2d.dynamics.*;

// A reference to our box2d world
Box2DProcessing box2d;

// Movers, jsut like before!
Ball[] ball = new Ball[100];

Boundary[] wall = new Boundary[4];

void setup() {
  size(640, 360);
  smooth();

  box2d = new Box2DProcessing(this);
  box2d.createWorld();
  box2d.setGravity(0, -10);

  for (int i = 0; i < ball.length; i++) {
    ball[i] = new Ball(random(0.01, 10), random(width), random(height));
  }
  wall[0] = new Boundary(width/2, height, width, 1);
  wall[1] = new Boundary(width/2, 0, width, 1);
  wall[2] = new Boundary(0, height/2, 1, height);
  wall[3] = new Boundary(width, height/2, 1, height);
}

void draw() {
  background(255);
  box2d.setGravity((mouseX-width/2)*0.05, (height/2-mouseY)*0.05);
  box2d.step();
  for (int i = 0; i < ball.length; i++) {
    ball[i].display();
  }
  for ( int i = 0; i < wall.length; i++ ) {
    wall[i].display();
  }
}

Ball.pde
class Ball {
  // We need to keep track of a Body and a radius
  Body body;
  float r;
  float x,y;
  Ball(float r_, float x_, float y_) {
    r = r_;
    x = x_;
    y = y_;
    // Define a body
    BodyDef bd = new BodyDef();
    bd.type = BodyType.DYNAMIC;

    // Set its position
    bd.position = box2d.coordPixelsToWorld(x, y);
    body = box2d.world.createBody(bd);

    // Make the body's shape a circle
    CircleShape cs = new CircleShape();
    cs.m_radius = box2d.scalarPixelsToWorld(r);

    // Define a fixture
    FixtureDef fd = new FixtureDef();
    fd.shape = cs;
    // Parameters that affect physics
    fd.density = 1;
    fd.friction = 0.3;
    fd.restitution = 0.5;
    body.createFixture(fd);
    body.setLinearVelocity(new Vec2(random(-5, 5), random(-5, -5)));
    body.setAngularVelocity(random(-1, 1));
  }
  void display() {
    // We look at each body and get its screen position
    Vec2 pos = box2d.getBodyPixelCoord(body);
    // Get its angle of rotation
    float a = body.getAngle();
    pushMatrix();
    translate(pos.x, pos.y);
    rotate(a);
    fill(120, 120,200);
    //stroke(0);
    //strokeWeight(1);
    noStroke();
    ellipse(0, 0, r*2, r*2);
    // Let's add a line so we can see the rotation
    line(0, 0, r, 0);
    popMatrix();
  }
}
Boundary.pde
class Boundary {

  // A boundary is a simple rectangle with x,y,width,and height
  float x;
  float y;
  float w;
  float h;
  
  // But we also have to make a body for box2d to know about it
  Body b;

  Boundary(float x_,float y_, float w_, float h_) {
    x = x_;
    y = y_;
    w = w_;
    h = h_;

    // Define the polygon
    PolygonShape sd = new PolygonShape();
    // Figure out the box2d coordinates
    float box2dW = box2d.scalarPixelsToWorld(w/2);
    float box2dH = box2d.scalarPixelsToWorld(h/2);
    // We're just a box
    sd.setAsBox(box2dW, box2dH);


    // Create the body
    BodyDef bd = new BodyDef();
    bd.type = BodyType.STATIC;
    bd.position.set(box2d.coordPixelsToWorld(x,y));
    b = box2d.createBody(bd);
    
    // Attached the shape to the body using a Fixture
    b.createFixture(sd,1);
    
    b.setUserData(this);
  }

  // Draw the boundary, if it were at an angle we'd have to do something fancier
  void display() {
    fill(0);
    stroke(0);
    rectMode(CENTER);
    rect(x,y,w,h);
  }

}

平成29年6月27日で作成した、衝突速度に応じて音量が変わるプログラムは下記のURLからダウンロードできます
ダウンロード

ジェスチャ認識

ジェスチャとは「腕を振る」、「アルファベットのaを書く」と言った具合に、ユーザが身体を利用した入力全般をさします。コンピュータ操作にはボタンやダイヤル等が 一般的でしたが、人間は身体を利用して多くのコミュニケーションを図ることができます。このような人間が社会生活等で自然に行っている行為をコンピュータが理解して ユーザインタフェースに結びつける研究のことをNatural User Interfaceと呼びます。ユーザは既に普段利用している行為を入力にできるため、ユーザ側の学習コストは ほとんどありません。一方、コンピュータにとっては自然なことではないので、ジェスチャを識別する為には学習が必要になります。ジェスチャ認識に限った話ではありませんが、 あるデータをコンピュータが学習して、ユーザの意図や入力を認識することを総じて機械学習と呼びます。ここではProcessingを利用し、ジェスチャ入力を実装してみます。

One dollar Recognizer

One Dollar Recognizerはシンプルなアルゴリズムで高精度のジェスチャ認識を 可能にするアルゴリズムです。最近では深層学習がジェスチャ入力等にも使われるようになってきましたが、反応速度やマイクロコントローラなどへの実装を考えると、依然として このようなシンプルな仕組みは利用価値があります。まずはProcessingで動かしてみましょう。

Processingのライブラリのインポートにて、contribution managerを開きます。検索filterに$1と入れてみましょう。最初にでてくる $1 Unistroke Recognizerをインストール してください。Installが終わったらProcessingを立ち上げ直し、exmapleを確認し、$1 Recognierがあることを確かめてください。幾つか実際にサンプルを動かしてみましょう。 ここで利用しているアルゴリズムに関する論文は$1 Recognizer論文からダウンロード してください。

アルファベットを認識するプログラム

import de.voidplus.dollar.*;

OneDollar one;
String name;
IntList stroke = new IntList();

void setup() {
  size(250, 250);
  background(255);

  name = "-";
  one = new OneDollar(this);
  println(one);                  
  one.setVerbose(true);         
  one.setMaxTime(3000);

  one.learn("1", new int[]{127, 53, 127, 55, 126, 58, 126, 61, 125, 64, 124, 69, 122, 78, 120, 85, 120, 91, 118, 98, 117, 106, 115, 120, 115, 124, 113, 132, 113, 140, 112, 146, 112, 152, 111, 157, 111, 158, 111, 163, 110, 166, 110, 169, 110, 172, 110, 174, 110, 178, 110, 179, 110, 181, 110, 182, 110, 184, 110, 185, 110, 185, 110, 185, 110, 185, 110, 186});
  one.learn("2", new int[]{73, 58, 74, 58, 76, 58, 79, 58, 82, 58, 86, 58, 91, 58, 96, 58, 100, 58, 105, 58, 109, 58, 113, 59, 116, 61, 117, 61, 122, 64, 124, 65, 126, 67, 128, 69, 130, 72, 132, 75, 134, 78, 136, 81, 138, 84, 138, 85, 140, 88, 141, 91, 142, 94, 143, 96, 144, 98, 145, 102, 146, 104, 146, 107, 147, 110, 147, 113, 147, 117, 147, 119, 147, 122, 147, 124, 147, 126, 146, 128, 146, 130, 145, 132, 144, 135, 142, 137, 141, 139, 139, 142, 137, 144, 135, 148, 132, 151, 130, 154, 127, 157, 125, 160, 123, 162, 120, 165, 118, 167, 116, 169, 113, 171, 111, 173, 108, 174, 106, 176, 104, 177, 102, 178, 99, 180, 97, 181, 95, 181, 94, 183, 92, 184, 90, 184, 89, 185, 88, 186, 88, 186, 87, 186, 86, 187, 86, 187, 85, 188, 85, 188, 85, 188, 84, 188, 84, 189, 84, 189, 84, 189, 85, 188, 87, 188, 89, 188, 92, 188, 95, 188, 99, 188, 104, 188, 109, 187, 114, 187, 119, 187, 125, 187, 130, 187, 135, 187, 140, 187, 145, 187, 149, 187, 153, 187, 155, 187, 161, 187, 163, 187, 166, 187, 168, 187, 171, 187, 172, 187, 175, 187, 177, 187, 178, 187, 180, 187, 181, 187, 182, 187, 183, 187, 184, 187, 185, 187, 186, 187, 186, 187, 187, 187, 188, 187, 189, 187, 189, 187, 189, 187, 190, 187, 190, 187, 190, 187, 190, 187, 190, 187, 190, 187});
  one.learn("3", new int[]{86,38,88,38,92,38,96,38,100,38,105,38,109,38,114,38,117,38,122,38,125,39,129,40,132,41,135,43,138,44,140,45,144,47,146,49,148,50,150,52,152,53,154,55,155,56,157,58,158,59,159,61,160,62,161,65,161,69,162,70,162,72,162,74,162,76,162,78,161,79,161,81,160,83,158,85,156,87,155,89,152,92,150,95,147,98,145,102,141,105,138,108,134,111,131,113,128,115,125,118,122,119,120,120,118,121,115,122,113,123,110,124,107,125,104,126,101,126,100,127,97,127,95,127,93,127,92,128,90,128,89,128,88,128,87,128,87,128,87,128,87,128,87,128,88,127,91,127,94,127,99,126,103,126,108,126,114,126,121,126,127,126,133,126,138,127,144,129,149,131,154,133,158,135,161,137,163,139,166,141,168,142,170,144,172,146,174,147,176,148,177,149,178,151,179,152,180,153,181,155,181,155,182,157,182,158,182,159,182,160,182,161,182,162,182,164,182,166,182,167,182,169,181,172,180,173,179,175,178,177,175,180,174,181,172,183,171,184,170,186,168,187,167,188,165,189,164,190,163,192,162,193,160,193,159,195,157,196,155,197,152,198,150,199,147,200,143,200,140,201,136,202,132,202,128,203,123,203,119,203,114,203,109,203,104,203,100,203,96,203,92,202,90,202,87,201,84,201,82,200,80,200,79,200,78,199,77,199,77,199,76,198,76,198,76,198,76,198});
  one.learn("4", new int[]{117,51,117,52,116,54,115,55,113,58,110,61,107,65,104,69,100,74,97,78,93,83,89,87,86,92,83,96,80,100,79,102,77,104,76,107,75,109,74,110,73,112,73,112,73,113,73,114,73,114,72,115,72,115,72,116,72,116,72,117,72,117,72,117,72,117,72,118,72,119,72,119,72,120,72,120,71,121,71,121,71,121,71,121,71,122,71,122,71,122,72,122,73,122,76,121,79,121,83,121,88,120,94,120,101,120,108,120,114,120,121,120,128,120,133,120,139,119,143,119,147,119,149,119,153,119,156,119,158,119,161,118,163,118,165,118,166,118,168,118,169,118,169,118,170,118,170,118,171,118,171,118,171,118,135,73,135,74,135,76,135,78,135,80,135,83,135,87,135,91,135,95,135,99,135,104,135,108,135,114,135,120,135,125,135,130,135,136,135,140,135,146,135,150,135,155,135,159,135,162,135,167,135,168,135,173,135,175,135,178,135,180,135,183,135,185,135,187,134,189,134,190,134,192,134,193,134,193,134,194,134,195,134,196,134,196,134,197,134,197,134,198,134,199,134,199,134,199,134,200,134,200,134,201,134,201,134,201,134,202,134,202,134,202,134,202,134,203,134,203,134,203,134,203});
  one.learn("5", new int[]{80,65,80,66,80,67,80,69,80,71,80,74,80,76,80,80,80,83,80,86,80,90,80,94,80,97,80,102,79,104,79,108,79,112,79,116,78,120,78,123,78,128,77,129,77,132,77,135,77,137,77,139,77,139,77,141,77,141,77,141,77,141,78,141,79,141,80,140,82,139,84,138,86,138,89,137,94,135,97,134,101,133,107,133,111,132,115,132,119,131,123,131,127,131,128,131,131,131,134,131,136,132,137,133,139,134,141,135,142,135,144,137,145,137,146,138,146,139,147,140,147,140,148,141,148,142,149,143,149,144,149,145,149,147,149,148,149,150,149,152,149,154,149,156,149,158,148,160,148,162,147,164,146,166,145,168,144,170,143,171,142,173,141,175,140,177,138,178,137,180,136,181,135,182,133,184,131,185,129,187,127,188,124,190,121,191,118,192,114,193,110,194,107,195,103,196,101,196,98,196,95,196,93,196,91,196,89,196,87,196,86,196,85,195,85,195,84,195,84,195,84,195,84,194,83,194,83,194,79,71,82,71,86,71,89,71,95,71,101,71,108,71,113,71,119,71,124,71,125,71,129,71,131,71,133,71,134,71,135,71,135,71,135,71,136,71,136,71,136,71,136,71,137,71,137,71,137,71,137,71,138,71,138,71,138,71,138,71,139,71,139,71,139,71,139,71,140,71,140,71,140,71});
  one.disableAutoCheck();

  one.bind("1 2 3 4 5", "detected");
}

void detected(String gesture, float percent, int startX, int startY, int centroidX, int centroidY, int endX, int endY) {
  println("Gesture: "+gesture+", "+startX+"/"+startY+", "+centroidX+"/"+centroidY+", "+endX+"/"+endY);    
  name = gesture;
}

void draw() {
  background(255);

  fill(0); 
  noStroke();
  text("Detected gesture: "+name, 30, 40);
  one.draw();

  fill(0);
  for ( int i = 0; i < stroke.size(); i=i+2 ) {
    ellipse(stroke.get(i), stroke.get(i+1), 3, 3);
  }
}

void mouseDragged() {
  one.track(mouseX, mouseY);
  stroke.append(mouseX);
  stroke.append(mouseY);
}

void keyPressed()
{
  if ( key == ' ') {
    one.clean();
  }
  if ( key == ENTER ) {
    print("new int[]{");
    for ( int i = 0; i < stroke.size(); i++ ) {
      if ( i != 0 )print(",");
      print(stroke.get(i));
    }
    println("});");


    one.check();
    stroke.clear();
  }
}