ImmersalのREST APIでマップ作成

#はじめに
前回の記事でImmersalのサンプルをレビューしました。今回はImmersalのREST APIを話します。最近の携帯のカメラで写ったが画像はかなりすごいです。そのせいで、写った画像のサイズもかなり大きいです。一枚画像には3~10Mbまではかかりそう。自分のデータプランは最小の3Gb/月なので、アップロードが大変。アップロードスピードも携帯ネットワークを使用すれば遅いと思います。この記事でAPI叩きでマッピング方法を纏めます。

前回の記事
https://qiita.com/brocolly/items/d97f406aa18440d291a3

参考
https://immersal.gitbook.io/sdk/unity/unity-sample-project
https://immersal.gitbook.io/sdk/cloud-service/rest-api


#準備

* Unity 2019.4
* AR Foundation 4
* ARCoreかARKitをサポートされる携帯デバイス
* Immersal Developerアカウント

この記事ではARCoreサポートされたAndroid携帯、Xiaomi Redmi Note 7を利用します。

#手順

手順を話す前に、基本の流れを説明します。

1. Androidアプリで撮影
1. 画像とPoseからJSON作成、読み込む
1. REST APIでアップロード (Python)
1. モデル作成
1. Server LocalizationとVPSデモ

##Androidアプリで撮影
SampleアプリのMappingシーンをベースとして、画像をアップロードしないように、新しスクリプトを作ります。

```cs:ImmersalRestExt.cs
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Net.Http;
using System.IO;
using System.Text;
using UnityEngine;

namespace Immersal.REST
{
    [Serializable]
    public struct SDKImageRequestExt
    {
    //コード入れる
    }

    public class JobCaptureExtAsync : JobAsync
    {
    //コード入れる
    }
}
```

まずはJSONスキームを作成します。```Assets/ImmersalSDK/Core/Scripts/Rest.cs```と言うスクリプトを開けて、```SDKImageRequest```を検索します。これが元のJSONスキームです。新しいのJSONスキームで、```endpoint```の編集を削除します。新しコードは下になります。このコードは上の```ImmersalRestExt.cs```に入れます。

```cs
...
[Serializable]
public struct SDKImageRequestExt
{
    public string token;
    public int run;
    public int index;
    public bool anchor;
    public double px;
    public double py;
    public double pz;
    public double r00;
    public double r01;
    public double r02;
    public double r10;
    public double r11;
    public double r12;
    public double r20;
    public double r21;
    public double r22;
    public double fx;
    public double fy;
    public double ox;
    public double oy;
    public double latitude;
    public double longitude;
    public double altitude;
    public string b64;
}
...
```

次は画像アップロードの機能をJSON作成に変更します。ベースコードは```Assets/ImmersalSDK/Core/Scripts/RestJobAsync.cs```であります。このコードでImmersalのアップロード機能を新しいコードで削除します。新しコードは下になります。このコードは上の```ImmersalRestExt.cs```に入れます。

```cs
...
public class JobCaptureExtAsync : JobAsync
{
    public int run;
    public int index;
    public bool anchor;
    public Vector4 intrinsics;
    public Matrix4x4 rotation;
    public Vector3 position;
    public double latitude;
    public double longitude;
    public double altitude;
    public string encodedImage;
    public string imagePath;
    public Action<SDKImageResult> OnResult;

    public override async Task RunJobAsync()
    {
        Debug.Log("*************************** JobCaptureAsync ***************************");
        this.OnStart?.Invoke();

        SDKImageRequestExt r = new SDKImageRequestExt();
        r.token = "自分のtokenを入れてください";
        r.run = this.run;
        r.index = this.index;
        r.anchor = this.anchor;
        r.px = position.x;
        r.py = position.y;
        r.pz = position.z;
        r.r00 = rotation.m00;
        r.r01 = rotation.m01;
        r.r02 = rotation.m02;
        r.r10 = rotation.m10;
        r.r11 = rotation.m11;
        r.r12 = rotation.m12;
        r.r20 = rotation.m20;
        r.r21 = rotation.m21;
        r.r22 = rotation.m22;
        r.fx = intrinsics.x;
        r.fy = intrinsics.y;
        r.ox = intrinsics.z;
        r.oy = intrinsics.w;
        r.latitude = latitude;
        r.longitude = longitude;
        r.altitude = altitude;
        byte[] image = File.ReadAllBytes(imagePath);
        r.b64 = System.Convert.ToBase64String(image);

        //JSON作成とアプリのデータパスに保存します。
        string jsonData = JsonUtility.ToJson(r);
        var pathJSON = Application.persistentDataPath + "/Capture_"+ this.index.ToString() +".json";
        StreamWriter writer = new StreamWriter(pathJSON, false);
        writer.WriteLine(jsonData);
        writer.Close();   

    }
}
...
```

これでアップロード機能JSON作成に変更します。次は```Assets/ImmersalSDK/Samples/Scripts/Mapping/Mapper.cs```を編集します。```protected override async void Capture(bool anchor)```の下に```JobCaptureAsync```から```JobCaptureExtAsync```に変更します。


```cs
JobCaptureExtAsync j = new JobCaptureExtAsync();
//JobCaptureAsync j = new JobCaptureAsync();
```


これでコード編集は終わり、アプリをビルドします。ビルドする時PlayerSettingsでPackage Nameを覚えてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1156398/bac8f192-671f-d9be-405d-e81620c955c0.png)

これでは実験します。Sample通り複数の画像を取ってください。撮影した時、画像の数はここに更新してないので、ご注意。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1156398/a391c24f-d553-d71c-5208-c6c2472b946a.png" alt="drawing" width="300"/>


##画像とPoseからJSON作成、読み込む

JSONファイルはこれで保存しました。Androidをパソコンで繋がって、データを読み込みます。保存したデータのパスは```Android/data/「packagename」/files```です。「packagename」は、先ほど説明した、PlayerSettingsでビルドする前です。「Capture_~」ファイルは全て画像とPoseのJSONデータです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1156398/288869c0-d78c-975f-5ec8-8c84229f5a9f.png)

そのファイルを全てパソコンの同じフォルダーで移動してください。

##REST APIでアップロード (Python)
まずはワークスペースをリセットしよう。

参考
https://immersal.gitbook.io/sdk/cloud-service/rest-api/python-examples#clear-the-workspace-image-bank

```python:clear-workspace.py
import requests
import json

user_token = "自分のtoken"


def ClearWorkspace(url, token, deleteAnchorImage):
    complete_url = url + '/clear'

    data = {
        "token" : token,
        "bank" : 0, # default workspace/image bank
        "anchor" : deleteAnchorImage
    }

    json_data = json.dumps(data)

    r = requests.post(complete_url, data=json_data)
    print(r.text)
    
ClearWorkspace('https://api.immersal.com', user_token, True)
```

コードを実行して、ワークスペースがリセットになる。次は画像アップロードをします。
参考
https://immersal.gitbook.io/sdk/cloud-service/rest-api/python-examples#submit-an-image-to-workspace-image-bank

```python:submit-image.py
import requests
import json
import base64
import os

folder_data = "OfficeLocker" #集まったデータのフォルダ名


user_token = "自分のtoken"

def SubmitImage(url, token, dataPath):
    complete_url = url + '/captureb64'
    f = open(dataPath, "r")
    data = json.load(f)
    data["run"] = 0;
    json_data = json.dumps(data)
    # print(json_data)

    r = requests.post(complete_url, data=json_data)
    print(r.text)


for subdirs, dirs, files in os.walk(folder_data):
    for file in files:
        if file.endswith((".json")):
            dataPath = os.path.join(subdirs, file)
            SubmitImage('https://api.immersal.com', user_token, dataPath)

print("================================")
print("Upload Complete")
```

コードを実行して、画像のアップロードします。アップロード時間は画像の数によって異なります。
画像アップロードが完了した場合は、次のマップ作成をします。

##マップとモデル作成
13枚の画像でマップとモデルを作成します。作成したマップはポータルで見られる。

参考
https://immersal.gitbook.io/sdk/cloud-service/rest-api/python-examples#start-map-construction

```python:create-map.py
import requests
import json
import base64
import os

map_name = "OfficeLocker" #マップ/モデル名

user_token = "自分のtoken"

def StartMapConstruction(url, token, mapName, windowSize):
    complete_url = url + '/construct'

    data = {
        "token": token,
        "bank": 0,
        "name": mapName,
        "window_size" : windowSize,
        "featureCount": 1024,
        "preservePoses": False
    }

    json_data = json.dumps(data)

    r = requests.post(complete_url, data=json_data)
    print(r.text)

StartMapConstruction('https://api.immersal.com', user_token, map_name, 0)

```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1156398/4295d790-a95d-2233-800d-5113ed9d7900.png)

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1156398/6b3efe69-3fa5-01f4-710d-d2e49f94e7b9.png" alt="drawing" width="700"/>

##Server LocalizationとVPSデモ
まずはREST APIでServer Localizationを試します。まず、さっきのロッカーの画像を撮影しました。
参考
https://immersal.gitbook.io/sdk/cloud-service/rest-api/python-examples#server-localization

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1156398/5c2baae0-4993-0528-ef9e-5c66568dee16.jpeg" alt="drawing" width="500"/>

```python:server-localize.py
import requests
import json
import base64

user_token = "自分のtokenを入れて"

def ConvertToBase64(src_filepath):
    with open(src_filepath, 'rb') as imageFileAsBinary:
        fileContent = imageFileAsBinary.read()
        b64_encoded_img = base64.b64encode(fileContent)

        return b64_encoded_img

def ServerLocalize(url, token, imagePath):
    complete_url = url + '/localizeb64'

    data = {
        "token": token,
        "fx": 1455.738159, # image focal length in pixels on x axis
        "fy": 1455.738159, # image focal length in pixels on y axis
        "ox": 962.615967, # image principal point on x axis
        "oy": 694.292175, # image principal point on y axis
        "b64": str(ConvertToBase64(imagePath), 'utf-8'),
        "mapIds": [{"id": 1234}, {"id": 4567}] # 自分のマップリストからLocalizeする
    }

    json_data = json.dumps(data)

    r = requests.post(complete_url, data=json_data)
    print(r.text)

ServerLocalize('https://api.immersal.com', user_token, 'image_test.jpg')
```

コード実行します。画像はマップにいるなら、結果は下になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1156398/ff56879d-f783-1c7d-c5df-3ba8699af43a.png)

画像はマップのリストの中に一部じゃない場合は結果はしたになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1156398/660eb78f-e40b-862b-ee63-7c49f3ac09de.png)




前回の記事でモデル配置を説明したので、あのサンプルに参考して、今回はVPS(Visual Positioning Service)を試します。

参考
https://qiita.com/brocolly/items/d97f406aa18440d291a3#content-placement--%E3%83%A2%E3%83%87%E3%83%AB%E9%85%8D%E7%BD%AE

https://twitter.com/hf_mandak/status/1427596789713039360?s=20