梶研 [リアルタイムでスマホの姿勢を表示するアプリ]
2024年07月16日
リアルタイムでスマホの姿勢を表示するアプリ
出席率
- 3年セミナー:??%
スケジュール
短期的な予定
- [ ] mocopi と お料理センシング
- [x] シーンとランドマークを決める(~2月上旬)
- [x] SVM で動作判別する
- [x] 機械学習を深める
- [ ] お料理センシング
- [x] お料理でどんな動作があるかを知る
- [x] レシピを決める
- [x] 関節を3次元座標に変換する
- [ ]
関節を2次元座標に変換する - [x] ST-GCN を大体理解
- [ ] ST-GCN で動作推定
- [ ] 伊達巻きで動作推定する
- [ ] レシピ(手順書)を元に動作推定を補う
- [ ] 論文書く
- [ ] 発表
- [ ] BookWorm
- [x] Pasori と デスクトップアプリを接続する(技術検証)
- [x] nfc読み込み機能 & 画面を作る
- [ ] API と連携させる
- [x] 管理者画面を作る
長期的な予定
- 6月 精度は置いておき、動作認識を完成させる
- 7月 リファクターと精度を向上させる
- 8月 仕上げ
- 9月 論文書き始め
- 10月末 論文提出
- 12月 WiNF
進捗報告
ST-GCN
進捗ありません
---
リアルタイムでスマホの姿勢を表示するアプリ
オーキャンでリアルタイムで表示できるアプリあれば嬉しいかも
技術構成
スマホ側 (モバイルアプリ)
React Native (expo): 使ってみたかった
PC側 (デスクトップアプリ)
Tauri (Rust, React - Vite)
通信
候補
- Web Bluetooth API
- Bluetooth
- Socket(TCP)
- WebSocket
Web Bluetooth API 使ってみる
ブラウザでBLE通信ができる WebAPI(ブラウザーAPI)
// デバイスをフィルター
const options: RequestDeviceOptions = {
filters: [
{
namePrefix: "デバイス名のPrefix",
},
],
};
// デバイスを探す & 接続
const device = await navigator.bluetooth
.requestDevice(options)
.then((device) => {
return device;
})
.catch((error) => {
console.error(error);
return null;
});
// 通信のためのあれこれ
const server = await device?.gatt?.connect();
const service = await server?.getPrimaryService('battery_service'); // PrimaryServiceの指定
const characteristic = await service?.getCharacteristic(service.uuid);
console.log(characteristic);
// 受信時のイベント
characteristic?.addEventListener('characteristicvaluechanged', (e) => {
console.log(e);
}) 相手もブラウザの場合、PrimaryService ってなに...?
const service = await server?.getPrimaryService('battery_service'); どうやら、BLE通信でセンサー等としか接続できないらしい?
少なくともブラウザ同士でやるものじゃない
Bluetooth やめた
Rust側 が面倒そう.
Socket通信(TCP) 使ってみる
接続すれば双方向から通信できるいいやつ
(HTTP はサーバーから送信はできない)
Rust(Tauri)で実装(サーバ側)
// サーバー起動
pub fn start_server(app: &mut App) {
let app_handle = app.app_handle();
thread::spawn(move || {
let port_number = 12345;
let listener = TcpListener::bind(SocketAddr::new(
IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
port_number,
))
.expect("Failed to bind to address");
print!("Server started");
emit_socket_payload(
&app_handle,
SocketState::Started,
"Server started".to_string(),
);
recv(listener, app_handle);
});
print!("Server Stop");
} // 接続待ち
fn recv(listener: TcpListener, app_handle: AppHandle) {
loop {
match listener.accept() {
Ok((mut socket, addr)) => {
emit_socket_payload(
&app_handle,
SocketState::Connected,
format!("Accepted connection from {:?}", addr),
);
read(&mut socket, &app_handle);
}
Err(e) => {
eprintln!("Accept error: {}", e);
std::process::exit(1);
}
}
}
} // 受信しまくる
fn read(socket: &mut std::net::TcpStream, app_handle: &AppHandle) {
loop {
let mut buffer = [0; 1024];
match socket.read(&mut buffer) {
Ok(0) => {
emit_socket_payload(
app_handle,
SocketState::Disconnected,
"Client disconnected".to_string(),
);
break;
}
Ok(bytes_read) => {
let message = String::from_utf8_lossy(&buffer[..bytes_read]);
emit_socket_payload(app_handle, SocketState::Received, message.to_string());
}
Err(ref e) if e.kind() == ErrorKind::WouldBlock => {
continue;
}
Err(e) => {
emit_socket_payload(
app_handle,
SocketState::Error,
format!("Error reading from client: {}", e).to_string(),
);
break;
}
}
}
// Close the connection if it's still open.
if let Err(e) = socket.shutdown(Shutdown::Both) {
emit_socket_payload(
app_handle,
SocketState::Error,
format!("Shutdown failed: {}", e).to_string(),
);
}
} // React(フロント)に送信
fn emit_socket_payload(app_handle: &AppHandle, state: SocketState, data: String) {
let payload = SocketPayload { state, data };
app_handle.emit_all("socket", payload.clone()).unwrap();
} 簡単にできた.
React
いい感じにした
export const useSocket = (handler: recvHandler, event: EventName = 'socket') => {
const isListening = useRef(false);
useEffect(() => {
if (isListening.current) return;
isListening.current = true;
(async () => {
console.log('Listening to socket events');
const unlisten = await listen(event, (event: SocketEvent) => {
handler(event.payload.state, event.payload.data);
});
return () => {
unlisten();
isListening.current = false;
};
})();
}, []);
}; React Native
react-native-tcp-socket というライブラリがあった
// 接続
const client = TcpSocket.createConnection({ port: portNum, host: ip }, () => {});
// 送信
client?.write(sendData);
// 切断
client?.destroy(); 楽にできた
センシングする
expo には DeviceMotion っていう便利なやつがいた.
加速度, 線形加速度, 角速度, 回転量, デバイスの向き を扱える
export const useSensor = (handler?: Handler, sampleingRate = 1 / 60) => {
const [data, setData] = useState<DeviceMotionMeasurement>();
const [subscription, setSubscription] = useState<Subscription>();
/**
* センサーの読み込みを開始する
*/
const subscribe = () => {
const deviceMotion = DeviceMotion.addListener((data) => setData(data));
DeviceMotion.setUpdateInterval(sampleingRate);
setSubscription(deviceMotion);
};
/**
* センサーの読み込みを停止する
*/
const unsubscribe = () => {
subscription?.remove();
setSubscription(undefined);
};
if (handler && data) handler(data);
return [data, { subscribe, unsubscribe }] as const;
}; 簡単にできた
<iframe width="1052" height="788" src="https://www.youtube.com/embed/jKNEi2YUSuA" title="リアルタイムでスマホの姿勢を表示したい (センサーデータ付き)" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
スマホを表示する
Three.js でできた
<iframe width="1052" height="788" src="https://www.youtube.com/embed/TLSF8Vi4zUI" title="リアルタイムでスマホの姿勢を表示したい (自動回転)" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
センサーの値にフィルターを掛ける
データ
- 22Hz前後
- 1秒前まで持っている(変更可)
- 加速度(重力含む) と 角速度
- 各x, y, z
フィルター
相補フィルターでやるつもり
- 加速度のノルムの分散を出す
- 分散が 0.05 以上 であれば動いているとみなす
- 動いている場合
- 現在の姿勢(回転量) に足していく(積分)
- 動いていない場合
- 重力加速度から下方向を出す
- 動いている場合
現状
お家では動いたけど...
ReactNative から socket が繋がらない...
(ping は飛ばせる)
動画撮ってなかった...
- 動いていない状態は問題なく表示できた
- 動いている状態では暴れ出した
- 多分周波数あたりのなんやかんやをどうにかすればいい感じになるはず
TODO
- センサデータを0.5秒に1回まとめて送信するようにしたい
- 0.5秒くらいの遅延は許して欲しい
- 現状はセンサの周波数だけ送信している (Reactが可哀想)
- フィルタを修正したい
進路関係
pluszero (長期インターン) が幸福です
余談
余談はありません.
梶研入ってから1週目(2023/4/11 ~ 4/18)以外は欠かさずに余談を生成していたので悲しいです.
余談は任意(強制)です
by makino