해당 내용은 WWDC 2023: Meet Object Capture for iOS 세션 내용을 토대로 작성되었습니다.
데모 프로젝트를 사용해볼 수 있으며, 이제 간단하게 iOS 에서 LiDAR 스캐너가 장착된 디바이스를 통해 USDZ 파일을 생성할 수 있다.
360도 모든 면을 캡쳐한 후 뒤집어 바닥또한 캡쳐하여 스캔을 완료한 후 iOS 온디바이스에서 재구성을 거쳐 USDZ 파일이 생성된다.
기존에 low-texture 인 개체의 경우 Object Capture의 한계가 있었으나, LiDAR 스캐너를 통한 개선점과
Object Capture 방법에 대한 소개, 관련 API 소개,
그리고 추가적인 개선사항들을 알 수 있습니다.
의자를 예시로, low-texture인 영역의 경우 RGB 이미지들로만은 완전히 캡쳐할 수 없습니다.
하지만 해당 부분을 LiDAR 스캐너를 통해 얻은 밀도 있는 Point Cloud Data를 통해 완전한 Object를 생성할 수 있습니다.
하지만 극단적으로 low-texture 상황인 반사되는 물질, 투명한 물질, 너무 얇은 물질들의 경우는 제한적입니다.
Guided capture
기능을 통해 Object Capture 모든 과정이 자동화되어 있어 손쉽게 고품질의 USDZ 파일을 생성할 수 있습니다.
Guided capture 기능은 실시간으로 RGB 이미지와 LiDAR 데이터를 수집합니다.
이때 좋은 품질이 되기 위해서는 다양한 각도의 RGB 이미지와 LiDAR 데이터가 필요합니다.
첫번째로 Automatic capture
기능을 통해 측정되고 있는 개체의 형상을 나타내는 capture dial
을 표시합니다.
capture dial
을 통해 개체의 측정된 영역을 확인할 수 있고, 모든 면을 측정하도록 유도할 수 있습니다.
두번째로 Capture feedback
기능을 통해 알맞지 않는 상황이 발생할 경우를 인지하여 사용자에게 바람직한 상황으로 유도하기 위한 기능을 제공합니다.
빛이 부족한 상황이나, 빠르게 움직여 이미지가 흐릿한 경우, 그리고 카메라 앵글에서 너무 가깝거나 멀거나, 혹은 벗어나는 경우에 대해 경고를 나타냅니다.
세번째로 Flipping objects
기능을 통해 객체를 뒤집어서 추가적인 측정을 할지 여부에 대해 판단합니다.
객체를 뒤집기에 알맞은 상황으로는 객체가 딱딱하거나 객체의 질감(표면 무늬)가 풍부한 경우 권장되지만, 물체 모양이 변형될 수 있을 정도로 딱딱하지 않는 물체나 객체의 질감이 반복적인 무늬인 경우, 그리고 질감이 없는 민무늬 형태의 경우는 바람직하지 않습니다.
객체를 뒤집기에 알맞은지 여부는 API를 통해 제안됩니다.
객체를 뒤집어서 추가적인 측정이 알맞은 경우는 객체를 올바르게 뒤집을 수 있도록 애니메이션으로 뒤집는 방향에 대해 표시합니다.
고품질로 측정되기 위해 모든 면의 이미지를 얻을 수 있도록 3 방향으로 스캔하는 것이 좋으며, 세부적으로는 그림자, 반사를 줄이기 위한 확산 조명을 사용하는 것이 좋고, 서로 다른 방향간의 이미지 중첩이 중요합니다.
객체를 뒤집기에 알맞지 않는 경우는 3가지의 다양한 시야각에서 스캔합니다.
또한 민무늬와 같은 질감의 객체의 경우 눈에 잘 띄는 질감이 있는 바닥, 또는 배경이 좋습니다.
이러한 과정은 모두 자동화되어 제공되며, 다양한 각도로 측정된 이미지와 LiDAR 데이터를 통해 고품질의 USDZ 파일이 손쉽게 생성됩니다.
해당 Object Capture
기능은 LiDAR 스캐너가 장착된 iPhone 12 Pro 및 iPad Pro 2021 이후 모델에서 사용할 수 있습니다.
Object Capture API는 Image Capture
단계와 Model Reconstruction
단계로 이뤄지며, SwiftUI에서 사용할 수 있습니다.
RealityKit
과 SwiftUI
프레임워크를 사용하며, ObjectCapcureSession
과 ObjectCaptureView
조합으로 구성됩니다.
ObjectCaptureSession
의 경우 Object Capcure 전체 과정의 상태값을 지니며, 참조 유형의 인스턴스 입니다.
Session의 상태값은 Initializing
-> Ready
-> Detecting
-> Capturing
-> Finishing
-> Completed
값들로 변화됩니다.
Initializing
: Session이 생성될 때 설정됩니다.Ready
, Detecting
, Capturing
, Finishing
상태값으로 변화되며, 자동으로 Completed
상태가 됩니다.Completed
상태가 되면 Model Reconstruction
과정으로 이어집니다.ObjectCaptureView
의 경우 Session의 상태값을 기준으로 Image Capture 일련의 과정을 자동으로 View
형태로 표시합니다.
2D 텍스트나 버튼이 없는 형태이며, 추가적인 Text나 Button의 표시가 필요한 경우 ZStack을 통해 겹쳐서 표시하면 됩니다.
import RealityKit
import SwiftUI
var session = ObjectCaptureSession()
initializing
이 됩니다.
var configuration = ObjectCaptureSession.Configuration()
configuration.checkpointDirectory = getCodumentsDir().appendingPathComponent("Snapshots/")
session.start(imagesDirectory: getDocumentsDir().appendingPathComponent("Images/"), configuration: configuration)
Snapshots/
경로와 캡쳐된 이미지가 저장될 Images/
경로를 설정한 후 start()
함수를 통해 Object Capture가 시작됩니다..ready
가 됩니다.
import RealityKit
import SwiftUI
struct CapturePrimaryView: View {
var body: some View {
ZStack {
ObjectCaptureView(session: session)
}
}
}
.ready
이면 객체를 선택하도록 안내하는 윤곽이 표시됩니다.
var body: some View {
ZStack {
ObjectCaptureView(session: session)
if case .ready = session.state {
CreateButton(label: "Continue") {
session.startDetecting()
}
}
}
}
.ready
일 때 캡처할 객체를 설정하기 위한 Button을 표시합니다.session.startDetecting()
함수를 실행합니다..detecting
이 됩니다.
var body: some View {
ZStack {
ObjectCaptureView(session: session)
if case .ready = session.state {
CreateButton(label: "Continue") {
session.startDetecting()
}
} else if case .detecting = session.state {
CreateButton(label: "Start Capture") {
session.startCapturing()
}
}
}
}
.detecting
이 되면 캡처할 객체 주위에 경계상자가 표시되며, 캡처를 진행하기 위한 Button을 표시합니다.session.startCapturing()
함수를 실행합니다..capturing
이 됩니다.session.resetDetection()
함수를 실행하여 .ready
상태로 돌아갈 수 있습니다.
.capturing
이 되면 자동으로 측정되고 있는 개체의 형상을 나타내는 capture dial
이 표시됩니다.
var body: some View {
if session.userCompletedScanPass {
VStack {
}
} else {
ZStack {
ObjectCaptureView(session: session)
}
}
}
session.userCompletedScanPass
값이 true
로 설정됩니다.session.beginNewScanPassAfterFlip()
함수를 실행하여 다음 Scan pass를 진행합니다. 이때 session의 상태값은 .ready
가 됩니다.session.beginNewScanPass()
함수를 실행하여 다른 높이에서 Scan pass를 진행합니다. 이때 session의 상태값은 .capturing
으로 유지됩니다.
var body: some View {
if session.userCompletedScanPass {
VStack {
ObjectCapturePointCloudView(session: session)
CreateButton(label: "Finish") {
session.finish()
}
}
} else {
ZStack {
ObjectCaptureView(session: session)
}
}
}
session.finish()
함수를 실행하여 Object Capture를 종료합니다..finishing
이 됩니다..completed
로 변합니다..failed
가 되며, 새로운 session을 생성하여 다시 진행해야 합니다.ObjectCapturePointCloudView
를 통해 측정된 객체를 Point Cloud로 표시할 수 있습니다.
var body: some View {
ReconstructionProgressView()
.task {
var configuration = PhotogrammetrySession.Configuration ()
configuration.checkpointDirectory = getDocumentsDir ()
.appendingPathComponent ("Snapshots/")
let session = try PhotogrammetrySession (
input: getDocumentsDir () .appendingPathComponent ("Images/"),
configuration: configuration)
try session.process (requests: [
•modelFile (url: getDocumentsDir ().appendingPathComponent ("model.usdz") )
])
for try await output in session.outputs {
switch output {
case •processingComplete:
handleComplete ()
// Handle other Output messages here.
}
}
}
}
}
Reconstruction
을 지원합니다.
ReconstructionProgressView()
를 통해 Reconstruction 상태를 표시할 수 있습니다.checkPointDirectory
를 설정한 경우 configuration
을 생성합니다.PhotogrammetrySession
으로 session을 생성합니다.input
값이며, configuration
을 인자로 넣습니다..modelFile
를 통해 생성하고자 하는 usdz 모델명을 설정합니다.
.processingComplete
값이 될 때 까지 기다립니다.