ROSのTFとは?簡単に説明(サンプルコード付き)

はじめに

ROSの便利機能にして基本機能TF(Transform、トランスフォーム)について、出来るだけ難しい言葉を使わずに説明していきたいと思います。

static transformの書き方

TFとは?

TF(transform)とは「ある座標系」と「他の座標系」の位置関係を表すものです。

座標系とは基準点のことで、例えば部屋の基準点なら部屋の隅、ロボットの基準点なら重心、センサの基準点ならセンサ底面などです。

例として次のような車輪ロボットを考えてみます。

車輪ロボットとセンサ

このとき、ロボットの重心位置においたbase_link座標系からセンサのlaser座標系に行くには「x方向に20cm、z方向に10cm」と表現できます。これが「TF」です。

※ 正確には、2つの座標系の関係を3次元で表すには、位置(x,y,z)と姿勢(ロール、ピッチ、ヨー)の6つのパラメータが必要です。また、姿勢を4つのパラメータで表す方法(4元数、クォータニオン)もあります。

TFの特徴

複数のTFをつなげることが出来る

TFは2つの座標系の位置関係を表すものですが、普通ロボットや自動車は沢山の部品で出来ています。それらを1つの基準点(たとえば上の図のロボット座標系)からすべて表すのは大変です。
したがってTFは数珠つなぎに記述することが一般的です。例えば先程のロボットの例で、車体の上面の中心にtop_center座標系を作るとレーザセンサやカメラまでの位置を計測しやすくなるため便利になります。base_link座標系→top_center座標系→laser、camera座標系と数珠つなぎになっているのが分かります。

ロボットの座標系

ROSでは座標系のことを「frame(フレーム)」と呼びます。rvizに表示される黄色の線は「あるフレーム」から「他のフレーム」へのTF、つまり一方のフレームから見たもう一方のフレームの位置と姿勢がROSに登録されている事を示しています。元になるフレームを「parent frame(親フレーム)」または単に「frame」、目的のフレームを「child frame(子フレーム)」と呼びます。

2フレーム間のTFをROSに登録する

ROSで利用するすべてのTFはROSシステムに登録されます。親フレーム名、子フレーム名、位置関係、タイムスタンプ等の情報をメッセージにまとめてROSに送信します。またTFには有効期限があるため、1度登録したら終わりではなく10ms毎など定期的に登録し直す必要があります。(例外として、有効期限が無いstatic_transformというものもあります)

つながっているTF同士の位置関係は自動計算される

すべてのフレーム間の位置関係をROSに登録しておくことで、任意の2フレーム間のTFをROSが自動計算してくれます。例えば上の例でbase_linkフレームとcameraフレームは直接繋がっていませんが、これを自動計算できる仕組みがROSに備わっています。

逆に、TFが途中で切れていると、2フレーム間のTFが取得できず、エラーになります。

ROSに登録されてから一定時間経つと無効になる

TFは一度誰かが発信してROSシステムに登録された後、一定時間経つと無効になります。ずっと表示させたければすべてのTFを定期的に再発信する必要があります。

静的TFと動的TF

TFには例えばロボットのセンサのように位置関係が永久に変わらないものと、部屋からみたロボットの位置のように変わり続けるものがあります。前者を静的TF、後者を単にTFと言います。

TFを発信する

静的TFを発信するコードを書いてみます。最も簡単なのは次のようなlaunchファイルを作ることです。

<launch>

  <node pkg="tf" type="static_transform_publisher" name="robot_to_sensor" args="0.2 0 0.1 0 0 0 robot sensor 10" />

</launch>

「<node」 から 「/>」までがTFを発信するプログラムを実行する部分です。それぞれの引数の意味は次のとおりです。

引数値の例備考
pkg“tf” (固定)パッケージの名前
type“static_transform_publisher” (固定)プログラムの名前
name“(任意の名前)”実行時の名前。同名のプログラムがあると実行できないので適宜変更。
args(例)”0.2 0 0.1 0 0 0 robot sensor 10″
“[x] [y] [z] [yaw] [pitch] [roll] [parent_frame] [child_frame] [Hz]”
x方向、y方向、z方向、ヨー、ピッチ、ロール、親フレーム、子フレーム、発信の間隔[Hz]

nameが同じプログラムを同時に作るとエラーになります。これを自分のパッケージ→lanchフォルダの中に入れて実行すると、argsで指定したTFが発信されます。

roslaunch my_package static_tf.launch

発信されているTFをrvizで見てみましょう。rvizを起動して、左下部あたりにある「Add」をクリック、「By display type」から「TF」を追加して下さい。

静的TFが表示された!

2つの座標軸が表示されていれば成功です!
rvizの左側の「Fixed Frame」を「robot」や「sensor」に切り替えることで、どのフレームを中心に据えるか選ぶ事が出来ます。lanchファイルで複数のプログラムを起動すれば、TFの数珠つなぎも簡単に実現できます。

<launch>

  <node pkg="tf" type="static_transform_publisher" name="robot_to_sensor" args="0.2 0 0.1 0 0 0 robot sensor 10" />
 <node pkg="tf" type="static_transform_publisher" name="map_to_robot" args="1.0 0.5 0 0 0 0 map robot 10" />

</launch>

しかし、これだと常に同じTFしか発信できません。
次回は常に変化する位置関係の発信方法をご紹介したいと思います。

ROS初心者の方はまずはこちらの本がオススメです。

動的TFの書き方>>

お疲れ様キーボードを作る!

動機

以前Twitterでこんな投稿を見つけました。

「お疲れ様でした」や「ありがとうございます」等の定型文が入力できるキーボードです。
めっちゃ便利そうなので自分で作ってみることにしました!

仕様

とりあえずの構想はこんな感じで行きます

  • キーを押したことをArduino Nanoで認識
  • Arduino NanoからUSB経由でパソコンにデータを送信
  • パソコン側でPythonプログラムがデータを受信
  • プログラムが文字を打ち込む

必要なものと環境

  • 300円デバイス、Arduino Nano
  • ボタンスイッチ
  • Python3.5(3.6以上だと動作しない可能性あり?)
  • Windows 10(ここは何でも構いません)

ボタンとArduino

まず、ボタンを押したことをArduinoで検知する部分を作ります。押すと導通するスイッチとArduinoをはんだ付けでつなぎます。

Arduinoとスイッチ4つを配線

スイッチを押すとD2~D4が0Vに落ちます。

基盤にはんだ付け

Arduino側プログラム

スイッチの0V落ちを検知して、パソコンにデータを送るプログラムを作成します。

const int pin1 = 2;
const int pin2 = 3;
const int pin3 = 4;
const int pin4 = 5;

void setup() {
  Serial.begin(9600);
  pinMode(pin1,INPUT_PULLUP);
  pinMode(pin2,INPUT_PULLUP);
  pinMode(pin3,INPUT_PULLUP);
  pinMode(pin4,INPUT_PULLUP);
}

void loop() {
  int x1,x2,x3,x4;
  x1 = digitalRead(pin1);
  x2 = digitalRead(pin2);
  x3 = digitalRead(pin3);
  x4 = digitalRead(pin4);
  
  if(x1==0) Serial.println(1);
  if(x2==0) Serial.println(2);
  if(x3==0) Serial.println(3);
  if(x4==0) Serial.println(4);
  if(x1*x2*x3*x4!=0) Serial.println(0);
  
  delay(10);
}

スイッチ1~4が押されていれば(電圧が0Vならば)その数字を、すべて押されていなければ(x1~x4がすべて5Vならば)0を送信します。
スイッチが押されていない時(端子に何も接続されていない時)の電圧は、通常は不安定にならないように5Vまたは0Vに接続しなければいけませんがArduinoでは内部的にプルアップまたはプルダウンを選べるようです。

pinMode(pin1,INPUT_PULLUP);

このプログラムだとボタンを押している間ものすごい速さでデータが送信され続けます。一見無駄なようですが、データを受ける側のタイミングが悪いと上手に受け取れなかったりするので、受ける側で対処することにします。

パソコン側Pythonプログラム

最後に、Arduinoからデータを受け取って文字を打ち込むプログラムを作ります。シリアル通信が簡単に書けるPythonを使っていきます。

#coding utf-8
#シリアル通信で受け取ったデータによって
#定型文をエディタにペーストする
import serial
import time
import pyautogui as pa
import pyperclip

def put_text(t):
	tmp = pyperclip.paste()	#今のクリップボードのデータを一時保存しておく
	pyperclip.copy(t)	#テキストをクリップボードに読み込む
	pa.hotkey("ctrl","v")	#”Ctrl+V”を入力
	pyperclip.copy(tmp)	#保存しておいたデータをクリップボードに戻す
	return

def main():
	#通信ポートおよびボーレートの設定
	ser = serial.Serial('COM4',9600)

	while True:
		#データを一行受信
		x = ser.readline()

		#受け取ったデータによって処理を実行
		if (x==b'1\r\n'):
			put_text("お疲れ様です")
		if (x==b'2\r\n'):
			put_text("ありがとうございます")

		#0が送られてくるまで受信のみ
		while (x!=b'0\r\n'):
			x = ser.readline()
	ser.close()

if __name__=="__main__":
	main()

通信ポートの設定の部分はArduinoが接続しているポートに合わせて変えて下さい。(windowsなら「COM◯」、Linuxなら「/dev/ttyUSB0」等)。
pyautoguiやpyperclipなどのライブラリも適宜pipで入れて下さい。僕はpyautoguiがインストール出来ずに割と悩みましたがPython3.5を使うことで解決しました。この辺の原因は謎です。

pip install pyserial
pip install pyautogui
pip install pyperclip

 

エラー処理も何もしていないプログラムなので、参考程度に使用して下さい。

テストしてみる

ArduinoKeyboard.pyを実行しておきます。その状態でスイッチを押すと…

ちゃんと打ててる!

出来ました!

2017/12/26追記:

別の環境で動かそうとしてみたところ、serialパッケージが無いと言われる。pipでインストールしたけど…

pip install serial

エラーが…
serialじゃなくてpyserialらしい

pip uninstall serial
pip install pyserial

これで動いた

 

 

しりとリツイート

しりとリツイートって何?

数多のツイートから無作為にピックアップしてはじめの文字と終わりの文字でしりとりをするツイッターbotです。

現在の機能

  • 毎日午後10時に鍵なしツイートの中からピックアップ
  • ある程度探してしりとりが繋がらなかったらその回は何もしない
  • 「ば」→「は」のような感じで、割とゆるめなルール
  • 様々な文字種に対応(ひらがな、カタカナ、漢字)
  • 末尾のURL、「!」や「?」は無視
  • 名前やプロフィールに「bot」「Bot」「BOT」が入ってる場合は除外

コード紹介

twitterの認証とかはpythonがめちゃ便利なので、pythonで全部書いてしまいました。色々と関数化したら長くなってしまいましたが最後のmain関数が本体です。continueすると以降の処理は飛ばしてfor文の最初まで戻るらしいです。使いようによっては便利ですね。

#coding:utf-8

from requests_oauthlib import OAuth1Session
import json, urllib2
from datetime import datetime
import os
import sys
import time
import re
import TwitterKEYS #twitterのキーを保存しておくファイル
from subprocess import Popen, PIPE

#カタカナ→ひらがなに変換する関数
def henkan(text):
	hiragana=[unichr(i) for i in range(12353,12436)]
	katakana=[unichr(i) for i in range(12449,12532)]
	kana=""
	
	if u'ア' <= text <= u'ワ':
		for text in list(text):
			for i in range(83):
				if text == katakana[i]:
					kana+=hiragana[i]
		return kana
	else:
		return text

#小文字、濁音を変換する関数
def change(text):
	if len(text) == 1:
		n = ord(text)
		#ぁ〜ぉ→あ〜お
		if 0x3041 <= n <= 0x304A and n % 2 == 1:
			text = unichr( n + 1 )
		#が〜ぢ→か〜ち
		elif 0x304C <= n <= 0x3062 and n % 2 == 0:
			text = unichr( n - 1 )
		#っ、づ→つ
		elif 0x3063<= n <= 0x3065:
			text = unichr( 0x3064)
		#で〜ど→て〜と
		elif 0x3066 <= n <= 0x3069 and n % 2 == 1:
			text = unichr( n - 1 )
		#ば、ぱ〜ぼ、ぽ→は〜ほ
		elif 0x306F <= n <= 0x307D and n % 3 != 0:
			text = unichr( n - (n%3) )
		#ゃ、ゅ、ょ→や、ゆ、よ
		elif 0x3083 <= n <= 0x3088 and n % 2 == 1:
			text = unichr( n + 1 )
	else:
		print "change error."
	return text

#漢字の読み仮名をとってくる関数→「mecab」で実現
def get_yomigana(text):
	p1 = Popen(['echo', text], stdout = PIPE)
	p2 = Popen(['mecab', '-Oyomi'], stdin = p1.stdout, stdout = PIPE)
	p1.stdout.close()
	output = p2.communicate()[0]
	return re.sub(r'\n', '', output)

#末尾に英数字があったら削除する関数
def skip(text):
	skip_list = u"/: .abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#」…。、??!!〜))ー"
	while skip_list.find(text[-1:])>=0:
		text = text[:-1]
	return text

#テキストをツイートする関数
def tweet(text):
	#ツイート投稿URL
	URL_tweet = 'https://api.twitter.com/1.1/statuses/update.json'
	
	twitter = OAuth1Session(TwitterKEYS.CK,TwitterKEYS.CS,TwitterKEYS.AT,TwitterKEYS.AS)
	
	params = {"status": text}
	req = twitter.post(URL_tweet,params = params)

#テキストの最初の文字をひらがなで返す
def check_first_letter(text):
	#最初の文字をひらがな、カタカナ、先頭4文字の読み仮名の順にチェック
	#最初の文字をひらがなで出す
	first_letter = text[0:1]
	if u'あ' <= first_letter <= u'わ':
		print first_letter.encode('utf-8')
	else:
		first_letter = henkan(first_letter)
		if u'あ' <= first_letter <= u'わ':
			print first_letter.encode('utf-8')
		else:
			yomi = get_yomigana(text[0:4]).decode('utf-8')
			first_letter = henkan(yomi[0:1])
			if u'あ' <= first_letter<= u'わ':
				print first_letter.encode('utf-8')
			else:
				print "cannot define first letter."
				first_letter = u'ん'
	return first_letter

#テキストの最後の文字をひらがなで返す
def check_last_letter(text):
	last_letter = text[-1:]
	last_letter = henkan(last_letter)
	last_letter = change(last_letter)
	return last_letter

#ここからメイン関数
def main():
	
	#はじめの文字をファイルから読み出す
	logfile_path = "/home/ppdr/chain.txt"
	f = open(logfile_path,"r")
	start = f.read().decode('utf-8')[0:1]
	f.close()
	
	print start.encode('utf-8')
	
	#はじめの文字で検索
	req_count = 100	#取得するツイート数
	params = {"q" : start + ' -rt','lang':'ja','count':req_count}	#検索時のパラメータ?
	twitter = OAuth1Session(TwitterKEYS.CK,TwitterKEYS.CS,TwitterKEYS.AT,TwitterKEYS.AS)	#twitteキーを使って認証
	req = twitter.get("https://api.twitter.com/1.1/search/tweets.json",params = params)	#検索結果をjsonで取得
	
	if req.status_code == 200:
		js = json.loads(req.text)
		for i in range(0, req_count):
			txt = js['statuses'][i]['text']
			
			#最初の文字が一致しない場合、除外
			if not(check_first_letter(txt) == start):
				continue
			
			#末尾の英数字を削除
			txt = skip(txt)
			print "skipped:    "+txt.encode('utf-8')
			
			#最後の文字がつぎにつながらない場合、除外
			last_letter = check_last_letter(txt)
			if not(u'あ' <= last_letter <= u'わ'):
				continue
			
			#最後の文字が前回と同じ場合、除外
			if (last_letter == start):
				continue
			
			#ユーザーネーム、プロフィール、スクリーンネームのいずれかに「bot」が入っていたら除外
			bot_check_text = js['statuses'][i]['user']['name']+js['statuses'][i]['user']['description']+js['statuses'][i]['user']['screen_name']
			if ('bot' in bot_check_text) or ('Bot' in bot_check_text) or ('BOT' in bot_check_text):
				continue
			
			#条件確定
			#リツイート
			txt_id = js['statuses'][i]['id']
			req = twitter.post('https://api.twitter.com/1.1/statuses/retweet/'+str(txt_id)+'.json')
			
			if req.status_code == 200:
				print "post success!"
				
				#リツイートが成功したらつぎの文字をファイルに書き出す
				f = open(logfile_path,"w")
				f.write(last_letter.encode('utf-8'))
				f.close()
				
				#つぎの文字をツイート
				txt = u"リツイートしりとり!\n\nつぎの文字は・・・\n『" + txt[-1:] + u"』!!"
				time.sleep(1)
				tweet(txt)
				break
			else:
				print "post filure"
				break
	
	else:
		print "error!"
		print req.status_code
	
if __name__ == "__main__":
	main()

トイレの自動洗浄を自作で後付けしてみた②(プログラム編)

前回自宅トイレのレバーをひねる動きを自動化すべく、貯水タンクの栓を引っ張る機構とセンサの設置を行いました。今回は用が済んだら流れてくれるようにArduinoにプログラムを書き込みたいと思います。

基本動作

実現したい動作は大体こういう感じです

  • 便器から離れたら水を流す
  • 立った時も座った時も反応する
  • 間違えて流さない
  • 離れたらすぐに反応してほしい

この条件を満たすようにプログラムを考えます。とりあえずの構想は、

  1. センサで測った距離が80cm以上の時は何もしない
  2. 距離が80cm以下で6秒間センサ値が変化しなかったら、座っているまたは立っていると判断し流しスタンバイ状態
  3. 距離が大きく離れたらモータを動かして流す
  4. 5秒くらいしたら止める
  5. 繰り返し

こんな感じで行こうと思います。

実際のコード

実際にAruduinoに書いた制御プログラムです。

#include <Servo.h>

const int servoPin = 3;    // サーボの接続ピン
const int ledPin = 8;     //表示用LEDの接続ピン
const int sensorPin = 0;  //距離センサの接続ピン
Servo myservo;             // サーボオブジェクトを生成
int pos = 0;               // サーボのポジション(変数)

void setup()
{
  myservo.attach(servoPin);  // サーボ変数をピンに割り当て
  pinMode(ledPin, OUTPUT);
}

//map関数をdoublueで使えるように
double dmap(double value, double fromMin, double fromMax, double toMin, double toMax) {
  double a = (toMax - toMin) / (fromMax - fromMin);
  double b = toMax - a * fromMax;
  return a * value + b;
}

//センサーで距離を計測
double measure_dist() {
  double data = analogRead(sensorPin);  //センサーからの値(0~1023の整数)を取得
  double v = dmap(data, 0, 1023, 0.0, 5.0);   //0~1023→0V~5Vに変換
  double dist_inv = dmap(v, 0.4, 3.1, 0.015, 0.15);   //0V~5V→距離の逆数に変換
  return 1.0 / dist_inv;    //距離を返す
}

void loop()
{
  double dist = 0;
  double dist_old = 0;
  int i = 0;
  while (i < 5) {
    dist = measure_dist();
    
    //距離が変化していなかったらカウントを増やす
    //変化していたらカウントを0にする
    if (abs(dist - dist_old) < 5 and dist < 40) {
      i++;
    } else {
      i = 0;
    }

    dist_old = dist;
    delay(1000);
  }

  digitalWrite(ledPin, 1);

  //最大10分経ったらリセットする
  int sec = 0;
  while (sec < 60 * 10) {
    dist = measure_dist();

    //距離が離れたら水を流す
    if ( dist - dist_old > 10) {
      digitalWrite(ledPin, 0);
      myservo.write(180);   //180[deg]にする
      delay(6000);    //6秒待つ
      myservo.write(0);   //0[deg]にする
      break;
    }
    
    delay(1000);
    sec++;
  }
  
}

 

トイレの自動洗浄を自作で後付けしてみた①(ハードウェア編)

動機

サービスエリアとかのトイレで、立ち上がるとセンサーで感知して自動で流してくれるやつが割と便利なので自宅にも欲しいと思ったのが動機。駆動部品にはサーボモータが使えそう。人の動き検知には超音波距離センサが使えそう。
あと安かったから3つも買ってしまったArduino nanoが使い道がなくて持て余してたからです。

基本構想

最初はサーボモータでレバー捻ればいいんでしょと思ったけれど、レバーに直接モータをつなぐだけではトルク不足で動きませんでしたorz
家族も使うものなので、ここは元の動きをそのまま残しつつ機能を追加することを目標に設計が必要そうです。

取り敢えず貯水タンクを開けてみる↓

ゴチャゴチャしている…(・_・;)
ちょっと考えて機構はこんな感じと判明しました。

機構の簡単な図解

貯水タンクを横から見た図です。レバーを手前or奥にひねるとチェーンで繋がった小のふたor大のふたが開くようになっています。

他に水位が一定に戻るまで水を貯めるためのウキが付いていますが、今回は関係ないので省きます。

というわけで流すにはフタをモータで引っ張れば良さそうです。元の動きを阻害しない点、取り付けやすい点を考えて、こんな感じにしました。

モータ取り付け後の図↓

使用する部品

Arduinoは最安のnanoで大丈夫です。モータはSG90が有名ですがちょっとパワーが足りないかも…。ギリギリ行けますが高トルク版のSG92Rだと安心です。距離センサはシャープ製の赤外線センサを使いました。(ちょっと高いので距離が測れる他のセンサで代用するのもあり)

モータの取付け

モータはタンクに固定しなければならないのですが、横側に何故かいい感じの穴が空いていたので(サイズもぴったり!?)そこにはめました。穴がない方はガムテープで固定するなり接着剤とかで固定するなりすればいいと思います。要は紐が引っ張れればどこに付けてもいいです。僕が使ったサーボモータSG90では引っ張るトルクが結構ギリギリで、回転半径を決めるのに苦労しました(といっても糸を通す穴を変えるだけですが)。あまり小さくしてしまうと引っ張る長さが足りなくて流れなかったり、トルクが足りないとモータが動かなかったりします。あとArduino nanoだと出力電流が足りなくて、別で電源を取る形になっています。Arduino UNOで試したら力が倍くらいになりました。

0[deg]→180[deg]まで目一杯回してフタの開閉を行うことが出来ました!

センサの取付け

座ったり立ったりするのを検知するセンサはこの赤外線距離センサを使います。

シャープ測距モジュール GP2Y0A21YK
http://akizukidenshi.com/catalog/g/gI-02551/

このセンサは数cm〜80cmの距離に応じて電圧を出力してくれるもので、5V動作なのでArduinoやラズパイにそのまま繋ぐだけの有能センサ(ただし、精度はお察し)です。こいつを便器の下辺りに取り付けておきます。

センサの横に赤色LEDをつけてます。↓

回路づくり

モータとセンサとArduinoNanoを繋げるために基盤を作りました。
モータとセンサの電源は、Arduinoの5V出力から取っている記事が多いですが、それだとモータを回すのに十分な電力が取れませんでしたorz。

代わりにUSBから5V電源を取ることにしました。(ArduinoはUSBを電源にする代わりに安定した5V電源をVin端子とGND端子に繋ぐことで動かすことが出来るみたいです。)

USB type-Bが刺せるアダプタ。amazonで10個で150円くらい!↓

回路図を書くとこんな感じです。(まだ回路図を書くのに慣れていないので、見づらいですがご容赦を)

回路図↓

実際の基盤。10円玉4個くらいのサイズ↓

ハードウェア構成は取り敢えずこんな感じです。次回は制御プログラムの方を作っていきます。

ダーツでどこを狙ったら良いの?期待値を計算した

ダーツで何処を狙うのが得か

こんにちは。ダーツをプレイしたことある方が一度は抱くであろう疑問が、「ダーツってブルか20のトリプルどっちを狙ったほうが良いの?」です。みなさんはどこを狙っているでしょうか?今回は狙った点から正規分布に従う確率で外れたところに当たるとして、ダーツボードの各点上の期待値を計算してみました。
式で表すのは大変なので、とりあえず値だけ知れれば良いやの精神でプログラムを書いて計算してみました。

グラフで表すと、こんな感じになりました。

(色が濃いほど期待値が高い)

ブルが最も期待値が高く、次がT20でその次でT19という結果になりました。やはりブルを狙うのが最も効率が良いようです。なんとなくわかる気がします。
これで長年の問題に決着がつきそうです。

ちなみに、さらに上手い人(分散が小さい人)だとこうなります。

上の図と色の濃さが違うのは多分プログラム上のミスです。ここらへんに私の未熟さが滲み出ていますね。比較で見て下さい。

上手い人でもブルを狙ったほうが良いという結果になりました。これもなんとなく体感でそんな感じがしますね。
ただし、さらに上手で狙ったところに必ず当てられる人は別です。

逆に下手な人(分散が大きい人)はこうなります。

こちらはブルよりも19のトリプル付近を狙ったほうがやや良いみたいです。
ちょっと慣れてきて狙えるようになったなら、フィニッシュの練習も兼ねてT19を狙ってみてはいかがでしょうか。

どうやって計算したか?

上の画像はPythonで計算して出力しました。本当は数式で表せれば最高なのですが、当たった位置の得点が離散的なので、積分の形で書くのがものすごく煩わしくて断念しました。
計算方法はおよそ次のような感じです。

①各点の得点を求める
②狙った位置(x,y)について、その点の周辺に当たる確率をガウス分布を用いて計算する
③当たる確率と、そこに当たった時の点数の掛け算を合計して期待値を出す。

これをボード上のすべての点で計算しているだけです。期待値の計算方法としては中学レベルの期待値の計算をしているに過ぎません。狙った点の周りに当たる確率については確率密度関数の知識をめっちゃ簡単化して使っているだけです。

詳しい計算の方法は、あとで時間があったら書こうと思います。

人が通ったらシャッターを切る防犯カメラ

こんにちは。前回カメラストリーミングをやったのですが、ぶっちゃけずっと動画データをインターネットに流し続けるのもなんか無駄な感じがします。そこで今回は以前に作ったカメラストリーミングを応用して人が通ったときだけシャッターを切るカメラを作ってみたいと思います。

目的

人が通ったときだけシャッターを切るカメラを作ります。
防犯や入退室チェック、ペットや野生動物の観察にも使えて割と応用がききます。
切るタイミングも変えれば一定間隔で撮影してタイムラプス動画みたいなのも作れます。

準備するもの

fswebcamのインストール

カメラキャプチャにはfswebcamというソフトを使いましょう。いつもどおり次のコマンドでインストールします。

sudo apt-get install fswebcam

webカメラをラズパイに接続したら、撮影できるかどうかテストしてみましょう。ターミナルに次のコマンドを打つだけです。

fswebcam image.jpg
ppdr@raspberrypi:~ $ fswebcam image.jpg
--- Opening /dev/video0...
Trying source module v4l2...
/dev/video0 opened.
No input was specified, using the first.
Adjusting resolution from 384x288 to 352x288.
--- Capturing frame...
Captured frame in 0.00 seconds.
--- Processing captured image...
Writing JPEG image to 'image.jpg'.

勝手にカメラが認識されてとっても簡単です。image.jpgというファイルに画像が出力されていると思います。

ちなみに、よく使いそうなパラメータはこんな感じです。

-qメッセージを表示せずに実行
-l [seconds][seconds]秒ごとに撮影。タイムラプス撮影できそう。
-d [device]デバイス名を指定
-D [number][number]秒待ったあと撮影。起動直後だとうまく撮影できないときに。
-r [size]動画サイズを指定
–greyscale白黒で撮影
–no-banner画像に表示される時間などの文字を無くす
–overlaypngファイルを画像に重ねる。一部だけ隠したい時とかに便利??
–jpeg ,–pngファイル名を指定してjpg,pngで保存する。省略することもできます。

人感センサ

最も有名で作例も多いこちらの人感センサを使います。パーツ屋さんで500円くらいで買いました。

モジュールになっていて便利

このセンサ、両端の端子を5VとGNDにつなぐだけで真ん中の端子から出力を出してくれるスグレモノです。人などの熱源の動きを感知すると出力は3Vになり、動きがなくなると0Vになります。
ブレッドボードに次のように配線しました。

赤をラズパイの5V、黒をGND、オレンジを17番ピン、青を24番ピンに接続しています。ちなみに赤色LEDは確認用です。
さて、センサをテストしてみましょう。プログラミングは僕の愛用のpythonを使います。(コンパイルしなくても動く便利)

 

SENSOR_pin = 17
LED_pin = 24

import wiringpi as wp
import time

wp.wiringPiSetupGpio()
wp.pinMode(SENSOR_pin,0)
wp.pinMode(LED_pin,1)

state = 0
prev_state = 0
wp.digitalWrite(LED_pin,0)

try:
  while 1:
    state = wp.digitalRead(SENSOR_pin)

    if (state != prev_state):
      if state > prev_state:
        wp.digitalWrite(LED_pin,1)
      else:
        wp.digitalWrite(LED_pin,0)

    prev_state = state

    time.sleep(0.1)

except KeyboardInterrupt:
  wp.digitalWrite(LED_pin,0)
  print "KeyboardInterrupt"

今の出力の値(state)を読み込み、前回の値から立ち上がっていればLEDをオンに、立ち下がっていればオフにします。

ラズパイで防犯カメラを作る!(PHPで外部から起動)

前回の記事で自宅カメラの動画をストリーミング再生できるようになりました。しかし、前回のままだとmjpg-streamerをずっと起動しておく必要があるので、セキュリティ的にも不安です。外出先から見たいときだけ起動して終わったら消せるようになると便利なので、今回はそれに挑戦します。

目的

  • WEBページ上に自宅カメラの動画を埋め込む
  • 読み込んだときに起動・ボタンで終了できるようにする(phpを使用)

準備

前回の記事のセットアップでmjpg-streamerが起動できるようになっていることが前提です。
WEBページでPHPを動かすので、サーバの設定とポート開放をしておきましょう。

方法

こんな感じでcamera.phpをサーバのCGIが動くディレクトリに作成します。

<html>
<head>
<title>Camera Streaming</title>
</head>
<body>
camera streaming.<br>
<?php 
 $output = shell_exec('/home/ppdr/mjpg-streamer/mjpg_streamer -b -i "/home/ppdr/mjpg-streamer/input_uvc.so -f 10 -r 320x240 -d /dev/video0 -y -n" -o "/home/ppdr/mjpg-streamer/output_http.so -w /usr/local/www -p 5555 -c ppdr:password"');
 ?>
 
 <iframe width="320" height="240" src="http://192.168.0.24:5555/stream_simple.html" scrolling="no" frameborder="0"></iframe>

<?php 
 if(isset($_POST['stop'])){
 $output = shell_exec('kill -9 `pidof mjpg_streamer`');
 }
 ?>
 <form action="cam.php" method="post">
 <input type="submit" name="stop" value="stop" style="width:50%; height:10%" />
 </form>
</body>
</html>

一つ目の<?phpで始まる部分でシェルスクリプトを動かし、前回と同じコマンドでカメラを起動しています。

パラメータに-bでバックグラウンドで実行、-pでポート番号を指定、-cでパスワードを設定しています。
サーバから起動するなら実行ユーザはwww-data等になります。そのままでは権限が無いのでカメラが起動できませんので、/dev/vido0の「その他のユーザ」の権限を追加しておきましょう。

sudo chmod o+rw /dev/video0

<iframe>は他のページを埋め込むタグです。アドレスとポート番号を指定し、stream_simple.htmlを指定します。
これはmjpg-streamerの動画だけのページです。

2つめの<?phpでボタンを押したときのコマンドを指定しています。mjpg-streamerの公式ドキュメントにあるコマンドそのままです。

こんな感じで見れました。

ラズパイとWEBカメラで動画ストリーム配信ができる!

こんにちは。最近パソコン周りを整理していたらWEBカメラを見つけました。前からラズパイを使って防犯カメラ的なものを作れたらいいなーと思ってましたので、今回はWEBカメラでストリーミングしてみようと思います。

目的

・WEBカメラをラズパイに接続する
・動画をストリーミング配信する

今回使うカメラはこちらです。たまたま家にあっただけで、USB接続のカメラならもっと安いのでもいいと思います。

ELECOM PCカメラ 200万画素 筒型 UCAM-DLA200Hシリーズ

MJPG-streamerのインストール

Linuxでストリーミング再生できるソフトである「MJPG-streamer」を使います。このソフトはapt-getでインストールできないらしいので、「Subversion」でインストールします。まず必要なソフトをインストールしておきます。

sudo apt-get update
sudo apt-get install subversion libjpeg-dev imagemagick

続いて「MJPG-streamer」を取得してインストールします。

svn co https://svn.code.sf.net/p/mjpg-streamer/code/mjpg-streamer mjpg-streamer
cd mjpg-streamer
make
sudo make install

ストリーミング配信してみる

まずはWEBカメラがラズパイにきちんと認識されているか確認します。

ppdr@raspberrypi:~ $ lsusb
Bus 001 Device 006: ID 14cd:125c Super Top SD card reader
Bus 001 Device 007: ID 056e:700a Elecom Co., Ltd 
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter
Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp. 
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

Elecom…みたいな感じのカメラの名前が出ていれば認識されています。デバイスとしての名前も確認しておきましょう。

ppdr@raspberrypi:~ $ ls /dev

...
video0
...

私の名前はvideo0という名前で認識されました。どれがカメラかわからない方は抜いたり指したりして追加された名前を確認してみましょう。

次のコマンドでMJPG-streamerを起動しましょう。

cd ~/mjpg-streamer
sudo ./mjpg_streamer -i "./input_uvc.so -f 10 -r 320x240 -d /dev/video0 -y -n" -o "./output_http.so -w /usr/local/www -p 8080 -c ppdr:password"

-fはフレームレートで、1秒に何回動画を更新するかです。少ないほど通信量が小さくなります。
-rは動画のサイズです。あまり大きくしないほうが止まらずに見れます。
-pはストリーミングするポート番号です。webページやリモートデスクトップで使用している80や443など以外の番号ならなんでもいいでしょう。ポートが使用中だと次のようなエラーになります。

bind: Address already in use

WEB上に公開して誰でも見れてしまうとセキュリティ的に(プライバシー的に?)良くないので-cでパスワードをかけます。[ユーザ名]:[パスワード]で指定します。

エラーがでなければストリーミング配信が始まります。
ネットワークにつながっている他のPCのブラウザのアドレス欄に、[ラズパイのIPアドレス]:[ポート番号]を入力してエンターを押します。(例)192.168.0.7:8080

こんな感じのページに移動して、カメラの映像を見ることが出来ました!

使い道① 外出先から自宅カメラの様子を確認

防犯カメラ的な使い方をするなら、外出先から見れると便利です。その場合はVPN接続がおすすめです。ラズパイに外出先からVPNする方法は長くなるので次回解説します。

使い道② 動画をWEBに公開

動画を他の人に見てもらいたい場合は、

  • DDNSサービスを利用してラズパイに接続できるようにする
  • ルーターのポートを開放する

両方とも解説しているサイトが多数あるので、ここでの説明は割愛します。VPNで自分だけ見る場合はいいですが、外部に公開する場合はパスワードをきっちり設定しましょう。

GRAN BOARD dashを買って3ヶ月たった感想&レビュー

動機

こんにちは。今回は趣味のダーツの話です。始めたての頃はゲームセンターなどで貸しダーツで練習いていたのですが、やっぱりもっと練習したいなあと思いちょっと前にGRAN BOARD dashを買いました。
購入の前にネットでレビューを確認してみたのですが、GRAN BOARDシリーズの中でも格安のdashについてはあまり情報が無かったので、今回レビューしてみたいと思います。

買ったのはこいつです。

こんな感じに壁に釘を打って直接設置しています。

タブレットやスマホとリンクして当たった点数を自動で判定してくれるのが嬉しい機能です。普通のダーツボードも使ったことはありますが、やっぱり自分で計算するのは面倒というか気分が出ないので・・・。他にはこの値段でオンライン対戦に対応している(GRAN BOARDシリーズはすべて対応)のも魅力ですね。

注目ポイント① 音はうるさいの?

ダーツボードのレビューを見たときに一番多かったのが、「安物は音がうるさいので、高くても静音機能付きを買ったほうが絶対に良い」です。これ、本当です。今からでも買い換えようかなと思うこともあります笑。
実際どのくらいの音がするかというと、よく「ティッシュの箱を落としたくらい」といいますが、そんな軽い音どころではないです。バシィッって感じでプラスチックの塊どうしがぶつかる音がします。例えるのは難しいですが、ピンポン玉を思い切り壁にぶつけるとか、リモコンを床に落とすくらいの音がします。

私は夜に投げることも多いのですが、隣と下の部屋にいる家族に聞いてみたところ、「音は聞こえるが気にならない程度」とのこと。
まあ当たった時の音はどちらかというと高周波が多いみたいで、壁に反射して隣の部屋までは伝わりにくいんでしょうか。(マンションの隣の部屋とかで足音は聞こえるのに話し声は聞こえづらいのと似ていると思います。)

注目ポイント② 性能はどうなの?

バシッと刺さって気持ちいい

レビューを見ると安いダーツボードだと刺さり具合が悪いとかよく抜けるという話も見かけますが、そこんところはどうでしょうか?
他のダーツボードとの比較はできないのですが、ほぼちゃんと刺さってくれます。3ゲームに一回くらい刺さらないときもあるかな?くらいです。結構弱い力で投げても刺さってくれるので、床を傷つける心配もありません。

注目ポイント③ アプリってどんなの?

これは買う時は全く気にしてなかったのですが、アプリはかなり良くできています。それこそお店で投げてるのとほぼ変わらないくらいかそれ以上で、01ゲーム、CRICKETゲーム各種はもちろんのこと、練習ゲームやパーティゲーム、オンライン対戦も無料ででき、さらにブルに当たった回数やスコアの記録をグラフ化してくれたりと、とっっっっても楽しいです。

デザインもかっこいい

さらに、BULLに当たった時やアワードの演出も多数の種類の中から選べてしっかり鳴ってくれます。ほんとにお店で投げている気分になれて、非常に気持ちがいいです。点数自動計算の機能も完璧で、誤反応もありません。最高だと思います。

アプリが気になる人は、製品を持っていなくてもストアから無料でダウンロードできるので、使ってみることをおすすめします。もちろんAndroid・iOS両方対応です。

その他の良い所・悪い所

使ってみて色々良い所と悪い所が色々分かってきました。

【良い点】

  • オンラインが楽しい
  • Bluetooth接続だけど電池持ちが良い(3ヶ月くらい)
  • 比較的安い

【悪い点】

  • 音が気になる
  • 静音化できない
  • 本体のボタンが安っぽい
  • スマホの画面だとちょっと小さい

やっぱり音は一番気になるポイントですね。エアコンパテを詰めてみたり裏にスポンジを貼り付けたりして静音化も試してみましたが、どれも対して効果がありませんでした。(エアコンパテはがんばって詰めてみたけど殆ど効果がなく、投げるたびにパテがチップに付くので後悔しました。剥がすのも穴に詰まってかなり時間を取られてしまいました。これからやる予定の人はご注意を!)

本体のボタンはプラスチックっぽくて、押すとカコッという音がします。ちょっと安っぽいですが、まあ気にするほどのことでも無いといえば無いです。

私はタブレットの画面で見ているので快適そのものですが、スマホで見るとちょっと小さくて見づらいかな?という感想です。

まとめ&こんな人にオススメ!

ということで、オススメできるのは次のような人です

  • 一軒家、または音を出してもいい家に住んでいる
  • お店でダーツをするのが楽しい
  • 人と対戦するのが好き
  • 沢山練習したい
  • ダーツボードを買うのは初めてだ

逆に、次のような人にはオススメできません。

  • マンションに住んでいる
  • 他人を気にせず投げたい
  • 多少お金を出してもいい

自宅ダーツ、思った以上に楽しいです。ダーツ初めてしばらくたったけど楽しいなぁという人は是非検討してみて下さい!

静音性を求める人へ

可能な限り音が少ないやつがいい…って方はこちらのダーツボードもおすすめです。

このダーツボード、知り合いの家で使ったことがあるのですが、静音性が半端ないです!「トッ」っていう感じのゴムに当たったような音がします。マンションで深夜に投げまくっても気づかないレベル。

ただ、このボードはセンサーやアプリはなく、当たったところを自分で確認して計算しなければいけません。
やっぱりアプリが無いと楽しさは半減するので、練習だけしたい人用かもしれませんね。