AM2321とArduinoで温湿度計を作った

温湿度センサAM2321を使って、持ち歩き用の温湿度計を作ったけど、携帯するには、ちょっと大きくなっちゃった。
f:id:manpukukoji:20170924080014j:plain
AM2321は、以前に秋月で購入しておいた物だけど、端子のピッチが1.27とへんてこなんで使いにくい。今は、秋月でも通販していないみたいで、かわりにピッチが2.54のAM2320が販売されているようですね。
今回は、できれば持ち歩きたいと思って、プリント基板を作ってみたけど、片面基盤なんで、あまり小さくならなかった。ちなみに、僕の場合、プリント基板は、トレーシングペーパーに印刷して、ポジ感光基盤をブラックライトで感光させて作っている。
ブラックライトは、最近電気やとかホームセンタでは、手に入りにくく、車用品店でなんとか購入。
Arduinoは、ATMEGA328にブートローダを書き込んでチップ単体で3V駆動して使用。これだと、I2Cの電圧を変換しなくてすむしね。
ほんで、回路は、こんな感じ。
f:id:manpukukoji:20170924080102j:plain
で、プログラムは、これまた、あちこちのサイトを参考にさせていただき、ほとんどコピペに近いが、以下のように作成。
タクトスイッチは、温度と湿度の切り替え用
実際のところ、3Vでは、7セグの表示が暗くて、昼のアウトドアでは、よく見えない。
また、ボタン電池だと電池の持ちもいまいち・・・。
と、いうわけで、実際のところ、安定化電源から電源をとりデスクトップで室内の温度と湿度を表示している。

#include <SevSeg.h>
#include <Wire.h>

#define ADDR 0x5C

byte HH,HL,TH,TL,dummy;
int Temp, Humid, DispData;
volatile int chMode = HIGH;
volatile unsigned long time_prev = 0, time_now;

SevSeg sevseg; //Instantiate a seven segment object

void dispchg() {
  time_now = millis(); //現在の割り込み時刻を取得
  if( time_now-time_prev > 20){  
    chMode= !chMode;
  }
   time_prev = time_now;
}

void setup() {
  byte numDigits = 3;
  byte digitPins[] = {3, 6, 7}; // 左が上位桁
  byte segmentPins[] = {4, 8, 10, 12, 13, 5, 9, 11}; // A..G, DPの順

 pinMode(2,INPUT_PULLUP);

  sevseg.begin(COMMON_ANODE, numDigits, digitPins, segmentPins);
    Wire.begin();

 attachInterrupt(0,dispchg,RISING);
}

void loop() {
  int i,j;
  //------------------------------
    Wire.beginTransmission(ADDR);
  //Wire.write(0);
  delay(1);
  Wire.endTransmission();
  
  Wire.beginTransmission(ADDR);
  Wire.write(0x03);
  Wire.write(0x00);
  Wire.write(0x04);
  Wire.endTransmission();
  
  delayMicroseconds(1500);
  
  Wire.requestFrom(ADDR,6);
  delayMicroseconds(30);
  Wire.read();
  Wire.read();
  HH=Wire.read();
  HL=Wire.read();
  TH=Wire.read();
  TL=Wire.read();
  
  Humid=HH*256+HL;
  if(TH & 0x80){
    Temp=-1*((TH & 0x7F)*256+TL);
  }
  else{
    Temp=TH*256+TL;
  }

  
  //------------------------------
   if (chMode == HIGH) {DispData = Humid;}
   else {DispData = Temp;}
  
    for (j=0;j<100;j++){
    sevseg.setNumber(DispData, 1);
    sevseg.refreshDisplay();
    }
    delay(10);

  }

ホタルの光の色をLEDで再現・・・できるか?

f:id:manpukukoji:20160815221142j:plainホタルの光を測定する装置ができたので、早速、データを取ってみた。
何度か失敗した挙句、なんとか、それっぽいデータがとれたので、カラーLEDで再現してみた。

まず、カラーLEDでRGBをそれぞれ単色で点灯して、カラーを見てみたところ、単色で点灯しても他の色を含んでいるようだ。
ちなみに、手元のLEDでは、それぞれの色でカラーメータで3000付近に合わせたところ以下のような値が得られた。

Red Green Blue
3016 15 4
25 3042 297
8 102 2995

これを見ると、赤はそれほどでもないけれども、緑と青は、割とお互いに影響を受けているのがわかる。
これだと、得られたデータとぴったり同じ値で光らせるのは、ちょっと難しいかも・・・。
とりあえず、Arduinoで点灯回路を組んでみた。
可変抵抗で電圧をAD変換して、その値にあわせて、LEDのRGBをそれぞれPWM点灯している。

ともあれ、ホタルのデータに近い値で光らせてみたのがこの写真。

見た感じ、かなり緑に近い感じがする。 実際のホタルは、もう少し黄色が強い感じがするので、どうなのかな・・・? むしろ、周りのArduinoについている黄色のLEDの方がホタルの光の色に近いような気がする・・・。
もう少し、いろいろ試してみる必要がありそうだ。

ホタルの明滅をデータロギング

f:id:manpukukoji:20160814093208j:plain蛍の光をデータに落とすデータロガーを作ってみた。
データは、ホタルの明滅パターンを取るための、光の強度をサンプリングする光センサと、ホタルの光の色をRGBでサンプリングする、カラーセンサの2つのセンサからなっています。
これら2つのセンサからのデータをArduinoでA/D変換、サンプリングしてSDカードに記録するというもの。
回路図は、こんな感じ。
f:id:manpukukoji:20160809205608j:plain

光センサは、S9648にLM358で増幅してArduinoでA/D変換、 カラーセンサは、S9706を使ってRGBのデータを取り込み、ロジックレベル変換モジュールを通して、SDカードに記録している。
センサは、ピンポン玉を半分に切って、底部にセンサをセットして、この中にホタルを入れて、少しの間ピンポン玉の中で光ってもらう。 一応、積分球のようにホタルが中で移動しても平均的な反射光をサンプリングするような効果を期待したけど、センサがむき出しなので、あまり意味はないかもしれない。まぁ、これでいいのだ!

これまでも同様の光センサで、明滅パターンを取っていたのだけど、これまでは、カラーセンサがなく、シリアル通信でPCと直結していたので、機動性に問題があった。また、ホタルの飛ぶところは、暗いので、PCのバックライトの明かりが明るすぎて、何かと問題が多いので、今回のものに改良してみた。

使い方は、電源をONにすると、LEDが3回点滅して、光の強度測定モードで始動します。
ロギング開始ボタンを押すと、光センサからのデータがAD変換されてSDカードにdatalog.txtというファイルに記録されます。
電源をONする際に、ロギング停止ボタンを押したまま電源を入れると、カラー測定モードで始動します。
同様にロギング開始ボタンを押すと、カラーセンサからのデータがcolorlog.txt というファイルに記録されます。
入力データが上限値をこえると、LEDが点灯して入力レベルの調整が必要なことを教えてくれます。
まぁ、詳細は、スケッチを追っていただければわかると思います。
カラー測定データをシリアルモニタで見る場合は、シリアルモニタを立ち上げる際にロギング停止ボタンを押しておかないと、強度測定モードになってしまうので注意が必要です。

Arduinoのスケッチは、これまた、あちこちのサイトを参考にさせてもらって、つぎはぎして、以下のようなものになった。
自分でも、どこのスケッチを引っ張ってきたのかわからなくなっちゃっているけど、カラーセンサに関しては、こちらのサイトから、主な部分は、ほぼ、そのまま引用させていただいている。
建築発明工作ゼミ2008: Arduino デジタルカラーセンサ S9706

他にもSDカードのライブラリとかA/D変換とかセンサの扱いとかいろいろとコピペさせていただいています。
まぁ、まさにスパゲッティのようなスケッチできれいに作れていないし、改良すべきところも沢山あるけど、ひとつの参考までに、以下、スケッチです。

#include <SD.h>

#define LED_PIN 4	// 4番ピンをデーターLogging 開始LEDに設定       Light Intensity
#define STARTBUTTON 2	// 2番ピンをデータLogging開始に設定		Light Intensity
#define STOPBUTTON 3	// 3番ピンをデータLogging開始に設定		Light Intensity
#define PEAK_LED 7	// 7番ピンをピークLEDに設定		        Light Intensity

#define RANGE 6		//  8番ピンをRange端子に設定		        Color Sensor
#define GATE  16	//  9番ピンをGate端子に設定			Color Sensor
#define CK   5		// 10番ピンをCK端子に設定			Color Sensor
#define DOUT 15		// 11番ピンをDout端子に設定			Color Sensor

volatile int loggmode=0;		//Logging mode 設定のための変数 (1=Light Intensity mode / 0=Color mode)

int red,green,blue;   //RGB三色の変数を用意 for Color Sensor

// この値は使用しているシールドや基板に合わせて変更すること。たとえば、
// イーサーネットシールドは 4
// Adafruit のSDシールドは 10
// Sparkfun のSDシールドは 8
const int chipSelect = 10;	//自作のSDシールドは10番ピンで作成
unsigned long tm,ptm;		//期間(millis)の計算のための変数
int mm;
volatile int loggsts=0;	  //Logging Startのフラグ
volatile int datapos=0;  //data start endのマーク
File dataFile;

//start buttonを押したときの割り込み処理
void loggstrt()
{
  loggsts=1;
  digitalWrite(LED_PIN,HIGH);
  datapos = 1;
}

//Stop Buttonを押したときの割り込み処理
void loggstp()
{
  loggsts=0;
  digitalWrite(LED_PIN,LOW);
  digitalWrite(PEAK_LED,LOW);
  datapos = 2;  
}

void setup()
{
  int i;
  
  pinMode(LED_PIN,OUTPUT);
  pinMode(PEAK_LED,OUTPUT);

    //Range,Gate,CK端子をデジタル出力に設定
  pinMode(RANGE,OUTPUT);
  pinMode(GATE,OUTPUT);
  pinMode(CK,OUTPUT);
  //Dout端子をデジタル入力に設定
  pinMode(DOUT,INPUT);


  // シリアルポート初期化
  Serial.begin(9600);

  Serial.print(F("Initializing SD card..."));

  // SSピン(Unoは10番、Megaは53番)は使わない場合でも出力にする必要があります。
  // そうしないと、SPIがスレーブモードに移行し、SDライブラリが動作しなくなります。
  pinMode(SS, OUTPUT);

  // SDライブラリを初期化
  if (!SD.begin(chipSelect)) {
    Serial.println(F("Card failed, or not present"));
    // 失敗、何もしない
    while(1);
  }
  Serial.println(F("ok."));

  // 日付と時刻を返す関数を登録
  SdFile::dateTimeCallback( &dateTime );
  
   tm = millis();
   mm=0;
  
    //Pull Up 設定
    pinMode(STARTBUTTON, INPUT_PULLUP);
    pinMode(STOPBUTTON, INPUT_PULLUP);

	//Logg mode の設定(0=Light Intensity / 1=Color sensor)…Logging停止ボタンを押して電源をいれるとカラーモードになる。
	loggmode = digitalRead(STOPBUTTON);

	// カラーモードのときには、LEDを5回点滅
	if (loggmode==0){
		for(i=1;i<6;i++){
			digitalWrite(LED_PIN,HIGH); digitalWrite(PEAK_LED,HIGH); delay(500);
			digitalWrite(LED_PIN,LOW); digitalWrite(PEAK_LED,LOW); delay(500);
		}
        }
 	// 光強度モードのときには、LEDを3回点滅
	if (loggmode==1){
		for(i=1;i<4;i++){
			digitalWrite(LED_PIN,HIGH); digitalWrite(PEAK_LED,HIGH); delay(500);
			digitalWrite(LED_PIN,LOW); digitalWrite(PEAK_LED,LOW); delay(500);
		}
        }
   
    //割り込み設定
    attachInterrupt(0,loggstrt,FALLING);
    attachInterrupt(1,loggstp,FALLING);
    
    loggsts=0;
}


void loop()
{
// ------------ Light Intnsity Logging  ----------------------------------------
	if(loggmode==1)
	{	
	  if(loggsts==1)
	  {
		// ファイルを開く
		   dataFile = SD.open("datalog.txt", FILE_WRITE);

		 // もしファイルが開けたら値を書き込む
		  if (dataFile) {
		    ptm=tm;
		    tm=millis();
		    int value = analogRead(0);
    
                    if(datapos == 1){dataFile.println("S");}
		    dataFile.print(tm-ptm);
		    dataFile.print(",");
		    dataFile.println(value);
                    if(datapos == 2){dataFile.println("E");}
                    datapos = 0;
		    dataFile.close();
		    // シリアルポートにも出力
		    Serial.print(tm-ptm);
		    Serial.print(",");
		    Serial.println(value);
    
			//データが最大値に達したら、LEDを点灯
		    if (value>730){digitalWrite(PEAK_LED,HIGH);}
		    else {digitalWrite(PEAK_LED,LOW);}

    
		  }
		  // ファイルが開けなかったらエラーを出力
		  else {
		    Serial.println(F("error opening datalog.txt"));
		  } 

		  // 一秒待つ
		//  delay(1000);

	  } // ---------- End of loggsts==1 ... Logging Start ------------------------------
	}	// ---------- End of loggmode==1 ... Light Intensity Logging ----------------------------------


// ---------------Color Logging --------------------------------------------
	if(loggmode==0)
	{
		if(loggsts==1)
		{
				// ファイルを開く
		   dataFile = SD.open("colorlog.txt", FILE_WRITE);

		 // もしファイルが開けたら値を書き込む
		  if (dataFile) {
//		    int value = analogRead(0);
			// >>>>>>>>>>>>>>>>>>>>>>>>>>
			  //測光時間用の可変抵抗器の読み込み(アナログ入力:0番ピン)
			int val=1000;

			//Gate,CK端子をLowに設定
			digitalWrite(GATE,LOW);
			digitalWrite(CK,LOW);
			delayMicroseconds(2000);//2000マイクロ秒待機

			//感度設定(HIGH:高感度に設定)  
			digitalWrite(RANGE,HIGH);

			//測光開始(光量の積算を開始) 
			digitalWrite(GATE,HIGH);
			//測光時間(valを代入し可変的に設定)
			delay(val+1);
  
			//測光終了(光量の積算を終了) 
			digitalWrite(GATE,LOW);
			delayMicroseconds(4);//4マイクロ秒待機
  
			red=shiftIn();//赤の処理
			green=shiftIn();//緑の処理
			blue=shiftIn();//青の処理

			//Gate端子をHighに戻す  
			digitalWrite(GATE,HIGH);

		    ptm=tm;
		    tm=millis();

                    if(datapos == 1){dataFile.println("S");}
		    dataFile.print(tm-ptm);
		    dataFile.print(",");
		    dataFile.print(val,DEC);
		    dataFile.print(",");
		    dataFile.print(red,DEC);
			dataFile.print(",");
			dataFile.print(green,DEC);
			dataFile.print(",");
			dataFile.println(blue,DEC);
                    if(datapos == 2){dataFile.println("E");}
                    datapos = 0;
		    dataFile.close();

		    // シリアルポートにも出力
		    Serial.print(tm-ptm);
		    Serial.print(",");
		    Serial.print(val,DEC);
		    Serial.print(",");
			Serial.print(red,DEC);
			Serial.print(",");
			Serial.print(green,DEC);
			Serial.print(",");
			Serial.println(blue,DEC);
 

			// >>>>>>>>>>>>>>>>>>>>>>>>>
    
  
			//データが最大値に達したら、LEDを点灯
		    if ((red>4000)||(green>4000)||(blue>4000)) {digitalWrite(PEAK_LED,HIGH);}
		    else {digitalWrite(PEAK_LED,LOW);}

    
		  }
		  // ファイルが開けなかったらエラーを出力
		  else {
		    Serial.println(F("error opening datalog.txt"));
		  } 

		  // 一秒待つ
		//  delay(1000);

		}	// End of loggsts==1 ... Logging Start ------------------------
	}	// ---------- End of Loggmode==0 ... Color Logging ----------------

}	// End of loop

void dateTime(uint16_t* date, uint16_t* time)
{
  uint16_t year = 2015;
  uint8_t month = 7, day = 4, hour = 12, minute = 0, second = 0;

  // GPSやRTCから日付と時間を取得
  // FAT_DATEマクロでフィールドを埋めて日付を返す
  *date = FAT_DATE(year, month, day);

  // FAT_TIMEマクロでフィールドを埋めて時間を返す
  *time = FAT_TIME(hour, minute, second);
}

//12ビット分のパルス送信と読み込み処理
int shiftIn(){
  int result=0;//検出結果用の変数を用意(0:初期化)
  for(int i=0;i<12;i++){//12ビット分の繰り返し処理
    digitalWrite(CK,HIGH);//1ビット分のクロックパルス出力(HIGH)
    delayMicroseconds(1);//1マイクロ秒待機
    if(digitalRead(DOUT)==HIGH){//Dout端子からの出力がHighの場合
      result+=(1<<i);//12ビットのi桁目に1を代入(i桁分だけ左にシフト)
    }
    digitalWrite(CK,LOW);//1ビット分のクロックパルス出力(LOW)
    delayMicroseconds(1);//1マイクロ秒待機
  }
  delayMicroseconds(3);//3マイクロ秒待機
  return result;//結果を出力
}


 

ラズベリーパイ2にコマンドボタンをつけてみた

ラズベリーパイにスイッチをつけて、GPIOからシャットダウンしたり、リブートしたりしている人がいる。
確かに、リモートで接続していると、接続が切れてしまったりすると、どうしようもなくなって、やむなく、”えいっ!”っと、電源スイッチを切るのだけど、気の小さい僕には、精神衛生上、はなはだ、よろしくない。
と、言うわけで、シャットダウンスイッチをつけてみた。
せっかくなので、同時に、アプリケーションをスタートしたり、ストップしたりするコマンドボタンも作ってみた。
このコマンドボタンは、監視カメラ用に走らせている、motion のプログラムを起動したり、停止させたりするために利用している。 まぁ、コマンドスイッチ自体は、Python のプログラムしだいで、どのようにも利用できる。
ボタンは、前回のRTCの基盤のあいたところにスイッチを並べてみた。LED点灯用のピンも設定したが、途中で面倒になり、実装は、していない。 /^^;)

スイッチは、前回のRTCモジュールを組んだときの基板のあいたところにヘッダピンとともに並べてみた。
f:id:manpukukoji:20160723214957j:plain
Paithonのスクリプトは、以下のような感じで組んでみた。

#!/usr/bin/python
#coding: UTF-8
import time
import RPi.GPIO as GPIO
import os

System_Reboot=5
System_Shutdown=6
Monitor_Start=13
Monitor_Stop=19
pin_out=21

def SysRBoot_callback(gpio_pin):

	GPIO.output(pin_out, True)
	print "Reboot now!"
	os.system("sudo shutdown -r now")

def SysShut_callback(gpio_pin):

	GPIO.output(pin_out, True)
	print "Shutdown now!"
	os.system("sudo shutdown -h now")

def MStart_callback(gpio_pin):

	if os.path.exists("/var/run/motion/motion.pid"):
		time.sleep(1)
	else:
		os.system("sudo service motion start")
		print "motion start"

def MStop_callback(gpio_pin):

	if os.path.exists("/var/run/motion/motion.pid"):
		os.system("sudo service motion stop")
		print "motion stop"

GPIO.setmode(GPIO.BCM)
#GPIO.cleanup()
GPIO.setup(System_Reboot,GPIO.IN,pull_up_down=GPIO.PUD_UP)
GPIO.setup(System_Shutdown,GPIO.IN,pull_up_down=GPIO.PUD_UP)
GPIO.setup(Monitor_Start,GPIO.IN,pull_up_down=GPIO.PUD_UP)
GPIO.setup(Monitor_Stop,GPIO.IN,pull_up_down=GPIO.PUD_UP)
GPIO.setup(pin_out,GPIO.OUT)
GPIO.add_event_detect(System_Reboot,GPIO.RISING)
GPIO.add_event_detect(System_Shutdown,GPIO.RISING)
GPIO.add_event_detect(Monitor_Start,GPIO.RISING)
GPIO.add_event_detect(Monitor_Stop,GPIO.RISING)
GPIO.add_event_callback(System_Reboot,SysRBoot_callback)
GPIO.add_event_callback(System_Shutdown,SysShut_callback)
GPIO.add_event_callback(Monitor_Start,MStart_callback)
GPIO.add_event_callback(Monitor_Stop,MStop_callback)

try:
	while True:
		time.sleep(1)
except KeyboardInterrupt:
	GPIO.cleanup()

RasPi2にRTC(RX-8564) をつけてみる

f:id:manpukukoji:20160723215217j:plainラズベリーパイ2は、時計を持っていないので、シャットダウンすると再立ち上げの際に時刻をセットしなおさないと最後にシャットダウンした時間からの時刻になってしまう。 もちろん、インターネットに接続されていると、ネットワークからNTPで時刻をセットするので問題ないのだけれども、スタンドアロンで使うときには、毎回時刻をセットしなければ正しい時刻が得られない。
と、言うわけで、電源を落としても時刻を保持できるようにRTCモジュールを接続した。
すでに、RTCの接続については、沢山のサイトで説明されているので、あちこち参考にさせていただいた。
今回、僕が使ったのは、マルツで売っている MRX-8564 といいうモジュールでエプソンのRX-8564LC というICが乗っかっている。 これに、ボタン電池をくっつけて、ラズパイにI2Cで接続。
基盤作成は、コマンド起動用のスイッチとあわせて、こんな感じで作ってみた。
f:id:manpukukoji:20160723212535j:plain

じつをいうと、最初、ラズパイの電源からのラインにダイオードを入れてなかったので、RTCをつなぐと、いきなりラズパイのLEDが点灯したので、ラズパイが起動したのかと思って、びっくりした! さすがにボタン電池では、起動するところまでは行かなかったみたいだけど、LEDくらいは、点くんだね。 まぁ、当たり前っちゃ、あたりまえ何だけど・・・。
あわてて、こちらにもダイオードを追加したしだいです。

ラズベリーパイの設定は、以下のサイトを参考にさせてもらった。
http://tomosoft.jp/design/?p=5812
http://news.mynavi.jp/articles/2014/08/21/raspberry-pi4/002.html
http://wp.developapp.net/?p=3362

これらのサイトに詳しく説明されているので改めて説明するまでも無いけど、
僕の場合、最後の rc.local を編集して、自動起動するところで、ひっかかった。
確かに、設定をしてあるのだけれども、シャットダウンしてしばらくしてから立ち上げると、正しい時刻が設定されていない。 が、マニュアルで設定して、hwclock を表示させると、正しい時刻を表示するので、RTC自体は、ちゃんと動いているみたい。
つまり、立ち上げのときにちゃんと設定されていないみたいなので、もう一度、rc.local を良く見てたら、ちゃんと、以下のように書いてある。
“In order to enable or disable this script just change the execution bits.
By default this script does nothing”
「このスクリプトを有効/無効にするには、実行ビットを書き換えてね。
デフォルトでは、このスクリプトは、なんにもせんからね。」
と、いうことで、
$ sudo chmod +x /etc/rc.local
と、やったら、ちゃんと立ち上がりで時刻がRTCから設定されるようになった。

ラズパイも、ハードもOSもどんどんアップデートされているので、先にやった人のときは、必要なかった事が必要になってたり、逆に、必要だったことが必要なかったり、編集すべき設定ファイルが変わっていたりで、なかなか、簡単にはいかないもんですね…。

ネスカフェバリスタとラズベリーパイで顔を認識したら、ナンパするバリスタ君にしてみた

ネスカフェバリスタラズベリーパイを接続してUSBカメラからキャプチャーした画像をOpenCVを使ったPythonプログラムで顔認識させ、人の顔が認識されたら、”コーヒー飲まない?”と、お誘いするバリスタ君にしてみた。
youtu.be
 
バリスタとラズパイ以外の使用機材は、以下のようなもの
  USBカメラ:iBUFFALO BSW20KM15
  WiFiアダプタ:WLI-UC-GNU2
  セルフパワーのUSBハブ
ラズベリーパイには、Python用のOpenCVをインストール。
以下のサイトを参考にさせていただいた。
Raspberry Piで画像処理ライブラリ”OpenCV”使って”顔認識”試してみた: EeePCの軌跡

上記の参考サイトを見ながら、OpenCVとサンプルをインストール

 $ sudo apt-get install libopencv-dev
 $ sudo apt-get install python-opencv

サンプルは、上記参考サイトにあるように、face.xmlとfacedetect.py をコピーし、まずは、顔認識をテストしてみる。
が、以前にも書いたが、これまで使っていたtightVNCserver では、うまく表示させることができなかったので、x11VNC というソフトをインストール。 ( $ sudo apt-get install x11vnc )
が、そのままでは、解像度が低く、VNCで画面の一部しか表示されなかったので、

 $ sudo nano /boot/config.txt
以下の部分を修正(#をはずす)
  #framebuffer_width = 1280
    #framebuffer_height = 720

と、設定を少しいじくる。
これで、なんとか顔認識ができるようになった。
いろいろなサイトで紹介されているようにモニター画面に映った顔の部分に赤い四角で枠が入るあれ、です。
このサンプルをもとに、顔が認識されたら、シリアル通信でAquesTalkPicoという音声合成ICにメッセージを送ってお話させます。
シリアル通信で発声させるのは、前回
ラズベリーパイからシリアル通信で音声合成ICを発声させてみた - 満腹居士 七転八倒の記
でも書いたが、ラズパイのPin#01, 06, 08 からICにつなぐ。
プログラムは、facedetect.py のオリジナルの部分も含めて、以下のようにした。

#!/usr/bin/python
"""
This program is demonstration for face and object detection using haar-like features.
The program finds faces in a camera image or video stream and displays a red box around them.

Original C implementation by:  ?
Python implementation by: Roman Stanchak, James Bowman
"""
import sys
import cv2.cv as cv
from optparse import OptionParser
import time
import serial
# Parameters for haar detection
# From the API:
# The default parameters (scale_factor=2, min_neighbors=3, flags=0) are tuned
# for accurate yet slow object detection. For a faster operation on real video
# images the settings are:
# scale_factor=1.2, min_neighbors=2, flags=CV_HAAR_DO_CANNY_PRUNING,
# min_size=<minimum possible face size

min_size = (20, 20)
image_scale = 2
haar_scale = 1.2
min_neighbors = 2
haar_flags = 0
count_facecheck = 0

con=serial.Serial('/dev/ttyAMA0', 9600, timeout=10)

def detect_and_draw(img, cascade):
    global count_facecheck
    # allocate temporary images
    gray = cv.CreateImage((img.width,img.height), 8, 1)
    small_img = cv.CreateImage((cv.Round(img.width / image_scale),
                               cv.Round (img.height / image_scale)), 8, 1)

    # convert color input image to grayscale
    cv.CvtColor(img, gray, cv.CV_BGR2GRAY)

    # scale input image for faster processing
    cv.Resize(gray, small_img, cv.CV_INTER_LINEAR)

    cv.EqualizeHist(small_img, small_img)

    if(cascade):
        t = cv.GetTickCount()
        faces = cv.HaarDetectObjects(small_img, cascade, cv.CreateMemStorage(0),
                                     haar_scale, min_neighbors, haar_flags, min_size)
        t = cv.GetTickCount() - t
        # print "detection time = %gms" % (t/(cv.GetTickFrequency()*1000.))
        if faces:
            for ((x, y, w, h), n) in faces:
                # the input to cv.HaarDetectObjects was resized, so scale the
                # bounding box of each face and convert it to two CvPoints
                pt1 = (int(x * image_scale), int(y * image_scale))
                pt2 = (int((x + w) * image_scale), int((y + h) * image_scale))
                cv.Rectangle(img, pt1, pt2, cv.RGB(255, 0, 0), 3, 8, 0)
                print "detection time = %gms" % (t/(cv.GetTickFrequency()*1000.))
            print count_facecheck
            count_facecheck += 1
            if count_facecheck == 5:
              con.write("? ko-hi'- nomima/se'nka?\r")

            elif count_facecheck == 50:
              con.write("? ne'e ne'e, ko-hi'- nomo'uyo\r")

            elif count_facecheck == 100:
              con.write("ko-hi'- noma'naika na'-\r")

            elif count_facecheck == 300:
              count_facecheck = 0

        else:
                count_facecheck = 0

    cv.ShowImage("result", img)

if __name__ == '__main__':

#    global count_facecheck
    parser = OptionParser(usage = "usage: %prog [options] [filename|camera_index]")
    parser.add_option("-c", "--cascade", action="store", dest="cascade", type="str", help="Haar cascade file, default %default", default = "../data/haarcascades/haarcascade_frontalface_alt.xml")
    (options, args) = parser.parse_args()

    cascade = cv.Load(options.cascade)

    if len(args) != 1:
        parser.print_help()
        sys.exit(1)

    input_name = args[0]
    if input_name.isdigit():
        capture = cv.CreateCameraCapture(int(input_name))
    else:
        capture = None

    cv.NamedWindow("result", 1)

    width = 320 #leave None for auto-detection
    height = 240 #leave None for auto-detection

    if width is None:
        width = int(cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_WIDTH))
    else:
        cv.SetCaptureProperty(capture,cv.CV_CAP_PROP_FRAME_WIDTH,width)

    if height is None:
        height = int(cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_HEIGHT))
    else:
        cv.SetCaptureProperty(capture,cv.CV_CAP_PROP_FRAME_HEIGHT,height)

    if capture:
        frame_copy = None

        while True:

            frame = cv.QueryFrame(capture)
            if not frame:
                cv.WaitKey(0)
                break
            if not frame_copy:
                frame_copy = cv.CreateImage((frame.width,frame.height),
                                            cv.IPL_DEPTH_8U, frame.nChannels)

#                frame_copy = cv.CreateImage((frame.width,frame.height),
#                                            cv.IPL_DEPTH_8U, frame.nChannels)

            if frame.origin == cv.IPL_ORIGIN_TL:
                cv.Copy(frame, frame_copy)
            else:
                cv.Flip(frame, frame_copy, 0)

            detect_and_draw(frame_copy, cascade)

            if cv.WaitKey(10) >= 0:
                break
    else:
        image = cv.LoadImage(input_name, 1)
        detect_and_draw(image, cascade)
        cv.WaitKey(0)

    cv.DestroyWindow("result")

発声部分のプログラムは、簡単なのですぐわかると思うが、
顔を認識した回数をカウントして、何回かになると発声するようにしている。
最初は、「コーヒー飲みませんか?」 と、お誘いする。
2回目は、「ねぇ、ねぇ、コーヒー飲もうよ!」と、ちょっと、うざい感じで・・・。
3回目は、「コーヒー飲まないかなぁ~?」と、ちょっと、未練たらしく・・・。

3回発声すると、リセットされて、しばらく間をとる。3回の間に顔認識がはずれた時にも、リセットされて、また、一回目の発声からはじまる。
てな、具合。

別に、バリスタなくても良いんじゃないかって?
あっ、気付いちゃった?
まぁ、そうなんだけど、コミュニケーションとるのに、何か、それなりのネタがいるじゃないですか。
だから、バリスタで入れるコーヒーをネタにナンパをしかけるという作戦で・・・。

これまでの一連の試みで、人が近くに来たのを認識し、「コーヒーはいかが?!」と、声をかけて、音声認識で返事を受け取って、コーヒーを入れる。と、いう、サービスがほぼ自動化できるようになった。
これに、手足をつけてロボット化すると、給仕ロボットができちゃいそうですね。

ラズベリーパイからシリアル通信で音声合成ICを発声させてみた

これまで、音声合成IC(Aques talk pico)で発声させるのにプリセットのメッセージをラズパイのGPIOからコントロールしていたが、ラズパイから直接シリアル通信で発生させるようにセットしてみた。
f:id:manpukukoji:20160306114053j:plain
プリセットメッセージを使うと、メッセージ数にもよるが、GPIOのピンを3つ、4つ使うことになるけどシリアル通信で直接メッセージを送ると、送信だけで良いのでGPIO14のピン1本で事足りるので節約になるね。
シリアル通信のための設定は、いろいろなサイトで説明してくれているが、なんと、今回、僕の環境では、何もしなくてもそのまま通信できちゃったよ???

これまでのWebの情報だとシリアル通信するためには、以下の3つが必要だそうです。
1.シリアルコンソールのdisable化
2./boot/cmdline.txt の編集
3./etc/inittab の編集

僕の環境は、
Raspberry Pi2
NOOBS ver. 1.7.0. (Raspbian Kernel 4.1.17)
になっているが、上記の設定をしなくてもできちゃうみたい・・・?!

ちなみに、NOOBSのバージョンは、もう、1.8.0になっていた。
で、現在の/boot/cmdline.txt は、以下のようになっていた。
/etc/inittab は、そもそも、はじめから見つからなかった。

pi@raspberrypi:~$ cat /boot/cmdline.txt
dwc_otg.lpm_enable=0 console=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p7 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait

ラズパイとATP3011との接続は、電源用の3.3VとGround、ラズパイのTxd Pin#08(GPIO14) の3本で接続
回路図は、こちら
f:id:manpukukoji:20160306113408j:plain
写真では、IR受信用のセンサとリモコンコード解読用のPICが付いているが、今回は、使わないので回路図からは、はずしてある。

簡単な発声プログラムは、Pythonで以下のようなプログラムを書いてみた。

pi@raspberrypi:~ $ cat wrserial.py
import serial
import time

def main():
    con=serial.Serial('/dev/ttyAMA0', 9600, timeout=10)
    print con.portstr
    time.sleep(1)
    str="? ohayo-\r\n"
    con.write(str)

if __name__ == '__main__':
    main()

これで、

 $sudo python wrserial.py

と、やると、”おはよー”と挨拶してくれるはず・・・。