计算机视觉——Opencv(身份证号识别)
模板构建:利用标准数字图像,建立模板库。图像预处理:灰度化 → 顶帽操作 → 闭运算 → 二值化,得到清晰的卡号区域。区域筛选:根据宽高比和大小,精确定位卡号段。逐位识别:通过模板匹配,识别每一位数字。结果展示:输出卡种 + 卡号,并可视化标注。
·
图片准备


导入库和相应模块
import numpy as np
import cv2
import myutils
自建模块myutils
import cv2
"""
myutils.py - 自定义工具函数模块
包含轮廓排序和图像缩放两个常用功能,
用于银行卡号识别系统中的图像处理
"""
def sort_contours(cnts, method='left-to-right'):
"""
对轮廓进行排序(按指定方向)
参数:
cnts: 轮廓列表,由cv2.findContours()返回
method: 排序方法,可选值包括:
'left-to-right' (默认) - 从左到右
'right-to-left' - 从右到左
'top-to-bottom' - 从上到下
'bottom-to-top' - 从下到上
返回:
排序后的轮廓列表和对应的边界框列表
"""
# 初始化排序方向标志和排序依据索引
reverse = False # 是否反向排序
i = 0 # 排序依据的维度索引(0表示x轴,1表示y轴)
# 确定是否需要反向排序
if method == 'right-to-left' or method == 'bottom-to-top':
reverse = True
# 确定排序依据是x轴还是y轴
# 垂直方向排序(上下)用y坐标,水平方向排序(左右)用x坐标
if method == 'top-to-bottom' or method == 'bottom-to-top':
i = 1 # 使用y坐标排序
# 为每个轮廓计算边界框(x, y, w, h)
boundingBoxes = [cv2.boundingRect(c) for c in cnts]
# 将轮廓与对应的边界框组合,按指定维度排序后再拆分
# sorted()的key参数指定按边界框的第i个值(x或y)排序
(cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes),
key=lambda b: b[1][i], # b[1]是边界框,b[1][i]是x或y坐标
reverse=reverse))
return cnts, boundingBoxes
def resize(image, width=None, height=None, inter=cv2.INTER_AREA):
"""
按比例缩放图像(保持原图宽高比)
参数:
image: 输入图像
width: 目标宽度(若为None则按height计算)
height: 目标高度(若为None则按width计算)
inter: 插值方法,默认cv2.INTER_AREA(适合缩小图像)
返回:
缩放后的图像
"""
# 初始化目标尺寸
dim = None
# 获取原图高度和宽度
(h, w) = image.shape[:2] # shape[:2]取前两个值(高度、宽度),忽略通道数
# 若宽高均未指定,则返回原图
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)
return resized
接着定义信用卡类型字典,用来根据首位数字判断卡种:
FIRST_NUMBER = {"3": "American Express",
"4": "Visa",
"5": "MasterCard",
"6": "Discover Card"}
展示图像:
def cv_show(name, img): # 绘图展示
cv2.imshow(name, img)
cv2.waitKey(0)
模板图像中数字的定位处理
识别卡号之前,需要先有一份 数字模板,作为对比参考
img = cv2.imread("kahao.png")
cv_show('img', img)
ref = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 灰度图
cv_show('ref', ref)
ref = cv2.threshold(ref, 10, 255, cv2.THRESH_BINARY_INV)[1] # 二值图像 黑底白字,方便找轮廓
cv_show('ref', ref)
计算轮廓
_, refCnts, hierarchy = cv2.findContours(ref, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img, refCnts, -1, (0, 0, 255), 3)
cv_show('img', img)
cv2.findContours()函数最重要的参数为 原图,即黑白的(不是灰度图)
cv2.RETR_EXTERNAL表示只检测外轮廓
cv2.CHAIN_APPROX_SIMPLE压缩水平垂直
然后按从左到右排序,依次提取每个数字,统一缩放到 (57, 88) 的大小
refCnts = myutils.sort_contours(refCnts, method="left-to-right")[0] # 排序,从左到右,从上到下
digits = {} # 保存每一个数字对应的模板
for (i, c) in enumerate(refCnts): # 遍历每一个轮廓
(x, y, w, h) = cv2.boundingRect(c) # 计算外接矩形并且resize成合适大小
roi = cv2.resize(ref[y:y + h, x:x + w], (57, 88)) # 缩放成指定的大小
cv_show('roi', roi)
digits[i] = roi # 每一个数字对应每一个模板
print(digits)
此时,digits 就是一个数字模板库,包含 0-9 的标准图案。
身份证的图像处理
image = cv2.imread("card.jpg")
cv_show('image', image)
# image = myutils.resize(image, width=300) # 设置图像的大小
image = cv2.resize(image,(640,400))
print(image.shape)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv_show('gray', gray)
使用 顶帽操作 突出明亮的数字区域:
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15, 5)) # 初始化卷积核
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rectKernel) # 顶帽 = 原始图像 - 开运算结果(来增强亮部区域)
cv_show('tophat', tophat)
使用顶帽操作可以突出更明亮的区域,消除背景元素,原因是谱系图下变化小,不被腐蚀掉。
找到数字边框
-
闭操作 让数字连成一片。
-
二值化 + OTSU 自动分割前景和背景。
-
再闭操作 去掉细小噪声。
-
轮廓提取 找到数字所在的大区域。
# 1、通过闭操作(先膨胀,再腐蚀)将数字连在一起
closeX = cv2.morphologyEx(tophat, cv2.MORPH_CLOSE, rectKernel)
cv_show('gradX', closeX)
# 2、THRESH_OTSU会自动寻找合适的阈值,适合双峰,需把阈值参数设置为0
thresh = cv2.threshold(closeX, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
cv_show('thresh', thresh)
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel) # 再来一个闭操作
cv_show('thresh1', thresh)
# 3、计算轮廓
_, threshCnts, h = 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), 3)
cv_show('img', cur_img)
遍历轮廓,找到数字部分轮廓区域
locs = []
for (i, c) in enumerate(cnts):
(x, y, w, h) = cv2.boundingRect(c) # 计算外接矩形
ar = w / float(h)
# 选择合适的区域,根据实际任务来,
if ar > 16 and ar < 20:
# if (w > 100 and w < 400) and (h > 3 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] # 适当加一点边界
cv_show('group', group)
# 预处理
group = cv2.threshold(group, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
# print(group.shape)
# group = cv2.resize(group, (300, 20))
# cv_show('group', group)
# 颜色反转
group = cv2.bitwise_not(group)
cv_show('group', group)
# 计算每一组的轮廓
group_, digitCnts, hierarchy = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
digitCnts = myutils.sort_contours(digitCnts, method="left-to-right")[0]
# 计算每一组中的每一个数值
for c in digitCnts:
# 找到当前数值的轮廓,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, 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), 1)
# cv2.putText()是OpenCV库中的一个函数,用于在图像上添加文本。
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)))
cv2.imwrite('card_result.jpg', image)
cv2.imshow("result", image)
cv2.waitKey(0)
运行结果:


总结
-
模板构建:利用标准数字图像,建立模板库。
-
图像预处理:灰度化 → 顶帽操作 → 闭运算 → 二值化,得到清晰的卡号区域。
-
区域筛选:根据宽高比和大小,精确定位卡号段。
-
逐位识别:通过模板匹配,识别每一位数字。
-
结果展示:输出卡种 + 卡号,并可视化标注。
更多推荐
所有评论(0)