Work with image recognise can bring some goosebumps to developers… but I say to you:
Every year, the native framework Vision, introduced in iOS 11, have more features and is more accurate. In iOS 13 is too much smart…
To create a Credit Card Scanner you need to do 3 steps:
1. Implement a camera layer in your view;
2. Add a recognise area.
3. Use Vision to make an OCR.
The result:
In my Open Source library you can find all code because here I’ll show you how I recognise the text!
While I’m developing I had a problem: dimensions! The size of view vs the size of recognise area vs the size of recognised image vs the device size.
To solve this I created some transforms, to use only one size as base.
let ciImage = CIImage(cvImageBuffer: frame)
let widht = UIScreen.main.bounds.width - (UIScreen.main.bounds.width * 0.2)
let height = widht - (widht * 0.45)
let viewX = (UIScreen.main.bounds.width / 2) - (widht / 2)
let viewY = (UIScreen.main.bounds.height / 2) - (height / 2) - 100 + height
let resizeFilter = CIFilter(name: "CILanczosScaleTransform")!
// Desired output size
let targetSize = CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
// Compute scale and corrective aspect ratio
let scale = targetSize.height / ciImage.extent.height
let aspectRatio = targetSize.width / (ciImage.extent.width * scale)
// Apply resizing
resizeFilter.setValue(ciImage, forKey: kCIInputImageKey)
resizeFilter.setValue(scale, forKey: kCIInputScaleKey)
resizeFilter.setValue(aspectRatio, forKey: kCIInputAspectRatioKey)
let outputImage = resizeFilter.outputImage
let croppedImage = outputImage!.cropped(to: CGRect(x: viewX, y: viewY, width: widht, height: height))
Now with the croppedImage (the card image) I can use Vision to make the OCR.
let request = VNRecognizeTextRequest()
request.recognitionLevel = .accurate
request.usesLanguageCorrection = false
let stillImageRequestHandler = VNImageRequestHandler(ciImage: croppedImage, options: [:])
try? stillImageRequestHandler.perform([request])
guard let texts = request.results as? [VNRecognizedTextObservation], texts.count > 0 else {
// no text detected
return
}
let arrayLines = texts.flatMap({ $0.topCandidates(20).map({ $0.string }) })
The arrayLines contains the 20 most accurate lines recognised.
Now with arrayLines I need to check if is card number, date or CVV.
if creditCardDate == nil &&
trimmed.count >= 5 && // 12/20
trimmed.count <= 7 && // 12/2020
trimmed.isDate {
creditCardDate = line
DispatchQueue.main.async {
self.labelCardDate?.text = line
self.tapticFeedback()
}
continue
}
It’s not hard, but the secret are the extensions:
// Date Pattern MM/YY or MM/YYYY
var isDate: Bool {
let arrayDate = components(separatedBy: "/")
if arrayDate.count == 2 {
let currentYear = Calendar.current.component(.year, from: Date())
if let month = Int(arrayDate[0]), let year = Int(arrayDate[1]) {
if month > 12 || month < 1 {
return false
}
if year < (currentYear - 2000 + 20) && year >= (currentYear - 2000) { // Between current year and 20 years ahead
return true
}
if year >= currentYear && year < (currentYear + 20) { // Between current year and 20 years ahead
return true
}
}
}
return false
}
That’s it!
I hope can I help you! If you like, please give your star on the Github Project.