• トップ
  • ブログ一覧
  • UnityのARCoreを使って長さ計測Androidアプリを作ってみる!
  • UnityのARCoreを使って長さ計測Androidアプリを作ってみる!

    メディアチームメディアチーム
    2020.02.27

    IT技術

    Unity で ARcorを使い「長さ測定アプリ」を作る!

    今回は「AR(拡張現実)」を用いた、「長さ計測 Android アプリ」を開発をしていきます!

    開発については、Unity(ユニティ)上で、「ARCore(エーアールコア)」を用います。

    ※記事を読む上での注意※

    1. すべてのAndroidでARCoreが使えるわけではありません。ARCore対応端末については以下URLを参照してください。
      https://developers.google.com/ar/discover/supported-devices
    2. 今回の記事ではARCoreのインストール方法についてはサポートしていません。
      ARcoreが公表している、Quickstart for Andoid
      https://developers.google.com/ar/develop/unity/quickstart-android
      を参照していただければなと思います。

    ARCore(エーアールコア)とは?

    「ARCore」は、Google がアンドロイド端末向けに提供している、ARプラットフォームです。

    特別なカメラやセンサーを用いずに、既存のスマートフォンに内蔵されているカメラや、モーションセンサーのみでARコンテンツを構築できます。

    技術的には、「VIO(visual inertial odmetry)」が用いられています。

    ざっくりいうと、「カメラ画像」+「加速度センサ」+「ジャイロセンサ」で、自己位置推定をしています。

    ちなみに、iOS 向けの ARプラットフォームには、Apple が開発している ARKit(エーアールキット)があります。

    関連記事

    featureImg2019.07.09iOSのAR機能「ARKit」とは?「AR」とは現実世界を拡張する技術ここ数年、AR(拡張現実)という技術が注目されています。ARとは、「Augmente...

    ARcore で長さを計測するための全体の流れ

    今回は、ARで計測したい個所の、「始点」と「終点」にオブジェクトを生成して、その間の長さを計算したいと思います。

    この記事で紹介する内容はコチラ!

    1.ゲームオブジェクトの座標を管理する
    2.座標をほかのクラスに引用する
    3.引用した座標の間の距離を計算して、UIで表示する。

    ARCore のサンプルアプリ「HelloAR」をカスタムする

    今回は、ARCore 内のサンプルアプリ「HelloAR(ハローエーアール)」をカスタムしていきます。

    HelloAR は、水平面を検出して、その水平面上をタップすることで、「ARオブジェクト」を表示することができるアプリです。

    早速 Unity 上で、「HelloAR」のシーンを開いてみましょう。

    まず、「Unity Project Window」上で

    「Assets」>「GoogleARCore」>「Examples」>「HelloAR」>「Scenes」>「HelloAR」

    と選択します。

    「HelloAR」のアイコンをダブルクリックすると、HelloAR のシーンが開きます。

    ARCoreに長さ測定機能を実装

    早速、実装していきましょう!

    HelloAR Controller.cs」を元にして作成します。

    オブジェクトの座標を管理する

    「HelloARController.cs」内で、「座標を格納するリスト」を作成します。

    オブジェクトが表示された地点の「ワールド座標」を、リストで管理します。

    一旦、「ゲームオブジェクト」をリストに入れて、リストから位置情報を引き出しています。

    HelloARController.cs のコード

    1namespace GoogleARCore.Examples.HelloAR
    2{
    3 using System.Collections.Generic;
    4 using GoogleARCore;
    5 using GoogleARCore.Examples.Common;
    6 using UnityEngine;
    7 using UnityEngine.EventSystems;
    8#if UNITY_EDITOR
    9 // Set up touch input propagation while using Instant Preview in the editor.
    10 using Input = InstantPreviewInput;
    11#endif
    12 /// <summary>
    13 /// Controls the HelloAR example.
    14 /// </summary>
    15 public class HelloARController : MonoBehaviour
    16 {
    17    /// <summary>
    18    /// The first-person camera being used to render the passthrough camera image (i.e. AR
    19    /// background).
    20    /// </summary>
    21    public Camera FirstPersonCamera;
    22
    23    /// <summary>
    24    /// A prefab to place when a raycast from a user touch hits a vertical plane.
    25    /// </summary>
    26    public GameObject GameObjectVerticalPlanePrefab;
    27
    28    /// <summary>
    29    /// A prefab to place when a raycast from a user touch hits a horizontal plane.
    30    /// </summary>
    31    public GameObject GameObjectHorizontalPlanePrefab;
    32
    33    /// <summary>
    34    /// A prefab to place when a raycast from a user touch hits a feature point.
    35    /// </summary>
    36    public GameObject GameObjectPointPrefab;
    37
    38    /// <summary>
    39    /// The rotation in degrees need to apply to prefab when it is placed.
    40    /// </summary>
    41    private const float k_PrefabRotation = 180.0f;
    42
    43    /// <summary>
    44    /// True if the app is in the process of quitting due to an ARCore connection error,
    45    /// otherwise false.
    46    /// </summary>
    47    private bool m_IsQuitting = false;
    48
    49    //オブジェクトの座標を入れる箱を作る
    50    public Vector3 pos1 = new Vector3(0,0,0);
    51    public Vector3 pos2 = new Vector3(0,0,0);
    52    //リスト作成
    53    List<GameObject> list_toggle_ = new List<GameObject>();
    54
    55    /// <summary>
    56    /// The Unity Awake() method.
    57    /// </summary>
    58    public void Awake()
    59    {
    60        // Enable ARCore to target 60fps camera capture frame rate on supported devices.
    61        // Note, Application.targetFrameRate is ignored when QualitySettings.vSyncCount != 0.
    62        Application.targetFrameRate = 60;
    63    }
    64    /// <summary>
    65    /// The Unity Update() method.
    66    /// </summary>
    67    public void Update()
    68    {
    69        _UpdateApplicationLifecycle();
    70        // If the player has not touched the screen, we are done with this update.
    71        Touch touch;
    72        if (Input.touchCount < 1 || (touch = Input.GetTouch(0)).phase != TouchPhase.Began)
    73        {
    74            return;
    75        }
    76        // Should not handle input if the player is pointing on UI.
    77        if (EventSystem.current.IsPointerOverGameObject(touch.fingerId))
    78        {
    79            return;
    80        }
    81        // Raycast against the location the player touched to search for planes.
    82        TrackableHit hit;
    83        TrackableHitFlags raycastFilter = TrackableHitFlags.PlaneWithinPolygon |
    84            TrackableHitFlags.FeaturePointWithSurfaceNormal;
    85        if (Frame.Raycast(touch.position.x, touch.position.y, raycastFilter, out hit))
    86        {
    87            // Use hit pose and camera pose to check if hittest is from the
    88            // back of the plane, if it is, no need to create the anchor.
    89            if ((hit.Trackable is DetectedPlane) &&
    90                Vector3.Dot(FirstPersonCamera.transform.position - hit.Pose.position,
    91                    hit.Pose.rotation * Vector3.up) < 0)
    92            {
    93                Debug.Log("Hit at back of the current DetectedPlane");
    94            }
    95            else
    96            {  
    97                    // Choose the prefab based on the Trackable that got hit.
    98                    GameObject prefab;
    99                    if (hit.Trackable is FeaturePoint)
    100                    {
    101                        prefab = GameObjectPointPrefab;
    102                    }
    103                    else if (hit.Trackable is DetectedPlane)
    104                    {
    105                        DetectedPlane detectedPlane = hit.Trackable as DetectedPlane;
    106                        if (detectedPlane.PlaneType == DetectedPlaneType.Vertical)
    107                        {
    108                            prefab = GameObjectVerticalPlanePrefab;
    109                        }
    110                        else
    111                        {
    112                            prefab = GameObjectHorizontalPlanePrefab;
    113                        }
    114                    }
    115                    else
    116                    {
    117                        prefab = GameObjectHorizontalPlanePrefab;
    118                    }
    119                    // Instantiate prefab at the hit pose.                    	
    120                    var gameObject = Instantiate(prefab, hit.Pose.position, hit.Pose.rotation);
    121                    //listにオブジェクトを追加
    122                    list_toggle_.Add(gameObject);
    123                    // Compensate for the hitPose rotation facing away from the raycast (i.e.
    124                    // camera).
    125                    gameObject.transform.Rotate(0, k_PrefabRotation, 0, Space.Self);
    126                    // Create an anchor to allow ARCore to track the hitpoint as understanding of
    127                    // the physical world evolves.
    128                    var anchor = hit.Trackable.CreateAnchor(hit.Pose);
    129                    // Make game object a child of the anchor.
    130                    gameObject.transform.parent = anchor.transform;
    131                	
    132            }
    133        }
    134        //オブジェクトの座標
    135        pos1 = list_toggle_[0].transform.position;
    136        pos2 = list_toggle_[1].transform.position;
    137    }
    138    /// <summary>
    139    /// Check and update the application lifecycle.
    140    /// </summary>
    141    /// private voidからpublic voidに変更
    142    public void _UpdateApplicationLifecycle()
    143    {
    144        // Exit the app when the 'back' button is pressed.
    145        if (Input.GetKey(KeyCode.Escape))
    146        {
    147            Application.Quit();
    148        }
    149        // Only allow the screen to sleep when not tracking.
    150        if (Session.Status != SessionStatus.Tracking)
    151        {
    152            Screen.sleepTimeout = SleepTimeout.SystemSetting;
    153        }
    154        else
    155        {
    156            Screen.sleepTimeout = SleepTimeout.NeverSleep;
    157        }
    158        if (m_IsQuitting)
    159        {
    160            return;
    161        }
    162        // Quit if ARCore was unable to connect and give Unity some time for the toast to
    163        // appear.
    164        if (Session.Status == SessionStatus.ErrorPermissionNotGranted)
    165        {
    166            _ShowAndroidToastMessage("Camera permission is needed to run this application.");
    167            m_IsQuitting = true;
    168            Invoke("_DoQuit", 0.5f);
    169        }
    170        else if (Session.Status.IsError())
    171        {
    172            _ShowAndroidToastMessage(
    173                "ARCore encountered a problem connecting.  Please start the app again.");
    174            m_IsQuitting = true;
    175            Invoke("_DoQuit", 0.5f);
    176        }
    177    }
    178    /// <summary>
    179    /// Actually quit the application.
    180    /// </summary>
    181    private void _DoQuit()
    182    {
    183        Application.Quit();
    184    }
    185    /// <summary>
    186    /// Show an Android toast message.
    187    /// </summary>
    188    /// <param name="message">Message string to show in the toast.</param>
    189    private void _ShowAndroidToastMessage(string message)
    190    {
    191        AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
    192        AndroidJavaObject unityActivity =
    193            unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
    194        if (unityActivity != null)
    195        {
    196            AndroidJavaClass toastClass = new AndroidJavaClass("android.widget.Toast");
    197            unityActivity.Call("runOnUiThread", new AndroidJavaRunnable(() =>
    198            {
    199                AndroidJavaObject toastObject =
    200                    toastClass.CallStatic<AndroidJavaObject>(
    201                        "makeText", unityActivity, message, 0);
    202                toastObject.Call("show");
    203            }));
    204        }
    205    }
    206 }
    207}

    UIを設定

    「2地点座標」から、オブジェクト間の長さを計算します。

    位置情報「pos1 , pos2」を HelloARController.cs から引用するため、「名前空間」を取得したり、HelloARcontroller を「script変数」に入れたりしています。

    まず、スクリプトを作りましょう。

    「Project」ウィンドウ上で、「create」>「C# Script」でスクリプトを作成。

    そして、この名前を「UIcontroller」に変更します。

    UIcontroller.cs コード

    1using System.Collections;
    2using System.Collections.Generic;
    3using UnityEngine;
    4using UnityEngine.UI; //UI使用する
    5using GoogleARCore.Examples.HelloAR; //名前空間を取得する
    6
    7 public class UITest : MonoBehaviour
    8 {
    9 GameObject AR; //HelloARcontrollerを入れる変数
    10 GameObject distance; //UIを入れる
    11
    12 HelloARController script; //HelloARControllerが入る変数
    13 // Start is called before the first frame update
    14 void Start()
    15 {
    16    this.distance = GameObject.Find("Distance");
    17    this.AR = GameObject.Find("HelloAR Controller");
    18    script = AR.GetComponent<HelloARController>();
    19    //GameObjectのHelloAR Controllerの中にあるHelloARController.csを取得して変数に格納
    20
    21 }
    22 // Update is called once per frame
    23 void Update()
    24 {
    25    //HelloARController.csから座標を取得
    26    Vector3 p1 = script.pos1;
    27    Vector3 p2 = script.pos2;
    28    	
    29    //長さを計算
    30    float length = (p1 - p2).magnitude;
    31    //UIのdistanceを変更
    32    this.distance.GetComponent<Text>().text = (length * 100f).ToString("F2") + "cm";
    33 }
    34}

    GameObject を作成

    次に、「UIcontroller」を動かす「GameObject」を作成します。

    「Hierarcy」ウィンドウから、「Create」>「Creat Empty」で作成します。

    「GameObject」の名前を、「UIcontroller」に変更して、「UIcontroller」のスクリプトをアタッチします。

    最後に Distance

    「Hierarcy」ウィンドウから、「Create」>「UI」>「Text」と選択。

    名前を「Distance」に変更します。

    下の写真の、赤枠部分のようになっていれば問題ありません。

    クオリティアップ

    上の実装で、長さは計測できるようになりましたが、少し体裁を整えたいと思います!

    既存の「prefab」だとビジュアル的に良くないので、「footprint」に変更したいと思います。

    こちら「Project」ウィンドウから、「Favorites」>「All Models」>「footprint」で見つけられます。

    また、「footprint」はそのままだと大きいので、スケールを「0.1」に変更します。

    ARcoreで完成した「長さ測定アプリ」を使ってみる

    さっそく計測してみましょう!

    試しに「1万円札」を計測してみたいと思います。

    画像の上部に、赤字で「15.74cm」と表示されました!

    1万円札の一辺は 16cm なので、「誤差2.6mm」での計測となりました。

    大体、「誤差1~3mm」ぐらいの精度のようです。

    この精度は、「対象のテクスチャ」に依るところが大きいようです。

    ※例えば、金属は光を反射し計測しづらいなど。

    さいごに

    今回は、簡易的にできる「長さ計測アプリ」を作成してみました。

    今後は、「特徴点」から計算される「記述子」を用いた対応付けを変えたりしていこうと思っています。

    また、「物体検出」や AR の技術的な所もまとめていけたらなと思います。

    お楽しみに!

    こちらの記事もオススメ!

    featureImg2020.08.14スマホ技術 特集Android開発Android開発をJavaからKotlinへ変えていくためのお勉強DelegatedPropert...

    featureImg2020.07.28Unity 特集知識編おすすめのゲームエンジン5選実装編※最新記事順に並べています。VR環境でテキストを表示する方法非同期式の入れ子処...

    featureImg2020.07.17ライトコード的「やってみた!」シリーズ「やってみた!」を集めました!(株)ライトコードが今まで作ってきた「やってみた!」記事を集めてみました!※作成日が新し...

    ライトコードでは、エンジニアを積極採用中!

    ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。

    採用情報へ

    メディアチーム
    メディアチーム
    Show more...

    おすすめ記事

    エンジニア大募集中!

    ライトコードでは、エンジニアを積極採用中です。

    特に、WEBエンジニアとモバイルエンジニアは是非ご応募お待ちしております!

    また、フリーランスエンジニア様も大募集中です。

    background