NNC+Spresenseでiris(アヤメ)の分類

導入

Spresenseでのニューラルネットワーク推論を試してみました。 Spresenseはカメラモジュールが優秀なこともあり、画像分類の記事が多かったため、非画像データを入力としたニューラルネットワーク動作の備忘録として記録します。

NNC(Neural Network Console)での学習

学習データをCSVで用意します。 入力を"xi"、出力を"yi"の形で各列にまとめます。 今回の出力は分類ラベルで1次元なので、インデックスなしの"y"で用意しています。

x__0,x__1,x__2,x__3,y
5.0,3.2,1.2,0.2,0
5.5,3.5,1.3,0.2,0
...

NNCで以下の図のように、4入力1出力のネットワークを作成します。

今回作成したネットワーク

その後、実行を押して学習と評価を行います。

学習結果
評価結果(混合行列)

良いモデルができたら、学習結果を右クリックしてnnbファイルをエクスポートし、SDカードに保存します。

Spresenseでの実装

Spresenseの拡張ボードに、nnbファイルの入ったSDカードを挿入し、以下のプログラムを書き込みます。 ここでは、小数データを空白区切りでシリアル入力することで、結果をシリアル出力するようにしました。

以下のように動作することが確認できました。

実行の様子

【Unity】NuGetForUnity+MQTTnetでMQTTのSubscribe

導入

Spresenseを使った自作コントローラでUnityのゲームを操作したかったので、MQTTを使ってSpresenseからUnityへセンサデータを送りたいと考えました。 動作するプログラムができたので、その備忘録です。

動作環境は以下です。

  • Unity 2022.3.22f1
  • Visual Studio 2022
  • NuGetForUnity v4.1.1
  • MQTTnet 4.3.7.1207

環境構築

1.NugetForUnityをUnityプロジェクトにインポートします。Releaseページからunitypackageをダウンロードするなどの方法で導入できます。 github.com

2.Unityプロジェクト内のNuGetタブから、「Manage NuGet Package」を押し、MQTTnetを検索してinstallします。

一番上の1つだけでOK

実装

以下のスクリプトでMQTT通信のSubscribeが可能です。 brokerAddressとtopicは目的のブローカーアドレスとトピック名に変更してください。

using System;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using MQTTnet;
using MQTTnet.Client;

public class MQTTSubscribeTest : MonoBehaviour
{
    private IMqttClient mqttClient;
    private string brokerAddress = "目的のMQTTブローカーのアドレス";
    private string topic = "目的のトピック";

    async void Start()
    {
        // MQTTクライアントの設定
        var factory = new MqttFactory();
        mqttClient = factory.CreateMqttClient();

        // 接続オプションの設定
        var options = new MqttClientOptionsBuilder()
            .WithClientId(Guid.NewGuid().ToString())
            .WithTcpServer(brokerAddress)
            .WithCleanSession()
            .Build();

        // 接続
        await mqttClient.ConnectAsync(options);
        Debug.Log("Connected to MQTT broker.");

        // トピックのSubscribe
        await mqttClient.SubscribeAsync(topic, MQTTnet.Protocol.MqttQualityOfServiceLevel.AtLeastOnce);
        Debug.Log($"Subscribed to topic: {topic}");

        // メッセージ受信時のイベントを設定
        mqttClient.ApplicationMessageReceivedAsync += MqttClient_ApplicationMessageReceived;
    }

    private Task MqttClient_ApplicationMessageReceived(MqttApplicationMessageReceivedEventArgs e)
    {
        string message = Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSegment);
        Debug.Log($"Received message: {message} from topic: {e.ApplicationMessage.Topic}");
        return Task.CompletedTask;
    }

    async void OnApplicationQuit()
    {
        // アプリケーション終了時にMQTT接続を切断
        if (mqttClient != null && mqttClient.IsConnected)
        {
            await mqttClient.DisconnectAsync();
            Debug.Log("Disconnected from MQTT broker.");
        }
    }
}

【Unity】OpenVR SDK経由でコントローラの姿勢を取得する -GetActionPose()-

概要

Unityで、SteamVR Pluginを使い、コントローラの姿勢情報を取る方法のメモです。 OpenVR Overlayアプリを作るときに使ったものです。
openVR SDK1.4.18以降から、姿勢をとるGetActionPose()関数の名前が少し変わったようで少し調べたので、備忘録として残します。

環境は、
・Unity 2022.3.22f1
・SteamVR Plugin 2.8.0(sdk2.0.10)
です

コード

SteamVR上での右手と左手のコントローラ姿勢を取得し、その姿勢をUnity上のオブジェクトに反映するスクリプトです。
バインドやアクションセットは、デフォルトのものを使用しています。 単一のposeに対して、入力ソースを右手と左手で切り替えることで、各手の姿勢を取得しています。

gist.github.com

補足

openVR SDK1.4.18より前では、姿勢を取得する関数は、GetPoseAction()だったようです。
しかし、1.4.18以降は、2つの関数に分けられ、

・GetPoseActionDataRelativeToNow()
・GetPoseActionDataForNextFrame()

の2つが利用できます。

元のGetPoseAction()に対応するのはGetPoseActionDataForNextFrame()のようです。
GetPoseActionDataRelativeToNow()は、追加で引数に秒数を渡すことで、その秒数前の入力を取得できるようです。(いまいち分かっていませんが)

公式リポジトリWikiも更新がされておらず、少し迷いました。

参考

・openvr公式リポジトリ

github.com

ArduinoIDEのDFUドライバをインストールする

概要

ArduinoIDEをインストールしたときに、各種ドライバをインストールするためのポップアップが出てくると思います。この初回起動時に、誤ってドライバをインストールしないまま進めてしまったときに、後から手動でインストールするための方法を示します。

環境はWindowsで、使用していたマイコンはArduinoUNO R4 Minimaです。

やり方

ドライバをインストールするためのexeファイル自体は、ArduinoIDEと一緒にインストールされています。そのため、以下のパスにあるexeファイルを実行することで、ドライバをインストールできます。
管理者権限で実行すると良いと思います。 C:\Users\[ユーザー名]\AppData\Local\Arduino15\packages\arduino\hardware\renesas_uno\1.1.0\drivers\dpinst-amd64.exe

※AppDataフォルダがない場合、エクスプローラの設定で隠しフォルダを表示するようにしてください。
※1.1.0フォルダは、バージョンによって数字が異なることがあります。
※PCが32bitの場合は、dpinst-x86.exeを実行します。

補足

正直ドライバにどのような種類があるかは、あまり分かっておりません。
解決してしまったので細かいエラーメッセージを記録していないのですが、
「Cannot open DFU device」や「No DFU capable USB device available」と表示されたときに、この手順で解決しました。

Google PayでVISAが使えない問題を解決した話(OPPO Reno 5A)

概要

Google PayでVISAの決済だけうまくいかなかったのですが、Google Play開発者サービスをインストールし直すことで解決しました。その過程で起こった問題などを、備忘録として残しておきます。
使用していたスマホOPPO Reno 5Aです。
記事にすると思っていなかったので、スクリーンショットなどは少ないですが、ご容赦ください。

起こっていた問題

Googleウォレットアプリでの支払い、すなわちGoogle Payでの支払いにおいて、VISA(三井住友カード)の決済だけがうまくいかない問題に遭遇しました。
QUICPaySuicaなどは使用できていましたが、VISAのみ、決済時にエラーが発生していました。
ウォレット(Googleアカウント)から、決済できないVISAを削除して入れなおしてもダメでした。

解決法

調べてみると、Google Play開発者サービスの初期化で直ったという人がいました。 そのため、設定のアプリの項目から「Google Play開発者サービス」のキャッシュとデータを一度削除しました。
しかし動作せず、むしろ、Googleウォレットアプリの表示がおかしくなり、画像の画面から移動できなくなりました。

Google Play開発者サービスのデータを消したらこの画面から動けなくなった

その後、Google Play開発者サービスを再インストールすることを考えました。 しかし、Google Play開発者サービスは設定からアンインストールはできず、Google Playストアで検索してもストアページが表示されませんでした。 ですが、なぜかWebブラウザ経由でならストアページに飛ぶことができました。 play.google.com

ストアページに移動すると、なぜか「アンインストール中」と表示されており、インストールもアンインストールもできない状態でした。 また、自分はベータテストに参加していたので、ベータを解除したり再登録してみても、この表示は変わりませんでした。

そこから、Google Playストアをリセットするため、設定のアプリの項目から「Google Playストア」のキャッシュとデータを削除しました。 その後、再びGoogle Play開発者サービスのストアページに飛ぶことで、インストールし直すことができました。 これによって、Google PayでのVISA決済が正常に行えるようになりました。

まとめ

Google PayのVISA決済がうまくいかない問題は、Google Play開発者サービスをインストールし直すことで直りました。 そのために、
1. ブラウザ経由でGoogle Play開発者サービスのGoogle Playストアページに移動
2. 1.で表示がおかしければ、Google Playストアのキャッシュとデータを削除した 後、Google Play開発者サービスを再インストール
3. ストアページで、Google Play開発者サービスをインストールまたは再インストール
という手順が必要でした。

ただし、私の場合はこれで解決した、というだけですので、同様の症状に遭遇した人は、あくまで自己責任でお試しください。

SpresenseでI2C通信 (Qwiic接続基板/M5StickC Joystick Hat)

導入

Spresenseを購入したのですが、エッジAIとしてはまだうまく使いこなせておらず…
ひとまず、M5Stackシリーズで使っていたI2Cデバイスを使ってみるところから始めてみました。
また、Spresense用のQuiic接続用の基盤も購入したので、そちらも使用してみました。

使用したもの

・Spresenseメインボード
・Spresense拡張ボード

SonyのAI推論も可能な高性能ボードコンピュータです。基本はArduinoと同じように使用可能です。
拡張ボードをつけることで、ArduinoUnoと同様のピン配置で使用することができます。
(今回やる内容では、拡張ボードは不要ですが、取り外すのが手間だったのでつけたまま使用しています。)
www.sony-semicon.com

・M5StickC Joystick Hat
I2C通信で、X,Y軸の変位(-127 - 127)と押し込み(0 or 1)を取得できるジョイスティックです。
docs.m5stack.com

・SPRESENSE用Qwiic接続基板
SpresenseのI2C通信に、Qwiic端子を使用するための基盤です。
SPRESENSE用Qwiic接続基板www.switch-science.com

配線

I2Cなので、4本のケーブルを繋ぐだけです。
まずは、Quiic接続基盤なしで、Spresenseのメインボードに直接ピンを繋いでみました。
Joystick Hatは、M5StickCに接続するためにピンが8本ありますが、実際に使用するのは4本だけです。

Joystick Hat Spresense
GND GND
VCC 3.3V
SDA D14
SCL D15

https://www.sony-semicon.com/ja/products/spresense/index.html

以下のように接続しました。
黄色がGND、紫色が3.3V、緑色がSCL、青色がSDAです。

Spresenseのピンへ直接接続

Qwiic接続基盤を用いて、Qwiic-4ピン(メス)ケーブルで接続すると、以下のようになります。
黒色がGND、赤色が3.3V、黄色がSCL、青色がSDAです。

Qwiic接続基盤を用いて接続

実践

以下のようなコードで、Joystick Hatの値を読み取って、シリアル通信でデータを表示するようにしました。

動作することが確認できました!

シリアルモニタで動作を確認

【Unity】放物線状にRaycastしたい!

導入

VRゲームをプレイしていると、テレポート先やジャンプ先を決めるときなどに、図のようなUIをよく見かけます。手から放物線のような曲線を放ち、それがヒットした位置に移動する、というものです。

VRでテレポート先を決めるUI

高い位置にある床面を選択することができたり、過度に遠すぎる地点は選択できないなどの特徴があり、実に素晴らしいUIだと思います。 しかし、弧を描くように判定を飛ばして、その衝突位置を取得する、という機能は、Unityには標準ではないように思います。 最も近いのは、直線的にRayを飛ばして、衝突地点の情報を得るPhysics.Raycast()ですよね。 そこで、放物線をサンプリングした点列をもとに、手前から順に短くRaycast()を繰り返すことで、放物線状にRaycast()を行ってみました。

曲線を線分で近似してRaycastを繰り返す
その記録とコードを紹介します。

実装

放物線状のRaycastを行うために、以下のCurveRaycasterクラスを作成しました。

using UnityEngine;

public class CurveRaycaster : MonoBehaviour
{
    //引数のVector3配列の手前から2点ずつ取り出し、Raycastを繰り返し、ヒットしたかどうかを返す関数
    //ヒットした場合、ヒット時の始点のindexをhitStartIndex、ヒット情報をhitで返す
    //ヒットしなかった場合、hitStartIndexに-1を入れ、hitには適当なものが入るので注意
    public static bool ContinuesRaycast(Vector3[] points, out int hitStartIndex, out RaycastHit hit)
    {
        for (int i = 0; i < points.Length - 1; i++)
        {
            Ray ray = new Ray(points[i], points[i + 1] - points[i]);

            if (Physics.Raycast(ray, out hit, Vector3.Distance(points[i], points[i+1])))
            {
                //hit位置までの線を1フレーム表示(エディタ上のみ)
#if UNITY_EDITOR
                Debug.DrawLine(points[i], hit.point, Color.red, Time.deltaTime);
#endif
                hitStartIndex = i;
                return true;
            }
            else
            {
                //2点間を結ぶ線を1フレーム表示(エディタ上のみ)
#if UNITY_EDITOR
                Debug.DrawLine(points[i], points[i+1], Color.red, Time.deltaTime);
#endif
            }
        }
        hitStartIndex = -1;
        //hitには適当なものを入れる(よくない?)
        hit = new RaycastHit();
        return false;
    }

    //初期位置と初速度と時刻から、放物線上の点を求める。
    public static Vector3 CalculateParabolaPosByTime(Vector3 initialPosition, Vector3 initialVelocity, float t)
    {
        Vector3 gravity = Physics.gravity;

        //t秒後の座標
        Vector3 pos_t = initialPosition + initialVelocity * t + (gravity * t * t) / 2.0f;

        return pos_t;
    }

    //初期位置、初期速度から求まる放物線上の、interval秒ごとの位置をnum個並べた座標列を返す
    public static Vector3[] CalculateParabolaPosArray(Vector3 initialPosition, Vector3 initialVelocity, int num, float interval)
    {
        Vector3[] posArray = new Vector3[num];
        for (int i = 0; i < num; i++)
        {
            posArray[i] = CalculateParabolaPosByTime(initialPosition, initialVelocity, i * interval);
        }
        return posArray;
    }
}

重要なのはContinuesRaycast()ですね。Vector3配列を引数で受け取り、「配列の頭から2点ずつ取り出し、その2点間でRaycastを行う」を繰り返し行います、 最初にhitした時点で、trueを返し、そのインデックスとhit情報をoutパラメータで返します。 今回は放物線を用いますが、任意の点列を入力にできます。

そして、放物線上の点を求めるCalculateParabolaPosByTime()と、その点列を取得するCalculateParabolaPosArray()を実装しました。

動作確認

今回は動作していることが分かりやすいように、図のような矢印状のオブジェクトを、ヒット地点に表示するようにしてみました。

使用したUIオブジェクト
動作確認用に作成したスクリプトは以下です。

using UnityEngine;

public class Test : MonoBehaviour
{
    [SerializeField] float initialSpeed;
    float interval = 0.05f;
    int num = 100;
    int lastHitStartIndex = -1;
    RaycastHit lastHit;

    //ヒット地点に表示したいUIオブジェクト
    [SerializeField] GameObject arrowUI;

    void Update()
    {
        //放物線の点列を生成
        Vector3[] points = CurveRaycaster.CalculateParabolaPosArray(transform.position, transform.forward * initialSpeed, num, interval);
        //生成した点列についてContinuesRaycast()
        if(CurveRaycaster.ContinuesRaycast(points, out lastHitStartIndex, out lastHit))
        {
            Debug.Log($"index:{lastHitStartIndex}, hit:{lastHit.collider.name}");

            //UIオブジェクトをアクティブにして、ヒット位置に移動させ、ヒット位置の法線方向を向かせる
            arrowUI.SetActive(true);
            arrowUI.transform.position = lastHit.point;
            arrowUI.transform.rotation = Quaternion.LookRotation(lastHit.normal);
        }
        else
        {
            //ヒットしてないときはUIを非アクティブに
            arrowUI.SetActive(false);
        }
    }
}

以下のように、正しく動作していますね。

動作確認

最後に

今回作成した内容では、
・LayerMaskを指定できない
・LineRendererではなくDebug.DrawRayを使っているので、Game内では線が見えない
という問題があるので、実際にゲームに実装する場合は、もう少し修正が必要ですね。 また、Spline機能をうまく使えたら良かったのですが、今回は考慮していません。 Spline側に、Vector3[]に変換する機能があるだろうと思うので、組み合わせることは可能だとは思いますが…

あまりこの内容を書いてる人がいなかったように思ったので執筆しました。
参考になれば幸いです。