3Dモーショントラッカーの構築:IMU、Arduino、Babylon.js、GridDBの統合

はじめに

このチュートリアルでは、物体の動きをリアルタイムで視覚化できる3Dモーション・トラッカーを作ります。モーション・データは、IMUセンサーとArduino Unoから収集され、Node.jsで処理され、Babylon.jsで可視化されます。GridDBは、将来の分析のためのデータ保存に使用されます。

プロジェクトのソースコード

このプロジェクトのソースコードは、以下のGitHub リポジトリにあります。このプロジェクトにはハードウェアソフトウェアのコンポーネントが必要です。

サーバーを実行するには、まず以下のコマンドを実行して依存関係をインストールする必要があります:

cd app/server
npm install

次に、以下のコマンドを使ってサーバーを実行します:

npm start

このシステムは、記録された実際のセンサーデータを使って、デフォルトではデモ版で動作します。URLhttp://localhost:3000にアクセスすると、デモデータが1秒ごとにブラウザに表示されます。

Arduinoのソースコードまたはスケッチはapp/hardwareディレクトリにあります。まず、Arduino Unoをプログラムする必要があります。こちらを読んでください。次に、必要なライブラリをインポートするために、開発環境のセットアップを読んでください。

注意: Arduinoを新しいUSBポートに接続するたびに、Node.jsサーバーを再起動することをお勧めします。

システムアーキテクチャ

このアーキテクチャは、ハードウェアインターフェイスとデータ管理を含むリアルタイムの3Dトラッキングシステムを示しています。MPU-9250センサーを搭載したArduino Unoがモーションデータを収集し、シリアルポート接続を介してNode.jsサーバーに送信します。Node.jsはデータを処理し、フィルタリング、解析、リアルタイム通信などのタスクを実行する。そして、データの保存と分析に使用されるデータベース、GridDBとのインターフェイスを持ちます。

さらに、Node.jsは、MPU-9250センサーが捉えたリアルタイムの方位を表す3Dキューブを表示するフロントエンドとして機能します。このフロントエンドは、センサーデータをリアルタイムで可視化するウェブアプリケーションです。

ハードウェア要件

IMU センサー

IMU センサー(Inertial Measurement Unit センサー)は、物体の動き、向き、および環境条件を測定するデバイスです。加速度計、ジャイロスコープ、地磁気計**で構成されます。加速度計は物体の加速度を測定し、ジャイロセンサーは角速度を測定し、磁力計は磁場を測定します。このプロジェクトで使用する IMU センサーは、MPU-9250です。MPU-9250 センサーを搭載した GY-91 モジュールを使用します。

Arduinoボード

このプロジェクトでは、Arduino Unoを使用してIMUセンサーからセンサーデータを読み取ります。Arduino UnoはATmega328Pをベースにしたマイコンボードです。14のデジタル入出力ピン、6つのアナログ入力、16MHzの水晶振動子、USB接続、電源ジャック、ICSPヘッダー、リセットボタンを備えている。

ソフトウェア要件

このプロジェクトで使用する主なソフトウェアコンポーネントは以下の通りです:

Arduino IDE

Arduino UnoはArduino Software (IDE)を使ってプログラミングします。公式ガイドに従ってArduino IDEをコンピュータにインストールしてください。

Node.js

Node.jsは、Webブラウザの外でJavaScriptコードを実行するオープンソースのクロスプラットフォームJavaScript実行環境です。スケーラブルなネットワーク・アプリケーションを構築するために使用されます。お使いのコンピュータにNode.jsをインストールするには、公式ガイドに従ってください。

Node.jsは、IMUセンサーからのモーションデータを処理し、データベースに格納し、ウェブブラウザに送信するためのサーバーとして使用されます。

GridDB

GridDBは、IoTやビッグデータアプリケーション向けのオープンソースのインメモリNoSQLデータベースです。時系列データや地理空間データに最適化されています。公式ガイドに従ってGridDBをコンピュータにインストールしてください。

GridDB は、将来の分析のために IMU センサーからのモーションデータを保存するために使用されます。

初期設定の手順

ハードウェアのセットアップ

このプロジェクトの部品表(BOM)です:

Item Quantity Description
Arduino Uno Board 1 Microcontroller platform for the project.
GY-91 IMU Sensor Module 1 Provides inertial measurement data.
USB Cable 1 Connects Arduino to a computer for power and programming.
Jumper Wires 4 2 for power, 2 for I2C communication.
Breadboard 1 Platform for prototyping and testing circuits.

お使いのIMUセンサーモジュールによっては、モジュールにヘッダーピンをはんだ付けする必要があります。この作業には、はんだごてと鉛または鉛フリーのはんだが必要です。

IMUセンサーモジュールとArduino Unoは、コンピュータからのUSBケーブルで給電されるため、スタンドアロンで電源を供給する必要はありません。IMU センサーモジュールと Arduino Uno の相互接続はジャンパー線を使用します。

開発環境のセットアップ

Arduino IDEを開き、MPU9250.zipライブラリパッケージをIDEにインポートします。パッケージはこちらからダウンロードできます。

ライブラリをインポートするには、Sketch > Include Library > Add ZIP Library...と進み、MPU9250.zipファイルを選択します。

IMUとArduinoの統合

IMUセンサーとArduinoの接続

IMUセンサーはI2Cインターフェースを使用してArduino Unoに接続します。デバイスの相互接続を以下の図に示します:

接続は以下の通り:

Arduino Uno Pin GY-91 IMU Sensor Pin
3.3V VCC
GND GND
A4 (SDA) SDA
A5 (SCL) SCL

Arduino Unoをプログラムするには、USBケーブルを使ってコンピュータに接続する必要があります。

Arduinoを使ってセンサーデータを読み込む

Arduinoを使ってセンサーデータを読み取るには、まずArduinoをプログラミングする必要があります。新しいスケッチを作成し、以下のコードをコピーしてスケッチに貼り付けます:

#include "MPU9250.h"

// an MPU9250 object with the MPU-9250 sensor on I2C bus 0 with address 0x68
MPU9250 IMU(Wire,0x68);
int status;

void setup() {
  // serial to display data
  Serial.begin(115200);
  while(!Serial) {}

  // start communication with IMU 
  status = IMU.begin();
  if (status < 0) {
    Serial.println("IMU initialization unsuccessful");
    Serial.println("Check IMU wiring or try cycling power");
    Serial.print("Status: ");
    Serial.println(status);
    while(1) {}
  }
}

void loop() {
  // read the sensor
  IMU.readSensor();
  // display the data
  Serial.print("ax:");
  Serial.print(IMU.getAccelX_mss(),6);
  Serial.print("\t");
  Serial.print("ay:");
  Serial.print(IMU.getAccelY_mss(),6);
  Serial.print("\t");
  Serial.print("az:");
  Serial.print(IMU.getAccelZ_mss(),6);
  Serial.print("\t");
  Serial.print("gx:");
  Serial.print(IMU.getGyroX_rads(),6);
  Serial.print("\t");
  Serial.print("gy:");
  Serial.print(IMU.getGyroY_rads(),6);
  Serial.print("\t");
  Serial.print("gz:");
  Serial.print(IMU.getGyroZ_rads(),6);
  Serial.print("\t");
  Serial.print("mx:");
  Serial.print(IMU.getMagX_uT(),6);
  Serial.print("\t");
  Serial.print("my:");
  Serial.print(IMU.getMagY_uT(),6);
  Serial.print("\t");
  Serial.print("my:");
  Serial.print(IMU.getMagZ_uT(),6);
  Serial.print("\t");
  Serial.print("s:");
  Serial.println(IMU.getTemperature_C(),6);
  delay(100);
}

このコードでは、I2C バス(Wire)のアドレス 0x68 からセンサーデータを 100 ミリ秒ごとに読み取るように指示されています。データはボーレート115200でシリアルポートに送られ、カンマで区切られた文字列の形をしています。最初の9つの値は加速度計ジャイロスコープ地磁気計のデータです。最後の値は摂氏での温度です。

スケッチをArduinoにアップロードする。

Arduino Unoをプログラムするには、Arduino IDEの左上にあるUploadボタン(矢印のアイコン)をクリックしてスケッチをアップロードします。

Arduino Unoにアップロードする前に、スケッチプログラムを検証することをお勧めします。スケッチを確認するには、チェックマークのアイコンをクリックします。

センサーデータのモニター

シリアルモニターを開いてセンサーデータを確認します。Arduino IDEの右上にある虫眼鏡アイコンをクリックすると、シリアルモニタを開くことができます。

シリアルモニターでは、センサーデータが以下のフォーマットで表示されるはずです:

ax:0 ay:0 az:-9.1 gx:0 gy:0 gz:0 mx:13.0 my:57.1 my:-105.3 s:27.4

また、minicomのような CLI シリアル通信ツールを使用してセンサーデータを読み取ることもできます。

Ubuntu Linux の場合、以下のコマンドで minicom をインストールすることができます:

sudo apt install minicom

次に以下のコマンドを実行してセンサーデータを読み込みます:

minicom -D /dev/ttyACM0 -b 115200

上記のコマンドを実行する前に、Arduino IDEのシリアルモニターを終了してください。

Node.jsとWebSocketの統合

Arduino からセンサーデータを読み込む

Node.jsはArduino Unoからシリアルポート経由でセンサーデータを読み込みます。このプロジェクトでは、serialportライブラリを使用してデータを読み取ります。

import { SerialPort } from 'serialport';
import { ReadlineParser } from '@serialport/parser-readline';
import { saveData, getAllData, getDatabyID, info } from './griddbservices.js';

// Serial port setup
const port = new SerialPort({ path: 'COM5', baudRate: 115200 });
const parser = port.pipe(new ReadlineParser({ delimiter: '\n' }));

parser.on('data', (data) => {
 console.log(data);
 const parsedData = parseSensorData(data);
 await saveData({ sensorData: JSON.stringify(parsedData) });
 broadcastData(JSON.stringify(parsedData));
});

その後、データはWebSocket経由でWebブラウザに「ブロードキャスト」され、将来の分析のためにGridDBにも保存されます。

await saveData({ sensorData: JSON.stringify(parsedData) });

前述したように、Arduino IDE シリアルモニターや minicom などのシリアルポートを使用するプログラムなど、既存のシリアルポート接続を閉じて、Node.js サーバーがセンサーデータを読み込めるようにする必要があります。

ウェブソケット

WebSocket は、単一の TCP 接続上で全二重通信チャネルを提供する通信プロトコルです。このプロジェクトでは、Node.js から Web ブラウザにセンサーデータをリアルタイムで送信するために使用します。Node.jsにWebSocketサーバを実装するために、wsライブラリを使用します。

import express from 'express';
import { createServer } from 'http';
import { WebSocketServer, WebSocket } from 'ws';

const app = express();
const server = createServer(app);
const wss = new WebSocketServer({ server });

wss.on('connection', (ws) => {
  console.log('WebSocket client connected');
  ws.on('close', () => console.log('WebSocket client disconnected'));
});

const broadcastData = (data) => {
 wss.clients.forEach((client) => {
  if (client.readyState === WebSocket.OPEN) {
   client.send(data);
  }
 });
};

データの処理と可視化

データの処理

データを解析し、座標系の調整や単位の変換などの処理を行います。センサーデータの単位を理解するために、必ず事前にセンサーデータシートを読んでください。

function parseSensorData(data) {
 // Parse the data string
 const sensorValues = data.split('\t').map(val => parseFloat(val.split(':')[1]));

 // The order of the data is ax, ay, az, gx, gy, gz, mx, my, mz
 const [ax, ay, az, gx, gy, gz, mx, my, mz, s] = sensorValues;

 // Normalize accelerometer data if needed (currently in m/s², convert to g's if necessary)
 const accel = {
  x: ax / 1000,
  y: ay / 1000,
  z: az / 1000
 };

 // Gyroscope data is in rad/s, which is what the Madgwick filter expects, so no conversion needed
 const gyro = { x: gx, y: gy, z: gz };

 // Magnetometer data is in microteslas (uT), convert to Teslas by dividing by 1,000,000 if necessary
 const mag = {
  x: mx / 1000000,
  y: my / 1000000,
  z: mz / 1000000
 };

 const temp = { s }

 return { accel, gyro, mag, s };
}

この解析されたデータは、WebSocketを介してウェブ・ブラウザに送信されます。

Babylon.jsによるビジュアライゼーション

モーション・データを視覚化するために、WebGLとWebVRを使って3Dゲームやアプリケーションを構築するためのJavaScriptフレームワークであるBabylon.jsを使用します。HTMLは以下の通りです:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>3D Sensor Visualization</title>
    <script src="https://cdn.babylonjs.com/babylon.js"></script>
    <style>
        #renderCanvas {
            width: 100%;
            height: 100vh;
            touch-action: none;
        }
    </style>
</head>

<body>
    <canvas id="renderCanvas"></canvas>
    <script>
        window.addEventListener('DOMContentLoaded', () => {
            const canvas = document.getElementById('renderCanvas');
            const engine = new BABYLON.Engine(canvas, true);

            const createScene = () => {
                const scene = new BABYLON.Scene(engine);
                const camera = new BABYLON.ArcRotateCamera("camera", -Math.PI / 2, Math.PI / 2.5, 10, new BABYLON.Vector3(0, 0, 0), scene);
                camera.attachControl(canvas, true);
                new BABYLON.HemisphericLight("light", new BABYLON.Vector3(1, 1, 0), scene);
                const box = BABYLON.MeshBuilder.CreateBox("box", { size: 2 }, scene);
                return { scene, box };
            };

            const { scene, box } = createScene();

            const ws = new WebSocket('ws://localhost:3000');
            ws.onmessage = (event) => {
                const sensorData = JSON.parse(event.data);
                const { gyro } = sensorData;

                // Update cube rotation with gyro data in radians/s
                // Assume you're receiving data at a rate of 60Hz (or adjust as per your rate)
                box.rotation.x += gyro.x / 10; // Update rotation based on gyro data
                box.rotation.y += gyro.y / 10;
                box.rotation.z += gyro.z / 10;
            };

            engine.runRenderLoop(() => {
                scene.render();
            });

            window.addEventListener('resize', () => {
                engine.resize();
            });
        });
    </script>
</body>

</html>

上のコードでは、カメラ、ライト、ボックスでシーンを作成しています。ボックスは、IMUセンサーからのジャイロスコープ・データに基づいて回転します。ジャイロデータはNode.jsからWebSocket経由で受信されます。

デフォルトでは、WebSocket URL は localhost でポート 3000 で実行されます。Node.jsサーバーのURLに合わせてWebSocket URLを変更し、この行のコードを変更する必要があります:

const ws = new WebSocket('ws://localhost:3000');

GridDBによるデータの保存

GridDBは、IMUセンサーからのモーションデータを将来の解析のために保存します。ファイル app/server/griddbservices.js は、GridDB の関数を含む libs/griddb.js のラッパーです。

解析したセンサーデータを GridDB に保存するコードは以下の通りです:

await saveData({ sensorData: JSON.stringify(parsedData) });

データはJSON文字列の形で保存されます。GridDBにどのようにデータが格納されているかは、libs/griddb.jsファイルを見てください。

function initContainer() {
    const conInfo = new griddb.ContainerInfo({
        name: containerName,
        columnInfoList: [
            ['id', griddb.Type.INTEGER],
            ['data', griddb.Type.STRING],
        ],
        type: griddb.ContainerType.COLLECTION,
        rowKey: true,
    });

    return conInfo;
}

GridDBからのデータ取得

GridDBから全てのデータを取得するコードは以下の通りです:

app.get('/data', async (req, res) => {
    log.info('Getting all data from GridDB');
    try {
        const result = await getAllData();
        res.json(result);
    } catch (error) {
        res.status(500).send('Error getting all data');
    }
})

URLhttp://localhost:3000/dataにアクセスすれば、ブラウザからデータを取り出すことができます。

ブログの内容について疑問や質問がある場合は Q&A サイトである Stack Overflow に質問を投稿しましょう。 GridDB 開発者やエンジニアから速やかな回答が得られるようにするためにも "griddb" タグをつけることをお忘れなく。 https://stackoverflow.com/questions/ask?tags=griddb

Leave a Reply

Your email address will not be published. Required fields are marked *