Help us understand the problem. What is going on with this article?

Unity+ARKit3のbody segmentation結果を配列として取得する

既知の問題(2019/9/16更新)

以下の方法は、セグメンテーションのテクスチャに限り、端末の向きを変更するとそれ以降値が変化しなくなる問題が起きています。(デプスのテクスチャは問題なし。Orientation設定は関係なし)
そのため、あまり上手い方法ではないかもしれませんので、そのつもりで利用してください。

やりたいこと

ARKit3を利用することで人物領域をセグメンテーションすることができる。
しかし、結果がTexture2Dとして取得されるので、そのままでは結果を加工しにくい。
なので、byteなどの配列として結果を取得したい。

ハマったポイント

Texure2Dなので、GetPixels()などを使えば一瞬だと思ったが、関数がうまく機能しないらしい。

原因(推定)

UnityのARKit3プラグインを確認していくと、このTexture2Dはネイティブで作られている模様。(CreateExternalTexture/UpdateExternalTextureでの作成・更新処理を発見)
Texure2Dはネイティブで作られたものをラップしているだけなので、GetPixels()などの関数が正しく機能しなかったと思われる。
参考(テラシュールブログさん):
http://tsubakit1.hateblo.jp/entry/20140104/1388830289
http://tsubakit1.hateblo.jp/category/トラブルシューティング?page=1469026740#RenderTextureをTexture2Dに流し込む

解決策

凹みTipsさんの
Low-Level Native Plugin Interface を利用してネイティブから Unity のテクスチャを高速に更新する方法を調べてみた - 凹みTips
によると、iOSにおいてはid<MTLTexture>がテクスチャの実体らしいので、以下のようなプラグインを書いて、配列として取得することで解決。
idから配列としてデータを取得する関数は以下。
https://developer.apple.com/documentation/metal/mtltexture/1516318-getbytes

/Assets/Plugins/iOS/TextureExtention.mm
#import <Metal/Metal.h>
#import <MetalKit/MetalKit.h>

extern "C" {
    void GetNativePixels(id<MTLTexture> texture, int width, int height, int sizePerRow, char *buffer) ;
}

void GetNativePixels(id<MTLTexture> texture, int width, int height, int sizePerRow, char *buffer) {
    [texture getBytes:buffer bytesPerRow:sizePerRow fromRegion:MTLRegionMake2D(0, 0, width, height) mipmapLevel:0];
}
/Assets/Plugins/TextureExtention.cs
using System;
using UnityEngine;
using System.Runtime.InteropServices;

public class TextureExtention { 
    [DllImport("__Internal")]
    public static extern void GetNativePixels(IntPtr texturePtr, int width, int height, int sizePerRow, [Out] byte[] buffer);

    public static void GetNativePixels(Texture2D humanTexture, ref byte[] buffer) {
        // humanTexture は ARKit3で取得されたセグメンテーション結果が格納されたテクスチャ
        IntPtr humanTexturePtr = humanTexture.GetNativeTexturePtr();
        int width = humanTexture.width;
        int height = humanTexture.height;
        int sizeParRow = humanTexture.width * 1; // ボディセグメンテーションではテクスチャフォーマットはR8なので 1ピクセル=1Byte
        GetNativePixels(humanTexturePtr, width, height, sizeParRow, buffer);
    }
}

補足

今回ネイティブプラグインを作って対処しましたが、Unity内でRenderTexture->Texuture2Dへの変換のようにテクスチャを本当のTexutre2Dコピーして、GetPixels()を使う方法でもできるかもしれません。
また、セグメンテーション結果ではなくデプス推定結果でも同じようにすれば大丈夫です。その際はテクスチャフォーマットがRFloatなので、その調整をする必要があります。

動作環境

Unity 2019.2.5f
Xcode 11 GM Seed

感想

GetPixels()が使えないという判断までも時間がかかったし、初めてのiOSネイティブプラグイン作成だったので色々苦労してしまいました…。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away