基于OCR光学字符识别银行卡

 最近在研究银行卡的识别扫描,借鉴了微信和银联钱包添加银行卡的style,回头研究了一下什么是OCR,它到底是用来做什么的?首先我们先来弄明白什么是OCR:OCR技术是光学字符识别的缩写(Optical Character Recognition),是通过扫描等光学输入方式将各种票据、报刊、书籍、文稿及其它印刷品的文字转化为图像信息,再利用文字识别技术将图像信息转化为可以使用的计算机输入技术。
 还有OCR的软件结构是什么:OCR软件主要是由下面几个部分组成。

 图像输入、预处理:
 图像输入:对于不同的图像格式,有着不同的存储格式,不同的压缩方式,目前有OpenCV,CxImage等开源项目 。

 预处理:主要包括二值化,噪声去除,倾斜较正等

 二值化:
 对摄像头拍摄的图片,大多数是彩色图像,彩色图像所含信息量巨大,对于图片的内容,我们可以简单的分为前景与背景,为了让计算机更快的,更好的识别文字,我们需要先对彩色图进行处理,使图片只前景信息与背景信息,可以简单的定义前景信息为黑色,背景信息为白色,这就是二值化图了。

 噪声去除:
 对于不同的文档,我们对噪声的定义可以不同,根据噪声的特征进行去噪,就叫做噪声去除

 倾斜较正:
 由于一般用户,在拍照文档时,都比较随意,因此拍照出来的图片不可避免的产生倾斜,这就需要文字识别软件进行较正。

 版面分析:
 将文档图片分段落,分行的过程就叫做版面分析,由于实际文档的多样性,复杂性,因此,目前还没有一个固定的,最优的切割模型。

 字符切割:
 由于拍照条件的限制,经常造成字符粘连,断笔,因此极大限制了识别系统的性能,这就需要文字识别软件有字符切割功能。

 字符识别:
 这一研究,已经是很早的事情了,比较早有模板匹配,后来以特征提取为主,由于文字的位移,笔画的粗细,断笔,粘连,旋转等因素的影响,极大影响特征的提取的难度。

 版面恢复:
 人们希望识别后的文字,仍然像原文档图片那样排列着,段落不变,位置不变,顺序不变,的输出到word文档,pdf文档等,这一过程就叫做版面恢复。

 后处理、校对:
 根据特定的语言上下文的关系,对识别结果进行较正,就是后处理。
 到此为止,我们对OCR有了些许的了解了,下面就正式进入今天的《基于OCR光学字符识别银行卡》主话题:通过网罗资料,获取了一款很多人都认为比较好的cardIO-SDK来辅助我完成我的工作,闲话少叙,我们依然直奔主题:
 首先:先去这个网站github.com/card-io/card.io-iOS-SDK去下载cardIO-SDK,
然后:添加到项目里
1、将下载的SDK包里名为CardIO的文件拖到工程里,在TARGETS-Build Phases - Link Binary With Librarys添加下面依赖库

* AudioToolbox
* AVFoundation
* CoreGraphics
* CoreMedia
* CoreVideo
* Foundation
* MobileCoreServices
* OpenGLES
* QuartzCore
* Security
* UIKit

如果是xcode5或者更新的版本,只需要添加下面的库

* AVFoundation
* AudioToolbox
* CoreMedia
* MobileCoreServices

并且保证Build Settings里面这两项都是YES:

* Enable Modules (C and Objective-C)

* Link Frameworks Automatically

2、在TARGETS-Build Settings添加 -lc++到Other Linker Flags

其次:就是怎么去使用这个sdk了;

请大家移步到这里。。。。。。。。。。。

图片一所示,已经集成好sdk的项目总览

image

图片二所示的是创建了一个自定义的ScanCardVC,是基于View的扫描的

image

然后看下ScanCardVC.m的实现;

#import "ScanCardVC.h"

#import "CardIO.h"
#import "CardIOView.h"

@interface ScanCardVC ()<CardIOViewDelegate>

@property (nonatomic, weak) IBOutlet CardIOView *scanV;

@end
@implementation ScanCardVC

/******************************************************************************
**** Customzied Method                                                                                              ****
******************************************************************************/
#pragma mark -
#pragma mark Customzied Method

/******************************************************************************
**** CardIOViewDelegate Method                                                                              ****
******************************************************************************/
#pragma mark -
#pragma mark  CardIOViewDelegate Method

- (void)cardIOView:(CardIOView *)cardIOView didScanCard:(CardIOCreditCardInfo *)cardInfo
{
cardInfo.scanned = YES;
NSString *cardNum = [NSString stringWithFormat:@"%@", cardInfo.cardNumber];
NSLog(@"cardNum %@",cardNum);

//卡类型
NSString *cardType = [CardIOCreditCardInfo displayStringForCardType:cardInfo.cardType
usingLanguageOrLocale:@"zh-Hans"];

//卡logo
UIImage *bankLogo = [CardIOCreditCardInfo logoForCardType:cardInfo.cardType];

//持卡人
NSString *cardholderName = cardInfo.cardholderName;

//扫描结果
NSString *redactedCardNumber = cardInfo.redactedCardNumber;  

  // 卡号
NSString *expiryMonth = [NSString stringWithFormat:@"%lu",(unsigned long)cardInfo.expiryMonth];          // 月
NSString *expiryYear  = [NSString stringWithFormat:@"%lu",(unsigned long)cardInfo.expiryYear];            // 年
NSString *cvv = cardInfo.cvv;                          // CVV 码

// 显示扫描结果

//    NSString *msg = [NSString stringWithFormat:@"Number: %@\n\n expiry: %2@/%@\n\n cvv: %@", [self dealCardNumber:redactedCardNumber], expiryMonth, expiryYear, cvv];

//    [[[UIAlertView alloc] initWithTitle:@"获取卡信息:"
//                                message:msg
//                              delegate:nil
//                      cancelButtonTitle:@"确定"
//                      otherButtonTitles:nil, nil] show];

NSLog(@"Received card info. Number: %@, expiry: %02lu/%lu, cvv: %@.", cardInfo.redactedCardNumber, (unsigned long)cardInfo.expiryMonth, (unsigned long)cardInfo.expiryYear, cardInfo.cvv);

NSMutableDictionary *bankInfo = [NSMutableDictionary dictionary];

[bankInfo setValue:cardNum forKey:@"cardNum"];
[bankInfo setValue:expiryYear forKey:@"expiryYear"];
[bankInfo setValue:expiryMonth forKey:@"expiryMonth"];
[bankInfo setValue:cardType forKey:@"cardType"];
[bankInfo setValue:cardholderName forKey:@"cardholderName"];
if (self.delegate)
{
[self.delegate scanCardVC:self didScanSuceessBankInfo:bankInfo];
}
[self.navigationController popViewControllerAnimated:YES];
}


// 对银行卡号进行每隔四位加空格处理,自定义方法
- (NSString *)dealCardNumber:(NSString *)cardNumber
{
//CardIOCreditCardInfo *info里面包含了银行卡的一些信息,如info.cardNumber是扫描的银行卡号,现实的是完整号码,而info.redactedCardNumber只显示银行卡后四位,前面的用*代替了,返回的银行卡号都没有空格

//可以用下面注释的方法来加空格
NSString *strTem = [cardNumber stringByReplacingOccurrencesOfString:@" " withString:@""];
NSString *strTem2 = @"";
if (strTem.length % 4 == 0)
{
NSUInteger count = strTem.length / 4;
for (int i = 0; i < count; i++)
{
NSString *str = [strTem substringWithRange:NSMakeRange(i * 4, 4)];

strTem2 = [strTem2 stringByAppendingString:[NSString stringWithFormat:@"%@ ", str]];
}
}
else
{
NSUInteger count = strTem.length / 4;
for (int j = 0; j <= count; j++)
{
if (j == count)
{
NSString *str = [strTem substringWithRange:NSMakeRange(j * 4, strTem.length % 4)];
strTem2 = [strTem2 stringByAppendingString:[NSString stringWithFormat:@"%@ ", str]];
}
else
{
NSString *str = [strTem substringWithRange:NSMakeRange(j * 4, 4)];
strTem2 = [strTem2 stringByAppendingString:[NSString stringWithFormat:@"%@ ", str]];
}
}
}
return strTem2;
}

/******************************************************************************
**** Default Lifecycle Method                                                                                      ****
******************************************************************************/
#pragma mark -
#pragma mark Default Lifecycle Method

//初始化扫描View
- (void)initScanView
{
//设置扫描的语言环境
self.scanV.languageOrLocale = @"zh-Hans";

//设置扫描的代理
self.scanV.delegate = self;
self.scanV.hideCardIOLogo = YES;//是否隐藏扫描的logo
self.scanV.useCardIOLogo = YES;

//修改扫描框里面的提示文字,设置为nil,则显示sdk默认的文字
self.scanV.scanInstructions = @"请将卡片放置框内进行扫描";
//    self.scanV.frame = self.scanV.cameraPreviewFrame;

// 默认情况下,在相机视图卡指南和按钮总是匹配设备的方向旋转
self.scanV.allowFreelyRotatingCardGuide = YES;

//设置在扫描成功后多长时间展现扫描成功界面
self.scanV.scannedImageDuration = 0.2f;
self.scanV.guideColor = [UIColor lightGrayColor];
}

- (void)viewDidLoad
{
[super viewDidLoad];
self.title = @"扫描银行卡";
[CardIOUtilities preloadCardIO];
[self initScanView];
}

@end

完了之后我们移步到ScanHomeVC.m里面,主要是学习下基于VC的扫描和基于View的扫描:

#import "ScanHomeVC.h"

#import "ScanCardVC.h"
#import "CardIO.h"
#import "CardIOPaymentViewController.h"
#import "Tools.h"

@interface ScanHomeVC ()<CardIOPaymentViewControllerDelegate,ScanCardVCDelegate>

@property (nonatomic, strong) CardIOPaymentViewController *cardIOVC;

@property (nonatomic, weak) IBOutlet UILabel *cardNumLb;

@end

@implementation ScanHomeVC

/******************************************************************************
**** Customzied Method                                                                                              ****
******************************************************************************/
#pragma mark -
#pragma mark Customzied Method

//继承vc扫描
- (IBAction)onScanOneBtnClicked:(id)sender
{
self.cardIOVC = [[CardIOPaymentViewController alloc]initWithPaymentDelegate:self];
[self setCardIO];
[self presentViewController:self.cardIOVC animated:YES completion:nil];
}

//继承view扫描
- (IBAction)onScanTwoBtnClicked:(id)sender
{
ScanCardVC *vc = [ScanCardVC new];
vc.delegate = self;
[self.navigationController pushViewController:vc animated:YES];
}
-    (void)scanCardVC:(ScanCardVC *)scanCardVC
didScanSuceessBankInfo:(NSDictionary *)bankInfo
{
NSString *cardNum = [Tools dealCardNumber:bankInfo[@"cardNum"]];
NSString *cardType = bankInfo[@"cardType"];
NSString *cardholderName = bankInfo[@"cardholderName"];
self.cardNumLb.adjustsFontSizeToFitWidth = YES;
self.cardNumLb.text = [NSString stringWithFormat:@"卡号:%@  卡类型:%@ 持卡人:%@",cardNum,cardType,cardholderName];
}

/******************************************************************************
**** CardIOPaymentViewController Delegate Method                                            ****
******************************************************************************/
#pragma mark -
#pragma mark CardIOPaymentViewController Delegate Method

//扫描
- (void)userDidProvideCreditCardInfo:(CardIOCreditCardInfo *)cardInfo
inPaymentViewController:(CardIOPaymentViewController *)paymentViewController
{
NSLog(@"cardInfo%@",cardInfo);
//扫描结果
NSString *redactedCardNumber = cardInfo.cardNumber;    // 卡号
NSUInteger expiryMonth = cardInfo.expiryMonth;          // 月
NSUInteger expiryYear = cardInfo.expiryYear;            // 年
NSString *cvv = cardInfo.cvv;                          // CVV 码
// 显示扫描结果
//    NSString *msg = [NSString stringWithFormat:@"Number: %@\n\n expiry: %02lu/%lu\n\n cvv: %@", [self dealCardNumber:redactedCardNumber], expiryMonth, expiryYear, cvv];

//    [[[UIAlertView alloc] initWithTitle:@"获取卡信息:"
//                                message:msg
//                              delegate:nil
//                      cancelButtonTitle:@"确定"
//                      otherButtonTitles:nil, nil] show];

NSLog(@"Received card info. Number: %@, expiry: %02lu/%lu, cvv: %@.", cardInfo.redactedCardNumber, (unsigned long)cardInfo.expiryMonth, (unsigned long)cardInfo.expiryYear, cardInfo.cvv);
// Use the card info...
[self.cardIOVC dismissViewControllerAnimated:YES completion:nil];
}

//取消扫描
- (void)userDidCancelPaymentViewController:(CardIOPaymentViewController *)paymentViewController
{
[self dismissViewControllerAnimated:YES completion:nil];
}

/******************************************************************************
**** Default Lifecycle Method                                                                                    ****
******************************************************************************/
#pragma mark -
#pragma mark Default Lifecycle Method

- (void)setCardIO
{
self.cardIOVC.languageOrLocale = @"zh-Hans";
//隐藏PayPal的logo
self.cardIOVC.hideCardIOLogo = YES;
self.cardIOVC.useCardIOLogo = NO;
self.cardIOVC.keepStatusBarStyleForCardIO = YES;
self.cardIOVC.allowFreelyRotatingCardGuide = YES;

//修改扫描边框颜色
self.cardIOVC.guideColor = [UIColor lightGrayColor];

//是否支持显示卡图标
self.cardIOVC.suppressScannedCardImage = YES;
self.cardIOVC.detectionMode = CardIODetectionModeAutomatic;
}

- (void)viewDidLoad
{
[super viewDidLoad];
self.title = @"扫描银行卡";
}

- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:YES];

//扫描的预加载
[CardIOUtilities preload];
}

@end

附上ScanHomeVC.xib

image

最后忘了一点,就是获取相机的权限:

<key>NSCameraUsageDescription</key>
<string>NSCameraUsageDescription内用内需访问相机(扫描银行卡)</string>

写在最后:这个sdk虽好,但是只能支持扫描信用卡以及一些少数的借记卡,扫描成功率挺高,但是还是要看自己的需求环境了。。。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270