본문 바로가기
유니티3D[Unity3D]

[Unity] AR Foundation Depth 거리 값 가져오기 (ARCore)

by 은유지니 2024. 5. 7.

ARCore에서 Depth API를 통해 Depth Map을 가져올 수 있는 API가 있다.
이를 이용하여, 내가 원하는 특정 스크린 좌표의 Depth값을 m 단위로 변환하여 값을 알아보도록 하자.

1. AR Occlustion Manager를 통해 Depth map을 받아온다.

2. OcclusionManager.TryAcquireEnvironmentDepthCpuImage를 통해 XRCpuImage를 가져온다.

3. XRCpuImage.Plane의 데이터(NativeArray<byte> Type)에서 내가 터치한 곳의 픽셀의 데이터 값(4개의 byte)을 얻는다.

4. 해당 byte값 4개를 가지고, XRCpuImage.format에 따라 거리 값을 가져온다.(convertPixelDataToDistanceInMeters 함수 참조)

이렇게 최종으로 가져와지는 Depth값은 m단위로, 실제 실행을 해보았는데 ARFoundation SDK로는 Raw Depth Map이 불러와지는 정도가 너무 느려서 정지상태가 아니라면 거의 부정확하다.
그래서 이 문제를 해결하고자 Niantic LightShip SDK(ARDK)를 활용하여 Depth Map을 가져오고, 이 Depth Map에서 거리값을 추출했다. Niantic LightShip SDK (ARDK) 도 ARFoundation SDK을 활용해서 성능을 일정부분 업그레이드시킨 정도라 AR Foundation SDK와 크게 다른 점은 없다고 봐도 무방하다.
결과적으로는, 1m 이내의 오차로 작동하는것을 확인했다. 1m 오차 이기에 정밀한 측정에는 사용할 수 없지만, 어떤 물체가 가까이 있는지 멀리 있는지 판단하는 정도로는 용이하다.

* 설명이 부족한 점이 있다면, 댓글 부탁드립니다! 지적은 언제나 환영입니다 😀👍

참조 소스코드
    private void Update()
    {
        // Update the image
        if (m_OcclusionManager.TryAcquireEnvironmentDepthCpuImage(out XRCpuImage image))
        {
            if(m_DepthImage != null) m_DepthImage.Dispose(); // 메모리 누수 방지
            m_DepthImage = image;

            ScreenPointManager.instance.SetDepthCPUImage(image);
        }
        else
        {
            Debug.LogWarning("NOT Update the image");
            return;
        }

    }


    public static float convertPixelDataToDistanceInMeters(byte[] data, XRCpuImage.Format format) {
        switch (format) {
            case XRCpuImage.Format.DepthUint16:
                return BitConverter.ToUInt16(data, 0) / 1000f; //data의 index 0부터 2개의 byte의 int값을 반환
            case XRCpuImage.Format.DepthFloat32:
                return BitConverter.ToSingle(data, 0);//data의 index 0부터 4개의 byte의 float값을 반환
            default:
                throw new Exception($"Format not supported: {format}");
        }
    }

    public static float GetDepthDistanceOfPoint(Vector2 point)
    {
            var uv = new Vector2(point.x / Screen.width, point.y / Screen.height);
            float x = uv.x;
            float y = uv.y;

            // float symmetryX = 1 - y;
            // float symmetryY = 1 - x;

            //depth map 매칭을 위해 선 대칭 이동 시킨 값
            float symmetryX = 1 - x;
            float symmetryY = 1 - y;

            var plane = m_DepthImage.GetPlane(0);
            var dataLength = plane.data.Length;
            var pixelStride = plane.pixelStride; //각 픽셀 당 byte수
            var rowStride = plane.rowStride; //각 행 당 byte수

            var centerRowIndex = (int)(dataLength / rowStride * symmetryX); //터치한 픽셀 y
            var centerPixelIndex = (int)(rowStride / pixelStride * symmetryY); //터치한 픽셀 x 
      
            //pixel 전체 데이터에서 해당 픽셀 인덱스 기준으로 해당 픽셀이 가지고 있는 byte 길이 만큼 가져옴
            var centerPixelData = plane.data.GetSubArray((int)centerRowIndex * rowStride + (int)centerPixelIndex * pixelStride, pixelStride); 
            //해당 픽셀 데이터를 해당 Image format에 맞게 depth 계산
            var depthInMeters = convertPixelDataToDistanceInMeters(centerPixelData.ToArray(), m_DepthImage.format);
            return depthInMeters;
    }