스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
VisionKit을 통한 컴퓨터 판독 코드 및 텍스트 캡처
VisionKit의 Data Scanner를 소개합니다. 이 프레임워크는 AVCapture와 Vision을 결합하여, 간단한 Swift API를 통해 컴퓨터 판독 코드 및 텍스트의 라이브 캡처를 지원합니다. 바코드 기호와 언어 선택을 지정하여 앱에서 캡처할 수 있는 콘텐츠의 유형을 제어하는 방법을 보여드립니다. 또한 앱에서 지침을 활성화하고 항목 강조 또는 관심 영역을 맞춤화하는 방법에 대해 살펴보며, 앱에서 항목을 감지한 후의 상호 작용을 처리하는 방법을 다룹니다. 정지 이미지 또는 일시 정지된 비디오 프레임을 통한 라이브 텍스트와의 상호 작용에 대해 더 자세히 알아보려면 WWDC22의 ‘Add Live Text interaction to your app(앱에 라이브 텍스트 상호 작용 추가)'를 시청하시기 바랍니다.
리소스
관련 비디오
Tech Talks
WWDC22
-
다운로드
♪♪
안녕하세요, 인풋 엔지니어 Ron Santos입니다 영상 피드의 기계 판독형 코드나 텍스트를 포착하는 방법 즉, '데이터 스캐닝'에 대해 오늘 말씀드리려고 합니다 '데이터 스캐닝'이란 정확히 뭘까요? 이건 데이터 읽기를 위해 센서를 사용하는 방식입니다
보통 이런 데이터는 텍스트 형태로 들어오죠 전화 번호, 날짜, 가격 등 이런 정보들이 있는 영수증이 있다고 가정해 봅시다
이때는 기계 판독형 코드로 들어올 수 있습니다 어디에서나 볼 수 있는 QR 코드처럼요 이미 데이터 스캐너를 사용해 보셨을 거예요 Camera 앱이나 iOS 15에 도입된 Live Text 기능을 통해서요 그리고 여러분은 매일 여러 가지 앱을 통해서 커스텀 스캐닝 경험을 하고 계실 겁니다 여러분만의 데이터 스캐너는 어떻게 만들 수 있을까요? 어떻게 하시겠어요? iOS SDK는 니즈에 따라 한 가지 이상의 솔루션을 제공할 겁니다 그 중 하나는 AVFoundation 프레임워크를 통해 카메라 그래프를 설정하여 입력값과 출력값을 세션에 연결하고 구성하여 기계 판독형 코드와 같은 AVMetadataObjects를 도출하는 건데요 텍스트를 캡처하고 싶으시다면 AVFoundation과 Vision을 결합하는 방법도 있습니다 이 표에서 메타데이터 출력값 대신에 영상 데이터 출력값을 만드는 거죠 이 영상 데이터 출력값은 Vision 프레임워크에서 텍스트와 바코드 인식 요청에 사용하기 위해 피드되는 샘플 버퍼를 전달하여 관찰 객체를 도출하게 됩니다 Vision을 사용한 데이터 스캐닝을 더 알아보고 싶으시다면 다음 세션을 참고하세요 두 프레임워크를 사용한 데이터 스캐닝은 이렇습니다 iOS 16에는 여러분 모두를 사로잡을 옵션이 생겼습니다 VisionKit 프레임워크에 DataScannerViewController를 도입했죠 이건 데이터 스캐닝만을 목적으로 하여 AvFoundation과 Vision의 기능을 결합한 겁니다 DataScannerViewController를 사용함으로써 라이브 카메라 미리보기 도움 안내 라벨 아이템 하이라이트 선택을 위해서도 사용되는 초점 맞추기 더 가까이에서 보기 위한 핀치투줌을 할 수 있습니다
이제 여러분과 같은 개발자를 위한 기능을 볼까요? DataScannerViewController는 UIViewController의 서브클래스로 뭘 선택하든 나타나게 할 수 있죠 인식된 아이템의 좌표는 항상 뷰 좌표계에 있어서 이미지 공간에서 Vision 좌표로 뷰 좌표로 전환하기 않도록 도와줍니다 또, 뷰 좌표계에 있는 관심 지역 구체화로 뷰의 활성화 부분을 제한할 수도 있죠 찾으려는 텍스트 유형을 제한하기 위해서도 콘텐츠 유형을 구체화할 수 있죠 기계 판독형 코드의 경우 찾으려는 심볼로지를 정확히 구체화할 수 있습니다 앱을 사용하면서 데이터 스캐닝은 정말 사소한 기능이라고 생각하겠죠 하지만 그런데도 수많은 코드가 필요해요 DataScannerViewController를 사용해 우리는 흔한 일을 수행하려고 하는 겁니다 여러분이 자신만의 시간에 집중할 수 있게요 이 기능을 앱에 추가해 볼게요 개인 정보 보호 문구부터 시작해 보죠 앱이 영상을 캡처하려고 하면 iOS는 유저에게 카메라 액세스 허용을 명확히 해달라고 요청합니다 니즈를 정당화하는 메시지를 넣고 싶으실 거예요 그러려면 'Privacy - Camera Usage Description'을 앱의 Info.plist 파일에 추가해야 합니다 사용자가 동의 대상을 알도록 가능한 기술적이어야 합니다 이제 코드로 넘어가 보죠 데이터 스캐너를 어디에 제시하고 싶으시건 VisionKit 임포팅부터 시작하세요
다음으로, 데이터 스캐닝은 모든 기기에서 지원되지 않으니 기능 노출 버튼이나 메뉴를 숨길 수 있도록 isSupported 클래스 프로퍼티를 사용하세요 그래야 사용할 수 없는 게 사용자에게 제시되지 않습니다
Apple Neural Engine을 2018년형 iPhone, iPad에 적용시 데이터 스캐닝이 지원됩니다 이용 가능성도 점검해 보고 싶으실텐데요 개인 정보 사용 설명을 기억해 보시겠어요? 사용자 카메라 액세스가 허용되고 제한 사항이 없다면 스캐닝을 이용할 수 있습니다 그런데 여기서는 액세스가 제한되죠? Screen Time의 콘텐츠와 프라이버시도 생각해 보세요 이제 인스턴스를 구성할 수 있습니다 먼저 관심 있는 데이터의 유형을 구체화해야 합니다 QR 코드와 텍스트 모두 스캔하고 싶다고 해봅시다
텍스트 인식자를 언어 교정과 같은 다양한 처리 측면에 대한 힌트로 사용하기 위해 언어 목록을 선택적으로 통과할 수 있죠 예상 언어를 안다면 목록에 넣으세요 두 언어가 비슷한 문자일 때 특히 유용합니다 언어를 제공하지 않는다면 사용자 선호 언어가 기본적으로 사용되죠 특정 텍스트 콘텐츠 유형도 요청할 수 있습니다 이 예시에서는 스캐너가 URL을 찾게 하고 싶은데요 인식할 데이터 유형을 명령했다면 DataScanner 인스턴스를 만들 수 있습니다 이전 예시에서는 바코드 심볼로지, 인식 언어 텍스트 콘텐츠 타입을 구체화했는데요 이번에는 각각에 대한 옵션들을 설명해 드리죠 바코드 심볼로지에서는 모든 동일 심볼로지를 Vision 바코드 감지자로 지원합니다 언어 측면에서는 Live Text 기능의 일환으로 DataScannerViewController가 완전 동일한 언어를 지원하죠 iOS 16부터 한국어와 일본어를 지원합니다 물론 향후 바뀔 수도 있지만요 가장 최신 목록을 불러오기 위해서는 supportedTextRecognitionLanguages 클래스 프로퍼티를 사용하세요 특정 의미를 가진 텍스트 스캐닝에 대해서는 DataScannerViewController 타입 7개가 있습니다 이제 사용자에 dataScanner를 제시할 건데요 다른 뷰 컨트롤러처럼 풀스크린이나 시트를 사용하거나 다른 뷰 계층에 함께 추가하여 제시하세요 자기 하기 나름입니다 제시가 완료된 이후에는 startScanning()을 호출해 데이터 찾기를 시작하세요 한 단계 뒤로 돌아가서 데이터 스캐너의 초기화 매개 변수에 대해 좀 알아봅시다 여기서는 recognizedDataTypes 하나를 사용했는데요 커스터마이징에 도움이 되는 매개 변수는 더 많습니다 하나씩 살펴보죠 이건 인식할 데이터 종류를 구체화할 수 있습니다 텍스트, 기계 판독형 코드 등 각 유형도요 이 변수는 안정적이거나 빠르거나 정확할 수 있죠 Fast는 신호 위 텍스트같이 크고 쉽게 읽을 수 있는 아이템을 예상하는 경우 해상도 대신 속도를 선택하죠 Accurate은 최고의 정확도를 선사합니다 소형 QR 코드나 아주 작은 시리얼 넘버같은 항목들에서요 처음에는 대부분 잘 작동하는 Balanced로 시작해 보세요 다음은 프레임 내에서 하나 이상의 아이템을 찾을 수 있는 옵션을 줍니다 여러 개의 바코드를 한 번에 인식할 때처럼요 거짓일 경우에는 사용자가 다른 곳을 탭할 때까지 가장 중앙에 있는 아이템을 기본적으로 인식합니다 하이 프레임 레이트 추적은 하이라이트 시 활성화시키세요 이건 카메라가 움직이거나 씬이 바뀌면 하이라이트가 가능한 가깝게 아이템을 쫓아갑니다 핀치투줌을 활성화하거나 비활성화할 수 있습니다 줌 단계를 수정하기 위해 사용할 수 있는 메소드도 있죠 안내를 활성화하면 사용자에게 지시하기 위해 안내 라벨이 스크린 상단에 활성화됩니다 마지막으로 필요할 경우 시스템 하이라이트가 가능합니다 하이라이트를 커스텀하려면 비활성화할 수도 있고요 데이터 스캐너 제시 방법을 보았으니 이걸 인식 아이템에 인제스트하는 방법을 봅시다 커스텀 하이라이트는 어떻게 하는지도요
먼저, 데이터 스캐너에 델리게이트를 제공합니다 델리게이트가 있다면 dataScanner didTapOn 메소드를 실행할 수 있죠 사용자가 아이템을 탭하면 호출됩니다 그럼 새로운 타입인 RecognizeItem의 인스턴스를 받게 됩니다 RecognizedItem은 텍스트나 바코드가 관련된 값인 열거형입니다 transcription 프로퍼티는 텍스트 인식 문자열을 보유하고 바코드는 페이로드에 문자열을 포함할 경우 payloadStringValue로 불러올 수 있습니다 RecognizedItem에 대해 2가지를 더 아셔야 합니다 먼저, 각 인식 아이템에는 아이템 생애 동안 추적하기 위해 사용하는 특수 식별자가 있죠 아이템의 생애는 뷰에 나타날 때 시작되고 더이상 보이지 않을 때 끝납니다 다음으로, 각 RecognizedItem은 바운즈 프로퍼티를 가집니다 바운즈는 정사각형이 아닌 4개의 점으로 구성되죠 각 코너당 하나가 있습니다 다음으로 씬에 있는 인식 아이템이 변하면 호출되면 관련 델리게이트 메소드 세 가지를 말씀드릴게요 첫번째는 didAdd입니다 씬에서 아이템이 새로 인식되면 호출되죠 커스텀 하이라이트를 만들고 싶다면 새로운 아이템마다 여기에 하나 만들어줘야 합니다 관련 아이템의 ID를 사용해 하이라이트 추적이 가능하죠 뷰 계층에 새로운 뷰를 추가할 때는 여기에 overlayContainerView를 추가하세요 그럼 카메라 미리보기 위와 다른 보충 크롬 아래에 나오죠
다음 메소드는 didUpdate입니다 아이템이 움직이거나 카메라가 움직이면 호출되죠 인식 테스트 문자가 변할 때 호출될 수도 있습니다 스캐너가 보는 텍스트가 길수록 문자에 대한 정확도가 높아지기 때문에 변하죠 만든 딕셔너리에서 하이라이트를 불러와 뷰가 새로 업데이트된 바운드로 이동할 수 있도록 업데이트 항목의 ID를 사용하세요 마지막으로 didRemove 델리게이트 메소드는 씬에서 아이템이 더이상 보이지 않으면 호출되죠 이 메소드에서는 제거 아이템과 관련된 하이라이트 뷰는 잊으셔도 됩니다 뷰 계층에서 제거할 수 있으니까요 즉, 아이템에서 하이라이트를 불러올 때 이 델리게이트 메소드들은 씬에 들어가는 움직이는 하이라이트를 통제해 하이라이트를 움직이거나 빼려면 꼭 필요합니다 세 델리게이트 메소드 각각에 대해서는 현재 인식된 모든 아이템의 배열이 주어질 겁니다 아이템은 읽기에 자연스러운 순서로 배치됩니다 사용자는 인덱스 1의 아이템보다 인덱스 0의 아이템을 먼저 읽는다는 뜻이기 때문에 해당 배열은 도움이 됩니다 이렇게 DataScannerViewController 사용법을 알아보았는데요 마무리 전에 사진 캡처와 같은 몇 가지 기능을 빠르게 말씀드릴게요 capturePhoto 메소드는 고품질 UIImage를 비동기 반환합니다
커스텀 하이라이트를 만들지 않으면 이 세 가지 메소드가 필요하지 않겠죠 대신 recognizedItem 프로퍼티를 사용할 수 있습니다 AsyncStream으로 씬이 변하면 계속 업데이트 됩니다
함께해 주셔서 감사합니다 iOS SDK는 AVFoundation, Vision 프레임워크와 함께 컴퓨터 비전 워크플로우 만들기 옵션을 제공합니다 개인 맞춤 구매 앱이나 창고 관리 앱 매장 사용 앱 등 라이브 영상 피드의 텍스트나 기계 판독형 코드를 스캔하는 앱을 만드신다면 DataScannerViewController를 VisionKit에 부여해 보세요 오늘 앱 스타일과 니즈에 맞는 맞춤형 경험 제공을 위해 사용할 수 있는 다양한 초기화 매개 변수와 델리에이트 메소드를 봤는데요
마지막으로 정적 이미지에 대한 VIsionKit의 Live Text 기능에 대해 배울 수 있는 다음 세션을 강력 추천합니다
다음에 만나죠 안녕히 계세요
-
-
4:40 - Creating a data scanner instance and present it
import VisionKit // Specify the types of data to recognize let recognizedDataTypes:Set<DataScannerViewController.RecognizedDataType> = [ .barcode(symbologies: [.qr]), // uncomment to filter on specific languages (e.g., Japanese) // .text(languages: ["ja"]) // uncomment to filter on specific content types (e.g., URLs) // .text(textContentType: .URL) ] // Create the data scanner, present it, and start scanning! let dataScanner = DataScannerViewController(recognizedDataTypes: recognizedDataTypes) present(dataScanner, animated: true) { try? dataScanner.startScanning() }
-
8:11 - Set a delegate
// Specify the types of data to recognize let recognizedDataTypes:Set<DataScannerViewController.RecognizedDataType> = [ .barcode(symbologies: [.qr]), .text(textContentType: .URL) ] // Create the data scanner, present it, and start scanning! let dataScanner = DataScannerViewController(recognizedDataTypes: recognizedDataTypes) dataScanner.delegate = self.present(dataScanner, animated: true) { try? dataScanner.startScanning() }
-
8:19 - Handling tap interactions
func dataScanner(_ dataScanner: DataScannerViewController, didTapOn item: RecognizedItem) { switch item { case .text(let text): print("text: \(text.transcript)") case .barcode(let barcode): print("barcode: \(barcode.payloadStringValue ?? "unknown")") default: print("unexpected item") } }
-
9:11 - Adding custom highlights via the didAdd delegate method
// Dictionary to store our custom highlights keyed by their associated item ID. var itemHighlightViews: [RecognizedItem.ID: HighlightView] = [:] // For each new item, create a new highlight view and add it to the view hierarchy. func dataScanner(_ dataScanner: DataScannerViewController, didAdd addItems: [RecognizedItem], allItems: [RecognizedItem]) { for item in addedItems { let newView = newHighlightView(forItem: item) itemHighlightViews[item.id] = newView dataScanner.overlayContainerView.addSubview(newView) } }
-
9:37 - Animating custom highlights during the didUpdate delegate method
// Animate highlight views to their new bounds func dataScanner(_ dataScanner: DataScannerViewController, didUpdate updatedItems: [RecognizedItem], allItems: [RecognizedItem]) { for item in updatedItems { if let view = itemHighlightViews[item.id] { animate(view: view, toNewBounds: item.bounds) } } }
-
10:03 - Removing custom highlights during the didRemove delegate callback
// Remove highlights when their associated items are removed. func dataScanner(_ dataScanner: DataScannerViewController, didRemove removedItems: [RecognizedItem], allItems: [RecognizedItem]) { for item in removedItems { if let view = itemHighlightViews[item.id] { itemHighlightViews.removeValue(forKey: item.id) view.removeFromSuperview() } } }
-
10:54 - Take a still photo and save it to the camera roll
// Take a still photo and save to the camera roll if let image = try? await dataScanner.capturePhoto() { UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil) }
-
11:10 - Using the recognizedItems async stream to keep track of items
// Send a notification when the recognized items change. var currentItems: [RecognizedItem] = [] func updateViaAsyncStream() async { guard let scanner = dataScannerViewController else { return } let stream = scanner.recognizedItems for await newItems: [RecognizedItem] in stream { let diff = newItems.difference(from: currentItems) { a, b in return a.id == b.id } if !diff.isEmpty { currentItems = newItems sendDidChangeNotification() } } }
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.