似せたックチャン の製作の話
2026-10-28
ハードオフで買った Amazon Echo Show 5 (第1世代) を カスタムROMで Android 11 化し、
M5Stack 発のロボット「スタックチャン」の模倣である「似せたックチャン」を作りました。
このロボットはローカル環境でAI技術を使った何かを作ることを目的に行いました。
後述の通り、自作のAIや機械学習を行ったわけではないものの、
インターネットに依存せずにローカルで動くエッジ推論についてある程度知ることができました。
この記事は完成品の紹介ではなく、制作の流れ、各段階の検証結果、設計判断の理由をまとめた記録です。
記事の要約
- 目的 AIや機械学習の技術を使いながら、低スペックなジャンク端末で、愛玩ロボットを制作する
- 制約 Android11、メモリ合計1GB、空き容量約1.2GB (SDカード増設不可)、古いArm命令セット
- 方針 ネイティブアプリではなく Web 技術 (HTML / JavaScript) で作る
- 進め方 概念実証を重ねて組み合わせる
- 結果 一部のセンサー機能を捨てたものの、完成した
1. きっかけ
東大宮駅前にあったハードオフで「Amazon Echo Show 5 第一世代」を安い値段で見かけたため、買ってみました。
いわゆる「アレクサのやつ」です。バッテリーではなく常時給電で使う端末です。
ChatGPTをはじめとした汎用AIが普及した今、Alexaをわざわざ使う理由はないため、改造してなにか面白いものを作ることにしました。
NASを作るには容量が…、ホームアシスタントにするには性能が…と考えた結果、後述する愛玩ロボットを制作することにしました。
2. 端末とカスタムROM
カスタムROMの導入
ありがたいことに、この機種には既にカスタムROM (いじりやすいように改良された OS イメージ) と
インストール手法が確立されていました。
それを使って LineageOS (Android 11) 化しました。
※ 自分の端末を自己責任で改造する話です。メーカー保証などはなくなります。
※ 改造した端末は自分で管理し、破棄する場合は純正ROMに戻したうえで粗大ごみとして処分します。
ハードウェアの制約
Android 11 とはいえ、各種性能に制約が大きいため、ネイティブアプリ(apk)ではなくWebベースで何かを作ることにしました。
| 項目 | 状況 |
|---|---|
| メモリ | 1GB |
| ストレージ空き | 約1.2GB |
| ストレージ増設 | 不可 |
| CPU | MediaTek MT8163 (いくつかの現代の命令セットが非対応) |
| 電源 | バッテリーなし |
| 画面 | 5.5インチ級のタッチ画面 (横長) |
| センサ類 | 距離計,照度計(上部)、カメラ、マイク |
3. スタックチャンについて
そんな中、スタックチャンという存在を思い出しました。
スタックチャンは、M5Stack という画面付きの小型マイコンを使って、機械の愛玩小動物を作るプロジェクトです。
M5Stack は ESP32 (Xtensa or RISC-V) に画面やセンサーを組み合わせた開発用モジュールで、
性能はかなり限られているものの、電子工作では多くの用途で使われています。
Amazon Echo Show 5 は M5Stack よりも画面が大きく、メモリもストレージもずっと多いため、似たようなものを作れるかも?
ということで、Echo Show でスタックチャンもどき「似せたックチャン」を作ることにしました。
4. 設計方針
なぜ Web ベースにしたか
本家スタックチャンは、組み込み向けの言語で作られています。
Android 上で同じことをするには apk を作ることになりますが、いろいろな制約がありそうだったためやめておきました。
そこで、手っ取り早く Web ベース で作ることにしました。
かつて FirefoxOS というものもあったように、実は低価格帯デバイス向けにはそこまで悪くない選択のはず。
なぜローカルで完結させるか
本家スタックチャンは Bluetooth などでスマホと連携できますが、今回は愛玩性を重視して端末内で完結させることにしました。
使ったカスタムROMは端末を自由に弄れるように、SELinux などのセキュリティ要素が無効になっています。
そのため、うかつにネットにつなぎたくないという事情もありました。
また、お金がないのでクラウドベースのAIサービスを使用しませんでした。
使えるセンサー
まず、使えるセンサーを調査しました。
| センサー | 場所 | 状況 |
|---|---|---|
| カメラ | インカメラ | 利用可 |
| マイク | 本体 | 利用可 |
| 照度センサー | 本体上部 | 利用可 (制約あり、後述) |
| 距離センサー | 本体上部 | 利用不可 |
技術構成
Firefox (フロントエンド)
├─ HTML5 / Canvas API ... 顔の描画
├─ MediaDevices API ... カメラ・マイクの取得
├─ Web Audio API ... 音量の解析、8bit風の音声生成
└─ MediaPipe (WASM) ... 顔検出
▲
│ http://localhost:8080 / (ws://localhost:8081)
▼
Termux (バックエンド)
└─ Python … 静的ファイル配信、センサー読み取り (PoC段階)
フロントエンド
- Firefox
- HTML5 (画面の描画)
- JavaScript
- Web Audio API
- MediaDevices API
- Canvas API
バックエンド
- Python (各種 API を使うための簡易サーバ
- Termux (Python 実行用)
5. 試行錯誤 (PoC)
一気に制作せずに段階を踏みました。詳しい段階は後述。
| 段階 | 検証したこと | 結果 |
|---|---|---|
| PoC 1 | ブラウザの API で、カメラ・マイク・照度が取れるか | 照度のみ失敗 |
| PoC 2 | 照度をブラウザ以外の経路で取れるか | 成功 (Termux-API + WebSocket) |
| PoC 3 | 顔検出ができるか | 成功 (MediaPipe) |
| PoC 4 | すべてを同時に動かせるか | メモリ不足で動かず |
| stable | 知見をもとに本実装 | 完成 |
PoC 1
最初の PoC は、使えそうな機能を一通り確認することが目的でした。
マイクについては、音声認識モデルが動かないであろうことは明白だったため、音量の数値を利用しました。
navigator.mediaDevices.getUserMediaでカメラ映像を画面に出力- Web Audio API の
createMediaStreamSourceで音量レベルを算出し、数値表示 - Ambient Light Sensor API で照度を取得
結果は次のとおりです。
- カメラ: 端末に負荷をかけるものの、利用可能
- 音量: 十分に利用可能
- 照度: 失敗
Androidのバージョンが絶妙に古いことからFirefoxのバージョンを落としたためか、照度計を利用できませんでした。
PoC 2
PoC 2 の目標は、照度を取得する方法を見つけることが目的でした。
試行錯誤の結果、Termux の Termux:API を使えば取得できることが分かりました。
この段階で、文献調査にPerplexityを利用しました。
そこで、次のコマンドで照度を取得し、Python から WebSocket でブラウザへ送る構成にしました。
termux-sensor -s "Light Sensor" -d 5
構成の流れは次のとおりです。
termux-sensor (照度を取得) → Python (JSONをパース) → WebSocket → ブラウザ (表示)
処理が重くなるとJSON構造が壊れてしまうことがあったので、半ば無理やりパースするようにしました。 結果、照度の取得に成功しました。
PoC 3
PoC 1 から、動画処理は難しいものの画像なら動きそうとわかりました。
AI・機械学習 の技術を使いたいので、低い性能でも動きそうな「顔の位置の検出」に挑戦しました。
顔の学習セットも、推論できるGPUも、知見もなかったため、今回は既存のモデルを活用することにしました。
-
MediaPipe Tasks Vision (FaceDetector) Google が公開している、ブラウザで動く画像処理ライブラリです。
WebAssembly (WASM) と JavaScript だけで動くため、アプリのビルドが不要で手軽なことから採用しました。
MediaPipe 顔検出ガイド -
BlazeFace (short range) カメラに顔が近い状況向けの、軽量な顔検出モデルです。
リアルタイムで動作するほど軽く、今回の使い方に合っていたため採用しました。
実装方法
ネット接続を回避したいため、CDNではなく同梱する形にしました。
mediapipe/
vision_bundle.mjs
wasm/*
models/
blaze_face_short_range.tflite
Termux上のPythonの簡易サーバライブラリを使い、localhostで動かしています。
mimetypes.add_type("text/javascript", ".mjs")
mimetypes.add_type("application/wasm", ".wasm")
mimetypes.add_type("application/octet-stream", ".tflite")
検出頻度の調整
愛玩ロボットの視線追従に使う予定だったため、リアルタイムの検出は不要でした。
そこで、2秒に1回 のペースで、カメラの現在のフレームを推論し、顔の座標を得る方式にしました (runningMode: "IMAGE")。
結果、うまくいきました。
Poc3では顔の位置を枠で重ねて表示するようにしてみました。
PoC 4
これまでの知見を活かし、同時に動かす検証をしました。
結果、メモリ不足で全く動きませんでした。
軽量化も思いつく限り試したものの、奥で動いているアプリが多すぎるため両立は難しい という結論になりました。
顔検出の方が体験への影響が大きく、また照度はキャリブレーションが要る などを踏まえ、
AI・機械学習の要素を取り入れたかったのでカメラと顔検知を優先し、照度計関連は廃止しました。
6. 最終実装
スタックチャンもどきを作る時が来ました。
Canvas に顔を描き、ちょっかいをかけるといろいろな反応をするようにしました。
機能
| 入力 | 反応 |
|---|---|
| 顔を検出 | 視線が人の顔を追う |
| 顔がない | 視線がきょろきょろ動く |
| 周囲の音が増える(音量の数値が急上昇) | 話しかけられたとみなし、喜ぶ |
| 画面をタッチ | ♡が飛ぶ |
| 時間経過 | まばたき、30秒おきに顔が変わる、8bit風にふにゃふにゃ喋る |
※顔が複数ある場合は、一番大きいものを採用する。 ※時間がたつと画面が暗くなる問題は、Wake Lock APIが使えなかったため「つけっぱ」という外部アプリで代用した。
リアルさを目指すと不気味になるので、愛玩動物のようなかわいらしさを重視しました。
また、隠し要素としていくつかの小ネタを追加しました
- 「・ω・」の顔の時は猫っぽく見えるようにひげを追加。
- しゃべるときに4%の確率で「Daisy Bell」(HAL 9000 が歌う曲として有名) の冒頭を歌う。
※Daisy Bellは日本を含む多くの国でパブリックドメインです。
実装のポイント
顔検出は 1 秒間隔にする
1日の連続稼働実験の結果、ギリギリ問題がない範囲である 1秒おき に推論を実行するようにしました。
視線の位置の扱い方
そのまま座標に顔を合わせると、1秒おきにカクカク動いてしまいます。
そこで、検出結果を「目標位置」として保存し、実際の描画では少しずつ近づけています。
// 顔の位置から目標を決める (カメラが左右反転して見えるため符号を反転)
eyeTarget.x = -(facePos.cx - 0.5) * 3.6 + CAMERA_RIGHT_OFFSET;
eyeTarget.y = (facePos.cy - 0.5) * 3.0;
// 毎フレーム、目標に6%ずつ近づける
eyeCurrent.x += (eyeTarget.x - eyeCurrent.x) * 0.06;
eyeCurrent.y += (eyeTarget.y - eyeCurrent.y) * 0.06;
本家のスタックチャンはサーボモーターで首を動かしますが、似せたックチャンには首がなくて画面が大きいため、顔の位置を移動させました。
カメラが画面中央にないため、補正値も入れました。
音への反応は「ノイズフロア」で判定する
固定のしきい値だと、静かな部屋では反応しすぎ、騒がしい部屋では反応しなくなります。
そこで、周囲の音量の移動平均を「ノイズフロア」(普段の騒音レベル) として追い続け、急に一定以上大きくなった時だけ反応するようにしました。
// 普段の音量を、ゆっくり追従する移動平均として保持
noiseFloor = noiseFloor * 0.98 + currentVol * 0.02;
// 普段より22以上大きければ「話しかけられた」とみなす
if ((currentVol - noiseFloor) > 22) { /* 喜ぶ */ }
音は生成する
ふにゃふにゃや「Daisy Bell」は Web Audio API の OscillatorNode で生成しました。
mp3ファイルを読み込ませるとメモリ使用量に不安があったため、知人に相談した結果、
昔のゲームのようにその時に音を生成する方式 を採用しました。
描画は Canvas で行う
目と口は Canvas の図形だけで描いています。上と同じような理由で画像は使いませんでした。
デバッグ表示を内蔵する
変な端末なのでエミュレータはありません。また、ブラウザのキャッシュなどの厄介な問題もあったため、
起動画面のチェックボックスでデバッグ表示を出せるようにしました。
- FPS、顔の座標、音量、ノイズフロアが表示される
- リソース表示コマンド「fastfetch」を実行するボタン
- しゃべらせるボタン
7. システムの制約まとめ
この制作でぶつかった制約です。
| 制約 | 影響 | 対処 |
|---|---|---|
| メモリ1GB | センサー取得系 と 顔検出の同時実行が不可 | 照度センサーを断念 |
| 空き容量約1.2GB | 大きなモデルを入れられない | 軽量な顔検出モデルを使用、音声と画像のファイルを持たない |
| CPUの命令セットが古い | apk開発が困難 | Web 技術で作る |
| ブラウザで照度が取れない | PoC 1 で失敗 | Termux-API 経由に変更、最終的に不採用 |
| ネットにつなぎたくない | クラウド AI を使えない | 端末内で完結する機能だけにする |
8. 今後の課題
- 照度計を活用できる別経路を探す
- 本家スタックチャンのような機能を増やす
9. 感想
一連の開発を通して、技術選定や変なAndroid端末に関するノウハウや、実装時に出てくる課題に関する知見を得られました。
特に、制約のある環境での取捨選択の重要性が身にしみてわかりました。
そしてなにより、ガラクタの買い物からカワイイ生き物を作れたので満足しています。
Gemini 曰く、
全ての機能を盛り込むのではなく、PoC4の同時試験で発覚した性能課題に基づき、不要な複雑性を生んでいた「照度センサー」を本番で削ぎ落とした(Less is More)ことで、プロダクト全体のパフォーマンスと信頼性を確保できた。
とのことです。
逆に、顔検知の部分を自作できなかったことが悔しい点でした。
今のところ既存の技術を組み合わせただけになっているため、いつかほかのプロジェクトでリベンジしていきたいです。
