项目要点
- _, ref = cv2.threshold(ref, 10, 255, cv2.THRESH_BINARY_INV) 二值化处理图片, 黑白化图片
- ref = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) 灰度化处理
- ref_contours, _ = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) 计算轮廓
- cv2.drawContours(img, ref_contours, -1, (0, 0, 255), 2) 画出外轮廓
- image = cv2.resize(image, (300, int(h * r))) 等比例缩放原图 # r = width / w
- 顶帽运算: tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rect_kernel) 顶帽 = 原图 - 开运算 开运算的效果是去除图像外的噪点, 原图 - 开运算就得到了去掉的噪点.
- 索贝尔算子: grad_x = cv2.Sobel(tophat, ddepth = cv2.CV_32F, dx = 1, dy = 0, ksize = -1) 边缘检测, 一般先分别求x, y 方向上的检测结果, 再进行合并 .
- 闭操作: grad_x = cv2.morphologyEx(grad_x, cv2.MORPH_CLOSE, rect_kernel) 闭运算 = 膨胀 + 腐蚀 填充图像内部的噪点 .
- _, thresh = cv2.threshold(grad_x, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU) 大津算法找到合适的阈值进行全局二值化 主要是通过阈值进行前后背景分割 .
- 模板匹配: result = cv2.matchTemplate(roi, digit_roi, cv2.TM_CCOEFF)
信用卡识别项目
基本思路:
总体思路就是取出信用卡中每一个数字作为去和模板中的10个数字进行模板匹配操作.
-
先对模板处理, 获取每个数字的模板及其对应的数字标识.
-
再对信用卡处理, 通过一系列预处理操作, 取出信用卡数字区域.
-
然后再取出每一个数字去和模板中的10个数字进行匹配.
1 先对模板处理
1.1 显示原始图片
# 先处理数字模板的图片
import cv2
import numpy as np# 模板图片
img = cv2.imread('./ocr_a_reference.png')
def cv_show(name, img): # 封装显示的函数cv2.imshow(name, img)cv2.waitKey(0)cv2.destroyAllWindows()print(img.shape) # (126, 800, 3)
cv_show('img', img)
1.2 灰度化处理
ref = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 灰度化处理
cv_show('ref', ref)
print(ref.shape) # (126, 800)
1.3 二值化图片
# 二值化处理
_, ref = cv2.threshold(ref, 10, 255, cv2.THRESH_BINARY_INV)
cv_show('ref', ref)
1.4 描绘轮廓
# 计算轮廓
ref_contours,_=cv2.findContours(ref.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
# 画出外轮廓
cv2.drawContours(img, ref_contours, -1, (0, 0, 255), 2)
cv_show('img', img)
print(np.array(ref_contours).shape) # (10,)
1.5 外接矩形
# 对轮廓进行排序, 按照数字大小进行排序, 方便使用
# 排序思路:根据每个数字的最大外接矩形的X轴坐标进行排序
# 计算每个轮廓的外接矩形
bounding_boxes = [cv2.boundingRect(c) for c in ref_contours]
print(len(bounding_boxes)) # 10
sorted(bounding_boxes, key = lambda b : b[0])
print('---------\n', bounding_boxes[0][:2]) # (15, 20)
cv2.rectangle(img, bounding_boxes[0][:2], (bounding_boxes[0][0]+bounding_boxes[0][2],bounding_boxes[0][1]+bounding_boxes[0][3]), (255, 0, 0), 2)
# * 去中括号
(ref_contours, bounding_boxes) = zip(*sorted(zip(ref_contours, bounding_boxes), key = lambda b : b[1][0]))
digits = {}
for (i, c) in enumerate(ref_contours):# 重新计算外接矩形(x, y, w, h) = cv2.boundingRect(c)# region of interest 感兴趣的区域,取出每个数字roi = ref[y: y+h, x: x+ w]# resize 成合适的图像roi = cv2.resize(roi, (57, 88))digits[i] = roiprint(len(digits)) # 10
cv_show('img', img)
- digits 是一个数字和特征对应的字典 .
2 信用卡处理
2.1 信用卡图片
# 对信用卡图片进行处理
image = cv2.imread('./credit_card_01.png')
print(image.shape) # (368, 583, 3)
cv_show('img',image)
2.2 调整图片尺寸
# 对信用卡图片resize
# 保证原图不拉伸, 计算原图的长宽比
h, w = image.shape[:2]
width = 300
r = width / w
image = cv2.resize(image, (300, int(h * r))) # 等比例缩放原图
print(image.shape) # (189, 300, 3)
cv_show('image', image)
2.3 灰度化处理图片
# 灰度化处理
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
print(gray.shape) # (189, 300)
cv_show('image', gray)
2.4 顶帽操作
# 形态学操作
# 顶帽操作,突出明亮区,初始化卷积核, 去除图像外的噪点
rect_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))
tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rect_kernel)
print(tophat.shape) # (189, 300)
cv_show('tophat', tophat)
2.5 索贝尔算子
# 索贝尔算子, 计算图像轮廓
grad_x = cv2.Sobel(tophat, ddepth = cv2.CV_32F, dx = 1, dy = 0, ksize = -1)
grad_x = np.absolute(grad_x)
# 再把grad_x 变成0 到255 之间的整数
min_val, max_val = np.min(grad_x), np.max(grad_x)
grad_x = ((grad_x - min_val)/ (max_val - min_val)) * 255 # 归一化 ?
# 修改 数据类型
grad_x = grad_x.astype('uint8')
cv_show('grad_x', grad_x) # 只用X方向的值
# 计算y轴梯度
grad_y = cv2.Sobel(tophat, ddepth = cv2.CV_32F, dx = 0, dy = 1, ksize = -1)
grad_y = np.absolute(grad_y)
# 再把grad_y 变成0 到255 之间的整数
min_val, max_val = np.min(grad_y), np.max(grad_y)
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)
. y轴的索贝尔算子梯度 . 索贝尔算子合并后效果
2.6 闭操作
# 闭操作 ,先膨胀,再腐蚀, 填充内部噪点
grad_x = cv2.morphologyEx(grad_x, cv2.MORPH_CLOSE, rect_kernel)
cv_show('grad_x', grad_x)
2.7 大津算法
# 大津算法找到合适的阈值进行全局二值化, 区分前景背景
_, thresh = cv2.threshold(grad_x, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
cv_show('thresh', thresh)
2.8 继续闭运算
# 再来闭操作, 中间还有空洞 5 * 5 的卷积核放入, 减少内部噪点
sq_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sq_kernel)
cv_show('thresh', thresh)
2.9 寻找轮廓
# 找轮廓
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('image', image_copy)
2.10 查找信用卡数字区
-
根据信用卡数字区的长宽特征, 找出数字区域 .
# 定义每一组匹配到的值的列表
# 遍历轮廓, 计算外接矩形,然后根据信用卡的长宽比,找到数字区
locs = []
for c in thresh_contours:# 计算外接矩形(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))print(locs)
# [(224, 102, 52, 14), (157, 102, 51, 14), (90, 102, 51, 14), (24, 102, 50, 14)]
3 模板匹配
# 对符合的轮廓进行排序
# 遍历每个外接矩形,通过外接矩形可以把原图中的数字抠出来
sorted(locs, key = lambda x : x[0])
out_put = []
for (i , (gx, gy, gw, gh)) in enumerate(locs):# 扣除数字,外轮廓留预值# 根据坐标调出在原图的位置截图group = gray[gy - 5 : gy + gh + 5, gx - 5: gx+ gw + 5] # gray 是灰度化处理后的图片# 对取出的灰色group做全局二值化处理, 黑白图group = cv2.threshold(group, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]# 计算轮廓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]))# print(len(digit_contours)) # 4 每次是四个数字, 每个数字的轮廓group_output = []# 遍历排好序的轮廓, 每个digit_contours 为每个数字的元组for c in digit_contours:# 找到当前的轮廓,resize成合适的大小, 进行模板匹配(x, y , w, h) = cv2.boundingRect(c)# 取出数字roi = group[y : y+ h, x : x + w]roi = cv2.resize(roi, (57, 88))# cv_show('roi', roi) # 显示每个数字# 进行模板匹配, 定义保存匹配得分的列表scores = []for (digit, digit_roi) in digits.items():result = cv2.matchTemplate(roi, digit_roi, cv2.TM_CCOEFF)# 只要最大值分数(_, score, _, _) = cv2.minMaxLoc(result)scores.append(score)# 找到分数最高的数字, 即匹配到的数字group_output.append(str(np.argmax(scores)))# print(scores) # 第一列在 9位置的值: -5058846.5# print(group_output) # 第一个值是 ['9']out_put = group_output + out_put# 画出轮廓显示数字cv2.rectangle(image, (gx - 5, gy - 5), (gx + gw + 5, gy + gh + 5), (0, 0, 255), 1)cv2.putText(image, ''.join(group_output), (gx, gy - 15), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
cv_show('image', image)
print(out_put) # ['4', '0', '0', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '0']
car_num = ''
for i in out_put:car_num += str(i)
print('信用卡账号:',car_num) # 信用卡账号: 4000123456789010