RyotaIkeuchi
2021年05月14日作成 (2021年05月14日更新)
製作品 2200
お菓子とってー → 発射!
デモ動画
しくみ、使い方
- 帽子の人が、レーダーと装置の稼働状態を確認しつつ、目視でユーザーの位置を確認し、装置が常にユーザーの方に向くようにパン角度をハンドルで操作する
- 帽子の人が、ユーザーの挙手したことを目視確認すると、ヘルメットの人に司令を出す
- ヘルメットの人はトランシーバーで司令を受け取り、装置を発射させる(投石機)
実際のしくみ
- スマホカメラでユーザーの顔の位置(姿勢推定の鼻の座標)を検出し、顔がスマホカメラの正面の向きにくるように、パン用のサーボモーターを動かす
- 姿勢推定で挙手を検知(右手首の座標が鼻よりも上にあり、一定以上の差があることを検知)すると、もう1つのサーボモーターを動かして発射する、
部品
数量 | |
---|---|
obniz Board 1Y | 1 |
Servo Kit 180‘ (M5Stack) | モーター2個(1セット) |
USB microB ピッチ変換基板 | 1 |
別途、USB電源が必要(モバイルバッテリー or ACアダプター+コンセント)
配線図
図のブレッドボードのGND, 電源にはモバイルバッテリーをピッチ変換基板経由で接続する
※obniz boardから電源をとって動作できるモーターもあるが、今回使用したモーターは別電源をとらないとエラーとなった
ソースコード
code.js
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
ctx.strokeStyle = "#00ff33";
const obniz = new Obniz("xxxx-xxxx");
obniz.onconnect = async function () {
let servo_pan = obniz.wired("ServoMotor", { gnd: 0, vcc: 1, signal: 2 });
let servo_throw = obniz.wired("ServoMotor", { gnd: 9, vcc: 10, signal: 11 });
const imageScaleFactor = 0.2;
const outputStride = 16;
const contentWidth = 800;
const contentHeight = 600;
let pan_angle_set = 90;
let throw_angle_set = 90;
bindPage();
async function bindPage() {
const net = await posenet.load();
let video;
try {
video = await loadVideo();
} catch (e) {
console.error(e);
return;
}
detectPoseInRealTime(video, net);
}
async function loadVideo() {
const video = await setupCamera();
video.play();
return video;
}
async function setupCamera() {
const video = document.getElementById("video");
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
const stream = await navigator.mediaDevices.getUserMedia({
audio: false,
video: true
});
video.srcObject = stream;
return new Promise((resolve) => {
video.onloadedmetadata = () => {
resolve(video);
};
});
} else {
const errorMessage =
"This browser does not support video capture, or this device does not have a camera";
alert(errorMessage);
return Promise.reject(errorMessage);
}
}
function detectPoseInRealTime(video, net) {
const flipHorizontal = true;
async function poseDetectionFrame() {
let poses = [];
const pose = await net.estimateSinglePose(
video,
imageScaleFactor,
flipHorizontal,
outputStride
);
poses.push(pose);
throw_something(poses);
pan(poses);
setTimeout(arguments.callee, 1000);
}
poseDetectionFrame();
}
function pan(poses) {
let nose = poses[0].keypoints[0].position;
let sholderLeft = poses[0].keypoints[5].position;
let sholderRight = poses[0].keypoints[6].position;
let center = { x: 0, y: 0 };
center.x = (sholderLeft.x + sholderRight.x) / 2;
center.y = (sholderLeft.y + sholderRight.y) / 2;
draw(nose, sholderLeft, sholderRight, center);
const delta = nose.x - canvas.width / 2;
const th_stop = 100;
// 十分中央付近にあればパン動作させない
if (delta < th_stop && delta > -1 * th_stop) return;
if (delta < 0) {
pan_angle_set += 10;
if (pan_angle_set > 180) pan_angle_set = 180;
} else {
pan_angle_set -= 10;
if (pan_angle_set < 0) pan_angle_set = 0;
}
servo_pan.angle(pan_angle_set);
}
function throw_something(poses) {
let nose = poses[0].keypoints[0].position;
let wristLeft = poses[0].keypoints[9].position;
let wristRight = poses[0].keypoints[10].position;
draw(nose, wristLeft, wristRight);
const wrist_y = Math.max(wristLeft.y, wristRight.y);
const delta = nose.y - wrist_y;
if (delta > 100) {
throw_angle_set = 40;
} else {
// slowly to 110
if (throw_angle_set < 150 - 10) throw_angle_set += 20;
}
servo_throw.angle(throw_angle_set);
}
function draw(nose, sholderLeft, sholderRight, center) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.moveTo(canvas.width / 2, canvas.height / 2);
ctx.lineTo(nose.x, nose.y);
ctx.closePath();
ctx.stroke();
ctx.font = "20pt Arial";
ctx.strokeText(Math.round(nose.x, 4), 100, 20);
ctx.strokeText(Math.round(nose.x - canvas.width / 2, 4), 100, 50);
ctx.strokeText(pan_angle_set, 100, 100);
ctx.strokeText(throw_angle_set, 100, 130);
}
};
こちらのobnizサンプルをベースにcode.jsのみを書き換えた
- 頭の傾きに合わせてサーボモーターを動かす(obnizサンプル)https://blog.obniz.com/make/move-the-servomotor-tilt-the-head
その他参考
- 第一回 フリースローチャレンジ(WRO) https://www.wroj.org/2020/rsports-2020-01
…投げる構造の参考として - 3代目マシュマロ・カタパルト https://sweetelectronics.wordpress.com/2014/03/21/marshmallow/
…今回のアイデアを思いついたとき、「おやつを投げる」というのは既にありそうだなと思って調べると見つかったもの。発射機構が美しいです。そしてジェスチャー認識まで既にやってらっしゃるようです。リスペクトです。
こだわり
レゴは、DUPLOと通常サイズのハイブリッド。
モーターをくっつけるなど細かいところは通常サイズで、下部の高さを稼ぐところはDUPLO。
obniz boardのメリットが出せるような構成にしようと思い、サーボモーターを複数接続し(モータードライバー回路がたくさん入っていて扱いやすい)、スマホ上でのtensorflowjsの姿勢推定と組み合わせた。
ご注意
機構部は簡易的にレゴを使ったが、発射するたびにレゴ部が外れたり、スマホが落下したりするので、普段使いする方は真面目につくってください。
パン用のサーボモーターがたびたび振動するので、そちらも真面目に設計・調整が必要。
おわりに
サンプルを流用して開発もスムーズに進めることが出来た。さすがobnizです。
-
RyotaIkeuchi
さんが
2021/05/14
に
編集
をしました。
(メッセージ: 初版)
-
RyotaIkeuchi
さんが
2021/05/14
に
編集
をしました。
ログインしてコメントを投稿する