【ARCore】Light estimationを利用

# ARCoreのLight estimationを利用

ARCoreに初期から搭載されているLight estimation機能を触ってみました。
なお、AndroidビルドのためのUnity設定等々は実施済みです。

## キーワード

ARCore, Unity, AR, ビデオシースルー, 環境光推定, Light estimation

## Light estimationとは

[Light estimation](https://developers.google.com/ar/discover/concepts#light_estimation) などを参照ください。
カメラ画像から環境光の強さや色合いを推定する機能です。

## バージョン情報など

- バージョン情報

|機能|バージョン|
|:--|:--|
|ARCore|1.1.0|
|Unity|2017.3.0f3|
|OS|Windows 10 バージョン1709|
|端末|Galaxy S8 SC-02J (docomoのやつです)|

- 作業日

    2017年4月上旬 21:30頃(外光はほぼない状態)

## 環境光推定を利用したアプリ作成

### SDK導入

[ダウンロードページ](https://developers.google.com/ar/develop/downloads)より、arcore-unity-sdk-v1.1.0.unitypackageをダウンロードしました。
新規プロジェクトを作成し、SDKを全てインポートしました。

### プレハブ確認

<b>Assets > GoogleARCore > Prefabs > Environment Light </b>を確認してみました。
EnvironmentalLightなるスクリプトがアタッチされていますので、開いて確認してみたところ、
```
Frame.LightEstimate.PixelIntensity
Frame.LightEstimate.ColorCorrection
```
あたりで環境光の推定結果を取得しているようでした。
[公式のリファレンス](https://developers.google.com/ar/reference/)によるとLightEstimateは3つのプロパティを持っているようでした。
PixelIntensityがピクセル強度の平均を0から1の範囲で算出しているので、明るさはこの値を利用できるはずです。

なお、<b>ピクセル強度</b>については強度が低い=より暗い、強度が強い=より明るい程度の理解でいます。

### PixelIntensityの確認

とりあえずPixelIntensityの値を確認を行いました。<br>
ARCoreが提供しているプレハブである、ARCore Deviceをシーンに配置しました。
環境光推定値を出力するスクリプトShowPixelIntensity.csを作成し、生成した空のGameObjectにアタッチしました。<br>

#### 方法

シーン上にuGUIのtextを配置し、ここにPixelIntensityの値を表示させました。
また、ARCore内のプレハブに含まれていたEnvironmentalLightで計算されている、
Frame.LightEstimate.ColorCorrectionと最終的にシェーダに適用されている値も表示してみました。

```
using UnityEngine;
using GoogleARCore;
using UnityEngine.UI;

public class ShowPixelIntensity : MonoBehaviour
{
    [SerializeField]
    private Text pixelIntensityText;
    [SerializeField]
    private Text colorCorrectionText;
    [SerializeField]
    private Text globalColorText;
    private void Update()
    {
        if (Frame.LightEstimate.State == LightEstimateState.NotValid)
            return;

        pixelIntensityText.text = Frame.LightEstimate.PixelIntensity.ToString("#.000");
        colorCorrectionText.text = Frame.LightEstimate.ColorCorrection.ToString("#.000");

        const float middleGray = 0.466f;
        float normalizedIntensity = Frame.LightEstimate.PixelIntensity / middleGray;
        globalColorText.text = (Frame.LightEstimate.ColorCorrection * normalizedIntensity).ToString("#.000");

    }
}
```

表示用のUIをシーンに追加し、ビルド設定を行いビルドしました。


#### 結果

部屋の明かりを変化させ、値の変化を確認しました。<br>

|蛍光灯の状態|条件備考|PixelIntensity|
|:--|:--|:--|
|全点灯|最もPixelIntensityが高かった場所|0.75|
|全点灯|机見下ろし|0.5|
|全点灯|平均|0.65|
|常夜灯||0.13|
|消灯|モニタの明かりのみ|0.1|
|消灯||0.08|
|全点灯|カメラを完全にふさいだ状態|0.08|

以下、それぞれの状態のカメラ映像になります。

|0.75付近(全点灯)|0.60付近|0.50付近|0.40付近|0.20付近(常夜灯)|0.15付近(側面にモニタ光)|0.08付近|
|:--|:--|:--|:--|:--|:--|:--|:--|
|![bandicam 2018-04-07 21-20-21-806.jpg](https://qiita-image-store.s3.amazonaws.com/0/248057/640fe7a8-7aac-3b24-be90-6daed6ee891c.jpeg)|![bandicam 2018-04-07 21-21-18-809.jpg](https://qiita-image-store.s3.amazonaws.com/0/248057/53ef34a5-9177-b37b-8a29-6068d7777c9d.jpeg)|![bandicam 2018-04-07 21-21-18-809.jpg](https://qiita-image-store.s3.amazonaws.com/0/248057/69bfd9b5-5a5e-9768-2e0a-40dfc85c848a.jpeg)|![bandicam 2018-04-07 21-21-28-440.jpg](https://qiita-image-store.s3.amazonaws.com/0/248057/e1b5e344-921b-45fd-fd13-8e7c866be342.jpeg)|![bandicam 2018-04-07 21-21-42-516.jpg](https://qiita-image-store.s3.amazonaws.com/0/248057/f1fe1296-c865-2281-8d5e-5d6361ce724d.jpeg)|![bandicam 2018-04-07 21-21-58-528.jpg](https://qiita-image-store.s3.amazonaws.com/0/248057/993048e6-5079-66b6-753d-3f627237169f.jpeg)|![bandicam 2018-04-07 21-09-58-308.jpg](https://qiita-image-store.s3.amazonaws.com/0/248057/f31aa752-c964-b738-b218-43138b6b4346.jpeg)
|

#### 注意点

画像から明るさを類推しているため、白に近いカーテンを見ているときと、黒に近いカーテンを見ている時では、恐らくPixelIntensityの値は変化します。

### オブジェクト追加

スケルトン([BSHGAME Skeletons Pack](https://assetstore.unity.com/packages/3d/characters/humanoids/skeletons-pack-1818))をお呼びし、明るさに応じて目を光らせて頂きました。
目の光らせ方は、テラシュールブログ様の記事、[目やパーツの一部を光らせる](http://tsubakit1.hateblo.jp/entry/2015/09/10/233000)を参照します。<br>
スケさんの眼窩付近にFlareSmallを設定したPoint lightを配置しました。

LightEstimate.PixelIntensityが小さくなるほどライトを強くするスクリプトを作成し、先ほど作成したライトを参照しておきます。

```
[SerializeField]
private Light light;

light.intensity = 1f - (Frame.LightEstimate.PixelIntensity);
```




### 感想

- 最後のライト部分がかなり適当ですが、外界の明るさに応じた処理を作成できました。
外部の影響によって動きが変わるものは、単純でも意外に面白かったです。

- ARCoreではカメラが光量を自動的に調節してしまうので、必ずしも光源に向かった状態がPixelIntensity最大ではありません。
実際に利用する際は実地でのテストが必須だと思われます。

参考：光源を向いた場合のPixelIntensity
![bandicam 2018-04-07 21-08-33-935.jpg](https://qiita-image-store.s3.amazonaws.com/0/248057/4fd854f2-6929-cba3-ac1d-dbed886a8f79.jpeg)
同様の理由で、昼間と夜間の室内を比較した場合、昼間のPixelIntensityの方が常に高いとは限りません。

- 夜間だけ火のつく燈篭とか作れればカッコイイ。

- Motion trackingと組み合わせ、特定の地点から特定の方向を向いた場合の光量を記憶できれば、明るい場所に向かって動くゾンビとか作れそうな気もしました。

- ARCore1.1.0から搭載のInstant Previewが超便利です。
特に設定していなくてもポップアップが出現し、それに従っていけば勝手に利用できる環境が構築されていました。
二回目以降はAndroid側でARCore Instant Previewを起動しておく必要があるようです。