最近在研究银行卡的识别扫描,借鉴了微信和银联钱包添加银行卡的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的项目总览
图片二所示的是创建了一个自定义的ScanCardVC,是基于View的扫描的
然后看下ScanCardVC.m的实现;
#import "ScanCardVC.h"
#import "CardIO.h"
#import "CardIOView.h"
@interface ScanCardVC ()
@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 ()
@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
最后忘了一点,就是获取相机的权限:
NSCameraUsageDescription
NSCameraUsageDescription内用内需访问相机(扫描银行卡)
写在最后:这个sdk虽好,但是只能支持扫描信用卡以及一些少数的借记卡,扫描成功率挺高,但是还是要看自己的需求环境了。。。