文章目录
一、案例实现的整体思路
- 下面是一个数字0~9的模板图片
- 案例身份证如下:
- 对数字模板的处理
- 通过对模板中的数字进行定位处理,将每个数字的轮廓和外接矩形都一一对应,并由小到大的排序
- 再将每一个数字都对应一个模板,并设置成相同的大小,用于对身份证号码进行匹配并识别
- 对身份证的处理
- 确定出身份证中信息部分的轮廓,确定出每个部分的外接矩形,通过外接矩形的坐标关系确定出身份证号码区域
- 对身份证号码区域的数字与模板数字做相同的处理
- 最后将处理后的模板数字与处理后的身份证号码区域的数字进行模板匹配,识别出对应的号码数字
二、代码实现
- 代码中会运用到轮廓检测与绘制和模板匹配,可以参考以下链接中的内容进行理解
1.首先定义两个函数
-
def cv_show()用于绘图展示
-
def sort_contours()用于对模板数字的排序
""" 绘图展示函数 """ def cv_show(name, img): cv2.imshow(name, img) cv2.waitKey(0) """ 用于对模板数字的排序的函数 """ # sort_contours() 函数传入的参数: # cnts:包含所有数字轮廓的列表 # method='left-to-right':排序的反向 # cv2.boundingRect() 函数用于绘制轮廓的最小外接矩形, # 返回一个包含四个值的元组:(x, y, w, h),分别代表边界框左上角的x坐标、y坐标、宽度和高度 # 通过每个数字外接接矩形框的左上角点的x和y坐标的大小,对每个模板数字进行排序 def sort_contours(cnts, method='left-to-right'): reverse = False i = 0 if method == 'right-to-left' or method == 'bottom-to-top': reverse = True if method == 'top-to-bottom' or method == 'bottom-to-top': i = 1 boundingBoxes = [cv2.boundingRect(c) for c in cnts] (cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes), key=lambda b: b[1][i], reverse=reverse)) # zip(*...)使用星号操作符解包排序后的元组列表,并将其重新组合成两个列表:一个包含所有轮廓,另一个包含所有边界框。 # 返回梳理轮廓,和外接矩形 return cnts, boundingBoxes
2.模板图像中数字的定位处理
- 代码如下:
# 读取模板图片 img = cv2.imread('template.png') cv_show('img', img) # 转换为灰度图 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 转换为灰度图 cv_show('gray', gray) # 转换为二值化图 ref = cv2.threshold(gray, 155, 255, cv2.THRESH_BINARY_INV)[1] # 再转换为二值图像 cv_show('ref', ref) # 计算轮廓: cv2.findContours()函数接受的参数为二值图,即黑白的(不是灰度图) # cv2.RETR_EXTERNAL 只检测外轮廓,cv2.CHAIN_APPROX_SIMPLE 只保留终点坐标 _, refCnts, hierarchy = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) cv2.drawContours(img, refCnts, -1, (0, 0, 255), 2) cv_show('img', img) refCnts = sort_contours(refCnts, method="left-to-right")[0] # 排序 ,从左到右,从上到下 digits = {} # 保存模板中每个数字对应的像素值 for (i, c) in enumerate(refCnts): # 遍历每一个轮廓 # 计算外接矩形并且resize成合适大小 (x, y, w, h) = cv2.boundingRect(c) roi = ref[y - 2:y + h + 2, x - 2:x + w + 2] # 适当增加一点外接矩形框的大小 roi = cv2.resize(roi, (57, 88)) # 缩放到指定的大小 # cv2.bitwise_not() 位非操作:反转图像中每个像素的位值,即将白色变为黑色,黑色变为白色, # 对于灰度图像,较亮的像素会变暗,较暗的像素会变亮。 roi = cv2.bitwise_not(roi) cv_show('roi', roi) digits[i] = roi # 每一个数字对应每一个模板
- 结果如下:
- 处理后的每一个数字模板如下所示
- 处理后的每一个数字模板如下所示
3.身份证号码数字的定位处理
-
代码如下:
# 读取身份证照片 image = cv2.imread('sfz.jpg') cv_show('image', image) # 转换为灰度图 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) cv_show('gray', gray) # 转换为二值图 ref = cv2.threshold(gray, 120, 255, cv2.THRESH_BINARY_INV)[1] cv_show('ref', ref) # 计算轮廓 t_, threshCnts, h = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) cnts = threshCnts cur_img = image.copy() # 画出轮廓 cv2.drawContours(cur_img, cnts, -1, (0, 0, 255), 2) cv_show('img', cur_img) # 遍历轮廓,找到数字部分像素区域 locs = [] for (i, c) in enumerate(cnts): # 算出所有轮廓的外接矩形 (x, y, w, h) = cv2.boundingRect(c) # 通过每个号码数字外接矩形的y轴坐标的大小,和x轴坐标的大小来确定号码数字的区域 if (y > 330 and y < 360) and x > 220: locs.append((x, y, w, h)) # 将符合的数字轮廓信息都添加到locs列表中 """ 因为经过cv2.boundingRect() 外接矩形框后的数字顺序是乱的 通过每个数字外接矩形框的左上角顶点的x坐标的大小进行重新排序 恢复到原身份证号码的数字顺序 """ locs = sorted(locs, key=lambda x: x[0]) # 将身份证号码数字进行与模板数字相同的操作 output = [] for (i, (gX, gY, gW, gH)) in enumerate(locs): group = gray[gY - 2:gY + gH + 2, gX - 2:gX + gW + 2] cv_show('group', group) # 预处理 group = cv2.threshold(group, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] cv_show('group', group) # 将每个数字都设置成与数字模板中每个数字的大小相同 roi = cv2.resize(group, (57, 88)) cv_show('roi', roi)
-
结果如下
- 身份证号码每一个数字处理后的效果如下:
- 身份证号码每一个数字处理后的效果如下:
4.使用模板匹配,计算匹配得分,找到正确结果
-
代码如下:
# 定义scores空列表用于存放所有的匹配得分 scores = [] # 定义groupOutput空列表用于存放匹配后的每一个正确的号码数字 groupOutput = [] 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空列表中添加正确的身份证号码 output.extend(groupOutput) # 打印出身份证号码 print("Credit Card #:{}".format("".join(output))) # 显示身份证图片匹配后的结果图 cv_show("Image", image) cv2.waitKey(0) cv2.destroyAllWindows()
-
结果如下
-
完整代码如下:
import numpy as np import cv2 def cv_show(name, img): cv2.imshow(name, img) cv2.waitKey(0) def sort_contours(cnts, method='left-to-right'): reverse = False i = 0 if method == 'right-to-left' or method == 'bottom-to-top': reverse = True if method == 'top-to-bottom' or method == 'bottom-to-top': i = 1 boundingBoxes = [cv2.boundingRect(c) for c in cnts] (cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes), key=lambda b: b[1][i], reverse=reverse)) # zip(*...)使用星号操作符解包排序后的元组列表,并将其重新组合成两个列表:一个包含所有轮廓,另一个包含所有边界框。 return cnts, boundingBoxes """------模板图像中数字的定位处理------""" # 读取模板图片 img = cv2.imread('template.png') cv_show('img', img) # 转换为灰度图 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 转换为灰度图 cv_show('gray', gray) # 转换为二值化图 ref = cv2.threshold(gray, 155, 255, cv2.THRESH_BINARY_INV)[1] # 再转换为二值图像 cv_show('ref', ref) # 计算轮廓: cv2.findContours()函数接受的参数为二值图,即黑白的(不是灰度图) # cv2.RETR_EXTERNAL 只检测外轮廓,cv2.CHAIN_APPROX_SIMPLE 只保留终点坐标 _, refCnts, hierarchy = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 画出轮廓 cv2.drawContours(img, refCnts, -1, (0, 0, 255), 2) cv_show('img', img) refCnts = sort_contours(refCnts, method="left-to-right")[0] # 排序 ,从左到右,从上到下 digits = {} # 保存模板中每个数字对应的像素值 for (i, c) in enumerate(refCnts): # 遍历每一个轮廓 # 计算外接矩形并且resize成合适大小 (x, y, w, h) = cv2.boundingRect(c) roi = ref[y - 2:y + h + 2, x - 2:x + w + 2] # 适当增加一点外接矩形框的大小 roi = cv2.resize(roi, (57, 88)) # 缩放到指定的大小 # cv2.bitwise_not() 位非操作:反转图像中每个像素的位值,即将白色变为黑色,黑色变为白色, # 对于灰度图像,较亮的像素会变暗,较暗的像素会变亮。 roi = cv2.bitwise_not(roi) cv_show('roi', roi) digits[i] = roi # 每一个数字对应每一个模板 # cv2.destroyAllWindows() """ 身份证号码数字的定位处理 """ # 读取身份证照片 image = cv2.imread('sfz.jpg') cv_show('image', image) # 转换为灰度图 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) cv_show('gray', gray) # 转换为二值图 ref = cv2.threshold(gray, 120, 255, cv2.THRESH_BINARY_INV)[1] cv_show('ref', ref) # 计算轮廓 t_, threshCnts, h = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) cnts = threshCnts cur_img = image.copy() # 画出轮廓 cv2.drawContours(cur_img, cnts, -1, (0, 0, 255), 2) cv_show('img', cur_img) # 遍历轮廓,找到数字部分像素区域 locs = [] for (i, c) in enumerate(cnts): # 算出所有轮廓的外接矩形 (x, y, w, h) = cv2.boundingRect(c) # 通过每个号码数字外接矩形的y轴坐标的大小,和x轴坐标的大小来确定号码数字的区域 if (y > 330 and y < 360) and x > 220: locs.append((x, y, w, h)) # 将符合的数字轮廓信息都添加到locs列表中 """ 因为经过cv2.boundingRect() 外接矩形框后的数字顺序是乱的 通过每个数字外接矩形框的左上角顶点的x坐标的大小进行重新排序 恢复到原身份证号码的数字顺序 """ locs = sorted(locs, key=lambda x: x[0]) # 将身份证号码数字进行与模板数字相同的操作 output = [] for (i, (gX, gY, gW, gH)) in enumerate(locs): group = gray[gY - 2:gY + gH + 2, gX - 2:gX + gW + 2] cv_show('group', group) # 预处理 group = cv2.threshold(group, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] cv_show('group', group) # 将每个数字都设置成与数字模板中每个数字的大小相同 roi = cv2.resize(group, (57, 88)) cv_show('roi', roi) ''' 使用模板匹配,计算匹配得分,找到正确结果 ''' # 定义scores空列表用于存放所有的匹配得分 scores = [] # 定义groupOutput空列表用于存放匹配后的每一个正确的号码数字 groupOutput = [] 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空列表中添加正确的身份证号码 output.extend(groupOutput) # 打印出身份证号码 print("Credit Card #:{}".format("".join(output))) # 显示身份证图片匹配后的结果图 cv_show("Image", image) cv2.waitKey(0) cv2.destroyAllWindows()