์ด์ ์ ์ด๋ ์๋น์ค์์๋ ๊ทธ๋ฌ์ง๋ง ๊ฐ์ ์ ๋ฐ์์ผ ํ๋ ์๋ฅ ์ค ์๊ธฐ ์ฒ๋ฆฌ๊ฐ ํ์ํ ๊ฒฝ์ฐ๊ฐ ์๋ค. ์ด๋ฒ ์๋น์ค ๋ํ ์ต๋ 5๊ฐ์ ์๋ฅ๋ฅผ ๋ฐ์์ผ ํ๊ณ , ๊ทธ ์๋ฅ๊ฐ ์ ํจํ์ง ์๊ธฐ๋ก ๊ฒ์ฆ ํ์ ๊ฐ์ ์ํฌ ์ ์๋ค๊ณ ํ๋ค.
์ฒจ๋ถ ํ ์ฌ์ฉ์๊ฐ ๋ณ๋๋ก ํ์ธํ๋ ๊ฒ์ด ์๋ ์ฒจ๋ถ๋จ- ์ํ ํ์ ํ ๋์ด๊ธฐ ๋๋ฌธ์, ์ฌ์ฉ์๊ฐ ๊ฐค๋ฌ๋ฆฌ์์ ํฐ์น ํ๋ฒ ์ ๋ชป ํด์ ์ด์ํ ์ฌ์ง์ด ๋ค์ด๊ฐ๋ฉด ์ ๋ฐฉ๋ฒ์ด ์๋ค. ์ด ํ CS์์๋ ์ฌ์ฉ์์ ์ฐ๋ฝํ๊ณ ์์ ํ๋ ๊ณผ์ ์ ๊ฑฐ์ณ์ผ ํ๋ค.
์์ ํ VisionKit์๊ฒ ๋งก๊ธฐ๋ ๊ฒ์ ๋ฌด๋ฆฌ์ด๊ฒ ์ง๋ง, ๊ธ์๋ฅผ ์ถ์ถํด์ ์์ฌ์ค๋ฌ์ด ์๋ฅ๋ ์ผ๋ฟ ์ฐฝ ํ๋ฒ ๋์์ ์ธ์ ์์ผ์ฃผ๋ ๊ฒ๋ ๋์์ง ์๊ฒ ๋ค๋ ์๊ฐ์ด ๋ค์๋ค. CS ํ ์ ๋ฌด๋ฅผ ์กฐ๊ธ ์ค์ฌ๋ค์ผ ์ ์์ง ์์๊น?!
์ ํ์์ ์ง์ํ๋ VisionKit์ผ๋ก ํ ์คํธ๋ฅผ ์ธ์ํ ๋ ์ ํ ์๋ฒ๋ฅผ ํ๋๊ฒ ์๋๋ผ ๊ธฐ๊ธฐ ๋ด๋ถ์์ ์งํํ๊ฒ ๋๋ค. ๋ณด์์ ์ผ๋ก๋ ๋ค๋ฅธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฐ๋ ๊ฒ ๋ณด๋ค ์์ ํ์ง ์์๊น ์ถ๋ค.
1. ๋ฌธ์ ์ค์บ๋ ๋ง๋ค๊ธฐ
2. ์ด๋ฏธ์ง ํ ์คํธ ์ธ์ํ๊ธฐ
1. ๋ฌธ์ ์ค์บ๋ ๋ง๋ค๊ธฐ
VNDocumentCameraViewController
VNDocumentCameraViewController๋ VisionKit์์ ์ ๊ณตํ๋ค. ๋ฌธ์ ์ค์บ์ ํ ์ ์๋๋ก ์นด๋ฉ๋ผ UI๋ฅผ ์ ๊ณตํด ์ค๋ค.
struct DocumentScannerView: UIViewControllerRepresentable {
}
SwiftUI์์ ์ฌ์ฉํ๋ ค๋ฉด UIViewControllerRepresentable๋ก ๊ตฌํํด ์ฃผ์ด์ผ ํ๋ค.
@Binding var recognizedText: String
func makeUIViewController(context: Context) -> VNDocumentCameraViewController {
let viewController = VNDocumentCameraViewController()
viewController.delegate = context.coordinator
return viewController
}
func updateUIViewController(_ uiViewController: VNDocumentCameraViewController, context: Context) { }
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
recognizedText๋ VisionKit์ด ์ธ์ํ ํ ์คํธ๊ฐ ๋ด๊ธฐ๊ฒ ๋๊ณ , ๋ฐ์ธ๋ฉ์ผ๋ก ์ฐ๊ฒฐ๋์๊ธฐ ๋๋ฌธ์ ์ด ๊ฐ์ด ์ ๋ฐ์ดํธ ๋๋ฉด recognizedText State๋ฅผ ์ฌ์ฉํ๋ View ๋ํ ์ ๋ฐ์ดํธ ๋์ด ํ๋ฉด์ ํ์ํ ์ ์๊ฒ ๋๋ค
class Coordinator: NSObject, VNDocumentCameraViewControllerDelegate { }
Coordinator ์ฝ๋๋ฅผ ์์ฑํ๋ค. VNDocumentCameraViewController์ ๋ธ๋ฆฌ๊ฒ์ดํธ๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด ํ๋กํ ์ฝ์ ์ฑํํด ์ค๋ค.
var parent: DocumentScannerView
init(_ parent: DocumentScannerView) {
self.parent = parent
}
Parent๋ DocumentScannerView
func documentCameraViewController(_ controller: VNDocumentCameraViewController, didFinishWith scan: VNDocumentCameraScan) {
for pageIndex in 0..<scan.pageCount {
let image = scan.imageOfPage(at: pageIndex)
recognizeText(in: image)
}
controller.dismiss(animated: true)
}
document camera๋ก ๋ถํฐ ์ค์บ๋ ๋ฌธ์๊ฐ ์ฑ๊ณต์ ์ผ๋ก ์ ์ฅ๋๋ฉด ์ด ๋ธ๋ฆฌ๊ฒ์ดํธ๋ฅผ ํธ์ถํ๋ค. ์ฌ๋ฌ์ฅ์ ์ค์บํ ์ ์๊ธฐ ๋๋ฌธ์ scan๋ ์ด๋ฏธ์ง๋ค์ด scan์ด๋ผ๋ ํ๋ผ๋ฏธํฐ์ ๋ด๊ฒจ ์จ๋ค. scan.imageOfPage(at: index)์ UIImage๋ฅผ ๋ฐํํ๊ฒ ๋๋ค.
private func recognizeText(in image: UIImage) {
guard let cgImage = image.cgImage else { return }
let request = VNRecognizeTextRequest { (request, error) in
guard let observations = request.results as? [VNRecognizedTextObservation],
error == nil else { return }
let recognizedStrings = observations.compactMap { observation in
observation.topCandidates(1).first?.string
}
DispatchQueue.main.async {
self.parent.recognizedText += recognizedStrings.joined(separator: "\n")
}
}
request.recognitionLanguages = ["ko"]
request.usesLanguageCorrection = true
let requestHandler = VNImageRequestHandler(cgImage: cgImage, options: [:])
do {
try requestHandler.perform([request])
} catch {
print("\(error).")
}
}
VNRecognizeTextRequest๋ Vision ํ๋ ์์ํฌ์ ํฌํจ๋์ด ์๊ณ ์ด๋ฅผ ์ด์ฉํ๋ฉด ์ด๋ฏธ์ง์์ ํ ์คํธ๋ฅผ ์ถ์ถํ ์ ์๋ค.
VNRecognizeTextRequest ๋จ๊ณ
1. request ์์ฑ
ํ ์คํธ๋ฅผ ์ฝ์ ๋ค ๋ฌด์์ ํ ์ง ๋ง๋ค์ด์ฃผ๋ ๋จ๊ณ์ด๋ค.
let request = VNRecognizeTextRequest { (request, error) in
guard let observations = request.results as? [VNRecognizedTextObservation],
error == nil else { return }
let recognizedStrings = observations.compactMap { observation in
observation.topCandidates(1).first?.string
}
DispatchQueue.main.async {
self.parent.recognizedText += recognizedStrings.joined(separator: "\n")
}
}
์ด๋ฏธ์ง๋ก ๋ถํฐ ํ ์คํธ๋ฅผ ์ธ์ํ ๋ค recognizedText ๋ฐ์ธ๋ฉ ๊ฐ์ ์ ๋ฐ์ดํธ ํด์ฃผ๋ ๋ก์ง์ด๋ค.
2. ์์ฑ ์ง์ ํ๊ธฐ
request.recognitionLanguages = ["ko"]
request.usesLanguageCorrection = true
๊ผญ ํ๊ตญ์ด๊ฐ ํฌํจ๋ ์ ์๋๋ก recognitionLanguages์ ์ง์ ํด ์ฃผ์ด์ผ ํ๋ค! ๊ฐ์ ์ด๋ฏธ์ง๋ผ ํ ์ง๋ผ๋ ํ๊ตญ์ด๋ฅผ ์์ด๋ก ์ธ์ํ์ ๋๋ ํ๊ตญ์ด๋ก ์ธ์ํ์ ๋๋ ๊ฒฐ๊ณผ๊ฐ ์์ ํ ๋ค๋ฅด๋ค!
3. requestHandler ์์ฑํ๊ธฐ
let requestHandler = VNImageRequestHandler(cgImage: cgImage, options: [:])
์ด๋ฏธ์ง์ ์ต์ ์ ์ง์
4. ์์ฑ ํ request๋ฅผ handler ํตํด ์คํํ๊ธฐ
do {
try requestHandler.perform([request])
} catch {
print("\(error).")
}
perform ์ request๋ค์ ๋ด์ ์ฃผ๋ฉด ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด request์์ ์ง์ ํ ํ์คํฌ๊ฐ ์คํ๋๋ค.
ContentView
struct ContentView: View {
@State private var recognizedText = ""
@State private var isShowDocumentScanner = false
var body: some View {
VStack {
ScrollView {
Text(recognizedText)
}
Button("Scan Documents") {
isShowDocumentScanner = true
recognizedText = ""
}
.sheet(isPresented: $isShowDocumentScanner) {
DocumentScannerView(recognizedText: $recognizedText)
}
}
.padding()
}
}
๋ค๋ชจ๋ ์์ญ ์ธ์ํ๋ฉด ์ค์บ๋๊ฐ ๋ ๋ค ์ฐ์ด๋ฒ๋ฆฐ๋ค. ์ค์บ๋ ์นด๋ฉ๋ผ ๋๋ฌด ์ ๋ง๋ค์ด ๋์;;; ์ ํ ๊ฐ์ฌํด์ฉ
์ด๋ฏธ์ง์์ ํ ์คํธ ์ถ์ถํ๊ธฐ
์์์ VNRecognizeTextRequest ํ ์คํธ ์ถ์ถํ๋ ์ฝ๋๋ง ๋ถ๋ฆฌํ๋ฉด
private func recognizeText(in image: UIImage?) {
guard let cgImage = image?.cgImage else { return }
let request = VNRecognizeTextRequest { (request, error) in
guard let observations = request.results as? [VNRecognizedTextObservation],
error == nil else { return }
let text = observations.compactMap { $0.topCandidates(1).first?.string }.joined(separator: "\n")
DispatchQueue.main.async {
print(text)
self.recognizedText = text
}
}
request.recognitionLanguages = ["ko"]
request.usesLanguageCorrection = true
let requestHandler = VNImageRequestHandler(cgImage: cgImage, options: [:])
do {
try requestHandler.perform([request])
} catch {
print("\(error)")
}
}
SwiftUI ์์ ๋ฐ๋ก ์จ์ฃผ๋ฉด ๋๋ค
@State private var selectedImage: UIImage? = UIImage()
@State private var isShowingImagePicker = false
@State private var recognizedText: String = ""
var body: some View {
VStack {
if let selectedImage = selectedImage {
Image(uiImage: selectedImage)
.resizable()
.scaledToFit()
.onChange(of: selectedImage) { image in
recognizedText = ""
recognizeText(in: image)
}
}
Button("Select Image") {
isShowingImagePicker = true
}
if !recognizedText.isEmpty {
Text(recognizedText)
.padding()
}
}
.sheet(isPresented: $isShowingImagePicker) {
ImagePicker(selectedImage: $selectedImage)
}
}
ImagePicker์ UIImage ํ์ ์ State๋ฅผ ์ ๋ฌํ๋ฉด ์ ํํ ์ฌ์ง์ ์ฝ๊ฒ observing ํ ์ ์๋ค ๐ฅน
๋ช ๊ฐ ๊ธ์ ๋นผ๊ณ ์์ฃผ ์ ์ถ์ถ ๋ฌ๋ค.
์ ์ ํ ๋๋ฌด ์ข์....
references
https://developer.apple.com/documentation/visionkit
VisionKit | Apple Developer Documentation
Identify and extract information in the environment using the device’s camera, or in images that your app displays.
developer.apple.com
https://gwonran.tistory.com/54
Swift] privacy-sensitive data ํด๊ฒฐํ๋ ๋ฒ
This app has usage desctiption. The app's Info.plist must contain an NSCameraUsageDescription key with a string value explaning to the user how the app uses this data. ์ด๋ฐ ์๋ฌ๊ฐ ๋ด๋ค. ๋ฐ๋ก ๊ตฌ๊ธ๋ง์ ์ฐฉ์ํ๋ค. [iOS tip] Privacy-sensitive
gwonran.tistory.com
https://ohwhatisthis.tistory.com/17
[iOS/Swift] VisionKit OCR Api ์์ ํ๊ธ
Swift ์ธ์ด๋ฅผ ์ฌ์ฉํ์ฌ Apple Developer์์ ์ ๊ณตํ๋ VisionKit Framework๋ฅผ ์ฌ์ฉํ์ฌ OCR ๊ฐ๋ฐ ์๋ก VisionKit์ Apple์์ ๊ฐ๋ฐํ OCR Api๋ก VisionKit์ ์ด๋ฏธ์ง์ iOS ์นด๋ฉ๋ผ์ Live Video ์ Text ๋ฐ ๊ตฌ์กฐํ๋ ๋ฐ์ดํฐ๋ฅผ
ohwhatisthis.tistory.com
ํน์ ๊ธ์ ๋ฌธ์ ์๊ฑฐ๋ ์์ ํด์ผ ํ ๋ถ๋ถ์ด ์๋ค๋ฉด
์๋ ค์ฃผ์๋ฉด ๊ฐ์ฌํ๊ฒ ์ต๋๋ค :)
'iOS ๐ > Library' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[SwiftUI/TipKit] ์ฌ์ฉ์์๊ฒ Tip ์ ๊ณตํ๊ธฐ (1) | 2023.12.02 |
---|---|
[SwiftData] Query and Filtering Data (0) | 2023.10.24 |