@trustchex/react-native-sdk 1.362.6 → 1.381.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/TrustchexSDK.podspec +3 -3
- package/android/build.gradle +3 -3
- package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraView.kt +64 -19
- package/android/src/main/java/com/trustchex/reactnativesdk/opencv/OpenCVModule.kt +636 -301
- package/ios/Camera/TrustchexCameraView.swift +166 -119
- package/ios/OpenCV/OpenCVHelper.h +0 -7
- package/ios/OpenCV/OpenCVHelper.mm +0 -60
- package/ios/OpenCV/OpenCVModule.h +0 -4
- package/ios/OpenCV/OpenCVModule.mm +440 -358
- package/lib/module/Shared/Components/DebugOverlay.js +541 -0
- package/lib/module/Shared/Components/FaceCamera.js +1 -0
- package/lib/module/Shared/Components/IdentityDocumentCamera.constants.js +44 -0
- package/lib/module/Shared/Components/IdentityDocumentCamera.flows.js +270 -0
- package/lib/module/Shared/Components/IdentityDocumentCamera.js +708 -1593
- package/lib/module/Shared/Components/IdentityDocumentCamera.types.js +3 -0
- package/lib/module/Shared/Components/IdentityDocumentCamera.utils.js +273 -0
- package/lib/module/Shared/Components/QrCodeScannerCamera.js +1 -8
- package/lib/module/Shared/Libs/mrz.utils.js +202 -9
- package/lib/module/Translation/Resources/en.js +0 -4
- package/lib/module/Translation/Resources/tr.js +0 -4
- package/lib/module/version.js +1 -1
- package/lib/typescript/src/Shared/Components/DebugOverlay.d.ts +30 -0
- package/lib/typescript/src/Shared/Components/DebugOverlay.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Components/FaceCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.constants.d.ts +35 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.constants.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts +3 -56
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.flows.d.ts +88 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.flows.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.types.d.ts +116 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.types.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts +93 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Components/QrCodeScannerCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/TrustchexCamera.d.ts +1 -0
- package/lib/typescript/src/Shared/Components/TrustchexCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts +8 -0
- package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/en.d.ts +0 -4
- package/lib/typescript/src/Translation/Resources/en.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/tr.d.ts +0 -4
- package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
- package/lib/typescript/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/Shared/Components/DebugOverlay.tsx +656 -0
- package/src/Shared/Components/FaceCamera.tsx +1 -0
- package/src/Shared/Components/IdentityDocumentCamera.constants.ts +44 -0
- package/src/Shared/Components/IdentityDocumentCamera.flows.ts +342 -0
- package/src/Shared/Components/IdentityDocumentCamera.tsx +1105 -2324
- package/src/Shared/Components/IdentityDocumentCamera.types.ts +136 -0
- package/src/Shared/Components/IdentityDocumentCamera.utils.ts +364 -0
- package/src/Shared/Components/QrCodeScannerCamera.tsx +1 -9
- package/src/Shared/Components/TrustchexCamera.tsx +1 -0
- package/src/Shared/Libs/mrz.utils.ts +238 -26
- package/src/Translation/Resources/en.ts +0 -4
- package/src/Translation/Resources/tr.ts +0 -4
- package/src/version.ts +1 -1
|
@@ -30,6 +30,14 @@ class TrustchexCameraView: UIView {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
+
@objc var resolution: String = "fullhd" {
|
|
34
|
+
didSet {
|
|
35
|
+
if resolution != oldValue {
|
|
36
|
+
// "hd" or "fullhd" - reinitialize camera with new resolution
|
|
37
|
+
setupCamera()
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
33
41
|
@objc var torchEnabled: Bool = false {
|
|
34
42
|
didSet {
|
|
35
43
|
if torchEnabled != oldValue {
|
|
@@ -114,27 +122,23 @@ class TrustchexCameraView: UIView {
|
|
|
114
122
|
// Add video input
|
|
115
123
|
let cameraPosition: AVCaptureDevice.Position = (_cameraType == "front") ? .front : .back
|
|
116
124
|
|
|
117
|
-
// Set quality based on
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
session.sessionPreset = .hd1920x1080
|
|
124
|
-
} else if session.canSetSessionPreset(.hd1280x720) {
|
|
125
|
-
session.sessionPreset = .hd1280x720
|
|
126
|
-
} else {
|
|
127
|
-
session.sessionPreset = .high
|
|
128
|
-
}
|
|
125
|
+
// Set quality based on resolution setting
|
|
126
|
+
// "hd": 720x1280 (HD) - lower bandwidth, faster processing
|
|
127
|
+
// "fullhd": 1920x1080 (Full HD, default) - sharp text/document capture
|
|
128
|
+
let sessionPreset: AVCaptureSession.Preset
|
|
129
|
+
if resolution.lowercased() == "hd" {
|
|
130
|
+
sessionPreset = .hd1280x720
|
|
129
131
|
} else {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
132
|
+
sessionPreset = .hd1920x1080 // Full HD (default)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if session.canSetSessionPreset(sessionPreset) {
|
|
136
|
+
session.sessionPreset = sessionPreset
|
|
137
|
+
} else if sessionPreset == .hd1920x1080 && session.canSetSessionPreset(.hd1280x720) {
|
|
138
|
+
// Fallback from Full HD to HD
|
|
139
|
+
session.sessionPreset = .hd1280x720
|
|
140
|
+
} else if session.canSetSessionPreset(.high) {
|
|
141
|
+
session.sessionPreset = .high
|
|
138
142
|
}
|
|
139
143
|
let camera = selectBestCamera(for: cameraPosition)
|
|
140
144
|
guard let camera = camera,
|
|
@@ -418,6 +422,12 @@ class TrustchexCameraView: UIView {
|
|
|
418
422
|
targetFps = fps
|
|
419
423
|
}
|
|
420
424
|
|
|
425
|
+
@objc(changeResolution:)
|
|
426
|
+
func changeResolution(_ res: String) {
|
|
427
|
+
// "hd" (720x1280) or "fullhd" (1920x1080, default)
|
|
428
|
+
resolution = res.lowercased() == "hd" ? "hd" : "fullhd"
|
|
429
|
+
}
|
|
430
|
+
|
|
421
431
|
@objc func setFocusPoint(_ x: NSNumber, _ y: NSNumber) {
|
|
422
432
|
sessionQueue.async { [weak self] in
|
|
423
433
|
guard let camera = self?.currentCamera else { return }
|
|
@@ -695,6 +705,12 @@ extension TrustchexCameraView: AVCaptureVideoDataOutputSampleBufferDelegate {
|
|
|
695
705
|
}
|
|
696
706
|
lastFrameTime = currentTime
|
|
697
707
|
|
|
708
|
+
// ML Kit Performance Optimization Tips Applied (iOS):
|
|
709
|
+
// 1. alwaysDiscardsLateVideoFrames = true throttles detector calls
|
|
710
|
+
// 2. Drop frames if detector is still busy (prevents queue buildup)
|
|
711
|
+
// 3. Process at 1080x1920 (Full HD) for optimal real-time detection
|
|
712
|
+
// 4. Use synchronous results(in:) API for video frames (Google recommended)
|
|
713
|
+
// 5. Dispatch to background queue to unblock videoQueue immediately
|
|
698
714
|
// Mark as processing on videoQueue
|
|
699
715
|
isProcessing = true
|
|
700
716
|
|
|
@@ -733,16 +749,30 @@ extension TrustchexCameraView: AVCaptureVideoDataOutputSampleBufferDelegate {
|
|
|
733
749
|
let ciImage = CIImage(cvPixelBuffer: pixelBuffer)
|
|
734
750
|
let orientedImage = isBufferLandscape ? ciImage.oriented(.right) : ciImage
|
|
735
751
|
|
|
736
|
-
//
|
|
752
|
+
// Compute brightness early for OCR-B enhancement decision
|
|
753
|
+
let brightness = computeBrightness(from: pixelBuffer, width: portraitWidth, height: portraitHeight)
|
|
754
|
+
|
|
755
|
+
// For text recognition, use original image directly
|
|
756
|
+
// ML Kit works best with native camera frames
|
|
757
|
+
let textEnhancedImage = orientedImage
|
|
758
|
+
|
|
759
|
+
// Create VisionImage from the image (enhanced or original) for better text recognition
|
|
737
760
|
// This ensures MLKit processes the image in the correct orientation
|
|
738
|
-
guard let cgImage = self.ciContext.createCGImage(
|
|
761
|
+
guard let cgImage = self.ciContext.createCGImage(textEnhancedImage, from: textEnhancedImage.extent) else {
|
|
739
762
|
resetProcessingState()
|
|
740
763
|
return
|
|
741
764
|
}
|
|
742
765
|
let visionImage = VisionImage(image: UIImage(cgImage: cgImage))
|
|
743
766
|
visionImage.orientation = .up // Already oriented correctly
|
|
744
767
|
|
|
745
|
-
// Use
|
|
768
|
+
// Use image for text recognition
|
|
769
|
+
// ML Kit text recognition best practices (iOS):
|
|
770
|
+
// - Requires minimum 16x16 pixels per character (ideal 16-24px per character)
|
|
771
|
+
// - Input image: 1080x1920 (portrait Full HD) provides excellent accuracy at real-time speed
|
|
772
|
+
// - Each character at ~30px = 36 characters per line @ 1080px width
|
|
773
|
+
// - Use synchronous results(in:) API from captureOutput(_:didOutput:from:)
|
|
774
|
+
// - Set AVCaptureVideoDataOutput.alwaysDiscardsLateVideoFrames = true (throttle)
|
|
775
|
+
// - Get results then render overlay in single step for optimal performance
|
|
746
776
|
let textVisionImage = visionImage
|
|
747
777
|
|
|
748
778
|
// Generate JPEG base64 only when JS side explicitly needs the image
|
|
@@ -755,125 +785,114 @@ extension TrustchexCameraView: AVCaptureVideoDataOutputSampleBufferDelegate {
|
|
|
755
785
|
}
|
|
756
786
|
}
|
|
757
787
|
|
|
758
|
-
//
|
|
759
|
-
//
|
|
760
|
-
|
|
788
|
+
// ML Kit Performance Best Practice (iOS):
|
|
789
|
+
// Use synchronous results(in:) API for video processing instead of async process()
|
|
790
|
+
// This is Google's recommended approach for real-time video frame processing
|
|
791
|
+
// Source: https://developers.google.com/ml-kit/vision/barcode-scanning/ios#performance-tips
|
|
761
792
|
var facesArray: [[String: Any]] = []
|
|
762
793
|
var textBlocksArray: [[String: Any]] = []
|
|
763
794
|
var barcodesArray: [[String: Any]] = []
|
|
764
795
|
|
|
796
|
+
// Face detection using synchronous API
|
|
765
797
|
if enableFaceDetection {
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
}
|
|
792
|
-
if face.hasRightEyeOpenProbability {
|
|
793
|
-
faceMap["rightEyeOpenProbability"] = Double(face.rightEyeOpenProbability)
|
|
794
|
-
}
|
|
795
|
-
facesArray.append(faceMap)
|
|
798
|
+
do {
|
|
799
|
+
let faces = try faceDetector.results(in: visionImage)
|
|
800
|
+
for face in faces {
|
|
801
|
+
var faceMap: [String: Any] = [:]
|
|
802
|
+
let isFront = self._cameraType == "front"
|
|
803
|
+
let faceX = isFront ? CGFloat(portraitWidth) - face.frame.origin.x - face.frame.width : face.frame.origin.x
|
|
804
|
+
|
|
805
|
+
faceMap["bounds"] = [
|
|
806
|
+
"x": Int(faceX),
|
|
807
|
+
"y": Int(face.frame.origin.y),
|
|
808
|
+
"width": Int(face.frame.width),
|
|
809
|
+
"height": Int(face.frame.height)
|
|
810
|
+
]
|
|
811
|
+
faceMap["yawAngle"] = face.hasHeadEulerAngleY ? Double(face.headEulerAngleY) : 0.0
|
|
812
|
+
faceMap["pitchAngle"] = face.hasHeadEulerAngleX ? Double(face.headEulerAngleX) : 0.0
|
|
813
|
+
faceMap["rollAngle"] = face.hasHeadEulerAngleZ ? Double(face.headEulerAngleZ) : 0.0
|
|
814
|
+
if face.hasTrackingID {
|
|
815
|
+
faceMap["trackingId"] = face.trackingID
|
|
816
|
+
}
|
|
817
|
+
// Only include probability fields when available (matching Android behavior)
|
|
818
|
+
if face.hasSmilingProbability {
|
|
819
|
+
faceMap["smilingProbability"] = Double(face.smilingProbability)
|
|
820
|
+
}
|
|
821
|
+
if face.hasLeftEyeOpenProbability {
|
|
822
|
+
faceMap["leftEyeOpenProbability"] = Double(face.leftEyeOpenProbability)
|
|
796
823
|
}
|
|
824
|
+
if face.hasRightEyeOpenProbability {
|
|
825
|
+
faceMap["rightEyeOpenProbability"] = Double(face.rightEyeOpenProbability)
|
|
826
|
+
}
|
|
827
|
+
facesArray.append(faceMap)
|
|
797
828
|
}
|
|
798
|
-
|
|
829
|
+
} catch {
|
|
830
|
+
// Face detection failed - continue with empty array
|
|
799
831
|
}
|
|
800
|
-
} else {
|
|
801
|
-
semaphore.signal()
|
|
802
832
|
}
|
|
803
833
|
|
|
804
|
-
// Text recognition
|
|
805
|
-
|
|
834
|
+
// Text recognition using synchronous API
|
|
835
|
+
// On iOS, this completes quickly at 1080x1920 resolution (~100-300ms per frame)
|
|
806
836
|
var resultText = ""
|
|
807
837
|
if enableTextRecognition {
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
blockMap["blockFrame"] = [
|
|
843
|
-
"x": blockX,
|
|
844
|
-
"y": blockY,
|
|
845
|
-
"width": blockWidth,
|
|
846
|
-
"height": blockHeight,
|
|
847
|
-
"boundingCenterX": blockX + blockWidth / 2,
|
|
848
|
-
"boundingCenterY": blockY + blockHeight / 2
|
|
849
|
-
]
|
|
850
|
-
textBlocksArray.append(blockMap)
|
|
838
|
+
do {
|
|
839
|
+
let text = try textRecognizer.results(in: textVisionImage)
|
|
840
|
+
resultText = text.text
|
|
841
|
+
for block in text.blocks {
|
|
842
|
+
var blockMap: [String: Any] = ["text": block.text]
|
|
843
|
+
let bb = block.frame
|
|
844
|
+
|
|
845
|
+
// When buffer is landscape (1920x1080) but we set orientation to .right,
|
|
846
|
+
// ML Kit might still return coordinates in landscape space.
|
|
847
|
+
// We need to rotate them to portrait space (1080x1920) to match face detection.
|
|
848
|
+
let blockX: Int
|
|
849
|
+
let blockY: Int
|
|
850
|
+
let blockWidth: Int
|
|
851
|
+
let blockHeight: Int
|
|
852
|
+
|
|
853
|
+
if isBufferLandscape {
|
|
854
|
+
// Rotate from landscape (1920x1080) to portrait (1080x1920)
|
|
855
|
+
// When rotating 90° clockwise (.right):
|
|
856
|
+
// new_x = old_y
|
|
857
|
+
// new_y = landscape_width - old_x - width
|
|
858
|
+
// new_width = old_height
|
|
859
|
+
// new_height = old_width
|
|
860
|
+
blockX = Int(bb.origin.y)
|
|
861
|
+
blockY = pixelWidth - Int(bb.origin.x) - Int(bb.width)
|
|
862
|
+
blockWidth = Int(bb.height)
|
|
863
|
+
blockHeight = Int(bb.width)
|
|
864
|
+
} else {
|
|
865
|
+
// Already portrait, use directly
|
|
866
|
+
blockX = Int(bb.origin.x)
|
|
867
|
+
blockY = Int(bb.origin.y)
|
|
868
|
+
blockWidth = Int(bb.width)
|
|
869
|
+
blockHeight = Int(bb.height)
|
|
851
870
|
}
|
|
871
|
+
|
|
872
|
+
blockMap["blockFrame"] = [
|
|
873
|
+
"x": blockX,
|
|
874
|
+
"y": blockY,
|
|
875
|
+
"width": blockWidth,
|
|
876
|
+
"height": blockHeight,
|
|
877
|
+
"boundingCenterX": blockX + blockWidth / 2,
|
|
878
|
+
"boundingCenterY": blockY + blockHeight / 2
|
|
879
|
+
]
|
|
880
|
+
textBlocksArray.append(blockMap)
|
|
852
881
|
}
|
|
853
|
-
|
|
882
|
+
} catch {
|
|
883
|
+
// Text recognition failed - continue with empty result
|
|
854
884
|
}
|
|
855
|
-
} else {
|
|
856
|
-
textSemaphore.signal()
|
|
857
885
|
}
|
|
858
886
|
|
|
859
887
|
// Barcode scanning - use native AVFoundation results (captured via metadata delegate)
|
|
860
888
|
// This is much faster than MLKit barcode scanning
|
|
861
|
-
let barcodeSemaphore = DispatchSemaphore(value: 0)
|
|
862
889
|
if enableBarcodeScanning {
|
|
863
890
|
// Use the barcodes detected by the native AVCaptureMetadataOutput
|
|
864
891
|
barcodesArray = lastDetectedBarcodes
|
|
865
|
-
barcodeSemaphore.signal()
|
|
866
|
-
} else {
|
|
867
|
-
barcodeSemaphore.signal()
|
|
868
892
|
}
|
|
869
893
|
|
|
870
|
-
//
|
|
871
|
-
|
|
872
|
-
_ = textSemaphore.wait(timeout: .now() + 2.0)
|
|
873
|
-
|
|
874
|
-
// Only compute brightness if we haven't timed out or crashed
|
|
875
|
-
// Brightness calculation restricted to scanning frame area (between 36% from top and 36% from bottom, 5% margins on sides)
|
|
876
|
-
let brightness = computeBrightness(from: pixelBuffer, width: portraitWidth, height: portraitHeight)
|
|
894
|
+
// Brightness was already computed earlier for OCR-B enhancement
|
|
895
|
+
// No need to recompute here
|
|
877
896
|
|
|
878
897
|
let currentTime = CACurrentMediaTime() * 1000 // Convert to milliseconds to match Android
|
|
879
898
|
|
|
@@ -962,6 +981,35 @@ extension TrustchexCameraView: AVCaptureVideoDataOutputSampleBufferDelegate {
|
|
|
962
981
|
|
|
963
982
|
return Double(sum) / Double(sampleCount)
|
|
964
983
|
}
|
|
984
|
+
|
|
985
|
+
/**
|
|
986
|
+
* Validates if the image dimensions are suitable for ML Kit text recognition (iOS).
|
|
987
|
+
*
|
|
988
|
+
* ML Kit text recognition requirements (iOS):
|
|
989
|
+
* - Minimum 16x16 pixels per character for acceptable accuracy
|
|
990
|
+
* - Ideal: 16-24 pixels per character for optimal performance
|
|
991
|
+
* - At 1080x1920 (portrait Full HD), assuming ~30px average character width:
|
|
992
|
+
* - Can fit ~36 characters per line (1080/30)
|
|
993
|
+
* - Can fit ~64 lines (1920/30)
|
|
994
|
+
* - Performance: ~100-300ms per frame at 1080x1920 with asynchronous API
|
|
995
|
+
*
|
|
996
|
+
* @return quality score (0.0-1.0) where 1.0 is optimal
|
|
997
|
+
*/
|
|
998
|
+
private func calculateTextRecognitionQuality(width: Int, height: Int) -> Double {
|
|
999
|
+
// iOS uses 1080x1920 as standard, so we prefer that
|
|
1000
|
+
let pixelScore: Double
|
|
1001
|
+
switch (width, height) {
|
|
1002
|
+
case (1080..., 1920...):
|
|
1003
|
+
pixelScore = 1.0 // Optimal (Full HD)
|
|
1004
|
+
case (720..., 1280...):
|
|
1005
|
+
pixelScore = 0.85 // Good (HD)
|
|
1006
|
+
case (640..., 960...):
|
|
1007
|
+
pixelScore = 0.65 // Acceptable
|
|
1008
|
+
default:
|
|
1009
|
+
pixelScore = 0.4 // Poor
|
|
1010
|
+
}
|
|
1011
|
+
return pixelScore
|
|
1012
|
+
}
|
|
965
1013
|
}
|
|
966
1014
|
|
|
967
1015
|
// MARK: - AVCaptureFileOutputRecordingDelegate
|
|
@@ -1131,7 +1179,6 @@ extension TrustchexCameraView: AVCaptureMetadataOutputObjectsDelegate {
|
|
|
1131
1179
|
lastDetectedBarcodes = barcodes
|
|
1132
1180
|
}
|
|
1133
1181
|
}
|
|
1134
|
-
|
|
1135
1182
|
// MARK: - Helper Extensions
|
|
1136
1183
|
extension Comparable {
|
|
1137
1184
|
func clamped(to limits: ClosedRange<Self>) -> Self {
|
|
@@ -5,13 +5,6 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
5
5
|
|
|
6
6
|
@interface OpenCVHelper : NSObject
|
|
7
7
|
|
|
8
|
-
/// Preprocesses an image for better OCR text recognition
|
|
9
|
-
/// Applies bilateral filtering, CLAHE, and sharpening to enhance text clarity
|
|
10
|
-
/// @param image The input UIImage to preprocess
|
|
11
|
-
/// @param applyThresholding Whether to apply adaptive thresholding (for binary output)
|
|
12
|
-
/// @return A preprocessed UIImage optimized for text recognition, or nil if preprocessing fails
|
|
13
|
-
+ (UIImage * _Nullable)preprocessImageForOCR:(UIImage *)image applyThresholding:(BOOL)applyThresholding;
|
|
14
|
-
|
|
15
8
|
@end
|
|
16
9
|
|
|
17
10
|
NS_ASSUME_NONNULL_END
|
|
@@ -65,64 +65,4 @@
|
|
|
65
65
|
return image;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
+ (UIImage *)preprocessImageForOCR:(UIImage *)image applyThresholding:(BOOL)applyThresholding {
|
|
69
|
-
@try {
|
|
70
|
-
if (!image) return nil;
|
|
71
|
-
|
|
72
|
-
cv::Mat mat = [self imageToMat:image];
|
|
73
|
-
if (mat.empty()) return nil;
|
|
74
|
-
|
|
75
|
-
// Step 1: Convert to grayscale
|
|
76
|
-
cv::Mat gray;
|
|
77
|
-
cv::cvtColor(mat, gray, cv::COLOR_RGB2GRAY);
|
|
78
|
-
mat.release();
|
|
79
|
-
|
|
80
|
-
// Step 2: Suppress background using blackhat morphology
|
|
81
|
-
cv::Mat blackhat;
|
|
82
|
-
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(15, 5));
|
|
83
|
-
cv::morphologyEx(gray, blackhat, cv::MORPH_BLACKHAT, kernel);
|
|
84
|
-
gray.release();
|
|
85
|
-
|
|
86
|
-
// Step 3: Advanced denoising - removes artifacts while keeping character details
|
|
87
|
-
cv::Mat denoised;
|
|
88
|
-
cv::fastNlMeansDenoising(blackhat, denoised, 6.0, 7, 21);
|
|
89
|
-
blackhat.release();
|
|
90
|
-
|
|
91
|
-
// Step 4: CLAHE for local contrast without over-amplifying noise
|
|
92
|
-
cv::Ptr<cv::CLAHE> clahe = cv::createCLAHE(2.0, cv::Size(8, 8));
|
|
93
|
-
cv::Mat enhanced;
|
|
94
|
-
clahe->apply(denoised, enhanced);
|
|
95
|
-
denoised.release();
|
|
96
|
-
|
|
97
|
-
// Step 5: Unsharp masking for clearer edges without halos
|
|
98
|
-
cv::Mat blurred;
|
|
99
|
-
cv::GaussianBlur(enhanced, blurred, cv::Size(0, 0), 1.2);
|
|
100
|
-
cv::Mat sharpened;
|
|
101
|
-
cv::addWeighted(enhanced, 1.8, blurred, -0.8, 0, sharpened);
|
|
102
|
-
blurred.release();
|
|
103
|
-
enhanced.release();
|
|
104
|
-
|
|
105
|
-
// Step 6: Normalize to full 0-255 range
|
|
106
|
-
// Ensures maximum contrast for ML Kit
|
|
107
|
-
cv::Mat result;
|
|
108
|
-
cv::normalize(sharpened, result, 0, 255, cv::NORM_MINMAX);
|
|
109
|
-
sharpened.release();
|
|
110
|
-
|
|
111
|
-
if (applyThresholding) {
|
|
112
|
-
cv::Mat thresholded;
|
|
113
|
-
cv::adaptiveThreshold(result, thresholded, 255, cv::ADAPTIVE_THRESH_GAUSSIAN_C, cv::THRESH_BINARY, 31, 10);
|
|
114
|
-
result.release();
|
|
115
|
-
result = thresholded;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
UIImage *resultImage = [self matToImage:result];
|
|
119
|
-
result.release();
|
|
120
|
-
|
|
121
|
-
return resultImage;
|
|
122
|
-
} @catch (NSException *exception) {
|
|
123
|
-
NSLog(@"OpenCV preprocessing error: %@", exception.reason);
|
|
124
|
-
return nil;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
68
|
@end
|
|
@@ -3,8 +3,4 @@
|
|
|
3
3
|
|
|
4
4
|
@interface OpenCVModule : NSObject <RCTBridgeModule>
|
|
5
5
|
|
|
6
|
-
// Synchronous method for preprocessingimage for OCR
|
|
7
|
-
// This is called directly from Swift camera code for better performance
|
|
8
|
-
- (UIImage * _Nullable)preprocessImageForOCRSync:(UIImage * _Nonnull)image applyThresholding:(BOOL)applyThresholding;
|
|
9
|
-
|
|
10
6
|
@end
|