【OpenCV plus Unity】リアルタイムで人物を消す

# 概要
![IMG_0090.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699230/2943a19b-2f71-aff3-aceb-9cc1b5e9319b.png)

左上：カメラ画像　/　右上：マスク画像　/　左下：加工後画像

HumanStencilの取得はARFoundationを、画像の加工にはOpenCV plus Unityを使用しています。

手順としては以下3ステップです。

1. ARFoundationからカメラ画像を取得する
1. ARFoundationからHumanStencilを取得する
1. 画像を加工して人を消す

ソースコード公開しています：https://github.com/AzetaTakuya/InvisibleHuman

# 環境
- iPad Pro 11インチ(第2世代)
- Unity 2021.2.0a8
- ARFoundation 4.1.0
- [OpenCV plus Unity](https://assetstore.unity.com/packages/tools/integration/opencv-plus-unity-85928?locale=ja-JP)

# 実装
<a id="anchor1"></a>
## <a href="#anchor1"></a>  1. ARFoundationからカメラ画像を取得する

ARCameraManagerのコールバックから、カメラ画像を取得します

参考：https://docs.unity3d.com/Packages/com.unity.xr.arfoundation@4.1/manual/cpu-camera-image.html

``` C#
[SerializeField] private ARCameraManager CameraManager;
private Texture2D RGB_Texture;

private void Start()
{
    CameraManager.frameReceived += OnARCameraFrameReceived;
}

unsafe void OnARCameraFrameReceived(ARCameraFrameEventArgs eventArgs)
{
    if (!CameraManager.TryAcquireLatestCpuImage(out XRCpuImage image)) return;

    var conversionParams = new XRCpuImage.ConversionParams
    {
        inputRect = new RectInt(0, 0, image.width, image.height),
        outputDimensions = new Vector2Int(image.width / 2, image.height / 2),
        outputFormat = TextureFormat.RGBA32,
        transformation = XRCpuImage.Transformation.MirrorY
    };

    int size = image.GetConvertedDataSize(conversionParams);
    var buffer = new NativeArray<byte>(size, Allocator.Temp);
    image.Convert(conversionParams, new IntPtr(buffer.GetUnsafePtr()), buffer.Length);
    image.Dispose();

    if (RGB_Texture == null)
    {
        var x = conversionParams.outputDimensions.x;
        var y = conversionParams.outputDimensions.y;
        RGB_Texture = new Texture2D(x, y, conversionParams.outputFormat, false);
    }
    
    RGB_Texture.LoadRawTextureData(buffer);
    RGB_Texture.Apply();

    buffer.Dispose();
}
```

<a id="anchor2"></a>
## <a href="#anchor2"></a>  2. ARFoundationからHuman Stencilを取得する
手順は以下の通りです。

- AROcclusionManagerのhumanStencilTextureを取得
- humanStencilTextureをGraphics.Blitで加工し、RenderTextureに出力
- 出力したRenderTextureをTexture2Dに変換

``` C#
[SerializeField] private AROcclusionManager OcclusionManager;
[SerializeField] private Material HumanStencil_Material;
private RenderTexture HumanStencil_RT;
private Texture2D Stencil_Texture;

private void Update()
{
    var currentStencil = OcclusionManager.humanStencilTexture;

    if (currentStencil == null) return;

    if (HumanStencil_RT == null)
    {
        HumanStencil_RT = RenderTexture.GetTemporary(currentStencil.width, currentStencil.height, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear);
        HumanStencil_RT.Create();
    }
    if (Stencil_Texture == null)
    {
        Stencil_Texture = new Texture2D(currentStencil.width, currentStencil.height);
    }

    Graphics.Blit(currentStencil, HumanStencil_RT, HumanStencil_Material);

    var currentRT = RenderTexture.active;
    RenderTexture.active = HumanStencil_RT;
    Stencil_Texture.ReadPixels(new UnityEngine.Rect(0, 0, Stencil_Texture.width, Stencil_Texture.height), 0, 0);
    Stencil_Texture.Apply();
    RenderTexture.active = currentRT;
}
```


<a id="anchor3"></a>
## <a href="#anchor3"></a>  3. 画像を加工して人を消す！！
手順は以下の通りです。

- HumanStencil
    - グレースケール化
    - 人物部分拡張
    - 画像サイズ変更
- カメラ画像
    - サイズ変更
    - 画像反転
- 出力画像
    - マスク画像を使ったカメラ画像の修正


``` C#
[SerializeField] private RawImage RGB_Image;
[SerializeField] private RawImage HumanStencil_Image;
[SerializeField] private RawImage Inpaint_Image;

private int width = 640, height = 480;
private CancellationTokenSource tokenSource;

private void Start()
{
    tokenSource = new CancellationTokenSource();
    var cancelToken = tokenSource.Token;
    _ = CaptureLoop(cancelToken);
}

private async Task CaptureLoop(CancellationToken cancelToken)
{
    byte[] dilateArray =
        {   1, 1, 1, 1, 1,
            1, 1, 1, 1, 1,
            1, 1, 1, 1, 1,
            1, 1, 1, 1, 1,
            1, 1, 1, 1, 1, };

    Texture2D stencilViewTexture = new Texture2D(width,height);
    Texture2D rgbViewTexture = new Texture2D(width, height);
    Texture2D inpaintViewTexture = new Texture2D(width, height);

    HumanStencil_Image.texture = stencilViewTexture;
    RGB_Image.texture = rgbViewTexture;
    Inpaint_Image.texture = inpaintViewTexture;

    while (!cancelToken.IsCancellationRequested)
    {
        await Task.Delay(20);

        if (RGB_Texture == null || Stencil_Texture == null) continue;

        using (Mat stencilMat = OpenCvSharp.Unity.TextureToMat(Stencil_Texture))
        using (Mat rgbMat = OpenCvSharp.Unity.TextureToMat(RGB_Texture))
        using (Mat inpaintMat = new Mat())
        {
            #region stencil texture
            Cv2.CvtColor(stencilMat, stencilMat, ColorConversionCodes.BGR2GRAY);
            Cv2.Dilate(stencilMat, stencilMat, InputArray.Create(dilateArray));
            Cv2.Resize(stencilMat, stencilMat, new OpenCvSharp.Size(width, height));
            stencilViewTexture = OpenCvSharp.Unity.MatToTexture(stencilMat, stencilViewTexture);
            #endregion

            #region rgb texture
            Cv2.Resize(rgbMat, rgbMat, new OpenCvSharp.Size(width, height));
            Cv2.Flip(rgbMat, rgbMat, FlipMode.Y);
            rgbViewTexture = OpenCvSharp.Unity.MatToTexture(rgbMat, rgbViewTexture);
            #endregion

            #region inpaint
            Cv2.Inpaint(rgbMat, stencilMat, inpaintMat, 3, InpaintMethod.NS);
            inpaintViewTexture = OpenCvSharp.Unity.MatToTexture(inpaintMat, inpaintViewTexture);
            #endregion

            stencilMat.Dispose();
            rgbMat.Dispose();
            inpaintMat.Dispose();
        }
    }
}
```

InpaintMethodはNSとTeleaがありますが、NSの方が高速だった為NSを採用しています。<br>
※ [usingステートメントを使用してもMatのメモリが解放されないらしい](https://qiita.com/qiita21409102/items/a264f68b5cbb394cc34c)ので、加えてDispose()を読んでいます。実際はDispose()だけで良いと思います。

# まとめ
AR×OpenCVは楽しい
# 参考
- #OpenCV　Sharpでメモリリークが起きる
    - https://qiita.com/qiita21409102/items/a264f68b5cbb394cc34c