WWDC23 - Explore enhancements to RoomPlan

백상휘·2023년 6월 7일
0

명실상부 이번 WWDC 의 메인 주제는 AR/VR 이 아닌가 싶다. 사실 이번 WWDC 를 각 잡고 보고 있는 이유는 바로 AR 기능을 탑재한 앱을 구상하고 있기 때문이었다.

RoomPlan 은 정교한 머신러닝 알고리즘을 사용하는 ARKit 을 이용해 벽, 창문, 문, 빈 곳, 물체 등을 인식한다. RoomCaptureView API 는 방을 스캐닝하는 경험을 직접 앱을 통해 경험할 수 있게 해준다. 결과는 3D Model 로 확인하거나 USDZ 파일로 추출할 수 있다.

이번 발표에서는 4 개의 항목을 다룬다.

  • Custom ARSession support
  • MultiRoom(여러 방을 하나의 공간으로 합침) support
  • Accessibility(Voice Over)
  • RoomPlan representations
  • Enhanced export function

주의! : 위의 사항들은 적어도 iOS 16 에서부터 적용되는 API 들이다. 그리고 RoomPlan 의 향상된 기능들은 iOS 17 에서부터 적용될 예정이다.

Custom ARSession support

원래는 주어진 ARSession 만을 사용했다면 이제는 ARWorldTrackingConfiguration 을 이용한 ARSession 을 생성 및 사용할 수 있다.

/// 이전 RoomCaptureSession 생성
public class RoomCaptureSession {
    public init() {
       ...
    }

    public func stop() {
       ...
    }
}
/// 새로운 RoomCaptureSession 생성
public class RoomCaptureSession {
    // Init: ARSession is an optional input for RoomCaptureSession
    public init(arSession: ARSession? = nil) {
       ...
  }

    // Stop: pauseARSession is used for whether to continue ARSession experience
    public func stop(pauseARSession: Bool = true) {
       ...
  }
}

사진과 비디오를 RoomPlan scan 에 추가

MultiRoom support

만약 당신이 거실, 내 방, 주방 등을 모두 스캔했다고 하자. 이걸 하나로 합치려면 어떤 것을 고려해야 할까?

우선, 모든 스캔 결과는 각각의 좌표평면을 가진다. 모두 같은 공간에서 스캐닝 한 것이 아니기 때문에 같은 좌표평면이라 할 수 없는 것이다. 이를 무시하고 서로 벽을 기준으로 마주 본 방을 그냥 붙여버리면 객체가 중복된다.

그러므로 우린 같은 여러 개의 스캔 결과를 하나의 좌표평면계로 합쳐야 한다. 이를 위해 애플에서는 두 가지 방법을 제시한다.

  1. 같은 ARSession 을 사용하여 스캔 한다.
  2. ARSession relocalization 을 이용한다.

Use Continuous ARSession

원래 RoomCaptureSession 이 종료되면 ARSession 도 멈추었다. 하지만 위에서 본 stop 함수의 변경으로 인해 같은 ARSession 을 반복 사용할 수 있게 되었다.

stop() 을 기준으로 ARSession 이 멈춰버린다.

stop(pauseARSession: false) 로 인해 ARSession 이 지속된다. 같은 좌표평면계를 사용하게 되는 것이다.

// Continuous ARSession

// start 1st scan
roomCaptureSession.run(configuration: captureSessionConfig)

// stop 1st scan with continuing ARSession
roomCaptureSession.stop(pauseARSession: false)

// start 2nd scan
roomCaptureSession.run(configuration: captureSessionConfig)

// stop 2nd scan(pauseARSession = true by default)
roomCaptureSession.stop()

ARSession relocalization

위의 방식은 같은 시간대에 여러 번의 스캔을 반복하는 데 사용할 수 있지만, relocalization 은 다른 시간대(내일 혹은 다음주 등)에 촬영하고 싶은 경우 사용할 수 있다.

우선 ARSession 이 중지할 때 ARWorldMap 을 저장해야 한다.

다음에 스캔을 반복할 경우 중지된 ARSession 에 저장된 ARWorldMap 을 불러와서 새로운 스캔을 시작하면 된다.

아래 코드는 최초 스캔을 진행한 후 ARWorldMap 을 저장하는 과정을 나타낸다. 참고로 ARWorldMap 은 NSObject 이기 때문에 NSKeyedArchiver 를 이용해 NSData 로 저장 가능하다.

// Capture with saving ARWorldMap

// start 1st scan
roomCaptureSession.run(configuration: captureSessionConfig)

// stop 1st scan (pauseARSession = true by default)
roomCaptureSession.stop()

// save ARWorldMap
roomCaptureSession.arSession.getCurrentWorldMap(completionHandler:{worldMap, error in ...})

그리고 아래 코드는 저장한 ARWorldMap 을 불러와서 다시 스캔하는 과정을 나타낸다.

// Capture with loading ARWorldMap

// load ARWorldMap
let arWorldMap = try NSKeyedUnarchiver.unarchivedObject(ofClass: ARWorldMap.self, from: data)

// run ARKit relocalization
let arWorldTrackingConfig = ARWorldTrackingConfiguration()
arWorldTrackingConfig.initialWorldMap=arWorldMap
roomCaptureSession.init()
roomCaptureSession.arSession.ru(arWorldTrackingConfig, options: [])

// Wait for relocalization to complete
// start 2nd scan
roomCaptureSession.run(configuration: captureSessionConfig)
// stop 2nd scan
roomCaptureSession.stop ()

이로써 1번째, 2번째 스캔은 같은 좌표평면계에서 스캐닝을 한 것과 같은 결과를 갖는다.

MultiRoom API

위의 두 방식을 이용해 여러 공간을 스캔했다면 이를 하나로 합쳐야 한다. 각 스캔에서 RoomBuilder API 를 이용하면 CapturedRoom 객체를 각각 얻을 수 있다.

그리고 할 일은? StructureBuilder 를 이용해서 CapturedRoom 객체를 하나로 합친다. 합쳐진 공간 데이터는 CapturedStructure 객체가 된다.

StructureBuilder API 사용 코드 예제는 아래와 같다.

// StructureBuilder

// create structureBuilder instance
let structureBuilder = StructureBuilder (option: [.beautifyObjects])

// load multiple capturedRoom results to capturedRoomArray
var capturedRoomArray: [CapturedRoom] = []

// run structureBuilder API to get capturedStructure
let capturedStructure = try await structureBuilder.capturedStructure(from: capturedRoomArray)

// export capturedStructure to usdz
try capturedStructure.export (to: destinationURL)

예제에서는 임의의 CapturedRoom 배열을 생성해서 기능을 구현하였다.

여기서 CapturedRoom, CapturedStructure 의 차이에 대해 잠시 짚고 넘어가자.

  • CapturedRoom = 하나의 공간을 스캔한 결과물을 말한다.
  • CapturedStructure = 여러 개의 CapturedRoom 을 하나로 합친 구조체이다.

MultiRoom API 와 관련하여서는 샘플 앱을 제공할 예정이다. 공식문서를 찾아보려 했지만 아직 페이지가 나오지는 않은 듯 하다.

USDZ 파일은 iOS, macOS 에서 바로 확인할 수 있는 파일 형식이다. USDZ 파일을 이용하면 Blender 같은 디지털 컨텐츠 생성 툴에서도 실행할 수 있다.

Considerations

MultiRoom 을 사용할 수 있는 공간을 고려할 때 가장 적합한 옵션들도 제공해 주었다.

  1. 침대 4개, 거실, 부엌, 식사 공간 등을 가진 1층 짜리 주거용 집
  2. 합치려고 하는 모든 공간의 크기는 2,000ft 제곱 혹은 186m 제곱
  3. 채광은 50lux 이상

Accessibility

RoomPlan API 들을 사용하는 앱에 시각적으로 불편한 부분을 가진 사람들을 위해 VoiceOver 를 이용한 음성 피드백을 제공한다.

시연 영상에서는 감정이 0 인 것 같은 사람이 'Point camera at bottom edge of wall', 'A fireplace', 'A wall', 'A window' 등을 인식한대로 읽어주고 있었다.

RoomPlan representations

지금까지 RoomPlan 의 스캔 결과는 한정된 물체만을 다루었다. 이번에 RoomPlan 은 기울어진 벽, 원형의 벽, 오븐 등의 오목한 공간 등도 스캔할 수 있게 되었다고 한다.

기존 스캔이 가능한 물건들에 대해서도 더 자세한 결과물을 제공하는데 소파를 예로 들었다. 1인용 소파, L 자 소파, 일자형 소파 등을 새로운 방식으로 수집하는 것이다.

이것이 가능한 이유는 스캔하여 수집하는 데이터 종류가 늘어난 데 있다. 위에서 언급한 CapturedRoom 이 스캔할 수 있는 카테고리들은 대부분 Surface, Object 라는 내부 구조체로 표현된 것들이다.

여기에 몇가지 추가 데이터들이 생겼다.

  • Section = 방 내부의 공간을 표현한다.
  • Surface -> Polygon = 이제 벽은 Polygon 을 이용하여 일정하지 않은 형태의 벽도 표현할 수 있게 되었다. (곡선의 벽, 빔이 부착된 벽 등)
  • Surface -> Category -> floor = Polygon 으로 표현될 수 있는 카테고리 중 하나이다.
  • Object -> Attribute = 더욱 정확한 물체 스캔을 위해 추가 되었다.

또한, Surface 와 Object 는 그들의 부모(예: 창문의 부모는 벽, 의자의 부모는 테이블) 를 참조하는 Parent 가 추가되었다.

코드로 자세히 보도록 하자.

보시다시피 모든 공간이 나뉘어져 있고, Section 당 라벨이 하나씩 부착되었다. Living Room, Dining Room, Undefined 가 하나의 공간이지만 Section 으로 나뉘고 있다.

public struct CapturedStructure: Codable, Sendable {
	public struct Section: Codable, Sendable {
		public enum Label: String, Codable, Sendable {
			case livingRoom
			case bedroom 
            case bathroom 
            case kitchen 
            case diningRoom 
            case unidentified
		}
		
        public var label: Label { get }
		
        // The center position of the section
		public var center: simd_float3 { get }
		
		// Indicator for which story, level, or floor
		public var story: Int { get }
	}
}

Polygon 은 평면이 아닌 구조들도 인식할 수 있도록 해준다. 또한 카테고리에 추가된 floor 를 통해 스캔한 바닥이 더 유려한 형태로 변경된다.

// Floors for Surfaces in CapturedRoom

public struct CapturedRoom: Codable, Sendable {

	// A 2D area in a room identified as a surface
	public struct Surface: Codable, Sendable {
	
		// A 2D polygon to represent walls and floors
		// in local plan coordinates
		public var polygonCorners: [sim_float3] { get }

		// classifications of a surface in a captured room
		public enum Category: Codable, Sendable {
			case floor
		}
	}
}
Polygon using wall Polygon using floor

개인적으로 가장 신기한 부분이다. 위에서 얘기한 Object 의 부모를 인식할 수 있는 능력 덕분에 아래와 같이 주방기구를 인식할 수도 있게 되었다.

// Parent identifiers for surfaces and objects in CapturedRoom
public struct CapturedRoom: Codable, Sendable {
	// A 2D area in a room identified as a surface
	public struct Surface: Codable, Sendable {

		// A unique UUID to indicate surface's parent
        public var parentIdentifier: UUID? { get }
	}

	// A 3D area in a room identified as an obiect
	public struct Object: Codable, Sendable {

		// A unique UUID to indicate object's parent
		public var parentIdentifier: UUID? { get }
	}
}

여기까지 살펴보며 한 가지 한계를 설명해 주었는데 의자를 가지고 설명해 주었다. 의자는 발이 4개일 수 있고, 하나일 수도 있다. 사무용 의자일 수도 있고 흔들 의자일 수도 있다. Object 의 Attribute 는 이러한 사물을 인식하는데 도움을 준다. 이와 관련하여서는 다음 발표에서 더 자세히 다룬다고 한다.

Enhanced export function

내보내기를 할 때 위의 추가사항들을 반영할 필요가 생겼다.

새로 추가된 방식은 UUID 문자열 값을 이용하여 각 객체를 매핑하는 방식이다.

기존에는 CapturedRoom 을 Export 할 때 USDZ 파일만 제공되고 JSON 파일로도 제공할 수 있었는데, 이제는 USDZ 파일에 매핑 정보를 담은 파일까지 얻을 수 있게 된 것이다.

이 매핑 정보는 유용하다. 각 요소는 물체와 매핑되어 있기 때문에 이번에 추가된 Model Provider 를 통하여 물체를 구체화 시킨 Model URL 로 변경한다.

위의 물체 하나만이 아닌 전체 공간에 대해서도 USDZ 파일의 매핑 정보를 통해 구체화 된 스캔 정보를 얻을 수 있게 된다.

이러한 Model URL 들을 이용해 3D Model Catalog 를 만들 수 있다.

우선 모델의 Category, Attribute 를 이용해 파일 구조를 생성한다.

이제 인덱스 파일을 생성한다. 아래는 인덱스 파일의 구조(RoomPlanCatalogCategoryAttribute) 이다. Category, Attribute 들을 포함하고 있으며 아래 모델 이름도 보인다.

Catalog 를 생성하여 모델을 저장한다.

생성한 Catalog 를 이용해 Model Provider 를 생성한다.

이제 Export 를 실행한다. 실제 공간과 USDZ 파일로 표현한 공간이 매우 유사해 보인다.

느낀점

우선 현업에서 사용하기에는 무리가 있는 프레임워크라는 생각은 든다. iOS 17 은 많은 유저를 포기해야 한다는 의미이기도 하다.

그래도 굉장히 신기한 앱을 만들 수는 있겠다는 생각이 들었다. iOS 16 나올 때와 다르게 이제는 윤곽이 잡혀가는 프레임워크라서 실제 앱을 만들어보면 재미있을 것 같다.

내 아이폰은 13 프로 맥스 이다. 이 폰을 사서 드디어 제대로 써먹어 보는 것 같다.

Reference

profile
plug-compatible programming unit

0개의 댓글