1 Overview
1.1 Opencv introduction
OpenCV is the abbreviation of Open Source Computer Vision Library. It was proposed and established by Intel in 1999 and is now supported by Willow Garage. It is a highly open source computer vision library that can implement Windows, Linux, Cross-platform operation on Mac and other platforms. opencv is an open source function library used for image processing, analysis, and machine vision. It has become a powerful tool for learning computer vision. In the fields of intrusion detection, specific target tracking, target detection, face detection, face recognition, face tracking and other fields, opencv can be said to be showing its talents. In this article, opencv is mainly used for bank card number identification.
1.2 Bank card number identification steps
The recognition process of bank card numbers mainly includes basic image operations of reading images, using templates to match the processed bank cards, and finally identifying the bank card number. The image operations involved include: grayscale conversion, binary conversion, threshold segmentation, contour detection, top hat operation, gradient operation, closing operation, and template matching.
1.2.1 Preprocessing template images
First, you need to cut out the numbers in the template separately, then cut out the numbers on the bank card separately, and finally compare the numbers on the bank card one by one against the template (0-9, 10 numbers).
The original image is as follows:
The storage path is: “../data/card_template.jpg”
Assume that each number in the template is cut into a rectangle. You can first find the outer contour of each number, and then obtain the outer rectangle based on the outline, and then you can cut it out. For outer contour processing, a binary image needs to be passed in. So the steps are as follows:
- Read in image template
template = cv2.imread('../data/card_template.jpg') ShowImage('template', template)
- Convert to grayscale
# Convert image to grayscale image_Gray = cv2.cvtColor(template, cv2.COLOR_RGB2GRAY) ShowImage('gray', image_Gray)
- Convert to binary image
# Convert to a binary image, [1] means return a binary image, [0] means return a threshold of 177 image_Binary = cv2.threshold(image_Gray, 177, 255, cv2.THRESH_BINARY_INV)[1] ShowImage('binary', image_Binary)
- Draw the outer outlines of the 10 numbers 0-9
# Extract contours refcnts, his = cv2.findContours(image_Binary.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) cv2.drawContours(template, refcnts, -1, (0,0,255), 2) ShowImage('contour', template)
- Calculate the bounding rectangle and resize it to the appropriate size
# Traverse each contour for (i, c) in enumerate(refCnts): # Calculate the bounding rectangle and resize it to the appropriate size (x, y, w, h) = cv2.boundingRect(c) #External rectangle roi = ref[y:y + h, x:x + w] roi = cv2.resize(roi, (57, 88)) # Each number corresponds to each template digits[i] = roi
1.2.2 Preprocessing bank card images
For bank card images, the background needs to be filtered out and the main information retained (steps 1-6 below). The above template is cut out in a rectangular shape, so the card number is also cut out in a rectangular shape for easy matching. The bank card number position is a group of four digits. You can process one group first, and then cut each number in each group for template matching. You can filter out other information on the bank card that is not the card number through the aspect ratio.
Bank card image storage path: “../data/credit03.jpg”
- Read in the bank card to be identified and convert it into a grayscale image
# Read the image and perform preprocessing image = cv2.imread("../data/credit03.jpg") ShowImage('card', image)
The displayed results are as follows:
image = resize(image, width=300) #Convert the image to grayscale gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) ShowImage('card_gray', gray)
The displayed results are as follows:
- Top hat operation: Top hat operation can highlight brighter areas (original input – opening operation (corrosion first and then expansion))
# Use the top hat operation to highlight brighter areas tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rectKernel) ShowImage('tophat_card', tophat)
The displayed results are as follows:
-
Gradient operation (Sobel operator): edge detection, can calculate the contour
gradx = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=-1) grady = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=0, dy=1, ksize=-1) gradx = np.absolute(gradx) minVal = np.min(gradx) maxVal = np.max(gradx) # (minVal, maxVal) = (np.min(gradx), np.max(gradx)) # The guaranteed value range is between 0-255 gradx = (255 * ((gradx - minVal) / (maxVal - minVal))) gradx = gradx.astype("uint8") print(np.array(gradx).shape) ShowImage('gradx_card', gradx)
The displayed results are as follows:
- Closing operation: connect the numbers together through the closing operation (first expansion, then erosion), which facilitates the subsequent calculation of the rectangular frame
# Through the closing operation, first expand and then corrode, connect the numbers together gradx = cv2.morphologyEx(gradx, cv2.MORPH_CLOSE,rectKernel) ShowImage('gradx_card', gradx)
The displayed results are as follows:
- Threshold Segmentation: Use thresholds to binarize images and focus on the processing part
# THRESH_OTSU will automatically find a suitable threshold, suitable for double peaks, and the threshold needs to be set to 0 thresh = cv2.threshold(gradx, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] ShowImage('thresh_card', thresh)
The displayed results are as follows:
- Then perform the closing operation: fill in the connected numbers in the picture a little fuller
# Perform another closing operation to fill the black area within the white frame. thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel) ShowImage('thresh2_card', thresh)
The displayed results are as follows:
-
Calculate the outer outline: After the above series of operations, we have clear candidates for the places where the numbers are in the bank card. Just like processing the template object, draw all the rectangular frames through the outer outline of the places that may be the numbers. You can filter again later.
# Calculate outline threshCnts, his = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) cnts = threshCnts cur_img = image.copy() cv2.drawContours(cur_img, cnts, -1, (0,0,255), 2) ShowImage('contour_card', cur_img)
The displayed results are as follows:
- Calculate the bounding rectangle and filter the matching rectangle
locs = [] # Traverse contours for (i, c) in enumerate(cnts): # Function is used to iterate over the elements in the sequence and their subscripts # Calculate rectangle (x, y, w, h) = cv2.boundingRect(c) ar = w/float(h) # Select the appropriate area, based on the actual task, here are four numbers as a group if ar > 2.5 and ar < 5.0: if (w > 40 and w < 85) and (h > 10 and h < 20): # Leave the ones that match locs.append((x,y,w,h)) # Sort the matching contours from left to right according to the value of x locs = sorted(locs, key=lambda x: x[0])
- Process each rectangle individually
output =[] # Iterate through each number in the contour for (i,(gx, gy, gw, gh)) in enumerate(locs): #Initialize the linked list groupOutput = [] # Extract each group according to the coordinates, and take a little more outside, otherwise you can’t see clearly. group = gray[gy-5:gy + gh + 5,gx-5:gx + gw + 5] ShowImage('group', group) # Preprocessing group = cv2.threshold(group, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] # Binarization ShowImage('group', group) # Find the outline of each group digitCnts, his = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # digitCnts = sortContours(digitCnts, method="LefttoRight")[0] # Sort the found contours digitCnts = sort_contours(digitCnts, method="left-to-right")[0]
1.2.3 Template matching score calculation
# Calculate each value in each group for c in digitCnts: # Find the outline of the current value and resize it to a suitable size (x,y,w,h) = cv2.boundingRect(c) roi = group[y:y + h, x:x + w] roi = cv2.resize(roi, (57,88)) ShowImage('roi', roi) scores = [] for(digit, digitROI) in digits.items(): # Template matching # result = cv2.matchTemplate(roi, digitROI, cv2.TM_CCOEFF) (_, score, _, _) = cv2.minMaxLoc(result) scores.append(score) # Get the most appropriate number groupOutput.append(str(np.argmax(scores)))
1.2.4 Drawing results
# Draw rectangle and font cv2.rectangle(image, (gx - 5, gy - 5), (gx + gw + 5, gy + gh + 5), (0,0,255),1) cv2.putText(image, "".join(groupOutput), (gx, gy-15), cv2.FONT_HERSHEY_SIMPLEX,0.65, (0,0,255),2) # got the answer output.extend(groupOutput)
2 Complete bank card number identification code
import cv2 import numpy as np def ShowImage(name, image): cv2.imshow(name, image) cv2.waitKey(0) #Waiting time, 0 means exit with any key cv2.destroyAllWindows() def sort_contours(cnts, method="left-to-right"): # reverse = False means ascending order. If reverse is not specified, ascending order will be defaulted. reverse=False i = 0 if method == "right-to-left" or method == "bottom-to-top": reverse = True # reverse = True means descending order if method == "top-to-bottom" or method == "bottom-to-top": i=1 # Use a smallest rectangle to wrap the found shape, represented by x, y, h, w boundingBoxes = [cv2.boundingRect(c) for c in cnts] # The zip function is used to pack iterable data and obtain the final output cnts and boundingBoxes. (cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes), key=lambda b: b[1][i], reverse=reverse)) return cnts, boundingBoxes def resize(image, width=None, height=None, inter=cv2.INTER_AREA): dim=None (h, w) = image.shape[:2] # Get the height and width of the image if width is None and height is None: return image if width is None: r = height / float(h) dim = (int(w * r), height) else: r = width / float(w) dim = (width, int(h * r)) resized = cv2.resize(image, dim, interpolation=inter) # Use the resize function of the cv library return resized template = cv2.imread('../data/card_template.jpg') ShowImage('template', template) #Convert the image to grayscale image_Gray = cv2.cvtColor(template, cv2.COLOR_RGB2GRAY) ShowImage('gray', image_Gray) # Convert to a binary image, [1] means return a binary image, [0] means return a threshold of 177 image_Binary = cv2.threshold(image_Gray, 177, 255, cv2.THRESH_BINARY_INV)[1] ShowImage('binary', image_Binary) #Extract contours refcnts, his = cv2.findContours(image_Binary.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) cv2.drawContours(template, refcnts, -1, (0, 0, 255), 2) ShowImage('contour', template) refcnts = sort_contours(refcnts, method="left-to-right")[0] digits = {} # Iterate through each contour for (i, c) in enumerate(refcnts): # The enumerate function is used to iterate over the elements in the sequence and their subscripts (x, y, w, h) = cv2.boundingRect(c) roi = image_Binary[y:y + h, x:x + w] roi = cv2.resize(roi, (57, 88)) digits[i] = roi #Initialize the convolution kernel rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9,3)) sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5)) # Read the image and perform preprocessing image = cv2.imread("../data/credit03.jpg") ShowImage('card', image) image = resize(image, width=300) #Convert the image to grayscale gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) ShowImage('card_gray', gray) # Use the top hat operation to highlight brighter areas tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rectKernel) ShowImage('tophat_card', tophat) gradx = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=-1) grady = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=0, dy=1, ksize=-1) gradx = np.absolute(gradx) minVal = np.min(gradx) maxVal = np.max(gradx) # (minVal, maxVal) = (np.min(gradx), np.max(gradx)) # The guaranteed value range is between 0-255 gradx = (255 * ((gradx - minVal) / (maxVal - minVal))) gradx = gradx.astype("uint8") print(np.array(gradx).shape) ShowImage('gradx_card', gradx) # Through the closing operation, first expand and then corrode, connect the numbers together gradx = cv2.morphologyEx(gradx, cv2.MORPH_CLOSE,rectKernel) ShowImage('gradx_card', gradx) # THRESH_OTSU will automatically find a suitable threshold, suitable for double peaks. The threshold needs to be set to 0 thresh = cv2.threshold(gradx, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] ShowImage('thresh_card', thresh) # Do another closing operation to fill the black area in the white frame thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel) ShowImage('thresh2_card', thresh) # Calculate contour threshCnts, his = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) cnts = threshCnts cur_img = image.copy() cv2.drawContours(cur_img, cnts, -1, (0,0,255), 2) ShowImage('contour_card', cur_img) locs = [] # Traverse contours for (i, c) in enumerate(cnts): # Function is used to iterate over the elements in the sequence and their subscripts # Calculate rectangle (x, y, w, h) = cv2.boundingRect(c) ar = w/float(h) # Select the appropriate area, based on the actual task, here are four numbers as a group if ar > 2.5 and ar < 5.0: if (w > 40 and w < 85) and (h > 10 and h < 20): # Leave the ones that match locs.append((x,y,w,h)) # Sort the matching contours from left to right according to the value of x locs = sorted(locs, key=lambda x: x[0]) output=[] # Iterate through each number in the contour for (i,(gx, gy, gw, gh)) in enumerate(locs): #Initialize the linked list groupOutput = [] # Extract each group according to the coordinates, and take a little more outside, otherwise you can’t see clearly. group = gray[gy-5:gy + gh + 5,gx-5:gx + gw + 5] ShowImage('group', group) # Preprocessing group = cv2.threshold(group, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] # Binarization ShowImage('group', group) # Find the outline of each group digitCnts, his = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # digitCnts = sortContours(digitCnts, method="LefttoRight")[0] # Sort the found contours digitCnts = sort_contours(digitCnts, method="left-to-right")[0] # Calculate each value in each group for c in digitCnts: # Find the outline of the current value and resize it to a suitable size (x,y,w,h) = cv2.boundingRect(c) roi = group[y:y + h, x:x + w] roi = cv2.resize(roi, (57,88)) ShowImage('roi', roi) scores = [] for(digit, digitROI) in digits.items(): # Template matching # result = cv2.matchTemplate(roi, digitROI, cv2.TM_CCOEFF) (_, score, _, _) = cv2.minMaxLoc(result) scores.append(score) # Get the most appropriate number groupOutput.append(str(np.argmax(scores))) # Draw rectangle and font cv2.rectangle(image, (gx - 5, gy - 5), (gx + gw + 5, gy + gh + 5), (0,0,255),1) cv2.putText(image, "".join(groupOutput), (gx, gy-15), cv2.FONT_HERSHEY_SIMPLEX,0.65, (0,0,255),2) # got the answer output.extend(groupOutput) ShowImage('card_result', image)
operation result:
The knowledge points of the article match the official knowledge files, and you can further learn related knowledge. OpenCV skill tree Home page Overview 24094 people are learning the system