今年も年末ダーツHack!-センサを使ったダーツの位置判定手法
夛田 一貴
この記事は RECRUIT MARKETING PARTNERS Advent Calendar 2018 の投稿記事です。
こんにちは、webエンジニアの夛田です。昨年のAdvent Calendar 2017ではダーツボードをハックするネタをご紹介しました。
あれからちょうど一年、自分がダーツ離れをしている間にダーツ業界では何やら新しい機種が誕生したようです。その新機種では、ダーツボードに刺さったダーツの位置をビット単位で検出することができるとのこと...! ダーツハック系エンジニアの自分としてこれを再現しないわけにはいきません。今回の記事ではこのトンデモ機能の再実装をしていきたいと思います。
ソフトダーツにおけるビット単位検出とは
そもそもこの機能は何が新しく、何がスゴいのでしょうか。昨年の記事でも取り上げましたが、従来のダーツボードというものは、ダーツが刺さったポイントをメンブレンスイッチの仕組みを使うことでセグメント単位で判別します。そのため、セグメント内に刺さったダーツが本来狙っていた部分からどのくらい離れてしまっているのかを機械的に判別することができませんでした1)もちろん視覚的に判別することは可能です。。
しかし、このビット単位検出機能によりその常識が覆されました。ソフトダーツボードのセグメントにはそれぞれ複数の小さな穴が空いています。ここにダーツが刺さるとによってセグメントが押し込まれ、そのセグメントが何であるかを判別していたのが従来のものである一方、新機種ではその小さな穴自体を特定することができるのです!
これにより自分の癖やミスを細かなデータとして蓄積することができ、それを振り返ることにより上達への大きな糧にすることができるようになりました2)例えば「狙ったセグメントには入っているが、右に寄りすぎているからもう少し中心に投げれるようにしよう」など。ダーツゲームの本質は狙ったところに正確に投げ入れるという非常にシンプルなものであるため、ビット単位で刺さった位置が特定できれば自分の癖を見出せるようになります。ということは、ゲームの上達に非常に大きく寄与する画期的な機能なのです!
再現手法の考察
さて、この画期的な機能ですが、本家機種ではどのように実装されているのでしょうか……!? と書きたいところなのですが、実はこれが正直よく分かっていません3)特に情報が公開されていないぽいです……。そのため、「何となくこうしているんじゃないか」という説をいくつか考えてみました。
- メンブレンスイッチの数を増やした
- セグメントの後ろからカメラを使って撮影し、画像処理している
- マイクや振動センサ等を使った測量をしている
実は自分は未プレイなので体験したがことないのですが、噂によると中々の高精度で誤検出が殆ど無いとのことです。精度だけを考えれば1つ目のスイッチを増やす方法が一番安定しそうですね4)カメラやマイクは環境によって左右される要素が多そう。
しかし、既存のダーツボードのメンブレンスイッチ回路を増やすのは現実的ではありません。また、2つ目の方法についても、既存のダーツボードの中にはカメラを仕込む空間やボード全体を撮影する画角をとらえるための距離がないため、こちらも難しそうです。そこで今回は3つ目に挙げたマイクや振動センサを使った測量方法を試してみることにします。
マイクや振動センサを使った位置検出
ダーツボードにダーツが刺さる際にはその衝撃で音や振動が発生します。つまり、この衝撃の発生点を突き止めることができればダーツの刺さった位置を特定することができます。今回は圧電素子と呼ばれる電子部品を使って、ダーツがボードに刺さった衝撃を検出することにしました。
圧電素子とは
圧電素子は通称ピエゾ素子とも呼ばれます。この素子は力を加えられると電圧が発生し(圧電効果)、逆に電圧をかけると力(ひずみ)が発生する(逆圧電効果)という特製を持っています。一般的には振動センサや圧電スピーカとして使われていることが多いです。
見た目や使い方は非常にシンプルで、センサから出ている2本の導線をそれぞれGNDとINPUTに繋ぐだけです。まずは実験的にこの圧電素子をダーツボード上に4つ設置し、衝撃の発生をセンシングしてみました。
圧電素子は直接衝撃を加えないとなかなか値が取得できないのですが、ダーツが刺さる際にダーツボード全体が大きく揺れるため、振動が良い感じに伝わっているようです5)硬い机などで試すと全然センシングできなかったりします……。
圧電素子の値から衝撃発生点を特定する
さて、先ほどの動画にて各圧電素子から得られる値はダーツが刺さる位置によって変化することが見てとれました。これは非常に直感的なもので、ダーツが刺さる位置から素子までの距離が近ければ近いほど衝撃が大きくなり(=値が大きくなる)、遠ければ遠いほど衝撃が少なくなります(=値が小さくなる)。
こうした複数のセンサ値の差からセンシング元までの距離や位置を特定する方法に三点測量と呼ばれるものがあります。
三点測量はその名の通り三つのセンサを用いた測量方法です。センサで得られた値を円の半径と見立て、そこから得られる三つの円が交わる点がセンシング元の位置であると推定する方法です。
各センサの値(円の半径)から円の方程式をたてることができます。センサa,b,cについて以下を立式します。
$$ ({x} - {x_a})^2+({y} - {y_a})^2 = {r_a}^2 $$
$$ ({x} - {x_b})^2+({y} - {y_b})^2 = {r_b}^2 $$
$$ ({x} - {x_c})^2+({y} - {y_c})^2 = {r_c}^2 $$
\({r_a}\), \({r_b}\), \({r_c}\)はそれぞれセンサから取れる値です。(今回は衝撃が強いほどダーツの位置がセンサに近いため、得られる値を最大値を分母とする逆数にします。e.g. val=100, max=200の場合, 200/100をrとする
)また、各センサの位置\(({x_a},{y_a})\),\(({x_b},{y_b})\),\(({x_c},{y_c})\)は既知のものなので、左上は(0,0), 左下は(0,1)、右上は(1,0)などと値を決めておきます。
これらの連立方程式を展開すると、以下の2式が得られます。
$$ {r_b}^2-{r_c}^2 = 2x({x_c}-{x_b}) + {x_b}^2-{x_c}^2+2y({y_c}-{y_b})+{y_b}^2-{y_c}^2 $$
$$ {r_b}^2-{r_a}^2 = 2x({x_a}-{x_b}) + {x_b}^2-{x_a}^2+2y({y_a}-{y_b})+{y_b}^2-{y_a}^2 $$
さらに式を整理し、
$$ x({x_c}-{x_b})+y({y_c}-{y_b})=\frac{ ({r_b}^2-{r_c}^2)- ({x_b}^2-{x_c}^2)-({y_b}^2-{y_c}^2)}{2}={V_a} $$
$$ x({x_a}-{x_b})+y({y_a}-{y_b})=\frac{ ({r_b}^2-{r_a}^2)- ({x_b}^2-{x_a}^2)-({y_b}^2-{y_a}^2)}{2}={V_b} $$
そして\((x, y)\)、つまり求めたい点の座標について解くと、
$$ y = \frac{{V_b}({x_c}-{x_b})-{V_a}({x_a}-{x_b})}{({y_a}-{y_b})({x_c}-{x_b})-({y_c}-{y_b})({x_a}-{x_b})} $$
$$ x = \frac{{V_a}-y({y_c}-{y_b})}{({x_c}-{x_b})} $$
以上により、求めたい点\((x,y)\)、つまりダーツが刺さったポイントが算出されます。こうした3点測量は無線やbeaconなどのRSSIを用いた端末位置情報検出等に使われています。
実装
前節にて複数の圧電素子を用いた衝撃発生点の特定方法がわかりました。あとはプログラムに落とし込むだけなので非常に簡単です。
回路も非常にシンプルで、左図の通り圧電素子に抵抗とツェナーダイオードを並列に繋ぎ、inputに流し込むだけです。回路を4つ用意し、ダーツボードの4隅に取り付けます。
プログラム
例によってArduinoでプロトタイピングしてみます。
まずは値の取得。前述の通り衝撃が強いほど近くの点とみなすことができるため、センサからの値の逆数をとります。また、その際にはあらかじめ四隅に最も近い点にダーツが刺さった際の各センサの最大値をキャリブレーションして定義しておきます。最後にこれらの値を配列に格納しておきます。
float rA = MAX_A / analogRead(PINA);
float rB = MAX_B / analogRead(PINB);
float rC = MAX_C / analogRead(PINC);
float rD = MAX_D / analogRead(PIND);
float radii[] = { rA, rB, rC, rD };
また、事前に各センサの位置を定義しておきましょう。
float xx[] = { 0, 0, 1, 1 }; //a.x, b.x, c.x, d.x
float yy[] = { 0, 1, 1, 0 }; //a.y, b.y, c.y, d.y
次に計算です。今回は4つのセンサ(それぞれa
,b
,c
,d
とする)があるため、形成される三角形は\(\triangle{abc}\), \(\triangle{bcd}\), \(\triangle{cda}\), \(\triangle{dab}\)の4つになります。それぞれの3点について先ほどの計算を行うのが以下になります。
for (int i = 0; i < 4; i++) {
Va = ((pow(radii[(i + 1) % 4], 2) - pow(radii[(i + 2) % 4], 2)) - (pow(xx[(i + 1) % 4], 2) - pow(xx[(i + 2) % 4], 2)) - (pow(yy[(i + 1) % 4], 2) - pow(yy[(i + 2) % 4], 2))) / 2;
Vb = ((pow(radii[(i + 1) % 4], 2) - pow(radii[(i) % 4], 2)) - (pow(xx[(i + 1) % 4], 2) - pow(xx[(i) % 4], 2)) - (pow(yy[(i + 1) % 4], 2) - pow(yy[(i) % 4], 2))) / 2;
y += (Vb * (xx[(i + 2) % 4] - xx[(i + 1) % 4]) - Va * (xx[(i) % 4] - xx[(i + 1) % 4])) / ((yy[(i) % 4] - yy[(i + 1) % 4]) * (xx[(i + 2) % 4] - xx[(i + 1) % 4]) - (yy[(i + 2) % 4] - yy[(i + 1) % 4]) * (xx[(i) % 4] - xx[(i + 1) % 4]));
x += (Va - y * (yy[(i + 2) % 4] - yy[(i + 1) % 4])) / (xx[(i + 2) % 4] - xx[(i + 1) % 4]);
}
\((x, y)\)について4点分計算し累計してあるので最後にこれの平均を取ってやれば完成です。
結果
考察
動画でも微妙にセンシング位置がずれているように、誤差や未検出が頻繁に起きてしまいました……6)めっちゃうまくいってる動画使いました原因は以下のようなものが考えられます。
- 単純にセンシング強度のみを頼りにしているため、キャリブレーション時の投げる強さと実際の強さが異なる等、不整合が起きやすい
- 圧電素子の値検出がまばらな時がある
- おおまかな交点が定まらないパターンもある
これらを解消するための手法ですが、以下のようなものが考えられます。
- TDOA(Time Difference of Arrival)を用いる
- 使用するセンサの値を増幅させ、コンパレータを通す
ここで挙げたTDOAとは、GPSやこの手の座標検出によく使われている手法です。今回のようにセンサの強度(無線やGPSでいうRSSI)を使うのではなく、各センサに衝撃が伝わる(無線やGPSでいう電波到達)までの時間差を用いる方法です。発信源が近いほど到達は早く、遠いほど到達が遅いため、この差分を使って式を立てることにより発信源を特定することができます。今回は手軽にRSSIライクなやり方でやってしまいましたが、精度をあげるにはTDOAを使うべきですね。
超絶精度な本家機種は一体どんなセンシング方法を採用しているのか、ますます気になってしまいました。
おわりに
この記事では、 ダーツボードにおけるダーツの接触点を複数の圧電素子を使うことによって特定するデモを紹介しました。今回紹介したものは古典的なセンシング方法ですが、ダーツに限らず応用先を身の回りのものに向けてみることで普段の趣味に一工夫加えることができます。近頃ではセンシングに必要なマイコンも簡単にBluetooth等を通じてwebやアプリに繋げることができるため、使い方次第では簡単に現実世界とソフトウェアをリンクさせた遊びを楽しむことができますので、皆様もぜひ試してみてください。
リクルートマーケティングパートナーズでは、プライベートの趣味でもエンジニアリング力をついつい発揮してしまう遊び心を持つエンジニア を 募集しております。