OpenCV项目3-图像处理之信用卡数字识别
基本思路
- (1) 先对模板处理, 获取每个数字的模板及其对应的数字标识
- (2) 再对信用卡处理, 通过一系列预处理操作, 取出信用卡数字区域
- (3) 然后再取出每一个数字去和模板中的10个数字进行匹配
- (4) 取概率最大的数字
1.图片显示函数
def cv_show(name, img):
cv2.imshow(name, img)
cv2.waitKey(0)
cv2.destroyAllWindows()
2.模板读取
import cv2
import numpy as np
img = cv2.imread('./ocr_a_reference.png') # 读取模板图片
# print(img.shape)
# cv_show('img', img)
3.模板灰度化、二值化
ref = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# cv_show('ref', ref)
_, ref = cv2.threshold(ref, 10, 255, cv2.THRESH_BINARY_INV)
# cv_show('ref', ref)
4.模板轮廓计算、绘画、排序
ref_contours, _ = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img, ref_contours, -1, (0, 0, 255), 3) # -1:所有轮廓
# cv_show('img', img)
print(np.array(ref_contours, dtype='object').shape) # 10:表示数字的轮廓 dtype='object':去警告
# 对轮廓按照数字大小进行排序 方便后面使用 排序思路: 根据每个数字的最大外接矩形的x轴坐标进行排序
bounding_boxes = [cv2.boundingRect(c) for c in ref_contours]# 计算每个轮廓的外接矩形
# print(bounding_boxes) # 这一步的顺序是小到大因为计算方法是右到左
# print(sorted(bounding_boxes, key=lambda b: b[0]))
(ref_contours, bounding_boxes) = zip(*sorted(zip(ref_contours, bounding_boxes), key=lambda b: b[1][0])) # *:拆开 zip:一一对应且合并 要把排序之后的外接矩形和轮廓建立对应关系
5.模板取数字、尺寸resize
digits = {}
for (i, c) in enumerate(ref_contours):
(x, y, w, h) = cv2.boundingRect(c) # 重新计算外接矩形
roi = ref[y:y + h, x: x + w] # 取出每个数字 roi:region of interest:感兴趣区域
roi = cv2.resize(roi, (57, 88))# resize:合成合适的大小
digits[i] = roi
# print(digits)
6.信用卡读取
image = cv2.imread('./credit_card_05.png')
# cv_show('image', image)
7.信用卡尺寸resize
h, w = image.shape[:2] # 为保证原图不拉伸需要计算出原图的长宽比
width = 300
r = width / w
image = cv2.resize(image, (300, int(h * r)))
# cv_show('image', image)
8.信用卡灰度化、卷积核、形态学顶帽
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# cv_show('gray', gray)
rect_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))
tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rect_kernel) # 顶帽突出更明亮的区域
# cv_show('tophat', tophat)
9.信用卡sobel算子
grad_x = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=-1) # 找出边沿
# print(grad_x)
grad_x = np.absolute(grad_x) # 对grad_x进行处理 只用x轴方向的梯度
min_val, max_val = np.min(grad_x), np.max(grad_x)# 再把grad_x变成0到255之间的整数
grad_x = ((grad_x - min_val) / (max_val - min_val)) * 255
grad_x = grad_x.astype('uint8')# 修改一下数据类型
# cv_show('grad_x', grad_x)
# grad_y = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=0, dy=1, ksize=-1)
# # print(grad_x)
# grad_y = np.absolute(grad_y) # 对grad_x进行处理
# min_val, max_val = np.min(grad_y), np.max(grad_y) # 再把grad_x变成0到255之间的整数
# grad_y = ((grad_y - min_val) / (max_val - min_val)) * 255
# grad_y = grad_y.astype('uint8') # 修改一下数据类型
# cv_show('grad_y', grad_y)
# cv_show('gray', grad_x + grad_y)
10.信用卡闭操作、全局二值化
grad_x = cv2.morphologyEx(grad_x, cv2.MORPH_CLOSE, rect_kernel) # 先膨胀再腐蚀可把数字连在一起.
# cv_show('gradx', grad_x)
# 通过大津(OTSU)算法找到合适的阈值, 进行全局二值化操作.
_, thresh = cv2.threshold(grad_x, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
# cv_show('thresh', thresh)
sq_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))# 中间还有空洞再来一个闭操作
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sq_kernel)
# cv_show('thresh', thresh)
11.信用卡轮廓计算、绘画、遍历
thresh_contours, _ = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
image_copy = image.copy() # 在原图上画
cv2.drawContours(image_copy, thresh_contours, -1, (0, 0, 255), 3)
# cv_show('img', image_copy)
# 遍历轮廓计算外接矩形, 然后根据实际信用卡数字区域的长宽比, 找到真正的数字区域
locs = []
output = []
for c in thresh_contours:
(x, y, w, h) = cv2.boundingRect(c)# 计算外接矩形
ar = w / float(h)# 计算外接矩形的长宽比例
# print(ar)
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))# 符合条件的外接矩形留下来
sorted(locs, key=lambda x: x[0])# 对符合要求的轮廓进行从左到右的排序.
12.数字区域抠出、二值化
for (i, (gx, gy, gw, gh)) in enumerate(locs): # 遍历每一个外接矩形, 通过外接矩形可以把原图中的数字抠出来
group = gray[gy - 5: gy + gh + 5, gx - 5: gx + gw + 5] # 抠出数字区域, 并且加点余量
# cv_show('group', group)
# 对取出灰色group做全局二值化处理
group = cv2.threshold(group, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
# cv_show('group', group)
13.轮廓计算、排序、尺寸resize
digit_contours, _ = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
bounding_boxes = [cv2.boundingRect(c) for c in digit_contours]
(digit_contours, _) = zip(*sorted(zip(digit_contours, bounding_boxes), key=lambda b: b[1][0]))# 轮廓排序
group_output = [] # 定义每一组匹配到的数字的存放列表
for c in digit_contours: # 遍历排好序的轮廓
(x, y, w, h) = cv2.boundingRect(c) # 找到当前数字的轮廓, resize成合适的大小
roi = group[y: y + h, x: x + w]# 取出数字
roi = cv2.resize(roi, (57, 88))
# cv_show('roi', roi)
14.模板匹配
scores = []# 定义保存匹配得分的列表
for (digit, digit_roi) in digits.items(): # items:取出key和值
result = cv2.matchTemplate(roi,digit_roi, cv2.TM_CCOEFF)
(_, score, _, _) = cv2.minMaxLoc(result)# 只要最大值即分数
scores.append(score)
group_output.append(str(np.argmax(scores)))# 找到分数最高的数字即匹配到的数字
15.轮廓绘制
cv2.rectangle(image, (gx - 5, gy - 5), (gx + gw + 5, gy + gh + 5), (0, 0, 255), 1)
16.数字显示
cv2.putText(image, ''.join(group_output), (gx, gy - 15), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
output.extend(group_output)
cv_show('image', image)
17.命令行识别信用卡数字
python card_number_ocr.py --image credit_card_01.png -t ocr_a_reference.png