-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[D-0] �Vision를 통해 영수증 스캔 기능 구현 (#91)
* feat: DocumentScanner scanDocument() 구현 * feat: `editImageWithScanResult()` 메서드 구현 * feat: 문서 스캔 기능 구현 및 maskLayer 추가 * fix: CreateOCRLedgerVC 메모리 누수 해결 * fix: 영수증 촬영 시 captureImageView에 원본 이미지가 표현되도록 수정 * chore: CVImageBuffer extension DocumentScanner로 이동 * refactor: transformBoundingBox 네이밍 및 코드 변경 - transformBoundingBox -> transformVisionToIOS 네이밍 변경 - CGAffineTransform -> CGRect로 변경 * refactor: editImageWithScanResult 반환 타입 변경 - CGImage -> CIImage * refactor: scanDocument 파라미터 및 request 설정값 변경
- Loading branch information
Showing
6 changed files
with
160 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
74 changes: 74 additions & 0 deletions
74
Projects/Feature/Ledger/Sources/Scene/Ledger/Creaters/Scan/Model/DocumentScanner.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import Vision | ||
import CoreImage | ||
|
||
actor DocumentScanner: Sendable { | ||
private var recentScanResult: VNRectangleObservation? | ||
|
||
func scanDocument(imageBuffer: CVImageBuffer, with previewSize: CGRect) async throws -> CGRect? { | ||
return try await withCheckedThrowingContinuation { [weak self] continuation in | ||
guard let self else { continuation.resume(returning: nil); return } | ||
let request = VNDetectRectanglesRequest { (request: VNRequest, error: Error?) in | ||
guard let results = request.results as? [VNRectangleObservation], | ||
let rectangleObservation = results.first else { | ||
continuation.resume(returning: nil); return | ||
} | ||
|
||
Task { | ||
await self.updateRecentScanResult(rectangleObservation) | ||
let rect = await self.transformVisionToIOS(rectangleObservation, to: previewSize) | ||
continuation.resume(returning: rect) | ||
} | ||
} | ||
|
||
request.minimumAspectRatio = 0.2 | ||
request.maximumAspectRatio = 1.0 | ||
request.minimumConfidence = 0.8 | ||
|
||
let handler = VNImageRequestHandler(cvPixelBuffer: imageBuffer, options: [:]) | ||
do { | ||
try handler.perform([request]) | ||
} catch { | ||
continuation.resume(throwing: error) | ||
} | ||
} | ||
} | ||
|
||
func editImageWithScanResult(_ imageData: Data) -> CIImage? { | ||
guard let ciImage = CIImage(data: imageData)?.oriented(.right), | ||
let recentScanResult else { return nil } | ||
|
||
let topLeft = recentScanResult.topLeft.scaled(to: ciImage.extent.size) | ||
let topRight = recentScanResult.topRight.scaled(to: ciImage.extent.size) | ||
let bottomLeft = recentScanResult.bottomLeft.scaled(to: ciImage.extent.size) | ||
let bottomRight = recentScanResult.bottomRight.scaled(to: ciImage.extent.size) | ||
|
||
return ciImage.applyingFilter("CIPerspectiveCorrection", parameters: [ | ||
"inputTopLeft": CIVector(cgPoint: topLeft), | ||
"inputTopRight": CIVector(cgPoint: topRight), | ||
"inputBottomLeft": CIVector(cgPoint: bottomLeft), | ||
"inputBottomRight": CIVector(cgPoint: bottomRight), | ||
]) | ||
} | ||
|
||
private func transformVisionToIOS(_ rectangleObservation: VNRectangleObservation, to previewSize: CGRect) -> CGRect { | ||
let visionRect = rectangleObservation.boundingBox | ||
return CGRect( | ||
origin: CGPoint(x: CGFloat(visionRect.minX * previewSize.width), y: CGFloat((1 - visionRect.maxY) * previewSize.height)), | ||
size: CGSize(width: visionRect.width * previewSize.width, height: visionRect.height * previewSize.height) | ||
) | ||
} | ||
|
||
private func updateRecentScanResult(_ rectangleObservation: VNRectangleObservation) { | ||
recentScanResult = rectangleObservation | ||
} | ||
} | ||
|
||
private extension CGPoint { | ||
func scaled(to size: CGSize) -> CGPoint { | ||
return CGPoint(x: self.x * size.width, | ||
y: self.y * size.height) | ||
} | ||
} | ||
|
||
extension CVImageBuffer: @unchecked @retroactive Sendable {} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters