Vision Framework를 사용해서 이미지속의 텍스트를 인식할 수 있는 OCR 기능을 구현해보려합니다
OCR 이란?
광학 문자 인식 Optical Character Recognition의 약자로 이미지나 스캔 문서에 있는 텍스트를 기계가 읽을 수 있는 텍스트로 변환하는 기술입니다.
애플에서 제공되는 텍스트 인식기능은
2가지 방법으로 이용합니다
fast path방식과 accurate path방식이 있습니다.
Fast
프레임워크의 문자 감지 기능을 사용해서 개별문자를 찾은다음, 작은 머신 러닝 모델을 사용하여 개별 문자와 단어를 인식
이 접근방식은 기존 OCR과 유사함
Accurate (default)
신경망을 이용해서 문자열과 줄로 이루어진 텍스트를 찾은다음 추가 분석을 수행하여 개별 단어와 문장을 찾음
이 접근방식은 사람이 텍스트를 읽는 방식과 훨씬 더 일치함
VNRequestTextRecognitionLevel타입으로 제공되고
VNRecognizeTextRequest의 recognitionLevel옵션을 변경하면 적용됩니다
let textRequest = VNRecognizeTextRequest()
textRequest.recognitionLevel = .accurate
구현
단순 OCR인식 코드구현은 간단합니다
사용하기 편리하게 제공된거같네요
guard let image = uiImage.cgImage else {
print("cgImage nil!!")
return
}
let textRequest = VNRecognizeTextRequest()
textRequest.revision = VNRecognizeTextRequestRevision3
textRequest.recognitionLanguages = ["ko-KR", "en-US"]
textRequest.usesLanguageCorrection = false
textRequest.minimumTextHeight = 0.1
textRequest.recognitionLevel = .accurate
let handler = VNImageRequestHandler(cgImage: image)
do {
// 요청실행
try handler.perform([textRequest])
// 요청값 반환
let text = textRequest.results?.compactMap { $0.topCandidates(1).first?.string }.joined(separator: "\n") ?? ""
print(text)
}
Request 옵션을 하나씩알아볼게요
revision - 엔진 버전 선택(최신이 좋아요 iOS지원 버전에맞게 선택하세요!)
recognitionLanguages - 인식할 언어
textRequest.supportedRecognitionLanguages()
위의코드를 실행해서 인식가능한 언어목록을 확인할 수 있어요
인식할 언어 ko-KR를 안넣어주면 한국어가 있어도 한국어로 인식하지않아요
usesLanguageCorrection - 인식 프로세스 중에 언어보정을 할건지 옵션이에요
비활성화하면 원시 인식 결과값이 반환되서 성능은 좋지만 정확도가 떨어져요
minimumTextHeight - 이미지 높이에 상대적인 크기의 텍스트를 인식하는 옵션이에요
예를들어 이미지높이의 절반인 텍스트로 인식을 제한하려면 0.5를 넣어요
크게크게 보기때문에 메모리 사용량이 줄고, 인식속도가 빨라지지만 작은크기 텍스트는 무시합니다.
기본값은 0.03125 라고하네요
(테스트할때 0.1~0.9 다 비슷하게나와서 체감을 못하겠어요 🤔)
recognitionLevel - 위에서 말한 인식요청 우선순위를 정하는 옵션입니다.
VNImageRequestHandler의 perform()
요청을 수행하는 함수입니다.
이때 인식이 시작됩니다
try handler.perform([textRequest])
VNRecognizeTextRequest.results
perform을 했을때 VNRecognizedTextObservation타입의 결과값 배열이 반환되요
let text = textRequest.results?.compactMap { $0.topCandidates(1).first?.string }.joined(separator: "\n") ?? ""
$0.topCandidates(1) - 인식결과값이 여러개인데 그중 우선순위 최상위 1개만 받겠다 라는 옵션이에요
인식한 문자 하나하나의 결과값을 joined시켜서 하나의 문장으로 만들어주는 코드에요
실험
인식시킬 테스트 이미지를 만들어봤어요
각 글씨크기별로 인식차이와 영어 한글 숫자 차이를 알아보려합니다!
결과를 극단적으로 비교하기위해서
우선 처음테스트는 정확도우선의 옵션을 설정했어요
let textRequest = VNRecognizeTextRequest()
textRequest.revision = VNRecognizeTextRequestRevision3
textRequest.recognitionLanguages = ["ko-KR", "en-US"]
textRequest.usesLanguageCorrection = true
textRequest.recognitionLevel = .accurate
출력값은 아래와같아요
12 point 포인트
14 point 포인트
16 point 포인트
20 point 포인트
24 point 포인트
32 point 포인트
36 point 포인트
40 point 포인트
48 point 포인트
0123456789 0123456789
01234567890123456789
0123456789 0123456789
0123456790123456789
01234567890123456789
01234567890123456789
01234567890123456789
01234567890123456789
01234567890123456789
64 point 포인트
01234567890123456789
1 장, 0.800754084 seconds
정확하게 잘 인식하네요
하지만 이때 고려해야할게 하나 있죠 바로 시간입니다
1장을 인식하는데 0.8초라 여러장의 사진을 인식하려한다면 고려해야할 부분이 많아지겠죠?
다음으론
빠름우선의 옵션을 설정해볼게요
let textRequest = VNRecognizeTextRequest()
textRequest.revision = VNRecognizeTextRequestRevision3
textRequest.recognitionLanguages = ["ko-KR", "en-US"]
textRequest.usesLanguageCorrection = false
// textRequest.minimumTextHeight = 0.1 << fast일때 설정하면 아무결과를 받지못합니다
textRequest.recognitionLevel = .fast
출력값은 아래와같아요
12 point &oI_
01234587890123456789
14 point EOI
01234567890123456789
16 point EOIE
01234567890123456789
20 point £oI E
01234567890123456789
24 point £oI E
01234567890123456789
32 point &OI E
01234567890123456789
36 point &oIE
01234567890123456789
40 point £OIE
01234567890123456789
48 point lOI E
01234567890123456789
64 point lOI E
01234567890123456789
1 장, 0.077625583 seconds
12point 테스트케이스인 1번째줄은 6과 8을 정확하게 인식하지못햇네요
나머지 숫자와 영어는 잘인식되늰데 한글을 인식하지못하네요
그리고 위코드에서 보이듯이 minimumTextHeight를 fast옵션과 함께주면 텍스트인식을 못해서 아무결과값을 받지못해요
시간도보면 0.07초로 10배차이가 나는걸 볼 수있어요
시간과 성능의 트레이드오프인 옵션들이라 테스트하면서 잘 선택하면 좋을 것 같아요!
마지막으로 이젠 여러장을 인식할때 async로 동시에 돌리고싶을때를 가정하고
코드를 한번 바꿔볼게요
텍스트인식 구현부는 아래와같구요
private func excuteRequest(uiImage: UIImage) async -> [VNRecognizedTextObservation] {
guard let image = uiImage.cgImage else {
print("cgImage nil!!")
return []
}
let textRequest = VNRecognizeTextRequest()
textRequest.revision = VNRecognizeTextRequestRevision3
textRequest.recognitionLanguages = ["ko-KR", "en-US"]
textRequest.usesLanguageCorrection = false
// textRequest.minimumTextHeight = 0.1
textRequest.recognitionLevel = .fast
let handler = VNImageRequestHandler(cgImage: image)
return await withUnsafeContinuation { continuation in
do {
try handler.perform([textRequest])
let observations = textRequest.results ?? []
continuation.resume(returning: observations)
} catch {
print(error)
continuation.resume(returning: [])
}
}
}
상위에서 이미지를 넣어주는 코드는 아래와같습니다.
func recognizeText() async {
let images: [UIImage] = [
.폰트테스트png,
.폰트테스트png,
]
let time = await clock.measure {
// 이미지 총갯수를 4개의 이미지그룹으로 분리
let imageGroups = images.splitFourStride()
let recognizationString = await withTaskGroup(of: String.self) { group in
imageGroups.forEach { imageGroup in
group.addTask {
await self.excute(imageGroup: imageGroup)
}
}
var result: String = ""
for await value in group {
result += value
}
return result
}
// 종합된 결과 텍스트
print(recognizationString)
}
// 인식한 장수, 인식까지 걸린시간
print(images.count, "장", time)
}
// 각 그룹별 인식결과 반환
private func excute(imageGroup: [UIImage]) async -> String {
var totalString = ""
for (i, image) in imageGroup.enumerated() {
let results = await self.excuteRequest(uiImage: image)
let text = results.compactMap { $0.topCandidates(1).first?.string }.joined(separator: "\n")
totalString += text
}
return totalString
}
10개의 이미지를 4개의 그룹으로 실행시킨 결과 아래와같이 동시에 잘 진행되는걸 확인할 수 있어요
비교를위해 그룹을 나누지않는다면 아래와같은 그림처럼 동시에 진행하진 않는걸 볼 수 있어요
'iyOmSd > Title: Swift' 카테고리의 다른 글
[Swift] Task Cancel (0) | 2025.05.23 |
---|---|
[Swift] Mergeable Libraries, 병합된 라이브러리 (0) | 2025.04.16 |
[Swift] Core Bluetooth Basic (0) | 2025.03.18 |
[Swift] WWDC24 Demystify explicitly built modules (0) | 2024.11.26 |
[Swift] WWDC24 Bring expression to your app with Genmoji (0) | 2024.11.24 |