The purpose of this tutorial is to help you build a credit card reader with OpenCV and machine learning techniques to identify the card number and the card type.
Let us get started!
Also read: How to read Images in Python using OpenCV?
Introduction to OCR
We have always seen Optical character recognition being used a lot in machine learning and deep learning. One of many such applications includes the identification and reading of credit cards and the card number.

The question that might come to your mind is Why? So this application could be of great help to banks and other financial institutions to digitally recognize the card numbers and type of card.
Implementation of a Credit Card Reader in Python
Now that we have understood the concept and what we are going to build by end of this tutorial.
Let’s start building the project step by step.
Step 1: Importing Modules
We’ll be working withnumpy and matplotlib along with the openCV module in this case.
import cv2
import imutils
import argparse
import numpy as np
from imutils import contours
from matplotlib import pyplot as plt
Step 2: Assign Card type
The card type is assigned according to the first digit of the card number. The same is displayed below.
FIRST_NUMBER = {
"3": "American Express",
"4": "Visa",
"5": "MasterCard",
"6": "Discover Card"}
Step 3: Loading and pre-processing of reference Image
In order to read the reference OCR image, we make use of the imread
function. The reference image contains the digits 0-9 in the OCR-A font which can be later on used to perform matching later in the pipeline.
The pre-processing of the image includes converting it to a gray image and then thresholding + inverting the image to get the binary inverted image.
ref = cv2.imread('ocr_a_reference.png')
ref = cv2.cvtColor(ref, cv2.COLOR_BGR2GRAY)
ref = cv2.threshold(ref, 10, 255, cv2.THRESH_BINARY_INV)[1]
Step 4: Detecting Contours
In this step, we find the contours present in the pre-processed image and then store the returned contour information. Next, we sort the contours from left-to-right as well as initialize a dictionary, digits, which map the digit name to the region of interest.
refCnts = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
refCnts = imutils.grab_contours(refCnts)
refCnts = contours.sort_contours(refCnts, method="left-to-right")[0]
digits = {}
Step 5: Creating Bounding Boxes around Digits
Now in this step, we loop through the image contours obtained in the previous step where each value holds the digit/number along with the contour information. We further compute a bounding box for each contour and store the (x, y) coordinates along with the height and width of the box computed.
for (i, c) in enumerate(refCnts):
(x, y, w, h) = cv2.boundingRect(c)
roi = ref[y:y + h, x:x + w]
roi = cv2.resize(roi, (57, 88))
digits[i] = roi
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
Step 6: Loading and Pre-processing of the Credit card Image
In this step, we load our photo of the credit card and then resize
the same to the width of 300 in order to maintain the aspect ratio.
This step is followed by converting the image to grayscale
. After this, we perform morphological operations
on the grayscale image.
The next step is to compute a Scharr gradient
and store the result as gradX
. Then we calculate the absolute value of the gradX array stored. We aim to scale all the values in the range of 0-255
.
Now this normalizing of values take place by computing the minimum and maximum value of the gradX and form an equation to achieve min-max normalization
.
Finally, we find the contours
and store them in a list and initialize a list to hold the digit group locations. Then loop through the contours the same way we did for the reference image in step 5
.
Next, we’ll sort the groupings from left to right
and initialize a list for the credit card digits.
image = cv2.imread('credit_card_03.png')
image = imutils.resize(image, width=300)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rectKernel)
gradX = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=-1)
gradX = np.absolute(gradX)
(minVal, maxVal) = (np.min(gradX), np.max(gradX))
gradX = (255 * ((gradX - minVal) / (maxVal - minVal)))
gradX = gradX.astype("uint8")
gradX = cv2.morphologyEx(gradX, cv2.MORPH_CLOSE, rectKernel)
thresh = cv2.threshold(gradX, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel)
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
locs = []
for (i, c) in enumerate(cnts):
(x, y, w, h) = cv2.boundingRect(c)
ar = w / float(h)
if ar > 2.5 and ar < 4.0:
if (w > 40 and w < 55) and (h > 10 and h < 20):
locs.append((x, y, w, h))
locs = sorted(locs, key=lambda x:x[0])
output = []
Now that we know where each group of four digits is, let’s loop through the four sorted groupings and determine the digits therein. The looping includes thresholding, detecting contours, and template matching as well.
for (i, (gX, gY, gW, gH)) in enumerate(locs):
groupOutput = []
group = gray[gY - 5:gY + gH + 5, gX - 5:gX + gW + 5]
group = cv2.threshold(group, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
digitCnts = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
digitCnts = imutils.grab_contours(digitCnts)
digitCnts = contours.sort_contours(digitCnts, method="left-to-right")[0]
for c in digitCnts:
(x, y, w, h) = cv2.boundingRect(c)
roi = group[y:y + h, x:x + w]
roi = cv2.resize(roi, (57, 88))
scores = []
for (digit, digitROI) in digits.items():
result = cv2.matchTemplate(roi, digitROI, cv2.TM_CCOEFF)
(_, score, _, _) = cv2.minMaxLoc(result)
scores.append(score)
groupOutput.append(str(np.argmax(scores)))
cv2.rectangle(image, (gX - 5, gY - 5),
(gX + gW + 5, gY + gH + 5), (0, 0, 255), 2)
cv2.putText(image, "".join(groupOutput), (gX, gY - 15),
cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2)
output.extend(groupOutput)
Step 7: Displaying the Final Results
The code below will display the final Card Type, Card Number, and the OCR applied image.
print("Credit Card Type: {}".format(FIRST_NUMBER[output[0]]))
print("Credit Card #: {}".format("".join(output)))
plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
plt.title('Image'); plt.show()
The Final Code
import cv2
import imutils
import argparse
import numpy as np
from imutils import contours
from matplotlib import pyplot as plt
FIRST_NUMBER = {
"3": "American Express",
"4": "Visa",
"5": "MasterCard",
"6": "Discover Card"}
ref = cv2.imread('ocr_a_reference.png')
ref = cv2.cvtColor(ref, cv2.COLOR_BGR2GRAY)
ref = cv2.threshold(ref, 10, 255, cv2.THRESH_BINARY_INV)[1]
refCnts = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
refCnts = imutils.grab_contours(refCnts)
refCnts = contours.sort_contours(refCnts, method="left-to-right")[0]
digits = {}
for (i, c) in enumerate(refCnts):
(x, y, w, h) = cv2.boundingRect(c)
roi = ref[y:y + h, x:x + w]
roi = cv2.resize(roi, (57, 88))
digits[i] = roi
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
image = cv2.imread('credit_card_03.png')
image = imutils.resize(image, width=300)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rectKernel)
gradX = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=-1)
gradX = np.absolute(gradX)
(minVal, maxVal) = (np.min(gradX), np.max(gradX))
gradX = (255 * ((gradX - minVal) / (maxVal - minVal)))
gradX = gradX.astype("uint8")
gradX = cv2.morphologyEx(gradX, cv2.MORPH_CLOSE, rectKernel)
thresh = cv2.threshold(gradX, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel)
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
locs = []
for (i, c) in enumerate(cnts):
(x, y, w, h) = cv2.boundingRect(c)
ar = w / float(h)
if ar > 2.5 and ar < 4.0:
if (w > 40 and w < 55) and (h > 10 and h < 20):
locs.append((x, y, w, h))
locs = sorted(locs, key=lambda x:x[0])
output = []
for (i, (gX, gY, gW, gH)) in enumerate(locs):
groupOutput = []
group = gray[gY - 5:gY + gH + 5, gX - 5:gX + gW + 5]
group = cv2.threshold(group, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
digitCnts = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
digitCnts = imutils.grab_contours(digitCnts)
digitCnts = contours.sort_contours(digitCnts, method="left-to-right")[0]
for c in digitCnts:
(x, y, w, h) = cv2.boundingRect(c)
roi = group[y:y + h, x:x + w]
roi = cv2.resize(roi, (57, 88))
scores = []
for (digit, digitROI) in digits.items():
result = cv2.matchTemplate(roi, digitROI, cv2.TM_CCOEFF)
(_, score, _, _) = cv2.minMaxLoc(result)
scores.append(score)
groupOutput.append(str(np.argmax(scores)))
cv2.rectangle(image, (gX - 5, gY - 5),
(gX + gW + 5, gY + gH + 5), (0, 0, 255), 2)
cv2.putText(image, "".join(groupOutput), (gX, gY - 15),
cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2)
output.extend(groupOutput)
print("Credit Card Type: {}".format(FIRST_NUMBER[output[0]]))
print("Credit Card #: {}".format("".join(output)))
plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
plt.title('Image'); plt.show()
Some Sample Outputs
Now let’s look at some sample outputs after implementing the code mentioned above on various credit card images.



Conclusion
I hope you understood the concept and loved the outputs. Try out the same with more images and get amazed with the results.
Happy Coding! 😇
Want to learn more? Check out the tutorials mentioned below:
- Python: Detecting Contours
- Boxplots: Edge Detection in Images using Python
- Image Processing in Python – Edge Detection, Resizing, Erosion, and Dilation