2013年11月19日火曜日

11/25の授業:Processing(Webカメラをつかった動体検知)

次回11/25の授業は引き続きWebカメラを用いて、動体検知の実験をします。
時間があれば、カメラをセンサーにして、接続されたArduinoと連動させたいと思います。

必要なもの:
・ノートパソコン
・Webカメラ(ノートパソコン内蔵型も可)
・Arduinoボード類


サンプル1(Processing):カメラがとらえた画面中で動くもの(画面に変化がある部分)を察知して、その動いている部分の平均座標値を求めて、その位置に円を描くサンプル。変数toleranceは、色の許容値なので、上げればより鈍く、下げればより敏感に反応します。

import processing.video.*;
Capture video;

int w=320;
int h=240;
color[] exColor=new color[w*h];
float x,y;
int sumX,sumY;
int pixelNum;
boolean movement=false;
int tolerance=40;

void setup(){
  size(320, 240); 
  video = new Capture(this, w, h);
  video.start();
  noStroke();
}

void draw() {
  if(video.available()){
    background(0);
    video.read();
    set(0,0,video);
    loadPixels();

    movement=false;
    for(int i=0;i<w*h;i++){     
      float difRed=abs(red(exColor[i])-red(video.pixels[i]));
      float difGreen=abs(green(exColor[i])-green(video.pixels[i]));
      float difBlue=abs(blue(exColor[i])-blue(video.pixels[i]));
     
      if(difRed>tolerance && difGreen>tolerance && difBlue>tolerance){
        movement=true;
        pixels[i]=color(0,255,0);
        sumX+=i%w;
        sumY+=i/w;
        pixelNum++;
      }
     
      exColor[i]=video.pixels[i];
    }
    updatePixels();
    if(movement==true){
      x=sumX/pixelNum;
      y=sumY/pixelNum;     
      sumX=0;
      sumY=0;
      pixelNum=0;
    }
  }
 
 
  fill(255,0,0);
  ellipse(x,y,20,20);
  fill(0,40,255);
}


サンプル2(Arduino):
動きを検知したProcessingのプログラムに対して、シリアル通信でArduinoと連結し、サーボモータを動かすサンプル。Processing上の動くオブジェクトのX座標(横の動き)に対して、サーボモータの首振りの角度が対応しています。

#include <Servo.h>
Servo myServo;

void setup(){
  Serial.begin(9600);
  myServo.attach(9);
 
}

void loop(){
  if(Serial.available()>0){
    int val=Serial.read();
    int x=map(val,0,255,0, 179);
    myServo.write(x);
  }
 
  delay(15); 
}


サンプル2(Processing):上記サーボモータをつないだArduinoに対するProcessing側のサンプルです。Processindgサンプル1の内容に変数float x2,y2を加えてlerp()をつかうことで、円の動きをやや滑らかにする計算をさせています。

import processing.video.*;
Capture video;
import processing.serial.*;
Serial myPort;

int w=320;
int h=240;
color[] exColor=new color[w*h];
float x,y;
float x2,y2;
int sumX,sumY;
int pixelNum;
boolean movement=false;
int tolerance=40;


void setup(){
  size(w, h); 
  video = new Capture(this, w, h);
  video.start();
  noStroke();
  myPort=new Serial(this,"/dev/tty.usbserial-A9005baQ",9600);
  //上記"/dev/tty.usbserial-A9005baQ"の部分には各自のシリアルポートをいれてください
}

void draw() {
  if(video.available()){
    background(0);
    video.read();
    set(0,0,video);
    loadPixels();

    movement=false;
    for(int i=0;i<w*h;i++){     
      float difRed=abs(red(exColor[i])-red(video.pixels[i]));
      float difGreen=abs(green(exColor[i])-green(video.pixels[i]));
      float difBlue=abs(blue(exColor[i])-blue(video.pixels[i]));
     
      if(difRed>tolerance && difGreen>tolerance && difBlue>tolerance){
        movement=true;
        pixels[i]=color(0,255,0);
        sumX+=i%w;
        sumY+=i/w;
        pixelNum++;
      }
     
      exColor[i]=video.pixels[i];
    }
    updatePixels();
    if(movement==true){
      x=sumX/pixelNum;
      y=sumY/pixelNum;     
      sumX=0;
      sumY=0;
      pixelNum=0;
    }
  }
 
 
  fill(255,0,0);
  //ellipse(x,y,20,20);
  //myPort.write(int(map(x,0,width,0,255)));
 
  x2=lerp(x,x2,0.9);
  y2=lerp(y,y2,0.9);
  ellipse(x2,y2,20,20); 
  myPort.write(int(map(x2,0,width,0,255)));
 
  fill(0,40,255);
}


4 件のコメント:

  1. こんにちは。
    サーボ制御のところでお聞きしたいことがあるのですがよろしいでしょうか?
    サーボの移動角度はどのような計算で求めているのでしょうか?

    返信削除
    返信
    1. こんにちは。
      Processing画面内の動体検知に追従する円ellipse()の移動範囲がX座標値0〜320になり、
      それをmap()を使って0〜255の範囲(1バイト分)に変換し、
      さらにint()でくくって0〜255の整数値としてシリアル通信でArduinoへ送信しています。
      myPort.write(int(map(x2,0,width,0,255)));
      この部分↑がそれに相当します。
      Arduinoは0〜255の値を受け取って、それをさらにmap()で0〜179へ変換し、そのままサーボの角度に代入されます。
      要は、Processingの画面幅0〜320pxをサーボの回転角度0〜179度に変換しているだけです。
      画面左端に円(動体)がある場合はX座標0でサーボ角も0、あるいは、画面中央でサーボ角も中央(90度)ということになります。
      上記の方法は簡単な実験であるため、視野角内の動体の位置(角度)とサーボ回転角とを一致をさせているわけではありません。
      もし、サーボの上にカメラを載せてターゲットを追従させるような動きにするならば、Arduinoのコードを以下のように変更すればいいと思います。

      #include
      Servo myServo;
      float x2;

      void setup(){
      Serial.begin(9600);
      myServo.attach(9);
      }

      void loop(){
      if(Serial.available()>0){
      int val=Serial.read();
      int x=map(val,0,255,0, 179);
      x2+=(x-x2)*0.1;
      myServo.write(int(x2));
      }
      delay(15);
      }

      サーボの動きが逆になっていれば反転して下さい。やや遅れて追従するような動きになると思うのですが、遅すぎるなら
      x2+=(x-x2)*0.1;
      この部分の0.1を0.2や0.3などへ上げてみて下さい。上げすぎるとオーバーシュートするかもしれません。

      削除
    2. 遅くなってしまいすいません。
      返信ありがとうございます。

      サーボの上にカメラを乗せ追尾するような動きにしたいと思い、コードを読ませてもらいました。
      ここで、再度質問があるのですがよろしいでしょうか?

      void loop(){
      if(Serial.available()>0){
      int val=Serial.read();
      int x=map(val,0,255,0, 179);
      x2+=(x-x2)*0.1;
      myServo.write(int(x2));
      }
      delay(15);
      }

      のコードの int val=Serial.read() とありますが、Processing画面内の範囲を変換した値をシリアル通信で受け取り代入しているということでいいのでしょうか?

      int val=Serial.read();
      int x=map(val,0,255,0, 179);
      x2+=(x-x2)*0.1;
      こちらのコードはどのような処理になっているのでしょうか?
      今のサーボの角度から、物体の座標の分だけ、移動させているのでしょうか?

      また、
      Processing側でx座標をいったん0~255の範囲に変換し、シリアル通信でarduinoに送りarduino側で0~179の範囲に変換してると思います。 
      processing側でx座標(0~320)を直接、回転角度(0~179)に変換し,arduinoにシリアル通信で送っても正常な動きになるのでしょうか?

      あまり知識がなく質問ばかりになってしまい申し訳ありません。
      よろしくお願いします。

      削除
  2. とても参考になる記事でした。
    ありがとうございます。

    返信削除