mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-28 08:30:11 +00:00
Merge branch 'tmp'
This commit is contained in:
commit
2316de38da
@ -7,8 +7,20 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
09053D7920A5CCF10029652D /* genann.c in Sources */ = {isa = PBXBuildFile; fileRef = 09053D7120A5CCEF0029652D /* genann.c */; };
|
||||
09053D7A20A5CCF10029652D /* fast-edge.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 09053D7220A5CCEF0029652D /* fast-edge.cpp */; };
|
||||
09053D7B20A5CCF10029652D /* TGPassportOCR.h in Headers */ = {isa = PBXBuildFile; fileRef = 09053D7320A5CCF00029652D /* TGPassportOCR.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
09053D7C20A5CCF10029652D /* ocr.h in Headers */ = {isa = PBXBuildFile; fileRef = 09053D7420A5CCF00029652D /* ocr.h */; };
|
||||
09053D7D20A5CCF10029652D /* TGPassportOCR.mm in Sources */ = {isa = PBXBuildFile; fileRef = 09053D7520A5CCF00029652D /* TGPassportOCR.mm */; };
|
||||
09053D7E20A5CCF10029652D /* genann.h in Headers */ = {isa = PBXBuildFile; fileRef = 09053D7620A5CCF00029652D /* genann.h */; };
|
||||
09053D7F20A5CCF10029652D /* fast-edge.h in Headers */ = {isa = PBXBuildFile; fileRef = 09053D7720A5CCF00029652D /* fast-edge.h */; };
|
||||
09053D8020A5CCF10029652D /* ocr.mm in Sources */ = {isa = PBXBuildFile; fileRef = 09053D7820A5CCF00029652D /* ocr.mm */; };
|
||||
090671C41F67F71700CCF2F5 /* TGLocationOptionsView.h in Headers */ = {isa = PBXBuildFile; fileRef = 090671C21F67F71700CCF2F5 /* TGLocationOptionsView.h */; };
|
||||
090671C51F67F71700CCF2F5 /* TGLocationOptionsView.m in Sources */ = {isa = PBXBuildFile; fileRef = 090671C31F67F71700CCF2F5 /* TGLocationOptionsView.m */; };
|
||||
0916FEA720A1EA7B0084A755 /* TGPassportScanView.h in Headers */ = {isa = PBXBuildFile; fileRef = 0916FEA520A1EA7B0084A755 /* TGPassportScanView.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
0916FEA820A1EA7B0084A755 /* TGPassportScanView.m in Sources */ = {isa = PBXBuildFile; fileRef = 0916FEA620A1EA7B0084A755 /* TGPassportScanView.m */; };
|
||||
0916FEAB20A1EBFA0084A755 /* TGPassportMRZ.m in Sources */ = {isa = PBXBuildFile; fileRef = 0916FEA920A1EBF90084A755 /* TGPassportMRZ.m */; };
|
||||
0916FEAC20A1EBFA0084A755 /* TGPassportMRZ.h in Headers */ = {isa = PBXBuildFile; fileRef = 0916FEAA20A1EBFA0084A755 /* TGPassportMRZ.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
09750F761F2FA816001B9886 /* SSignalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09750F741F2FA5E8001B9886 /* SSignalKit.framework */; };
|
||||
09750FB71F30DB0E001B9886 /* TGClipboardGalleryModel.h in Headers */ = {isa = PBXBuildFile; fileRef = 09750FAF1F30DB0E001B9886 /* TGClipboardGalleryModel.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
09750FB81F30DB0E001B9886 /* TGClipboardGalleryModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 09750FB01F30DB0E001B9886 /* TGClipboardGalleryModel.m */; };
|
||||
@ -1179,8 +1191,20 @@
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
09053D7120A5CCEF0029652D /* genann.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = genann.c; sourceTree = "<group>"; };
|
||||
09053D7220A5CCEF0029652D /* fast-edge.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = "fast-edge.cpp"; sourceTree = "<group>"; };
|
||||
09053D7320A5CCF00029652D /* TGPassportOCR.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGPassportOCR.h; sourceTree = "<group>"; };
|
||||
09053D7420A5CCF00029652D /* ocr.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ocr.h; sourceTree = "<group>"; };
|
||||
09053D7520A5CCF00029652D /* TGPassportOCR.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TGPassportOCR.mm; sourceTree = "<group>"; };
|
||||
09053D7620A5CCF00029652D /* genann.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = genann.h; sourceTree = "<group>"; };
|
||||
09053D7720A5CCF00029652D /* fast-edge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "fast-edge.h"; sourceTree = "<group>"; };
|
||||
09053D7820A5CCF00029652D /* ocr.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ocr.mm; sourceTree = "<group>"; };
|
||||
090671C21F67F71700CCF2F5 /* TGLocationOptionsView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGLocationOptionsView.h; sourceTree = "<group>"; };
|
||||
090671C31F67F71700CCF2F5 /* TGLocationOptionsView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGLocationOptionsView.m; sourceTree = "<group>"; };
|
||||
0916FEA520A1EA7B0084A755 /* TGPassportScanView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TGPassportScanView.h; sourceTree = "<group>"; };
|
||||
0916FEA620A1EA7B0084A755 /* TGPassportScanView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TGPassportScanView.m; sourceTree = "<group>"; };
|
||||
0916FEA920A1EBF90084A755 /* TGPassportMRZ.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGPassportMRZ.m; sourceTree = "<group>"; };
|
||||
0916FEAA20A1EBFA0084A755 /* TGPassportMRZ.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGPassportMRZ.h; sourceTree = "<group>"; };
|
||||
09750F741F2FA5E8001B9886 /* SSignalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SSignalKit.framework; path = "../../../../Library/Developer/Xcode/DerivedData/Telegraph-grvwvmixbmcefwboxkzfazvpcrxb/Build/Products/Release Hockeyapp-iphonesimulator/SSignalKit.framework"; sourceTree = "<group>"; };
|
||||
09750FAF1F30DB0E001B9886 /* TGClipboardGalleryModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGClipboardGalleryModel.h; sourceTree = "<group>"; };
|
||||
09750FB01F30DB0E001B9886 /* TGClipboardGalleryModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGClipboardGalleryModel.m; sourceTree = "<group>"; };
|
||||
@ -2365,6 +2389,25 @@
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
093134BC20A1EA3F003045EE /* Passport */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
09053D7220A5CCEF0029652D /* fast-edge.cpp */,
|
||||
09053D7720A5CCF00029652D /* fast-edge.h */,
|
||||
09053D7120A5CCEF0029652D /* genann.c */,
|
||||
09053D7620A5CCF00029652D /* genann.h */,
|
||||
09053D7420A5CCF00029652D /* ocr.h */,
|
||||
09053D7820A5CCF00029652D /* ocr.mm */,
|
||||
09053D7320A5CCF00029652D /* TGPassportOCR.h */,
|
||||
09053D7520A5CCF00029652D /* TGPassportOCR.mm */,
|
||||
0916FEA520A1EA7B0084A755 /* TGPassportScanView.h */,
|
||||
0916FEA620A1EA7B0084A755 /* TGPassportScanView.m */,
|
||||
0916FEAA20A1EBFA0084A755 /* TGPassportMRZ.h */,
|
||||
0916FEA920A1EBF90084A755 /* TGPassportMRZ.m */,
|
||||
);
|
||||
name = Passport;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
09750FAE1F30DAE1001B9886 /* Clipboard Menu */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -2406,6 +2449,7 @@
|
||||
D01777291F1F8F100044446D /* LegacyComponents */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
093134BC20A1EA3F003045EE /* Passport */,
|
||||
D0EB409E1F2FC0AA00838FE6 /* Resources */,
|
||||
D017776F1F1F91B00044446D /* Utils */,
|
||||
D01779B01F2139720044446D /* POP */,
|
||||
@ -3908,6 +3952,7 @@
|
||||
D01778191F1F961D0044446D /* TGMessageEntityHashtag.h in Headers */,
|
||||
D07BCA341F2A9B0400ED97AA /* TGModernGalleryEditableItemView.h in Headers */,
|
||||
D0177A911F221BB10044446D /* TGModernGalleryTransitionView.h in Headers */,
|
||||
0916FEAC20A1EBFA0084A755 /* TGPassportMRZ.h in Headers */,
|
||||
D01778E51F20CAE60044446D /* TGNavigationController.h in Headers */,
|
||||
D01777721F1F92420044446D /* TGPhoneUtils.h in Headers */,
|
||||
D07BC7721F2A2B3700ED97AA /* TGPhotoEditorCurvesHistogramView.h in Headers */,
|
||||
@ -3949,11 +3994,13 @@
|
||||
D01779261F20FE480044446D /* TGObserverProxy.h in Headers */,
|
||||
D07BCADF1F2B4F5E00ED97AA /* TGLegacyCameraController.h in Headers */,
|
||||
D017782F1F1F961D0044446D /* TGReplyMarkupAttachment.h in Headers */,
|
||||
0916FEA720A1EA7B0084A755 /* TGPassportScanView.h in Headers */,
|
||||
D07BCB211F2B646A00ED97AA /* TGPasscodeBackground.h in Headers */,
|
||||
D01778371F1F961D0044446D /* TGAudioMediaAttachment.h in Headers */,
|
||||
D07BC6F51F2A19A700ED97AA /* TGCameraSegmentsView.h in Headers */,
|
||||
D04269051F586A070037ECE8 /* TGVideoMessageScrubber.h in Headers */,
|
||||
D07BC87F1F2A365000ED97AA /* TGProgressWindow.h in Headers */,
|
||||
09053D7F20A5CCF10029652D /* fast-edge.h in Headers */,
|
||||
D07BC8001F2A2C0B00ED97AA /* PGGrainTool.h in Headers */,
|
||||
D01779EA1F2139980044446D /* POPAnimationPrivate.h in Headers */,
|
||||
D017775C1F1F8FE60044446D /* PSKeyValueStore.h in Headers */,
|
||||
@ -4011,6 +4058,7 @@
|
||||
090671C41F67F71700CCF2F5 /* TGLocationOptionsView.h in Headers */,
|
||||
D07BCAAE1F2B45DA00ED97AA /* TGFileUtils.h in Headers */,
|
||||
D0177B1E1F2641B10044446D /* PGCameraMovieWriter.h in Headers */,
|
||||
09053D7B20A5CCF10029652D /* TGPassportOCR.h in Headers */,
|
||||
D01779641F2103910044446D /* TGPaintUtils.h in Headers */,
|
||||
D07BCB741F2B6A5600ED97AA /* TGEmbedYoutubePlayerView.h in Headers */,
|
||||
D07BC8C51F2A37EC00ED97AA /* TGPhotoPaintEntityView.h in Headers */,
|
||||
@ -4050,6 +4098,7 @@
|
||||
D0177A071F2139980044446D /* POPSpringAnimation.h in Headers */,
|
||||
D07BC9951F2A481C00ED97AA /* TGStickerKeyboardTabSettingsCell.h in Headers */,
|
||||
D0177A341F21F1980044446D /* UIImage+TGMediaEditableItem.h in Headers */,
|
||||
09053D7E20A5CCF10029652D /* genann.h in Headers */,
|
||||
D07BC94E1F2A3EA900ED97AA /* TGHashtagPanelCell.h in Headers */,
|
||||
D07BCBAF1F2B6F6300ED97AA /* CBCoubAudioSource.h in Headers */,
|
||||
D07BC7841F2A2B3700ED97AA /* TGPhotoEditorSliderView.h in Headers */,
|
||||
@ -4117,6 +4166,7 @@
|
||||
D01779FE1F2139980044446D /* POPGeometry.h in Headers */,
|
||||
D07BCA981F2B443700ED97AA /* TGMediaAssetsPickerController.h in Headers */,
|
||||
D07BCBF51F2B72DC00ED97AA /* STKLocalFileDataSource.h in Headers */,
|
||||
09053D7C20A5CCF10029652D /* ocr.h in Headers */,
|
||||
D07BC9F11F2A9A2B00ED97AA /* TGMediaPickerCell.h in Headers */,
|
||||
D07BC8371F2A2D0C00ED97AA /* TGSecretTimerPickerItemView.h in Headers */,
|
||||
D07BC8D31F2A37EC00ED97AA /* TGPhotoPaintTextEntity.h in Headers */,
|
||||
@ -4690,6 +4740,7 @@
|
||||
D07BC9BB1F2A705D00ED97AA /* TGPhotoFilterCell.m in Sources */,
|
||||
D0177A571F21F7F40044446D /* TGDoubleTapGestureRecognizer.m in Sources */,
|
||||
D07BC7FF1F2A2C0B00ED97AA /* PGFadeTool.m in Sources */,
|
||||
0916FEAB20A1EBFA0084A755 /* TGPassportMRZ.m in Sources */,
|
||||
D01778C31F200AF70044446D /* TGAnimationBlockDelegate.m in Sources */,
|
||||
D07BC85F1F2A2DBD00ED97AA /* TGMenuSheetTitleItemView.m in Sources */,
|
||||
D07BC6EC1F2A19A700ED97AA /* TGCameraFlashActiveView.m in Sources */,
|
||||
@ -4821,6 +4872,7 @@
|
||||
D07BC85D1F2A2DBD00ED97AA /* TGMenuSheetItemView.m in Sources */,
|
||||
D07BC90E1F2A380D00ED97AA /* TGPainting.m in Sources */,
|
||||
D0177A441F21F62A0044446D /* TGMediaVideoConverter.m in Sources */,
|
||||
09053D8020A5CCF10029652D /* ocr.mm in Sources */,
|
||||
D01779A51F210A120044446D /* TGMediaAssetLegacyImageSignals.m in Sources */,
|
||||
D017777B1F1F927A0044446D /* NSObject+TGLock.m in Sources */,
|
||||
D01778281F1F961D0044446D /* TGMessageEntitiesAttachment.m in Sources */,
|
||||
@ -4836,6 +4888,7 @@
|
||||
D07BC7181F2A29B700ED97AA /* TGPhotoEditorController.m in Sources */,
|
||||
D07BC8251F2A2C0B00ED97AA /* PGSharpenTool.m in Sources */,
|
||||
D0177AF41F23DF6D0044446D /* TGImageManagerTask.m in Sources */,
|
||||
09053D7920A5CCF10029652D /* genann.c in Sources */,
|
||||
D017783F1F1F961D0044446D /* TGDocumentAttributeImageSize.m in Sources */,
|
||||
D01778E61F20CAE60044446D /* TGNavigationController.m in Sources */,
|
||||
D07BC8C61F2A37EC00ED97AA /* TGPhotoPaintEntityView.m in Sources */,
|
||||
@ -4922,6 +4975,7 @@
|
||||
D07BCB361F2B65F100ED97AA /* TGBuiltinWallpaperInfo.m in Sources */,
|
||||
D07BC90A1F2A380D00ED97AA /* TGPaintFaceDebugView.m in Sources */,
|
||||
D017785A1F1F961D0044446D /* TGImageMediaAttachment.m in Sources */,
|
||||
09053D7D20A5CCF10029652D /* TGPassportOCR.mm in Sources */,
|
||||
D017784C1F1F961D0044446D /* TGUnsupportedMediaAttachment.m in Sources */,
|
||||
D01779151F20F4500044446D /* TGStaticBackdropAreaData.m in Sources */,
|
||||
D07BC91C1F2A380D00ED97AA /* TGPaintRender.m in Sources */,
|
||||
@ -5031,6 +5085,7 @@
|
||||
D01778AA1F1FD0900044446D /* TGImageUtils.mm in Sources */,
|
||||
D07BCADA1F2B4F2800ED97AA /* TGOverlayFormsheetController.m in Sources */,
|
||||
D07BC8571F2A2DBD00ED97AA /* TGMenuSheetButtonItemView.m in Sources */,
|
||||
0916FEA820A1EA7B0084A755 /* TGPassportScanView.m in Sources */,
|
||||
D07BCA161F2A9A2B00ED97AA /* TGMediaPickerPhotoStripCell.m in Sources */,
|
||||
D07BCBAD1F2B6F6300ED97AA /* CBConstance.m in Sources */,
|
||||
D017780A1F1F961D0044446D /* TGGameMediaAttachment.m in Sources */,
|
||||
@ -5077,6 +5132,7 @@
|
||||
D07BCBD01F2B6F6300ED97AA /* NSDictionary+CBExtensions.m in Sources */,
|
||||
D07BC7731F2A2B3700ED97AA /* TGPhotoEditorCurvesHistogramView.m in Sources */,
|
||||
D07BCB3E1F2B65F100ED97AA /* TGWallpaperInfo.m in Sources */,
|
||||
09053D7A20A5CCF10029652D /* fast-edge.cpp in Sources */,
|
||||
D07BCA1C1F2A9A2B00ED97AA /* TGMediaPickerSelectionGestureRecognizer.m in Sources */,
|
||||
D07BC9141F2A380D00ED97AA /* TGPaintNeonBrush.m in Sources */,
|
||||
D01778161F1F961D0044446D /* TGMessageEntityCode.m in Sources */,
|
||||
|
@ -278,6 +278,9 @@ FOUNDATION_EXPORT const unsigned char LegacyComponentsVersionString[];
|
||||
#import <LegacyComponents/TGAttachmentCameraView.h>
|
||||
#import <LegacyComponents/TGMediaAvatarMenuMixin.h>
|
||||
#import <LegacyComponents/TGPassportAttachMenu.h>
|
||||
#import <LegacyComponents/TGPassportScanView.h>
|
||||
#import <LegacyComponents/TGPassportOCR.h>
|
||||
#import <LegacyComponents/TGPassportMRZ.h>
|
||||
#import <LegacyComponents/TGPasscodeEntryController.h>
|
||||
#import <LegacyComponents/TGEmbedPlayerView.h>
|
||||
#import <LegacyComponents/TGWallpaperInfo.h>
|
||||
|
BIN
LegacyComponents/Resources/LegacyComponentsResources.bundle/ocr_nn.bin
Executable file
BIN
LegacyComponents/Resources/LegacyComponentsResources.bundle/ocr_nn.bin
Executable file
Binary file not shown.
@ -30,6 +30,7 @@
|
||||
#import <LegacyComponents/TGMediaPickerGalleryVideoItemView.h>
|
||||
#import <LegacyComponents/TGModernGalleryVideoView.h>
|
||||
|
||||
#import "TGMediaVideoConverter.h"
|
||||
#import <LegacyComponents/TGMediaAssetImageSignals.h>
|
||||
#import <LegacyComponents/PGPhotoEditorValues.h>
|
||||
#import <LegacyComponents/TGVideoEditAdjustments.h>
|
||||
@ -2404,6 +2405,10 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
|
||||
|
||||
SSignal *thumbnailSignal = adjustments.trimStartValue > FLT_EPSILON ? trimmedVideoThumbnailSignal : videoThumbnailSignal;
|
||||
|
||||
TGMediaVideoConversionPreset preset = [TGMediaVideoConverter presetFromAdjustments:adjustments];
|
||||
CGSize dimensions = [TGMediaVideoConverter dimensionsFor:asset.originalSize adjustments:adjustments preset:preset];
|
||||
NSTimeInterval duration = adjustments.trimApplied ? (adjustments.trimEndValue - adjustments.trimStartValue) : video.videoDuration;
|
||||
|
||||
[signals addObject:[thumbnailSignal map:^id(UIImage *image)
|
||||
{
|
||||
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
|
||||
@ -2411,6 +2416,8 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
|
||||
dict[@"url"] = video.avAsset.URL;
|
||||
dict[@"previewImage"] = image;
|
||||
dict[@"adjustments"] = adjustments;
|
||||
dict[@"dimensions"] = [NSValue valueWithCGSize:dimensions];
|
||||
dict[@"duration"] = @(duration);
|
||||
|
||||
if (adjustments.paintingData.stickers.count > 0)
|
||||
dict[@"stickers"] = adjustments.paintingData.stickers;
|
||||
|
@ -749,6 +749,9 @@
|
||||
NSArray *entities = [editingContext entitiesForItem:asset];
|
||||
id<TGMediaEditAdjustments> adjustments = [editingContext adjustmentsForItem:asset];
|
||||
|
||||
CGSize dimensions = asset.originalSize;
|
||||
NSTimeInterval duration = asset.videoDuration;
|
||||
|
||||
[signals addObject:[inlineThumbnailSignal(asset) map:^id(UIImage *image)
|
||||
{
|
||||
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
|
||||
@ -757,6 +760,8 @@
|
||||
dict[@"asset"] = asset;
|
||||
dict[@"previewImage"] = image;
|
||||
dict[@"fileName"] = asset.fileName;
|
||||
dict[@"dimensions"] = [NSValue valueWithCGSize:dimensions];
|
||||
dict[@"duration"] = @(duration);
|
||||
|
||||
if (adjustments.paintingData.stickers.count > 0)
|
||||
dict[@"stickers"] = adjustments.paintingData.stickers;
|
||||
@ -799,6 +804,10 @@
|
||||
|
||||
SSignal *thumbnailSignal = adjustments.trimStartValue > FLT_EPSILON ? trimmedVideoThumbnailSignal : videoThumbnailSignal;
|
||||
|
||||
TGMediaVideoConversionPreset preset = [TGMediaVideoConverter presetFromAdjustments:adjustments];
|
||||
CGSize dimensions = [TGMediaVideoConverter dimensionsFor:asset.originalSize adjustments:adjustments preset:preset];
|
||||
NSTimeInterval duration = adjustments.trimApplied ? (adjustments.trimEndValue - adjustments.trimStartValue) : asset.videoDuration;
|
||||
|
||||
[signals addObject:[thumbnailSignal map:^id(UIImage *image)
|
||||
{
|
||||
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
|
||||
@ -807,6 +816,8 @@
|
||||
dict[@"asset"] = asset;
|
||||
dict[@"previewImage"] = image;
|
||||
dict[@"adjustments"] = adjustments;
|
||||
dict[@"dimensions"] = [NSValue valueWithCGSize:dimensions];
|
||||
dict[@"duration"] = @(duration);
|
||||
|
||||
if (adjustments.paintingData.stickers.count > 0)
|
||||
dict[@"stickers"] = adjustments.paintingData.stickers;
|
||||
|
@ -23,6 +23,7 @@
|
||||
+ (TGMediaVideoConversionPreset)bestAvailablePresetForDimensions:(CGSize)dimensions;
|
||||
+ (CGSize)_renderSizeWithCropSize:(CGSize)cropSize;
|
||||
|
||||
+ (TGMediaVideoConversionPreset)presetFromAdjustments:(TGMediaVideoEditAdjustments *)adjustments;
|
||||
+ (CGSize)dimensionsFor:(CGSize)dimensions adjustments:(TGMediaVideoEditAdjustments *)adjustments preset:(TGMediaVideoConversionPreset)preset;
|
||||
|
||||
@end
|
||||
|
@ -116,7 +116,7 @@
|
||||
return;
|
||||
|
||||
CGSize dimensions = [avAsset tracksWithMediaType:AVMediaTypeVideo].firstObject.naturalSize;
|
||||
TGMediaVideoConversionPreset preset = adjustments.sendAsGif ? TGMediaVideoConversionPresetAnimation : [self _presetFromAdjustments:adjustments];
|
||||
TGMediaVideoConversionPreset preset = adjustments.sendAsGif ? TGMediaVideoConversionPresetAnimation : [self presetFromAdjustments:adjustments];
|
||||
if (!CGSizeEqualToSize(dimensions, CGSizeZero) && preset != TGMediaVideoConversionPresetAnimation && preset != TGMediaVideoConversionPresetVideoMessage)
|
||||
{
|
||||
TGMediaVideoConversionPreset bestPreset = [self bestAvailablePresetForDimensions:dimensions];
|
||||
@ -524,7 +524,7 @@
|
||||
NSError *error;
|
||||
NSData *fileData = [NSData dataWithContentsOfURL:fileUrl options:NSDataReadingMappedIfSafe error:&error];
|
||||
if (error == nil)
|
||||
return [SSignal single:[self _hashForVideoWithFileData:fileData timingData:timingData preset:[self _presetFromAdjustments:adjustments]]];
|
||||
return [SSignal single:[self _hashForVideoWithFileData:fileData timingData:timingData preset:[self presetFromAdjustments:adjustments]]];
|
||||
else
|
||||
return [SSignal fail:error];
|
||||
}
|
||||
@ -572,7 +572,7 @@
|
||||
return hash;
|
||||
}
|
||||
|
||||
+ (TGMediaVideoConversionPreset)_presetFromAdjustments:(TGMediaVideoEditAdjustments *)adjustments
|
||||
+ (TGMediaVideoConversionPreset)presetFromAdjustments:(TGMediaVideoEditAdjustments *)adjustments
|
||||
{
|
||||
TGMediaVideoConversionPreset preset = adjustments.preset;
|
||||
if (preset == TGMediaVideoConversionPresetCompressedDefault)
|
||||
|
@ -4,8 +4,16 @@
|
||||
@class TGViewController;
|
||||
@class TGMenuSheetController;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
TGPassportAttachIntentDefault,
|
||||
TGPassportAttachIntentIdentityCard,
|
||||
TGPassportAttachIntentSelfie,
|
||||
TGPassportAttachIntentMultiple
|
||||
} TGPassportAttachIntent;
|
||||
|
||||
@interface TGPassportAttachMenu : NSObject
|
||||
|
||||
+ (TGMenuSheetController *)presentWithContext:(id<LegacyComponentsContext>)context parentController:(TGViewController *)parentController menuController:(TGMenuSheetController *)menuController title:(NSString *)title identity:(bool)identity selfie:(bool)selfie uploadAction:(void (^)(SSignal *, void (^)(void)))uploadAction sourceView:(UIView *)sourceView sourceRect:(CGRect (^)(void))sourceRect barButtonItem:(UIBarButtonItem *)barButtonItem;
|
||||
+ (TGMenuSheetController *)presentWithContext:(id<LegacyComponentsContext>)context parentController:(TGViewController *)parentController menuController:(TGMenuSheetController *)menuController title:(NSString *)title intent:(TGPassportAttachIntent)intent uploadAction:(void (^)(SSignal *, void (^)(void)))uploadAction sourceView:(UIView *)sourceView sourceRect:(CGRect (^)(void))sourceRect barButtonItem:(UIBarButtonItem *)barButtonItem;
|
||||
|
||||
@end
|
||||
|
@ -27,7 +27,7 @@
|
||||
|
||||
@implementation TGPassportAttachMenu
|
||||
|
||||
+ (TGMenuSheetController *)presentWithContext:(id<LegacyComponentsContext>)context parentController:(TGViewController *)parentController menuController:(TGMenuSheetController *)menuController title:(NSString *)title identity:(bool)identity selfie:(bool)selfie uploadAction:(void (^)(SSignal *, void (^)(void)))uploadAction sourceView:(UIView *)sourceView sourceRect:(CGRect (^)(void))sourceRect barButtonItem:(UIBarButtonItem *)barButtonItem
|
||||
+ (TGMenuSheetController *)presentWithContext:(id<LegacyComponentsContext>)context parentController:(TGViewController *)parentController menuController:(TGMenuSheetController *)menuController title:(NSString *)title intent:(TGPassportAttachIntent)intent uploadAction:(void (^)(SSignal *, void (^)(void)))uploadAction sourceView:(UIView *)sourceView sourceRect:(CGRect (^)(void))sourceRect barButtonItem:(UIBarButtonItem *)barButtonItem
|
||||
{
|
||||
if (uploadAction == nil)
|
||||
return nil;
|
||||
@ -51,7 +51,7 @@
|
||||
|
||||
__weak TGMenuSheetController *weakController = controller;
|
||||
__weak TGViewController *weakParentController = parentController;
|
||||
TGAttachmentCarouselItemView *carouselItem = [[TGAttachmentCarouselItemView alloc] initWithContext:context camera:true selfPortrait:selfie forProfilePhoto:false assetType:TGMediaAssetPhotoType saveEditedPhotos:false allowGrouping:false document:true];
|
||||
TGAttachmentCarouselItemView *carouselItem = [[TGAttachmentCarouselItemView alloc] initWithContext:context camera:true selfPortrait:intent == TGPassportAttachIntentSelfie forProfilePhoto:false assetType:TGMediaAssetPhotoType saveEditedPhotos:false allowGrouping:false document:true];
|
||||
__weak TGAttachmentCarouselItemView *weakCarouselItem = carouselItem;
|
||||
carouselItem.onlyCrop = true;
|
||||
carouselItem.parentController = parentController;
|
||||
@ -65,7 +65,7 @@
|
||||
if (strongParentController == nil)
|
||||
return;
|
||||
|
||||
[TGPassportAttachMenu _displayCameraWithView:cameraView menuController:strongController parentController:strongParentController context:context identity:identity uploadAction:uploadAction];
|
||||
[TGPassportAttachMenu _displayCameraWithView:cameraView menuController:strongController parentController:strongParentController context:context intent:intent uploadAction:uploadAction];
|
||||
};
|
||||
carouselItem.sendPressed = ^(TGMediaAsset *currentItem, __unused bool asFiles)
|
||||
{
|
||||
@ -99,7 +99,7 @@
|
||||
}];
|
||||
[itemViews addObject:galleryItem];
|
||||
|
||||
if (iosMajorVersion() >= 8 && !selfie)
|
||||
if (iosMajorVersion() >= 8 && intent != TGPassportAttachIntentSelfie)
|
||||
{
|
||||
TGMenuSheetButtonItemView *icloudItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Conversation.FileICloudDrive") type:TGMenuSheetButtonTypeDefault action:^
|
||||
{
|
||||
@ -270,7 +270,7 @@
|
||||
[parentController presentViewController:legacyCameraController animated:true completion:nil];
|
||||
}
|
||||
|
||||
+ (void)_displayCameraWithView:(TGAttachmentCameraView *)cameraView menuController:(TGMenuSheetController *)menuController parentController:(TGViewController *)parentController context:(id<LegacyComponentsContext>)context identity:(bool)identity uploadAction:(void (^)(SSignal *, void (^)(void)))uploadAction
|
||||
+ (void)_displayCameraWithView:(TGAttachmentCameraView *)cameraView menuController:(TGMenuSheetController *)menuController parentController:(TGViewController *)parentController context:(id<LegacyComponentsContext>)context intent:(bool)intent uploadAction:(void (^)(SSignal *, void (^)(void)))uploadAction
|
||||
{
|
||||
if (![[[LegacyComponentsGlobals provider] accessChecker] checkCameraAuthorizationStatusForIntent:TGCameraAccessIntentDefault alertDismissCompletion:nil])
|
||||
return;
|
||||
@ -291,9 +291,9 @@
|
||||
id<LegacyComponentsOverlayWindowManager> windowManager = [context makeOverlayWindowManager];
|
||||
|
||||
if (cameraView.previewView != nil)
|
||||
controller = [[TGCameraController alloc] initWithContext:[windowManager context] saveEditedPhotos:false saveCapturedMedia:false camera:cameraView.previewView.camera previewView:cameraView.previewView intent:identity ? TGCameraControllerPassportIdIntent : TGCameraControllerPassportIntent];
|
||||
controller = [[TGCameraController alloc] initWithContext:[windowManager context] saveEditedPhotos:false saveCapturedMedia:false camera:cameraView.previewView.camera previewView:cameraView.previewView intent:intent == TGPassportAttachIntentIdentityCard ? TGCameraControllerPassportIdIntent : TGCameraControllerPassportIntent];
|
||||
else
|
||||
controller = [[TGCameraController alloc] initWithContext:[windowManager context] saveEditedPhotos:false saveCapturedMedia:false intent:identity ? TGCameraControllerPassportIdIntent : TGCameraControllerPassportIntent];
|
||||
controller = [[TGCameraController alloc] initWithContext:[windowManager context] saveEditedPhotos:false saveCapturedMedia:false intent:intent == TGPassportAttachIntentIdentityCard ? TGCameraControllerPassportIdIntent : TGCameraControllerPassportIntent];
|
||||
|
||||
controller.shouldStoreCapturedAssets = false;
|
||||
|
||||
|
25
LegacyComponents/TGPassportMRZ.h
Normal file
25
LegacyComponents/TGPassportMRZ.h
Normal file
@ -0,0 +1,25 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface TGPassportMRZ : NSObject
|
||||
|
||||
@property (nonatomic, readonly) NSString *documentType;
|
||||
@property (nonatomic, readonly) NSString *documentSubtype;
|
||||
@property (nonatomic, readonly) NSString *issuingCountry;
|
||||
@property (nonatomic, readonly) NSString *lastName;
|
||||
@property (nonatomic, readonly) NSString *firstName;
|
||||
@property (nonatomic, readonly) NSString *documentNumber;
|
||||
@property (nonatomic, readonly) NSString *nationality;
|
||||
@property (nonatomic, readonly) NSDate *birthDate;
|
||||
@property (nonatomic, readonly) NSString *gender;
|
||||
@property (nonatomic, readonly) NSDate *expiryDate;
|
||||
@property (nonatomic, readonly) NSString *optional1;
|
||||
@property (nonatomic, readonly) NSString *optional2;
|
||||
|
||||
@property (nonatomic, readonly) NSString *mrz;
|
||||
|
||||
+ (instancetype)parseLines:(NSArray *)lines;
|
||||
|
||||
@end
|
||||
|
||||
extern const NSUInteger TGPassportTD1Length;
|
||||
extern const NSUInteger TGPassportTD23Length;
|
316
LegacyComponents/TGPassportMRZ.m
Normal file
316
LegacyComponents/TGPassportMRZ.m
Normal file
@ -0,0 +1,316 @@
|
||||
#import "TGPassportMRZ.h"
|
||||
|
||||
const NSUInteger TGPassportTD1Length = 30;
|
||||
const NSUInteger TGPassportTD23Length = 44;
|
||||
NSString *const TGPassportEmptyCharacter = @"<";
|
||||
|
||||
@implementation TGPassportMRZ
|
||||
|
||||
+ (instancetype)parseLines:(NSArray<NSString *> *)lines
|
||||
{
|
||||
if (lines.count == 2)
|
||||
{
|
||||
if (lines[0].length != TGPassportTD23Length || lines[1].length != TGPassportTD23Length)
|
||||
return nil;
|
||||
|
||||
TGPassportMRZ *result = [[TGPassportMRZ alloc] init];
|
||||
result->_documentType = [lines[0] substringToIndex:1];
|
||||
result->_documentSubtype = [self cleanString:[lines[0] substringWithRange:NSMakeRange(1, 1)]];
|
||||
result->_issuingCountry = [self cleanString:[lines[0] substringWithRange:NSMakeRange(2, 3)]];
|
||||
|
||||
NSCharacterSet *emptyCharacterSet = [NSCharacterSet characterSetWithCharactersInString:TGPassportEmptyCharacter];
|
||||
NSString *fullName = [[lines[0] substringWithRange:NSMakeRange(5, 39)] stringByTrimmingCharactersInSet:emptyCharacterSet];
|
||||
NSArray *names = [fullName componentsSeparatedByString:@"<<"];
|
||||
result->_lastName = [self nameString:names.firstObject];
|
||||
result->_firstName = [self nameString:names.lastObject];
|
||||
|
||||
NSString *documentNumber = [self ensureNumberString:[lines[1] substringToIndex:9]];
|
||||
NSInteger documentNumberCheck = [[self ensureNumberString:[lines[1] substringWithRange:NSMakeRange(9, 1)]] integerValue];
|
||||
if ([self isDataValid:documentNumber check:documentNumberCheck])
|
||||
result->_documentNumber = documentNumber;
|
||||
|
||||
result->_nationality = [lines[1] substringWithRange:NSMakeRange(10, 3)];
|
||||
NSString *birthDate = [self ensureNumberString:[lines[1] substringWithRange:NSMakeRange(13, 6)]];
|
||||
NSInteger birthDateCheck = [[self ensureNumberString:[lines[1] substringWithRange:NSMakeRange(19, 1)]] integerValue];
|
||||
if ([self isDataValid:birthDate check:birthDateCheck])
|
||||
result->_birthDate = [self dateFromString:birthDate];
|
||||
|
||||
NSString *gender = [lines[1] substringWithRange:NSMakeRange(20, 1)];
|
||||
if ([gender isEqualToString:TGPassportEmptyCharacter])
|
||||
gender = nil;
|
||||
result->_gender = gender;
|
||||
|
||||
NSString *expiryDate = [self ensureNumberString:[lines[1] substringWithRange:NSMakeRange(21, 6)]];
|
||||
NSInteger expiryDateCheck = [[self ensureNumberString:[lines[1] substringWithRange:NSMakeRange(27, 1)]] integerValue];
|
||||
if ([self isDataValid:expiryDate check:expiryDateCheck])
|
||||
result->_expiryDate = [self dateFromString:expiryDate];
|
||||
|
||||
NSString *optional1 = [lines[1] substringWithRange:NSMakeRange(28, 14)];
|
||||
NSString *optional1CheckString = [self ensureNumberString:[lines[1] substringWithRange:NSMakeRange(42, 1)]];
|
||||
NSInteger optional1CheckValue = [optional1CheckString isEqualToString:TGPassportEmptyCharacter] ? 0 : [optional1CheckString integerValue];
|
||||
if ([self isDataValid:optional1 check:optional1CheckValue])
|
||||
result->_optional1 = [self cleanString:optional1];
|
||||
|
||||
NSString *data = [NSString stringWithFormat:@"%@%d%@%d%@%d%@%@", documentNumber, (int)documentNumberCheck, birthDate, (int)birthDateCheck, expiryDate, (int)expiryDateCheck, optional1, optional1CheckString];
|
||||
NSInteger dataCheck = [[self ensureNumberString:[lines[1] substringWithRange:NSMakeRange(43, 1)]] integerValue];
|
||||
if ([self isDataValid:data check:dataCheck])
|
||||
{
|
||||
if ([result->_documentType isEqualToString:@"P"] && [result->_documentSubtype isEqualToString:@"N"] && [result->_issuingCountry isEqualToString:@"RUS"])
|
||||
{
|
||||
NSString *lastName = [self transliterateRussianMRZString:result->_lastName];
|
||||
result->_lastName = [self transliterateRussianName:lastName];
|
||||
NSString *firstName = [self transliterateRussianMRZString:result->_firstName];
|
||||
result->_firstName = [self transliterateRussianName:firstName];
|
||||
|
||||
NSString *lastSeriesDigit = [optional1 substringToIndex:1];
|
||||
NSMutableString *fullDocumentNo = [[NSMutableString alloc] init];
|
||||
[fullDocumentNo insertString:[result->_documentNumber substringToIndex:3] atIndex:0];
|
||||
[fullDocumentNo appendString:lastSeriesDigit];
|
||||
[fullDocumentNo appendString:[result->_documentNumber substringFromIndex:3]];
|
||||
result->_documentNumber = fullDocumentNo;
|
||||
}
|
||||
|
||||
|
||||
result->_mrz = [lines componentsJoinedByString:@"\n"];
|
||||
return result;
|
||||
}
|
||||
}
|
||||
else if (lines.count == 3)
|
||||
{
|
||||
if (lines[0].length != TGPassportTD1Length || lines[1].length != TGPassportTD1Length || lines[2].length != TGPassportTD1Length)
|
||||
return nil;
|
||||
|
||||
TGPassportMRZ *result = [[TGPassportMRZ alloc] init];
|
||||
result->_documentType = [lines[0] substringToIndex:1];
|
||||
result->_documentSubtype = [self cleanString:[lines[0] substringWithRange:NSMakeRange(1, 1)]];
|
||||
result->_issuingCountry = [self cleanString:[lines[0] substringWithRange:NSMakeRange(2, 3)]];
|
||||
|
||||
NSString *documentNumber = [self ensureNumberString:[lines[0] substringWithRange:NSMakeRange(5, 9)]];
|
||||
NSInteger documentNumberCheck = [[self ensureNumberString:[lines[0] substringWithRange:NSMakeRange(14, 1)]] integerValue];
|
||||
if ([self isDataValid:documentNumber check:documentNumberCheck])
|
||||
result->_documentNumber = documentNumber;
|
||||
|
||||
NSString *optional1 = [lines[0] substringWithRange:NSMakeRange(15, 15)];
|
||||
result->_optional1 = [self cleanString:optional1];
|
||||
|
||||
NSString *birthDate = [self ensureNumberString:[lines[1] substringToIndex:6]];
|
||||
NSInteger birthDateCheck = [[lines[1] substringWithRange:NSMakeRange(6, 1)] integerValue];
|
||||
if ([self isDataValid:birthDate check:birthDateCheck])
|
||||
result->_birthDate = [self dateFromString:birthDate];
|
||||
|
||||
NSString *gender = [lines[1] substringWithRange:NSMakeRange(7, 1)];
|
||||
if ([gender isEqualToString:TGPassportEmptyCharacter])
|
||||
gender = nil;
|
||||
result->_gender = gender;
|
||||
|
||||
NSString *expiryDate = [self ensureNumberString:[lines[1] substringWithRange:NSMakeRange(8, 6)]];
|
||||
NSInteger expiryDateCheck = [[self ensureNumberString:[lines[1] substringWithRange:NSMakeRange(14, 1)]] integerValue];
|
||||
if ([self isDataValid:expiryDate check:expiryDateCheck])
|
||||
result->_expiryDate = [self dateFromString:expiryDate];
|
||||
|
||||
result->_nationality = [lines[1] substringWithRange:NSMakeRange(15, 3)];
|
||||
|
||||
NSString *optional2 = [lines[1] substringWithRange:NSMakeRange(18, 11)];
|
||||
result->_optional2 = optional2;
|
||||
|
||||
NSCharacterSet *emptyCharacterSet = [NSCharacterSet characterSetWithCharactersInString:TGPassportEmptyCharacter];
|
||||
NSString *fullName = [self ensureAlphaString:lines[2]];
|
||||
fullName = [fullName stringByTrimmingCharactersInSet:emptyCharacterSet];
|
||||
NSArray *names = [fullName componentsSeparatedByString:@"<<"];
|
||||
result->_lastName = [self nameString:names.firstObject];
|
||||
result->_firstName = [self nameString:names.lastObject];
|
||||
result->_mrz = [lines componentsJoinedByString:@"\n"];
|
||||
|
||||
return result;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
+ (NSDateFormatter *)dateFormatter
|
||||
{
|
||||
static dispatch_once_t onceToken;
|
||||
static NSDateFormatter *dateFormatter;
|
||||
dispatch_once(&onceToken, ^
|
||||
{
|
||||
dateFormatter = [[NSDateFormatter alloc] init];
|
||||
dateFormatter.dateFormat = @"YYMMdd";
|
||||
dateFormatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
|
||||
});
|
||||
return dateFormatter;
|
||||
}
|
||||
|
||||
|
||||
+ (NSDate *)dateFromString:(NSString *)string
|
||||
{
|
||||
return [[self dateFormatter] dateFromString:string];
|
||||
}
|
||||
|
||||
+ (NSString *)cleanString:(NSString *)string
|
||||
{
|
||||
return [string stringByReplacingOccurrencesOfString:TGPassportEmptyCharacter withString:@""];
|
||||
}
|
||||
|
||||
+ (NSString *)nameString:(NSString *)string
|
||||
{
|
||||
return [string stringByReplacingOccurrencesOfString:TGPassportEmptyCharacter withString:@" "];
|
||||
}
|
||||
|
||||
+ (NSString *)ensureNumberString:(NSString *)string
|
||||
{
|
||||
return [[[[string stringByReplacingOccurrencesOfString:@"O" withString:@"0"] stringByReplacingOccurrencesOfString:@"U" withString:@"0"] stringByReplacingOccurrencesOfString:@"Q" withString:@"0"] stringByReplacingOccurrencesOfString:@"J" withString:@"0"];
|
||||
}
|
||||
|
||||
+ (NSString *)ensureAlphaString:(NSString *)string
|
||||
{
|
||||
NSCharacterSet *validChars = [NSCharacterSet characterSetWithCharactersInString:@"ABCDEFGHIJKLMNOPQRSTUVWXYZ<"];
|
||||
NSCharacterSet *invalidChars = [validChars invertedSet];
|
||||
|
||||
string = [string stringByReplacingOccurrencesOfString:@"0" withString:@"O"];
|
||||
return [self string:string byReplacingCharactersInSet:invalidChars withString:TGPassportEmptyCharacter];
|
||||
}
|
||||
|
||||
+ (NSString *)string:(NSString *)string byReplacingCharactersInSet:(NSCharacterSet *)charSet withString:(NSString *)aString {
|
||||
NSMutableString *s = [NSMutableString stringWithCapacity:string.length];
|
||||
for (NSUInteger i = 0; i < string.length; ++i) {
|
||||
unichar c = [string characterAtIndex:i];
|
||||
if (![charSet characterIsMember:c]) {
|
||||
[s appendFormat:@"%C", c];
|
||||
} else {
|
||||
[s appendString:aString];
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
+ (NSString *)transliterateRussianMRZString:(NSString *)string
|
||||
{
|
||||
NSDictionary *map = @
|
||||
{
|
||||
@"A": @"А",
|
||||
@"B": @"Б",
|
||||
@"V": @"В",
|
||||
@"G": @"Г",
|
||||
@"D": @"Д",
|
||||
@"E": @"Е",
|
||||
@"2": @"Ё",
|
||||
@"J": @"Ж",
|
||||
@"Z": @"З",
|
||||
@"I": @"И",
|
||||
@"Q": @"Й",
|
||||
@"K": @"К",
|
||||
@"L": @"Л",
|
||||
@"M": @"М",
|
||||
@"N": @"Н",
|
||||
@"O": @"О",
|
||||
@"P": @"П",
|
||||
@"R": @"Р",
|
||||
@"S": @"С",
|
||||
@"T": @"Т",
|
||||
@"U": @"У",
|
||||
@"F": @"Ф",
|
||||
@"H": @"Х",
|
||||
@"C": @"Ц",
|
||||
@"3": @"Ч",
|
||||
@"4": @"Ш",
|
||||
@"W": @"Щ",
|
||||
@"X": @"Ъ",
|
||||
@"Y": @"Ы",
|
||||
@"9": @"Ь",
|
||||
@"6": @"Э",
|
||||
@"7": @"Ю",
|
||||
@"8": @"Я",
|
||||
@" ": @" "
|
||||
};
|
||||
|
||||
NSMutableString *result = [[NSMutableString alloc] init];
|
||||
[string enumerateSubstringsInRange:NSMakeRange(0, string.length) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString *substring, __unused NSRange substringRange, __unused NSRange enclosingRange, __unused BOOL *stop)
|
||||
{
|
||||
if (substring == nil)
|
||||
return;
|
||||
|
||||
NSString *letter = map[substring];
|
||||
if (letter != nil)
|
||||
[result appendString:letter];
|
||||
}];
|
||||
return result;
|
||||
}
|
||||
|
||||
+ (NSString *)transliterateRussianName:(NSString *)string
|
||||
{
|
||||
NSDictionary *map = @
|
||||
{
|
||||
@"А": @"A",
|
||||
@"Б": @"B",
|
||||
@"В": @"V",
|
||||
@"Г": @"G",
|
||||
@"Д": @"D",
|
||||
@"Е": @"E",
|
||||
@"Ё": @"E",
|
||||
@"Ж": @"ZH",
|
||||
@"З": @"Z",
|
||||
@"И": @"I",
|
||||
@"Й": @"I",
|
||||
@"К": @"K",
|
||||
@"Л": @"L",
|
||||
@"М": @"M",
|
||||
@"Н": @"N",
|
||||
@"О": @"O",
|
||||
@"П": @"P",
|
||||
@"Р": @"R",
|
||||
@"С": @"S",
|
||||
@"Т": @"T",
|
||||
@"У": @"U",
|
||||
@"Ф": @"F",
|
||||
@"Х": @"KH",
|
||||
@"Ц": @"TS",
|
||||
@"Ч": @"CH",
|
||||
@"Ш": @"SH",
|
||||
@"Щ": @"SHCH",
|
||||
@"Ъ": @"IE",
|
||||
@"Ы": @"Y",
|
||||
@"Ь": @"",
|
||||
@"Э": @"E",
|
||||
@"Ю": @"IU",
|
||||
@"Я": @"IA",
|
||||
@" ": @" "
|
||||
};
|
||||
|
||||
NSMutableString *result = [[NSMutableString alloc] init];
|
||||
[string enumerateSubstringsInRange:NSMakeRange(0, string.length) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString *substring, __unused NSRange substringRange, __unused NSRange enclosingRange, __unused BOOL *stop)
|
||||
{
|
||||
if (substring == nil)
|
||||
return;
|
||||
|
||||
NSString *letter = map[substring];
|
||||
if (letter != nil)
|
||||
[result appendString:letter];
|
||||
}];
|
||||
return result;
|
||||
}
|
||||
|
||||
+ (bool)isDataValid:(NSString *)data check:(NSInteger)check
|
||||
{
|
||||
int32_t sum = 0;
|
||||
uint8_t w[3] = { 7, 3, 1 };
|
||||
|
||||
for (NSUInteger i = 0; i < data.length; i++)
|
||||
{
|
||||
unichar c = [data characterAtIndex:i];
|
||||
NSInteger d = 0;
|
||||
if (c >= '0' && c <= '9')
|
||||
d = c - '0';
|
||||
else if (c >= 'A' && c <= 'Z')
|
||||
d = (10 + c) - 'A';
|
||||
else if (c != '<')
|
||||
return false;
|
||||
|
||||
sum += d * w[i % 3];
|
||||
}
|
||||
|
||||
if (sum % 10 != check)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@end
|
8
LegacyComponents/TGPassportOCR.h
Normal file
8
LegacyComponents/TGPassportOCR.h
Normal file
@ -0,0 +1,8 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <SSignalKit/SSignalKit.h>
|
||||
|
||||
@interface TGPassportOCR : NSObject
|
||||
|
||||
+ (SSignal *)recognizeMRZInImage:(UIImage *)image;
|
||||
|
||||
@end
|
21
LegacyComponents/TGPassportOCR.mm
Normal file
21
LegacyComponents/TGPassportOCR.mm
Normal file
@ -0,0 +1,21 @@
|
||||
#import "TGPassportOCR.h"
|
||||
#import "TGPassportMRZ.h"
|
||||
#import "ocr.h"
|
||||
|
||||
@implementation TGPassportOCR
|
||||
|
||||
+ (SSignal *)recognizeMRZInImage:(UIImage *)image
|
||||
{
|
||||
return [[SSignal defer:^SSignal *
|
||||
{
|
||||
CGRect boundingRect;
|
||||
NSString *string = recognizeMRZ(image, &boundingRect);
|
||||
|
||||
NSArray *lines = [string componentsSeparatedByString:@"\n"];
|
||||
TGPassportMRZ *mrz = [TGPassportMRZ parseLines:lines];
|
||||
|
||||
return [SSignal single:mrz];
|
||||
}] startOn:[SQueue concurrentDefaultQueue]];
|
||||
}
|
||||
|
||||
@end
|
13
LegacyComponents/TGPassportScanView.h
Normal file
13
LegacyComponents/TGPassportScanView.h
Normal file
@ -0,0 +1,13 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class TGPassportMRZ;
|
||||
|
||||
@interface TGPassportScanView : UIView
|
||||
|
||||
@property (nonatomic, copy) void (^finishedWithMRZ)(TGPassportMRZ *);
|
||||
|
||||
- (void)start;
|
||||
- (void)stop;
|
||||
- (void)pause;
|
||||
|
||||
@end
|
98
LegacyComponents/TGPassportScanView.m
Normal file
98
LegacyComponents/TGPassportScanView.m
Normal file
@ -0,0 +1,98 @@
|
||||
#import "TGPassportScanView.h"
|
||||
#import "PGCamera.h"
|
||||
#import "TGCameraPreviewView.h"
|
||||
|
||||
#import "TGPassportOCR.h"
|
||||
|
||||
#import "LegacyComponentsInternal.h"
|
||||
|
||||
#import "TGTimerTarget.h"
|
||||
|
||||
@interface TGPassportScanView ()
|
||||
{
|
||||
PGCamera *_camera;
|
||||
TGCameraPreviewView *_previewView;
|
||||
|
||||
NSTimer *_timer;
|
||||
SMetaDisposable *_ocrDisposable;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation TGPassportScanView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil)
|
||||
{
|
||||
_camera = [[PGCamera alloc] initWithMode:PGCameraModePhoto position:PGCameraPositionRear];
|
||||
_previewView = [[TGCameraPreviewView alloc] initWithFrame:self.bounds];
|
||||
[self addSubview:_previewView];
|
||||
|
||||
[_camera attachPreviewView:_previewView];
|
||||
|
||||
_ocrDisposable = [[SMetaDisposable alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[_ocrDisposable dispose];
|
||||
}
|
||||
|
||||
- (void)start
|
||||
{
|
||||
[_camera startCaptureForResume:false completion:nil];
|
||||
|
||||
_timer = [TGTimerTarget scheduledMainThreadTimerWithTarget:self action:@selector(handleNextFrame) interval:1.0 repeat:false];
|
||||
}
|
||||
|
||||
- (void)stop
|
||||
{
|
||||
[_camera stopCaptureForPause:false completion:nil];
|
||||
_camera = nil;
|
||||
|
||||
[_timer invalidate];
|
||||
_timer = nil;
|
||||
}
|
||||
|
||||
- (void)pause
|
||||
{
|
||||
[_camera stopCaptureForPause:true completion:nil];
|
||||
}
|
||||
|
||||
- (void)handleNextFrame
|
||||
{
|
||||
__weak TGPassportScanView *weakSelf = self;
|
||||
[_camera captureNextFrameCompletion:^(UIImage *image)
|
||||
{
|
||||
[_ocrDisposable setDisposable:[[[TGPassportOCR recognizeMRZInImage:image] deliverOn:[SQueue mainQueue]] startWithNext:^(TGPassportMRZ *next)
|
||||
{
|
||||
__strong TGPassportScanView *strongSelf = weakSelf;
|
||||
if (strongSelf == nil)
|
||||
return;
|
||||
|
||||
if (next != nil)
|
||||
{
|
||||
[strongSelf->_camera stopCaptureForPause:true completion:nil];
|
||||
|
||||
if (strongSelf.finishedWithMRZ != nil)
|
||||
strongSelf.finishedWithMRZ(next);
|
||||
}
|
||||
else
|
||||
{
|
||||
strongSelf->_timer = [TGTimerTarget scheduledMainThreadTimerWithTarget:self action:@selector(handleNextFrame) interval:0.45 repeat:false];
|
||||
}
|
||||
}]];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
|
||||
_previewView.frame = self.bounds;
|
||||
}
|
||||
|
||||
@end
|
550
LegacyComponents/fast-edge.cpp
Executable file
550
LegacyComponents/fast-edge.cpp
Executable file
@ -0,0 +1,550 @@
|
||||
/*
|
||||
FAST-EDGE
|
||||
Copyright (c) 2009 Benjamin C. Haynor
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <time.h>
|
||||
#include "fast-edge.h"
|
||||
|
||||
#define LOW_THRESHOLD_PERCENTAGE 0.8 // percentage of the high threshold value that the low threshold shall be set at
|
||||
#define PI 3.14159265
|
||||
#define HIGH_THRESHOLD_PERCENTAGE 0.10 // percentage of pixels that meet the high threshold - for example 0.15 will ensure that at least 15% of edge pixels are considered to meet the high threshold
|
||||
|
||||
#define min(X,Y) ((X) < (Y) ? (X) : (Y))
|
||||
#define max(X,Y) ((X) < (Y) ? (Y) : (X))
|
||||
|
||||
namespace ocr{
|
||||
/*
|
||||
CANNY EDGE DETECT
|
||||
DOES NOT PERFORM NOISE REDUCTION - PERFORM NOISE REDUCTION PRIOR TO USE
|
||||
Noise reduction omitted, as some applications benefit from morphological operations such as opening or closing as opposed to Gaussian noise reduction
|
||||
If your application always takes the same size input image, uncomment the definitions of WIDTH and HEIGHT in the header file and define them to the size of your input image,
|
||||
otherwise the required intermediate arrays will be dynamically allocated.
|
||||
If WIDTH and HEIGHT are defined, the arrays will be allocated in the compiler directive that follows:
|
||||
*/
|
||||
#ifdef WIDTH
|
||||
int g[WIDTH * HEIGHT], dir[WIDTH * HEIGHT] = {0};
|
||||
unsigned char img_scratch_data[WIDTH * HEIGHT] = {0};
|
||||
#endif
|
||||
void canny_edge_detect(struct image * img_in, struct image * img_out) {
|
||||
struct image img_scratch;
|
||||
int high, low;
|
||||
#ifndef WIDTH
|
||||
int * g = (int*)calloc(static_cast<size_t>(img_in->width*img_in->height), sizeof(int));
|
||||
int * dir = (int*)calloc(static_cast<size_t>(img_in->width*img_in->height), sizeof(int));
|
||||
unsigned char * img_scratch_data = (unsigned char*)calloc(static_cast<size_t>(img_in->width*img_in->height), sizeof(unsigned char));
|
||||
#endif
|
||||
img_scratch.width = img_in->width;
|
||||
img_scratch.height = img_in->height;
|
||||
img_scratch.pixel_data = img_scratch_data;
|
||||
calc_gradient_sobel(img_in, g, dir);
|
||||
//printf("*** performing non-maximum suppression ***\n");
|
||||
non_max_suppression(&img_scratch, g, dir);
|
||||
estimate_threshold(&img_scratch, &high, &low);
|
||||
hysteresis(high, low, &img_scratch, img_out);
|
||||
#ifndef WIDTH
|
||||
free(g);
|
||||
free(dir);
|
||||
free(img_scratch_data);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
GAUSSIAN_NOISE_ REDUCE
|
||||
apply 5x5 Gaussian convolution filter, shrinks the image by 4 pixels in each direction, using Gaussian filter found here:
|
||||
http://en.wikipedia.org/wiki/Canny_edge_detector
|
||||
*/
|
||||
void gaussian_noise_reduce(struct image * img_in, struct image * img_out)
|
||||
{
|
||||
#ifdef CLOCK
|
||||
clock_t start = clock();
|
||||
#endif
|
||||
int w, h, x, y, max_x, max_y;
|
||||
w = img_in->width;
|
||||
h = img_in->height;
|
||||
img_out->width = w;
|
||||
img_out->height = h;
|
||||
max_x = w - 2;
|
||||
max_y = w * (h - 2);
|
||||
for (y = w * 2; y < max_y; y += w) {
|
||||
for (x = 2; x < max_x; x++) {
|
||||
img_out->pixel_data[x + y] = (2 * img_in->pixel_data[x + y - 2 - w - w] +
|
||||
4 * img_in->pixel_data[x + y - 1 - w - w] +
|
||||
5 * img_in->pixel_data[x + y - w - w] +
|
||||
4 * img_in->pixel_data[x + y + 1 - w - w] +
|
||||
2 * img_in->pixel_data[x + y + 2 - w - w] +
|
||||
4 * img_in->pixel_data[x + y - 2 - w] +
|
||||
9 * img_in->pixel_data[x + y - 1 - w] +
|
||||
12 * img_in->pixel_data[x + y - w] +
|
||||
9 * img_in->pixel_data[x + y + 1 - w] +
|
||||
4 * img_in->pixel_data[x + y + 2 - w] +
|
||||
5 * img_in->pixel_data[x + y - 2] +
|
||||
12 * img_in->pixel_data[x + y - 1] +
|
||||
15 * img_in->pixel_data[x + y] +
|
||||
12 * img_in->pixel_data[x + y + 1] +
|
||||
5 * img_in->pixel_data[x + y + 2] +
|
||||
4 * img_in->pixel_data[x + y - 2 + w] +
|
||||
9 * img_in->pixel_data[x + y - 1 + w] +
|
||||
12 * img_in->pixel_data[x + y + w] +
|
||||
9 * img_in->pixel_data[x + y + 1 + w] +
|
||||
4 * img_in->pixel_data[x + y + 2 + w] +
|
||||
2 * img_in->pixel_data[x + y - 2 + w + w] +
|
||||
4 * img_in->pixel_data[x + y - 1 + w + w] +
|
||||
5 * img_in->pixel_data[x + y + w + w] +
|
||||
4 * img_in->pixel_data[x + y + 1 + w + w] +
|
||||
2 * img_in->pixel_data[x + y + 2 + w + w]) / 159;
|
||||
}
|
||||
}
|
||||
#ifdef CLOCK
|
||||
printf("Gaussian noise reduction - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
CALC_GRADIENT_SOBEL
|
||||
calculates the result of the Sobel operator - http://en.wikipedia.org/wiki/Sobel_operator - and estimates edge direction angle
|
||||
*/
|
||||
/*void calc_gradient_sobel(struct image * img_in, int g_x[], int g_y[], int g[], int dir[]) {//float theta[]) {*/
|
||||
void calc_gradient_sobel(struct image * img_in, int g[], int dir[]) {
|
||||
#ifdef CLOCK
|
||||
clock_t start = clock();
|
||||
#endif
|
||||
int w, h, x, y, max_x, max_y, g_x, g_y;
|
||||
float g_div;
|
||||
w = img_in->width;
|
||||
h = img_in->height;
|
||||
max_x = w - 3;
|
||||
max_y = w * (h - 3);
|
||||
for (y = w * 3; y < max_y; y += w) {
|
||||
for (x = 3; x < max_x; x++) {
|
||||
g_x = (2 * img_in->pixel_data[x + y + 1]
|
||||
+ img_in->pixel_data[x + y - w + 1]
|
||||
+ img_in->pixel_data[x + y + w + 1]
|
||||
- 2 * img_in->pixel_data[x + y - 1]
|
||||
- img_in->pixel_data[x + y - w - 1]
|
||||
- img_in->pixel_data[x + y + w - 1]);
|
||||
g_y = 2 * img_in->pixel_data[x + y - w]
|
||||
+ img_in->pixel_data[x + y - w + 1]
|
||||
+ img_in->pixel_data[x + y - w - 1]
|
||||
- 2 * img_in->pixel_data[x + y + w]
|
||||
- img_in->pixel_data[x + y + w + 1]
|
||||
- img_in->pixel_data[x + y + w - 1];
|
||||
#ifndef ABS_APPROX
|
||||
g[x + y] = sqrt(g_x * g_x + g_y * g_y);
|
||||
#endif
|
||||
#ifdef ABS_APPROX
|
||||
g[x + y] = abs(g_x[x + y]) + abs(g_y[x + y]);
|
||||
#endif
|
||||
if (g_x == 0) {
|
||||
dir[x + y] = 2;
|
||||
} else {
|
||||
g_div = g_y / (float) g_x;
|
||||
/* the following commented-out code is slightly faster than the code that follows, but is a slightly worse approximation for determining the edge direction angle
|
||||
if (g_div < 0) {
|
||||
if (g_div < -1) {
|
||||
dir[n] = 0;
|
||||
} else {
|
||||
dir[n] = 1;
|
||||
}
|
||||
} else {
|
||||
if (g_div > 1) {
|
||||
dir[n] = 0;
|
||||
} else {
|
||||
dir[n] = 3;
|
||||
}
|
||||
}
|
||||
*/
|
||||
if (g_div < 0) {
|
||||
if (g_div < -2.41421356237) {
|
||||
dir[x + y] = 0;
|
||||
} else {
|
||||
if (g_div < -0.414213562373) {
|
||||
dir[x + y] = 1;
|
||||
} else {
|
||||
dir[x + y] = 2;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (g_div > 2.41421356237) {
|
||||
dir[x + y] = 0;
|
||||
} else {
|
||||
if (g_div > 0.414213562373) {
|
||||
dir[x + y] = 3;
|
||||
} else {
|
||||
dir[x + y] = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
#ifdef CLOCK
|
||||
printf("Calculate gradient Sobel - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
CALC_GRADIENT_SCHARR
|
||||
calculates the result of the Scharr version of the Sobel operator - http://en.wikipedia.org/wiki/Sobel_operator - and estimates edge direction angle
|
||||
may have better rotational symmetry
|
||||
*/
|
||||
void calc_gradient_scharr(struct image * img_in, int g_x[], int g_y[], int g[], int dir[]) {//float theta[]) {
|
||||
#ifdef CLOCK
|
||||
clock_t start = clock();
|
||||
#endif
|
||||
int w, h, x, y, max_x, max_y, n;
|
||||
float g_div;
|
||||
w = img_in->width;
|
||||
h = img_in->height;
|
||||
max_x = w - 1;
|
||||
max_y = w * (h - 1);
|
||||
n = 0;
|
||||
for (y = w; y < max_y; y += w) {
|
||||
for (x = 1; x < max_x; x++) {
|
||||
g_x[n] = (10 * img_in->pixel_data[x + y + 1]
|
||||
+ 3 * img_in->pixel_data[x + y - w + 1]
|
||||
+ 3 * img_in->pixel_data[x + y + w + 1]
|
||||
- 10 * img_in->pixel_data[x + y - 1]
|
||||
- 3 * img_in->pixel_data[x + y - w - 1]
|
||||
- 3 * img_in->pixel_data[x + y + w - 1]);
|
||||
g_y[n] = 10 * img_in->pixel_data[x + y - w]
|
||||
+ 3 * img_in->pixel_data[x + y - w + 1]
|
||||
+ 3 * img_in->pixel_data[x + y - w - 1]
|
||||
- 10 * img_in->pixel_data[x + y + w]
|
||||
- 3 * img_in->pixel_data[x + y + w + 1]
|
||||
- 3 * img_in->pixel_data[x + y + w - 1];
|
||||
#ifndef ABS_APPROX
|
||||
g[n] = sqrt(g_x[n] * g_x[n] + g_y[n] * g_y[n]);
|
||||
#endif
|
||||
#ifdef ABS_APPROX
|
||||
g[n] = abs(g_x[n]) + abs(g_y[n]);
|
||||
#endif
|
||||
if (g_x[n] == 0) {
|
||||
dir[n] = 2;
|
||||
} else {
|
||||
g_div = g_y[n] / (float) g_x[n];
|
||||
if (g_div < 0) {
|
||||
if (g_div < -2.41421356237) {
|
||||
dir[n] = 0;
|
||||
} else {
|
||||
if (g_div < -0.414213562373) {
|
||||
dir[n] = 1;
|
||||
} else {
|
||||
dir[n] = 2;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (g_div > 2.41421356237) {
|
||||
dir[n] = 0;
|
||||
} else {
|
||||
if (g_div > 0.414213562373) {
|
||||
dir[n] = 3;
|
||||
} else {
|
||||
dir[n] = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
n++;
|
||||
}
|
||||
}
|
||||
#ifdef CLOCK
|
||||
printf("Calculate gradient Scharr - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC);
|
||||
#endif
|
||||
}
|
||||
/*
|
||||
NON_MAX_SUPPRESSION
|
||||
using the estimates of the Gx and Gy image gradients and the edge direction angle determines whether the magnitude of the gradient assumes a local maximum in the gradient direction
|
||||
if the rounded edge direction angle is 0 degrees, checks the north and south directions
|
||||
if the rounded edge direction angle is 45 degrees, checks the northwest and southeast directions
|
||||
if the rounded edge direction angle is 90 degrees, checks the east and west directions
|
||||
if the rounded edge direction angle is 135 degrees, checks the northeast and southwest directions
|
||||
*/
|
||||
void non_max_suppression(struct image * img, int g[], int dir[]) {//float theta[]) {
|
||||
#ifdef CLOCK
|
||||
clock_t start = clock();
|
||||
#endif
|
||||
int w, h, x, y, max_x, max_y;
|
||||
w = img->width;
|
||||
h = img->height;
|
||||
max_x = w;
|
||||
max_y = w * h;
|
||||
for (y = 0; y < max_y; y += w) {
|
||||
for (x = 0; x < max_x; x++) {
|
||||
switch (dir[x + y]) {
|
||||
case 0:
|
||||
if(x+y-w-1<0){
|
||||
continue;
|
||||
}
|
||||
if (g[x + y] > g[x + y - w] && g[x + y] > g[x + y + w]) {
|
||||
if (g[x + y] > 255) {
|
||||
img->pixel_data[x + y] = 0xFF;
|
||||
} else {
|
||||
img->pixel_data[x + y] = g[x + y];
|
||||
}
|
||||
} else {
|
||||
img->pixel_data[x + y] = 0x00;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if(x+y-w-1<0){
|
||||
continue;
|
||||
}
|
||||
if (g[x + y] > g[x + y - w - 1] && g[x + y] > g[x + y + w + 1]) {
|
||||
if (g[x + y] > 255) {
|
||||
img->pixel_data[x + y] = 0xFF;
|
||||
} else {
|
||||
img->pixel_data[x + y] = g[x + y];
|
||||
}
|
||||
} else {
|
||||
img->pixel_data[x + y] = 0x00;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (g[x + y] > g[x + y - 1] && g[x + y] > g[x + y + 1]) {
|
||||
if (g[x + y] > 255) {
|
||||
img->pixel_data[x + y] = 0xFF;
|
||||
} else {
|
||||
img->pixel_data[x + y] = g[x + y];
|
||||
}
|
||||
} else {
|
||||
img->pixel_data[x + y] = 0x00;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if(x+y-w-1<0){
|
||||
continue;
|
||||
}
|
||||
if (g[x + y] > g[x + y - w + 1] && g[x + y] > g[x + y + w - 1]) {
|
||||
if (g[x + y] > 255) {
|
||||
img->pixel_data[x + y] = 0xFF;
|
||||
} else {
|
||||
img->pixel_data[x + y] = g[x + y];
|
||||
}
|
||||
} else {
|
||||
img->pixel_data[x + y] = 0x00;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
printf("ERROR - direction outside range 0 to 3");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifdef CLOCK
|
||||
printf("Non-maximum suppression - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC);
|
||||
#endif
|
||||
}
|
||||
/*
|
||||
ESTIMATE_THRESHOLD
|
||||
estimates hysteresis threshold, assuming that the top X% (as defined by the HIGH_THRESHOLD_PERCENTAGE) of edge pixels with the greatest intesity are true edges
|
||||
and that the low threshold is equal to the quantity of the high threshold plus the total number of 0s at the low end of the histogram divided by 2
|
||||
*/
|
||||
void estimate_threshold(struct image * img, int * high, int * low) {
|
||||
#ifdef CLOCK
|
||||
clock_t start = clock();
|
||||
#endif
|
||||
int i, max, pixels, high_cutoff;
|
||||
int histogram[256];
|
||||
max = img->width * img->height;
|
||||
for (i = 0; i < 256; i++) {
|
||||
histogram[i] = 0;
|
||||
}
|
||||
for (i = 0; i < max; i++) {
|
||||
histogram[img->pixel_data[i]]++;
|
||||
}
|
||||
pixels = (max - histogram[0]) * HIGH_THRESHOLD_PERCENTAGE;
|
||||
high_cutoff = 0;
|
||||
i = 255;
|
||||
while (high_cutoff < pixels) {
|
||||
high_cutoff += histogram[i];
|
||||
i--;
|
||||
}
|
||||
*high = i;
|
||||
i = 1;
|
||||
while (histogram[i] == 0) {
|
||||
i++;
|
||||
}
|
||||
*low = (*high + i) * LOW_THRESHOLD_PERCENTAGE;
|
||||
#ifdef PRINT_HISTOGRAM
|
||||
for (i = 0; i < 256; i++) {
|
||||
printf("i %d count %d\n", i, histogram[i]);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CLOCK
|
||||
printf("Estimate threshold - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC);
|
||||
#endif
|
||||
}
|
||||
|
||||
void hysteresis (int high, int low, struct image * img_in, struct image * img_out)
|
||||
{
|
||||
#ifdef CLOCK
|
||||
clock_t start = clock();
|
||||
#endif
|
||||
int x, y, n, max;
|
||||
max = img_in->width * img_in->height;
|
||||
for (n = 0; n < max; n++) {
|
||||
img_out->pixel_data[n] = 0x00;
|
||||
}
|
||||
for (y=0; y < img_out->height; y++) {
|
||||
for (x=0; x < img_out->width; x++) {
|
||||
if (img_in->pixel_data[y * img_out->width + x] >= high) {
|
||||
trace (x, y, low, img_in, img_out);
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifdef CLOCK
|
||||
printf("Hysteresis - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC);
|
||||
#endif
|
||||
}
|
||||
|
||||
int trace(int x, int y, int low, struct image * img_in, struct image * img_out)
|
||||
{
|
||||
int y_off, x_off;//, flag;
|
||||
if (img_out->pixel_data[y * img_out->width + x] == 0)
|
||||
{
|
||||
img_out->pixel_data[y * img_out->width + x] = 0xFF;
|
||||
for (y_off = -1; y_off <=1; y_off++)
|
||||
{
|
||||
for(x_off = -1; x_off <= 1; x_off++)
|
||||
{
|
||||
if (!(y == 0 && x_off == 0) && range(img_in, x + x_off, y + y_off) && img_in->pixel_data[(y + y_off) * img_out->width + x + x_off] >= low) {
|
||||
if (trace(x + x_off, y + y_off, low, img_in, img_out))
|
||||
{
|
||||
return(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return(1);
|
||||
}
|
||||
return(0);
|
||||
}
|
||||
|
||||
int range(struct image * img, int x, int y)
|
||||
{
|
||||
if ((x < 0) || (x >= img->width)) {
|
||||
return(0);
|
||||
}
|
||||
if ((y < 0) || (y >= img->height)) {
|
||||
return(0);
|
||||
}
|
||||
return(1);
|
||||
}
|
||||
|
||||
void dilate_1d_h(struct image * img, struct image * img_out) {
|
||||
int x, y, offset, y_max;
|
||||
y_max = img->height * (img->width - 2);
|
||||
for (y = 2 * img->width; y < y_max; y += img->width) {
|
||||
for (x = 2; x < img->width - 2; x++) {
|
||||
offset = x + y;
|
||||
img_out->pixel_data[offset] = max(max(max(max(img->pixel_data[offset-2], img->pixel_data[offset-1]), img->pixel_data[offset]), img->pixel_data[offset+1]), img->pixel_data[offset+2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dilate_1d_v(struct image * img, struct image * img_out) {
|
||||
int x, y, offset, y_max;
|
||||
y_max = img->height * (img->width - 2);
|
||||
for (y = 2 * img->width; y < y_max; y += img->width) {
|
||||
for (x = 2; x < img->width - 2; x++) {
|
||||
offset = x + y;
|
||||
img_out->pixel_data[offset] = max(max(max(max(img->pixel_data[offset-2 * img->width], img->pixel_data[offset-img->width]), img->pixel_data[offset]), img->pixel_data[offset+img->width]), img->pixel_data[offset+2*img->width]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void erode_1d_h(struct image * img, struct image * img_out) {
|
||||
int x, y, offset, y_max;
|
||||
y_max = img->height * (img->width - 2);
|
||||
for (y = 2 * img->width; y < y_max; y += img->width) {
|
||||
for (x = 2; x < img->width - 2; x++) {
|
||||
offset = x + y;
|
||||
img_out->pixel_data[offset] = min(min(min(min(img->pixel_data[offset-2], img->pixel_data[offset-1]), img->pixel_data[offset]), img->pixel_data[offset+1]), img->pixel_data[offset+2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void erode_1d_v(struct image * img, struct image * img_out) {
|
||||
int x, y, offset, y_max;
|
||||
y_max = img->height * (img->width - 2);
|
||||
for (y = 2 * img->width; y < y_max; y += img->width) {
|
||||
for (x = 2; x < img->width - 2; x++) {
|
||||
offset = x + y;
|
||||
img_out->pixel_data[offset] = min(min(min(min(img->pixel_data[offset-2 * img->width], img->pixel_data[offset-img->width]), img->pixel_data[offset]), img->pixel_data[offset+img->width]), img->pixel_data[offset+2*img->width]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void erode(struct image * img_in, struct image * img_scratch, struct image * img_out) {
|
||||
#ifdef CLOCK
|
||||
clock_t start = clock();
|
||||
#endif
|
||||
erode_1d_h(img_in, img_scratch);
|
||||
erode_1d_v(img_scratch, img_out);
|
||||
#ifdef CLOCK
|
||||
printf("Erosion - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC);
|
||||
#endif
|
||||
}
|
||||
|
||||
void dilate(struct image * img_in, struct image * img_scratch, struct image * img_out) {
|
||||
#ifdef CLOCK
|
||||
clock_t start = clock();
|
||||
#endif
|
||||
dilate_1d_h(img_in, img_scratch);
|
||||
dilate_1d_v(img_scratch, img_out);
|
||||
#ifdef CLOCK
|
||||
printf("Dilation - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC);
|
||||
#endif
|
||||
}
|
||||
|
||||
void morph_open(struct image * img_in, struct image * img_scratch, struct image * img_scratch2, struct image * img_out) {
|
||||
#ifdef CLOCK
|
||||
clock_t start = clock();
|
||||
#endif
|
||||
erode(img_in, img_scratch, img_scratch2);
|
||||
dilate(img_scratch2, img_scratch, img_out);
|
||||
#ifdef CLOCK
|
||||
printf("Morphological opening - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC);
|
||||
#endif
|
||||
}
|
||||
|
||||
void morph_close(struct image * img_in, struct image * img_scratch, struct image * img_scratch2, struct image * img_out) {
|
||||
#ifdef CLOCK
|
||||
clock_t start = clock();
|
||||
#endif
|
||||
dilate(img_in, img_scratch, img_scratch2);
|
||||
erode(img_scratch2, img_scratch, img_out);
|
||||
#ifdef CLOCK
|
||||
printf("Morphological closing - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC);
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
61
LegacyComponents/fast-edge.h
Executable file
61
LegacyComponents/fast-edge.h
Executable file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
FAST-EDGE
|
||||
Copyright (c) 2009 Benjamin C. Haynor
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef _FASTEDGE
|
||||
#define _FASTEDGE
|
||||
|
||||
namespace ocr{
|
||||
//#define WIDTH 640 // uncomment to define width for situations where width is always known
|
||||
//#define HEIGHT 480 // uncomment to define heigh for situations where height is always known
|
||||
|
||||
//#define CLOCK // uncomment to show running times of image processing functions (in seconds)
|
||||
//#define ABS_APPROX // uncomment to use the absolute value approximation of sqrt(Gx ^ 2 + Gy ^2)
|
||||
//#define PRINT_HISTOGRAM // uncomment to print the histogram used to estimate the threshold
|
||||
struct image {
|
||||
int width;
|
||||
int height;
|
||||
unsigned char * pixel_data;
|
||||
};
|
||||
|
||||
void canny_edge_detect(struct image * img_in, struct image * img_out);
|
||||
void gaussian_noise_reduce(struct image * img_in, struct image * img_out);
|
||||
void calc_gradient_sobel(struct image * img_in, int g[], int dir[]);
|
||||
void calc_gradient_scharr(struct image * img_in, int g_x[], int g_y[], int g[], int dir[]);
|
||||
void non_max_suppression(struct image * img, int g[], int dir[]);
|
||||
void estimate_threshold(struct image * img, int * high, int * low);
|
||||
void hysteresis (int high, int low, struct image * img_in, struct image * img_out);
|
||||
int trace (int x, int y, int low, struct image * img_in, struct image * img_out);
|
||||
int range (struct image * img, int x, int y);
|
||||
void dilate_1d_h(struct image * img, struct image * img_out);
|
||||
void dilate_1d_v(struct image * img, struct image * img_out);
|
||||
void erode_1d_h(struct image * img, struct image * img_out);
|
||||
void erode_1d_v(struct image * img, struct image * img_out);
|
||||
void erode(struct image * img_in, struct image * img_scratch, struct image * img_out);
|
||||
void dilate(struct image * img_in, struct image * img_scratch, struct image * img_out);
|
||||
void morph_open(struct image * img_in, struct image * img_scratch, struct image * img_scratch2, struct image * img_out);
|
||||
void morph_close(struct image * img_in, struct image * img_scratch, struct image * img_scratch2, struct image * img_out);
|
||||
}
|
||||
#endif
|
357
LegacyComponents/genann.c
Executable file
357
LegacyComponents/genann.c
Executable file
@ -0,0 +1,357 @@
|
||||
/*
|
||||
* GENANN - Minimal C Artificial Neural Network
|
||||
*
|
||||
* Copyright (c) 2015, 2016 Lewis Van Winkle
|
||||
*
|
||||
* http://CodePlea.com
|
||||
*
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1. The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgement in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "genann.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define LOOKUP_SIZE 4096
|
||||
|
||||
double genann_act_sigmoid(double a) {
|
||||
if (a < -45.0) return 0;
|
||||
if (a > 45.0) return 1;
|
||||
return 1.0 / (1 + exp(-a));
|
||||
}
|
||||
|
||||
|
||||
double genann_act_sigmoid_cached(double a) {
|
||||
/* If you're optimizing for memory usage, just
|
||||
* delete this entire function and replace references
|
||||
* of genann_act_sigmoid_cached to genann_act_sigmoid
|
||||
*/
|
||||
const double min = -15.0;
|
||||
const double max = 15.0;
|
||||
static double interval;
|
||||
static int initialized = 0;
|
||||
static double lookup[LOOKUP_SIZE];
|
||||
|
||||
/* Calculate entire lookup table on first run. */
|
||||
if (!initialized) {
|
||||
interval = (max - min) / LOOKUP_SIZE;
|
||||
int i;
|
||||
for (i = 0; i < LOOKUP_SIZE; ++i) {
|
||||
lookup[i] = genann_act_sigmoid(min + interval * i);
|
||||
}
|
||||
/* This is down here to make this thread safe. */
|
||||
initialized = 1;
|
||||
}
|
||||
|
||||
int i;
|
||||
i = (int)((a-min)/interval+0.5);
|
||||
if (i <= 0) return lookup[0];
|
||||
if (i >= LOOKUP_SIZE) return lookup[LOOKUP_SIZE-1];
|
||||
return lookup[i];
|
||||
}
|
||||
|
||||
|
||||
double genann_act_threshold(double a) {
|
||||
return a > 0;
|
||||
}
|
||||
|
||||
|
||||
double genann_act_linear(double a) {
|
||||
return a;
|
||||
}
|
||||
|
||||
|
||||
genann *genann_init(int inputs, int hidden_layers, int hidden, int outputs) {
|
||||
if (hidden_layers < 0) return 0;
|
||||
if (inputs < 1) return 0;
|
||||
if (outputs < 1) return 0;
|
||||
if (hidden_layers > 0 && hidden < 1) return 0;
|
||||
|
||||
|
||||
const int hidden_weights = hidden_layers ? (inputs+1) * hidden + (hidden_layers-1) * (hidden+1) * hidden : 0;
|
||||
const int output_weights = (hidden_layers ? (hidden+1) : (inputs+1)) * outputs;
|
||||
const int total_weights = (hidden_weights + output_weights);
|
||||
|
||||
const int total_neurons = (inputs + hidden * hidden_layers + outputs);
|
||||
|
||||
/* Allocate extra size for weights, outputs, and deltas. */
|
||||
const int size = sizeof(genann) + sizeof(double) * (total_weights + total_neurons + (total_neurons - inputs));
|
||||
genann *ret = malloc(size);
|
||||
if (!ret) return 0;
|
||||
|
||||
ret->inputs = inputs;
|
||||
ret->hidden_layers = hidden_layers;
|
||||
ret->hidden = hidden;
|
||||
ret->outputs = outputs;
|
||||
|
||||
ret->total_weights = total_weights;
|
||||
ret->total_neurons = total_neurons;
|
||||
|
||||
/* Set pointers. */
|
||||
ret->weight = (double*)((char*)ret + sizeof(genann));
|
||||
ret->output = ret->weight + ret->total_weights;
|
||||
ret->delta = ret->output + ret->total_neurons;
|
||||
|
||||
genann_randomize(ret);
|
||||
|
||||
ret->activation_hidden = genann_act_sigmoid_cached;
|
||||
ret->activation_output = genann_act_sigmoid_cached;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
genann *genann_read(FILE *in) {
|
||||
int inputs, hidden_layers, hidden, outputs;
|
||||
fscanf(in, "%d %d %d %d", &inputs, &hidden_layers, &hidden, &outputs);
|
||||
|
||||
genann *ann = genann_init(inputs, hidden_layers, hidden, outputs);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < ann->total_weights; ++i) {
|
||||
fscanf(in, " %le", ann->weight + i);
|
||||
}
|
||||
|
||||
return ann;
|
||||
}
|
||||
|
||||
|
||||
genann *genann_copy(genann const *ann) {
|
||||
const int size = sizeof(genann) + sizeof(double) * (ann->total_weights + ann->total_neurons + (ann->total_neurons - ann->inputs));
|
||||
genann *ret = malloc(size);
|
||||
if (!ret) return 0;
|
||||
|
||||
memcpy(ret, ann, size);
|
||||
|
||||
/* Set pointers. */
|
||||
ret->weight = (double*)((char*)ret + sizeof(genann));
|
||||
ret->output = ret->weight + ret->total_weights;
|
||||
ret->delta = ret->output + ret->total_neurons;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
void genann_randomize(genann *ann) {
|
||||
int i;
|
||||
for (i = 0; i < ann->total_weights; ++i) {
|
||||
double r = GENANN_RANDOM();
|
||||
/* Sets weights from -0.5 to 0.5. */
|
||||
ann->weight[i] = r - 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void genann_free(genann *ann) {
|
||||
/* The weight, output, and delta pointers go to the same buffer. */
|
||||
free(ann);
|
||||
}
|
||||
|
||||
|
||||
double const *genann_run(genann const *ann, double const *inputs) {
|
||||
double const *w = ann->weight;
|
||||
double *o = ann->output + ann->inputs;
|
||||
double const *i = ann->output;
|
||||
|
||||
/* Copy the inputs to the scratch area, where we also store each neuron's
|
||||
* output, for consistency. This way the first layer isn't a special case. */
|
||||
memcpy(ann->output, inputs, sizeof(double) * ann->inputs);
|
||||
|
||||
int h, j, k;
|
||||
|
||||
const genann_actfun act = ann->activation_hidden;
|
||||
const genann_actfun acto = ann->activation_output;
|
||||
|
||||
/* Figure hidden layers, if any. */
|
||||
for (h = 0; h < ann->hidden_layers; ++h) {
|
||||
for (j = 0; j < ann->hidden; ++j) {
|
||||
double sum = 0;
|
||||
for (k = 0; k < (h == 0 ? ann->inputs : ann->hidden) + 1; ++k) {
|
||||
if (k == 0) {
|
||||
sum += *w++ * -1.0;
|
||||
} else {
|
||||
sum += *w++ * i[k-1];
|
||||
}
|
||||
}
|
||||
*o++ = act(sum);
|
||||
}
|
||||
|
||||
|
||||
i += (h == 0 ? ann->inputs : ann->hidden);
|
||||
}
|
||||
|
||||
double const *ret = o;
|
||||
|
||||
/* Figure output layer. */
|
||||
for (j = 0; j < ann->outputs; ++j) {
|
||||
double sum = 0;
|
||||
for (k = 0; k < (ann->hidden_layers ? ann->hidden : ann->inputs) + 1; ++k) {
|
||||
if (k == 0) {
|
||||
sum += *w++ * -1.0;
|
||||
} else {
|
||||
sum += *w++ * i[k-1];
|
||||
}
|
||||
}
|
||||
*o++ = acto(sum);
|
||||
}
|
||||
|
||||
/* Sanity check that we used all weights and wrote all outputs. */
|
||||
assert(w - ann->weight == ann->total_weights);
|
||||
assert(o - ann->output == ann->total_neurons);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
void genann_train(genann const *ann, double const *inputs, double const *desired_outputs, double learning_rate) {
|
||||
/* To begin with, we must run the network forward. */
|
||||
genann_run(ann, inputs);
|
||||
|
||||
int h, j, k;
|
||||
|
||||
/* First set the output layer deltas. */
|
||||
{
|
||||
double const *o = ann->output + ann->inputs + ann->hidden * ann->hidden_layers; /* First output. */
|
||||
double *d = ann->delta + ann->hidden * ann->hidden_layers; /* First delta. */
|
||||
double const *t = desired_outputs; /* First desired output. */
|
||||
|
||||
|
||||
/* Set output layer deltas. */
|
||||
if (ann->activation_output == genann_act_linear) {
|
||||
for (j = 0; j < ann->outputs; ++j) {
|
||||
*d++ = *t++ - *o++;
|
||||
}
|
||||
} else {
|
||||
for (j = 0; j < ann->outputs; ++j) {
|
||||
*d++ = (*t - *o) * *o * (1.0 - *o);
|
||||
++o; ++t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Set hidden layer deltas, start on last layer and work backwards. */
|
||||
/* Note that loop is skipped in the case of hidden_layers == 0. */
|
||||
for (h = ann->hidden_layers - 1; h >= 0; --h) {
|
||||
|
||||
/* Find first output and delta in this layer. */
|
||||
double const *o = ann->output + ann->inputs + (h * ann->hidden);
|
||||
double *d = ann->delta + (h * ann->hidden);
|
||||
|
||||
/* Find first delta in following layer (which may be hidden or output). */
|
||||
double const * const dd = ann->delta + ((h+1) * ann->hidden);
|
||||
|
||||
/* Find first weight in following layer (which may be hidden or output). */
|
||||
double const * const ww = ann->weight + ((ann->inputs+1) * ann->hidden) + ((ann->hidden+1) * ann->hidden * (h));
|
||||
|
||||
for (j = 0; j < ann->hidden; ++j) {
|
||||
|
||||
double delta = 0;
|
||||
|
||||
for (k = 0; k < (h == ann->hidden_layers-1 ? ann->outputs : ann->hidden); ++k) {
|
||||
const double forward_delta = dd[k];
|
||||
const int windex = k * (ann->hidden + 1) + (j + 1);
|
||||
const double forward_weight = ww[windex];
|
||||
delta += forward_delta * forward_weight;
|
||||
}
|
||||
|
||||
*d = *o * (1.0-*o) * delta;
|
||||
++d; ++o;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Train the outputs. */
|
||||
{
|
||||
/* Find first output delta. */
|
||||
double const *d = ann->delta + ann->hidden * ann->hidden_layers; /* First output delta. */
|
||||
|
||||
/* Find first weight to first output delta. */
|
||||
double *w = ann->weight + (ann->hidden_layers
|
||||
? ((ann->inputs+1) * ann->hidden + (ann->hidden+1) * ann->hidden * (ann->hidden_layers-1))
|
||||
: (0));
|
||||
|
||||
/* Find first output in previous layer. */
|
||||
double const * const i = ann->output + (ann->hidden_layers
|
||||
? (ann->inputs + (ann->hidden) * (ann->hidden_layers-1))
|
||||
: 0);
|
||||
|
||||
/* Set output layer weights. */
|
||||
for (j = 0; j < ann->outputs; ++j) {
|
||||
for (k = 0; k < (ann->hidden_layers ? ann->hidden : ann->inputs) + 1; ++k) {
|
||||
if (k == 0) {
|
||||
*w++ += *d * learning_rate * -1.0;
|
||||
} else {
|
||||
*w++ += *d * learning_rate * i[k-1];
|
||||
}
|
||||
}
|
||||
|
||||
++d;
|
||||
}
|
||||
|
||||
assert(w - ann->weight == ann->total_weights);
|
||||
}
|
||||
|
||||
|
||||
/* Train the hidden layers. */
|
||||
for (h = ann->hidden_layers - 1; h >= 0; --h) {
|
||||
|
||||
/* Find first delta in this layer. */
|
||||
double const *d = ann->delta + (h * ann->hidden);
|
||||
|
||||
/* Find first input to this layer. */
|
||||
double const *i = ann->output + (h
|
||||
? (ann->inputs + ann->hidden * (h-1))
|
||||
: 0);
|
||||
|
||||
/* Find first weight to this layer. */
|
||||
double *w = ann->weight + (h
|
||||
? ((ann->inputs+1) * ann->hidden + (ann->hidden+1) * (ann->hidden) * (h-1))
|
||||
: 0);
|
||||
|
||||
|
||||
for (j = 0; j < ann->hidden; ++j) {
|
||||
for (k = 0; k < (h == 0 ? ann->inputs : ann->hidden) + 1; ++k) {
|
||||
if (k == 0) {
|
||||
*w++ += *d * learning_rate * -1.0;
|
||||
} else {
|
||||
*w++ += *d * learning_rate * i[k-1];
|
||||
}
|
||||
}
|
||||
++d;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void genann_write(genann const *ann, FILE *out) {
|
||||
fprintf(out, "%d %d %d %d", ann->inputs, ann->hidden_layers, ann->hidden, ann->outputs);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < ann->total_weights; ++i) {
|
||||
fprintf(out, " %.20e", ann->weight[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
110
LegacyComponents/genann.h
Executable file
110
LegacyComponents/genann.h
Executable file
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* GENANN - Minimal C Artificial Neural Network
|
||||
*
|
||||
* Copyright (c) 2015, 2016 Lewis Van Winkle
|
||||
*
|
||||
* http://CodePlea.com
|
||||
*
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1. The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgement in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __GENANN_H__
|
||||
#define __GENANN_H__
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef GENANN_RANDOM
|
||||
/* We use the following for uniform random numbers between 0 and 1.
|
||||
* If you have a better function, redefine this macro. */
|
||||
#define GENANN_RANDOM() (((double)rand())/RAND_MAX)
|
||||
#endif
|
||||
|
||||
|
||||
typedef double (*genann_actfun)(double a);
|
||||
|
||||
|
||||
typedef struct genann {
|
||||
/* How many inputs, outputs, and hidden neurons. */
|
||||
int inputs, hidden_layers, hidden, outputs;
|
||||
|
||||
/* Which activation function to use for hidden neurons. Default: gennann_act_sigmoid_cached*/
|
||||
genann_actfun activation_hidden;
|
||||
|
||||
/* Which activation function to use for output. Default: gennann_act_sigmoid_cached*/
|
||||
genann_actfun activation_output;
|
||||
|
||||
/* Total number of weights, and size of weights buffer. */
|
||||
int total_weights;
|
||||
|
||||
/* Total number of neurons + inputs and size of output buffer. */
|
||||
int total_neurons;
|
||||
|
||||
/* All weights (total_weights long). */
|
||||
double *weight;
|
||||
|
||||
/* Stores input array and output of each neuron (total_neurons long). */
|
||||
double *output;
|
||||
|
||||
/* Stores delta of each hidden and output neuron (total_neurons - inputs long). */
|
||||
double *delta;
|
||||
|
||||
} genann;
|
||||
|
||||
|
||||
|
||||
/* Creates and returns a new ann. */
|
||||
genann *genann_init(int inputs, int hidden_layers, int hidden, int outputs);
|
||||
|
||||
/* Creates ANN from file saved with genann_write. */
|
||||
genann *genann_read(FILE *in);
|
||||
|
||||
/* Sets weights randomly. Called by init. */
|
||||
void genann_randomize(genann *ann);
|
||||
|
||||
/* Returns a new copy of ann. */
|
||||
genann *genann_copy(genann const *ann);
|
||||
|
||||
/* Frees the memory used by an ann. */
|
||||
void genann_free(genann *ann);
|
||||
|
||||
/* Runs the feedforward algorithm to calculate the ann's output. */
|
||||
double const *genann_run(genann const *ann, double const *inputs);
|
||||
|
||||
/* Does a single backprop update. */
|
||||
void genann_train(genann const *ann, double const *inputs, double const *desired_outputs, double learning_rate);
|
||||
|
||||
/* Saves the ann. */
|
||||
void genann_write(genann const *ann, FILE *out);
|
||||
|
||||
|
||||
double genann_act_sigmoid(double a);
|
||||
double genann_act_sigmoid_cached(double a);
|
||||
double genann_act_threshold(double a);
|
||||
double genann_act_linear(double a);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /*__GENANN_H__*/
|
11
LegacyComponents/ocr.h
Normal file
11
LegacyComponents/ocr.h
Normal file
@ -0,0 +1,11 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
NSString *recognizeMRZ(UIImage *input, CGRect *boundingRect);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
645
LegacyComponents/ocr.mm
Executable file
645
LegacyComponents/ocr.mm
Executable file
@ -0,0 +1,645 @@
|
||||
#import "ocr.h"
|
||||
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <string>
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "fast-edge.h"
|
||||
#include "genann.h"
|
||||
|
||||
#import "LegacyComponentsInternal.h"
|
||||
|
||||
#ifndef max
|
||||
#define max(a, b) (a>b ? a : b)
|
||||
#define min(a, b) (a<b ? a : b)
|
||||
#endif
|
||||
|
||||
namespace ocr{
|
||||
struct line{
|
||||
double theta;
|
||||
double r;
|
||||
};
|
||||
|
||||
std::vector<line> detectLines(struct image* img, int threshold){
|
||||
// The size of the neighbourhood in which to search for other local maxima
|
||||
const int neighbourhoodSize = 4;
|
||||
|
||||
// How many discrete values of theta shall we check?
|
||||
const int maxTheta = 180;
|
||||
|
||||
// Using maxTheta, work out the step
|
||||
const double thetaStep = M_PI / maxTheta;
|
||||
|
||||
int width=img->width;
|
||||
int height=img->height;
|
||||
// Calculate the maximum height the hough array needs to have
|
||||
int houghHeight = (int) (sqrt(2.0) * max(height, width)) / 2;
|
||||
|
||||
// Double the height of the hough array to cope with negative r values
|
||||
int doubleHeight = 2 * houghHeight;
|
||||
|
||||
// Create the hough array
|
||||
int* houghArray = new int[maxTheta*doubleHeight];
|
||||
memset(houghArray, 0, sizeof(int)*maxTheta*doubleHeight);
|
||||
|
||||
// Find edge points and vote in array
|
||||
int centerX = width / 2;
|
||||
int centerY = height / 2;
|
||||
|
||||
// Count how many points there are
|
||||
int numPoints = 0;
|
||||
|
||||
// cache the values of sin and cos for faster processing
|
||||
double* sinCache = new double[maxTheta];
|
||||
double* cosCache = new double[maxTheta];
|
||||
for (int t = 0; t < maxTheta; t++) {
|
||||
double realTheta = t * thetaStep;
|
||||
sinCache[t] = sin(realTheta);
|
||||
cosCache[t] = cos(realTheta);
|
||||
}
|
||||
|
||||
// Now find edge points and update the hough array
|
||||
for (int x = 0; x < width; x++) {
|
||||
for (int y = 0; y < height; y++) {
|
||||
// Find non-black pixels
|
||||
if ((img->pixel_data[y*width+x] & 0x000000ff) != 0) {
|
||||
// Go through each value of theta
|
||||
for (int t = 0; t < maxTheta; t++) {
|
||||
|
||||
//Work out the r values for each theta step
|
||||
int r = (int) (((x - centerX) * cosCache[t]) + ((y - centerY) * sinCache[t]));
|
||||
|
||||
// this copes with negative values of r
|
||||
r += houghHeight;
|
||||
|
||||
if (r < 0 || r >= doubleHeight) continue;
|
||||
|
||||
// Increment the hough array
|
||||
houghArray[t*doubleHeight+r]++;
|
||||
|
||||
}
|
||||
|
||||
numPoints++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialise the vector of lines that we'll return
|
||||
std::vector<line> lines;
|
||||
|
||||
// Only proceed if the hough array is not empty
|
||||
if (numPoints == 0){
|
||||
delete[] houghArray;
|
||||
delete[] sinCache;
|
||||
delete[] cosCache;
|
||||
return lines;
|
||||
}
|
||||
|
||||
// Search for local peaks above threshold to draw
|
||||
for (int t = 0; t < maxTheta; t++) {
|
||||
//loop:
|
||||
for (int r = neighbourhoodSize; r < doubleHeight - neighbourhoodSize; r++) {
|
||||
|
||||
// Only consider points above threshold
|
||||
if (houghArray[t*doubleHeight+r] > threshold) {
|
||||
|
||||
int peak = houghArray[t*doubleHeight+r];
|
||||
|
||||
// Check that this peak is indeed the local maxima
|
||||
for (int dx = -neighbourhoodSize; dx <= neighbourhoodSize; dx++) {
|
||||
for (int dy = -neighbourhoodSize; dy <= neighbourhoodSize; dy++) {
|
||||
int dt = t + dx;
|
||||
int dr = r + dy;
|
||||
if (dt < 0) dt = dt + maxTheta;
|
||||
else if (dt >= maxTheta) dt = dt - maxTheta;
|
||||
if (houghArray[dt*doubleHeight+dr] > peak) {
|
||||
// found a bigger point nearby, skip
|
||||
goto loop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// calculate the true value of theta
|
||||
double theta = t * thetaStep;
|
||||
|
||||
// add the line to the vector
|
||||
line l={theta, (double)r-houghHeight};
|
||||
lines.push_back(l);
|
||||
|
||||
}
|
||||
loop:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
delete[] houghArray;
|
||||
delete[] sinCache;
|
||||
delete[] cosCache;
|
||||
return lines;
|
||||
}
|
||||
}
|
||||
|
||||
NSDictionary *findCornerPoints(UIImage *bitmap) {
|
||||
CGImageRef imageRef = bitmap.CGImage;
|
||||
uint32_t width = (uint32_t)CGImageGetWidth(imageRef);
|
||||
uint32_t height = (uint32_t)CGImageGetHeight(imageRef);
|
||||
|
||||
struct ocr::image imgIn, imgOut;
|
||||
imgIn.width = imgOut.width = width;
|
||||
imgIn.height = imgOut.height = height;
|
||||
imgIn.pixel_data = (uint8_t *)malloc(width * height);
|
||||
imgOut.pixel_data = (uint8_t *)calloc(width * height, 1);
|
||||
|
||||
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
||||
uint8_t *bitmapPixels = (uint8_t *)calloc(height * width * 4, sizeof(unsigned char));
|
||||
NSUInteger bytesPerPixel = 4;
|
||||
NSUInteger bytesPerRow = bytesPerPixel * width;
|
||||
NSUInteger bitsPerComponent = 8;
|
||||
CGContextRef context = CGBitmapContextCreate(bitmapPixels, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
|
||||
CGColorSpaceRelease(colorSpace);
|
||||
|
||||
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
|
||||
|
||||
CGContextRelease(context);
|
||||
|
||||
for(unsigned int y=0;y<height;y++){
|
||||
for(unsigned int x=0;x<width;x++){
|
||||
uint32_t px = bitmapPixels[(bytesPerRow * y) + x];
|
||||
imgIn.pixel_data[width*y+x]=(unsigned char) (((px & 0xFF) + ((px & 0xFF00) >> 8) + ((px & 0xFF0000) >> 16))/3);
|
||||
}
|
||||
}
|
||||
|
||||
ocr::canny_edge_detect(&imgIn, &imgOut);
|
||||
|
||||
std::vector<ocr::line> lines=ocr::detectLines(&imgOut, 100);
|
||||
for(NSUInteger i = 0; i < width * height; i++) {
|
||||
imgOut.pixel_data[i]/=2;
|
||||
}
|
||||
std::vector<std::vector<ocr::line>> parallelGroups;
|
||||
for(int i = 0; i < 36; i++) {
|
||||
parallelGroups.emplace_back();
|
||||
}
|
||||
ocr::line *left = NULL;
|
||||
ocr::line *right = NULL;
|
||||
ocr::line *top = NULL;
|
||||
ocr::line *bottom = NULL;
|
||||
for(std::vector<ocr::line>::iterator l = lines.begin(); l!= lines.end();) {
|
||||
// remove lines at irrelevant angles
|
||||
if(!(l->theta>M_PI*0.4 && l->theta<M_PI*0.6) && !(l->theta<M_PI*0.1 || l->theta>M_PI*0.9)){
|
||||
l=lines.erase(l);
|
||||
continue;
|
||||
}
|
||||
// remove vertical lines close to the middle of the image
|
||||
if((l->theta<M_PI*0.1 || l->theta>M_PI*0.9) && (uint32_t)abs((int)l->r) < height / 4){
|
||||
l=lines.erase(l);
|
||||
continue;
|
||||
}
|
||||
// find the leftmost and rightmost lines
|
||||
if(l->theta<M_PI*0.1 || l->theta>M_PI*0.9){
|
||||
double rk=l->theta<0.5 ? 1.0 : -1.0;
|
||||
if(!left || left->r>l->r*rk){
|
||||
left=&*l;
|
||||
}
|
||||
if(!right || right->r<l->r*rk){
|
||||
right=&*l;
|
||||
}
|
||||
}
|
||||
// group parallel-ish lines with 5-degree increments
|
||||
parallelGroups[(uint32_t)floor(l->theta / M_PI * 36)].push_back(*l);
|
||||
++l;
|
||||
}
|
||||
|
||||
// the text on the page tends to produce a lot of parallel lines - so we assume the top & bottom edges of the page
|
||||
// are topmost & bottommost lines in the largest group of horizontal lines
|
||||
std::vector<ocr::line>& largestParallelGroup=parallelGroups[0];
|
||||
for(std::vector<std::vector<ocr::line>>::iterator group=parallelGroups.begin();group!=parallelGroups.end();++group){
|
||||
if(largestParallelGroup.size()<group->size())
|
||||
largestParallelGroup=*group;
|
||||
}
|
||||
|
||||
for(std::vector<ocr::line>::iterator l=largestParallelGroup.begin();l!=largestParallelGroup.end();++l){
|
||||
// If the image is horizontal, we assume it's just the data page or an ID card so we're going for the topmost line.
|
||||
// If it's vertical, it likely contains both the data page and the page adjacent to it so we're going for the line that is closest to the center of the image.
|
||||
// Nobody in their right mind is going to be taking vertical pictures of ID cards, right?
|
||||
if(width>height){
|
||||
if(!top || top->r>l->r){
|
||||
top=&*l;
|
||||
}
|
||||
}else{
|
||||
if(!top || fabs(l->r)<fabs(top->r)){
|
||||
top=&*l;
|
||||
}
|
||||
}
|
||||
if(!bottom || bottom->r<l->r){
|
||||
bottom=&*l;
|
||||
}
|
||||
}
|
||||
|
||||
bool foundTopLeft=false, foundTopRight=false, foundBottomLeft=false, foundBottomRight=false;
|
||||
NSMutableDictionary *points = [[NSMutableDictionary alloc] init];
|
||||
|
||||
if(top && bottom && left && right){
|
||||
//LOGI("bottom theta %f", bottom->theta);
|
||||
if(bottom->theta>1.65 || bottom->theta<1.55){
|
||||
//LOGD("left: %f, right: %f\n", left->r, right->r);
|
||||
double centerX=width/2.0;
|
||||
double centerY=height/2.0;
|
||||
double ltsin=sin(left->theta);
|
||||
double ltcos=cos(left->theta);
|
||||
double rtsin=sin(right->theta);
|
||||
double rtcos=cos(right->theta);
|
||||
double ttsin=sin(top->theta);
|
||||
double ttcos=cos(top->theta);
|
||||
double btsin=sin(bottom->theta);
|
||||
double btcos=cos(bottom->theta);
|
||||
for (int y = -((int)height)/4; y < (int)height; y++) {
|
||||
int lx = (int) (((left->r - ((y - centerY) * ltsin)) / ltcos) + centerX);
|
||||
int ty = (int) (((top->r - ((lx - centerX) * ttcos)) / ttsin) + centerY);
|
||||
if(ty==y){
|
||||
points[@0]=@(lx);
|
||||
points[@1]=@(y);
|
||||
foundTopLeft=true;
|
||||
if(foundTopRight)
|
||||
break;
|
||||
}
|
||||
int rx = (int) (((right->r - ((y - centerY) * rtsin)) / rtcos) + centerX);
|
||||
ty = (int) (((top->r - ((rx - centerX) * ttcos)) / ttsin) + centerY);
|
||||
if(ty==y){
|
||||
points[@2]=@(rx);
|
||||
points[@3]=@(y);
|
||||
foundTopRight=true;
|
||||
if(foundTopLeft)
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (int y = height+height/3; y>=0; y--) {
|
||||
int lx = (int) (((left->r - ((y - centerY) * ltsin)) / ltcos) + centerX);
|
||||
int by = (int) (((bottom->r - ((lx - centerX) * btcos)) / btsin) + centerY);
|
||||
if(by==y){
|
||||
points[@4]=@(lx);
|
||||
points[@5]=@(y);
|
||||
foundBottomLeft=true;
|
||||
if(foundBottomRight)
|
||||
break;
|
||||
}
|
||||
int rx = (int) (((right->r - ((y - centerY) * rtsin)) / rtcos) + centerX);
|
||||
by = (int) (((bottom->r - ((rx - centerX) * btcos)) / btsin) + centerY);
|
||||
if(by==y){
|
||||
points[@6]=@(rx);
|
||||
points[@7]=@(y);
|
||||
foundBottomRight=true;
|
||||
if(foundBottomLeft)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
//LOGD("No perspective correction needed");
|
||||
}
|
||||
}
|
||||
|
||||
free(imgIn.pixel_data);
|
||||
free(imgOut.pixel_data);
|
||||
|
||||
if(foundTopLeft && foundTopRight && foundBottomLeft && foundBottomRight) {
|
||||
return points;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSArray *binarizeAndFindCharacters(UIImage *inBmp, UIImage **outBinaryImage) {
|
||||
CGImageRef imageRef = inBmp.CGImage;
|
||||
uint32_t width = (uint32_t)CGImageGetWidth(imageRef);
|
||||
uint32_t height = (uint32_t)CGImageGetHeight(imageRef);
|
||||
|
||||
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
||||
uint8_t *bitmapPixels = (uint8_t *)calloc(height * width * 4, sizeof(unsigned char));
|
||||
NSUInteger bytesPerPixel = 4;
|
||||
NSUInteger bytesPerRow = bytesPerPixel * width;
|
||||
NSUInteger bitsPerComponent = 8;
|
||||
CGContextRef context = CGBitmapContextCreate(bitmapPixels, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaNoneSkipFirst);
|
||||
CGColorSpaceRelease(colorSpace);
|
||||
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
|
||||
|
||||
CGContextRelease(context);
|
||||
|
||||
uint8_t *outPixels = (uint8_t *)malloc(width * height * 1);
|
||||
|
||||
uint32_t histogram[256]={0};
|
||||
uint32_t intensitySum=0;
|
||||
for(unsigned int y=0;y<height;y++){
|
||||
for(unsigned int x=0;x<width;x++){
|
||||
uint8_t *px = bitmapPixels + (bytesPerRow * y) + x * 4;
|
||||
uint8_t r = *(px + 1);
|
||||
uint8_t g = *(px + 2);
|
||||
uint8_t b = *(px + 3);
|
||||
int l = (r + g + b)/3.0;
|
||||
outPixels[(width * y) + x]=l;
|
||||
histogram[l]++;
|
||||
intensitySum+=l;
|
||||
}
|
||||
}
|
||||
uint32_t threshold=0;
|
||||
double best_sigma = 0.0;
|
||||
|
||||
int first_class_pixel_count = 0;
|
||||
int first_class_intensity_sum = 0;
|
||||
|
||||
for (int thresh = 0; thresh < 255; ++thresh) {
|
||||
first_class_pixel_count += histogram[thresh];
|
||||
first_class_intensity_sum += thresh * histogram[thresh];
|
||||
|
||||
double first_class_prob = first_class_pixel_count / (double) (width*height);
|
||||
double second_class_prob = 1.0 - first_class_prob;
|
||||
|
||||
double first_class_mean = first_class_intensity_sum / (double) first_class_pixel_count;
|
||||
double second_class_mean = (intensitySum - first_class_intensity_sum) / (double) ((width*height) - first_class_pixel_count);
|
||||
|
||||
double mean_delta = first_class_mean - second_class_mean;
|
||||
|
||||
double sigma = first_class_prob * second_class_prob * mean_delta * mean_delta;
|
||||
|
||||
if (sigma > best_sigma) {
|
||||
best_sigma = sigma;
|
||||
threshold = thresh;
|
||||
}
|
||||
}
|
||||
|
||||
for(unsigned int y=0;y<height;y++){
|
||||
for(unsigned int x=0;x<width;x++){
|
||||
uint8_t *px = bitmapPixels + (bytesPerRow * y) + x * 4;
|
||||
uint8_t r = *(px + 1);
|
||||
uint8_t g = *(px + 2);
|
||||
uint8_t b = *(px + 3);
|
||||
outPixels[(width * y) + x]=(r<threshold && g<threshold && b<threshold) ? (unsigned char)255 : (unsigned char)0;
|
||||
}
|
||||
}
|
||||
|
||||
// remove any single pixels without adjacent ones - these are usually noise
|
||||
for(unsigned int y=height/2;y<height-1;y++){
|
||||
for(unsigned int x=1;x<width-1;x++){
|
||||
if(outPixels[width * y + x]>0
|
||||
&& outPixels[width * y + x +1]==0
|
||||
&& outPixels[width * y + x -1]==0
|
||||
&& outPixels[width * (y + 1) + x]==0
|
||||
&& outPixels[width * (y - 1) + x]==0){
|
||||
outPixels[width * y + x]=0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (outBinaryImage != nil)
|
||||
{
|
||||
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
|
||||
CGContextRef context = CGBitmapContextCreate(outPixels, width, height, 8, width, colorSpace, kCGImageAlphaNone);
|
||||
CGColorSpaceRelease(colorSpace);
|
||||
|
||||
CGImageRef imgRef = CGBitmapContextCreateImage(context);
|
||||
UIImage *img = [UIImage imageWithCGImage:imgRef];
|
||||
CGImageRelease(imgRef);
|
||||
CGContextRelease(context);
|
||||
|
||||
*outBinaryImage = img;
|
||||
}
|
||||
// search from the bottom up for continuous areas of mostly empty pixels
|
||||
unsigned int consecutiveEmptyRows=0;
|
||||
std::vector<std::pair<unsigned int, unsigned int>> emptyAreaYs;
|
||||
for(unsigned int y=height-1;y>=height/2;y--){
|
||||
unsigned int consecutiveEmptyPixels=0;
|
||||
unsigned int maxEmptyPixels=0;
|
||||
for(unsigned int x=0;x<width;x++){
|
||||
if(outPixels[width * y + x]==0){
|
||||
consecutiveEmptyPixels++;
|
||||
}else{
|
||||
maxEmptyPixels=max(maxEmptyPixels, consecutiveEmptyPixels);
|
||||
consecutiveEmptyPixels=0;
|
||||
}
|
||||
}
|
||||
maxEmptyPixels=max(maxEmptyPixels, consecutiveEmptyPixels);
|
||||
if(maxEmptyPixels>width/10*8){
|
||||
consecutiveEmptyRows++;
|
||||
}else if(consecutiveEmptyRows>0){
|
||||
emptyAreaYs.emplace_back(y, y+consecutiveEmptyRows);
|
||||
consecutiveEmptyRows=0;
|
||||
}
|
||||
}
|
||||
|
||||
NSMutableArray *result = [[NSMutableArray alloc] init];
|
||||
// using the areas found above, do the same thing but horizontally and between them in an attempt to ultimately find the bounds of the MRZ characters
|
||||
for(std::vector<std::pair<unsigned int, unsigned int>>::iterator p=emptyAreaYs.begin();p!=emptyAreaYs.end();++p){
|
||||
std::vector<std::pair<unsigned int, unsigned int>>::iterator next=std::next(p);
|
||||
if(next!=emptyAreaYs.end()){
|
||||
unsigned int lineHeight=p->first-next->second;
|
||||
// An MRZ line can't really be this thin so this probably isn't one
|
||||
if(lineHeight<10)
|
||||
continue;
|
||||
unsigned int consecutiveEmptyCols=0;
|
||||
std::vector<std::pair<unsigned int, unsigned int>> emptyAreaXs;
|
||||
for(unsigned int x=0;x<width;x++){
|
||||
unsigned int consecutiveEmptyPixels=0;
|
||||
unsigned int maxEmptyPixels=0;
|
||||
unsigned int bottomFilledPixels=0; // count these separately because we want those L's recognized correctly
|
||||
for(unsigned int y=next->second;y<p->first;y++){
|
||||
if(outPixels[width * y + x]==0){
|
||||
consecutiveEmptyPixels++;
|
||||
}else{
|
||||
maxEmptyPixels=max(maxEmptyPixels, consecutiveEmptyPixels);
|
||||
consecutiveEmptyPixels=0;
|
||||
if(y>p->first-3)
|
||||
bottomFilledPixels++;
|
||||
}
|
||||
}
|
||||
maxEmptyPixels=max(maxEmptyPixels, consecutiveEmptyPixels);
|
||||
if(lineHeight-maxEmptyPixels<lineHeight/10 && bottomFilledPixels==0){
|
||||
consecutiveEmptyCols++;
|
||||
}else if(consecutiveEmptyCols>0){
|
||||
emptyAreaXs.emplace_back(x-consecutiveEmptyCols, x);
|
||||
consecutiveEmptyCols=0;
|
||||
}
|
||||
}
|
||||
if(consecutiveEmptyCols>0){
|
||||
emptyAreaXs.emplace_back(width-consecutiveEmptyCols, width);
|
||||
}
|
||||
if(emptyAreaXs.size()>30){
|
||||
bool foundLeftPadding=false;
|
||||
NSMutableArray *rects = [[NSMutableArray alloc] init];
|
||||
for(std::vector<std::pair<unsigned int, unsigned int>>::iterator h=emptyAreaXs.begin();h!=emptyAreaXs.end();++h){
|
||||
std::vector<std::pair<unsigned int, unsigned int>>::iterator nextH=std::next(h);
|
||||
if(!foundLeftPadding && h->second-h->first>width/35){
|
||||
foundLeftPadding=true;
|
||||
}else if(foundLeftPadding && h->second-h->first>width/30){
|
||||
if(rects.count>=30){
|
||||
break;
|
||||
}else{
|
||||
// restart the search because now we've (hopefully) found the real padding
|
||||
[rects removeAllObjects];
|
||||
}
|
||||
}
|
||||
if(nextH!=emptyAreaXs.end() && foundLeftPadding){
|
||||
unsigned int top=next->second;
|
||||
unsigned int bottom=p->first;
|
||||
// move the top and bottom edges towards each other as part of normalization
|
||||
for(unsigned int y=top;y<bottom;y++){
|
||||
bool found=false;
|
||||
for(unsigned int x=h->second; x<nextH->first; x++){
|
||||
if(outPixels[width * y + x]!=0){
|
||||
top=y;
|
||||
found=true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(found)
|
||||
break;
|
||||
}
|
||||
for(unsigned int y=bottom;y>top;y--){
|
||||
bool found=false;
|
||||
for(unsigned int x=h->second; x<nextH->first; x++){
|
||||
if(outPixels[width * y + x]!=0){
|
||||
bottom=y;
|
||||
found=true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(found)
|
||||
break;
|
||||
}
|
||||
if(bottom-top<lineHeight/2)
|
||||
continue;
|
||||
if(rects.count < 44){
|
||||
CGRect rect = CGRectMake(h->second, top, nextH->first - h->second, bottom - top);
|
||||
[rects addObject:[NSValue valueWithCGRect:rect]];
|
||||
}
|
||||
}
|
||||
}
|
||||
[result addObject:rects];
|
||||
if((rects.count>=44 && result.count == 2) || (rects.count>=30 && result.count==3)){
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(outPixels);
|
||||
|
||||
if(result.count == 0)
|
||||
return NULL;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
NSString *performRecognition(UIImage *bitmap, int numRows, int numCols)
|
||||
{
|
||||
NSString *filePath = TGComponentsPathForResource(@"ocr_nn", @"bin");
|
||||
NSData *nnData = [NSData dataWithContentsOfFile:filePath];
|
||||
|
||||
struct genann* ann=genann_init(150, 1, 90, 37);
|
||||
memcpy(ann->weight, nnData.bytes, sizeof(double)*ann->total_weights);
|
||||
|
||||
NSMutableString *res = [[NSMutableString alloc] init];
|
||||
const char* alphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890<";
|
||||
|
||||
CGImageRef imageRef = bitmap.CGImage;
|
||||
uint32_t width = (uint32_t)CGImageGetWidth(imageRef);
|
||||
uint32_t height = (uint32_t)CGImageGetHeight(imageRef);
|
||||
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
|
||||
uint8_t *bitmapPixels = (uint8_t *)calloc(height * width * 1, sizeof(unsigned char));
|
||||
NSUInteger bytesPerPixel = 1;
|
||||
NSUInteger bytesPerRow = bytesPerPixel * width;
|
||||
NSUInteger bitsPerComponent = 8;
|
||||
CGContextRef context = CGBitmapContextCreate(bitmapPixels, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaNone);
|
||||
CGColorSpaceRelease(colorSpace);
|
||||
|
||||
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
|
||||
CGContextRelease(context);
|
||||
|
||||
double nnInput[150];
|
||||
for(int row=0;row<numRows;row++){
|
||||
for(int col=0;col<numCols;col++){
|
||||
unsigned int offX=static_cast<unsigned int>(col*10);
|
||||
unsigned int offY=static_cast<unsigned int>(row*15);
|
||||
for(unsigned int y=0;y<15;y++){
|
||||
for(unsigned int x=0;x<10;x++){
|
||||
nnInput[y*10+x]=(double)bitmapPixels[bytesPerRow * (offY+y) + offX + x]/255.0;
|
||||
}
|
||||
}
|
||||
const double* nnOut=genann_run(ann, nnInput);
|
||||
unsigned int bestIndex=0;
|
||||
for(unsigned int i=0;i<37;i++){
|
||||
if(nnOut[i]>nnOut[bestIndex])
|
||||
bestIndex=i;
|
||||
}
|
||||
|
||||
[res appendString:[NSString stringWithFormat:@"%c", alphabet[bestIndex]]];
|
||||
}
|
||||
if(row!=numRows-1)
|
||||
[res appendString:@"\n"];
|
||||
}
|
||||
genann_free(ann);
|
||||
return res;
|
||||
}
|
||||
|
||||
UIImage *normalizeImage(UIImage *image)
|
||||
{
|
||||
if (image.imageOrientation == UIImageOrientationUp) return image;
|
||||
|
||||
UIGraphicsBeginImageContextWithOptions(image.size, NO, image.scale);
|
||||
[image drawInRect:(CGRect){0, 0, image.size}];
|
||||
UIImage *normalizedImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
return normalizedImage;
|
||||
}
|
||||
|
||||
NSString *recognizeMRZ(UIImage *input, CGRect *outBoundingRect)
|
||||
{
|
||||
input = normalizeImage(input);
|
||||
|
||||
UIImage *binaryImage;
|
||||
NSArray *charRects = binarizeAndFindCharacters(input, &binaryImage);
|
||||
if (charRects.count == 0)
|
||||
return nil;
|
||||
|
||||
uint32_t width = 10 * (int)[charRects.firstObject count];
|
||||
uint32_t height = 15 * (int)charRects.count;
|
||||
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
|
||||
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, width, colorSpace, kCGImageAlphaNone);
|
||||
CGColorSpaceRelease(colorSpace);
|
||||
|
||||
int x, y = 0;
|
||||
for (NSArray *line in charRects)
|
||||
{
|
||||
x = 0;
|
||||
for (NSValue *v in line)
|
||||
{
|
||||
CGRect rect = v.CGRectValue;
|
||||
CGRect dest = CGRectMake(x * 10, y * 15, 10, 15);
|
||||
|
||||
CGImageRef charImage = CGImageCreateWithImageInRect(binaryImage.CGImage, rect);
|
||||
CGContextDrawImage(context, dest, charImage);
|
||||
CGImageRelease(charImage);
|
||||
|
||||
x++;
|
||||
}
|
||||
y++;
|
||||
}
|
||||
|
||||
CGImageRef charsImageRef = CGBitmapContextCreateImage(context);
|
||||
CGContextRelease(context);
|
||||
|
||||
UIImage *charsImage = [UIImage imageWithCGImage:charsImageRef];
|
||||
CGImageRelease(charsImageRef);
|
||||
|
||||
NSString *result = performRecognition(charsImage, (int)charRects.count, (int)[charRects.firstObject count]);
|
||||
if (result != nil && outBoundingRect != NULL)
|
||||
{
|
||||
CGRect firstRect = [[charRects.firstObject firstObject] CGRectValue];
|
||||
firstRect.origin.y = input.size.height - firstRect.origin.y;
|
||||
CGRect lastRect = [[charRects.lastObject lastObject] CGRectValue];
|
||||
lastRect.origin.y = input.size.height - lastRect.origin.y;
|
||||
CGRect boundingRect = CGRectMake(CGRectGetMinX(firstRect), CGRectGetMinY(firstRect), CGRectGetMaxX(lastRect) - CGRectGetMinX(firstRect), CGRectGetMaxY(lastRect) - CGRectGetMinY(firstRect));
|
||||
*outBoundingRect = boundingRect;
|
||||
}
|
||||
return result;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user