Python+Opencv身份证号码区域提取及识别实现


Posted in Python onAugust 25, 2020

前端时间智能信息处理实训,我选择的课题为身份证号码识别,对中华人民共和国公民身份证进行识别,提取并识别其中的身份证号码,将身份证号码识别为字符串的形式输出。现在实训结束了将代码发布出来供大家参考,识别的方式并不复杂,并加了一些注释,如果有什么问题可共同讨论。最后重要的事情说三遍:请勿直接抄袭,请勿直接抄袭,请勿直接抄袭!尤其是我的学弟学妹们,还是要自己做的,小心直接拿我的用被老师发现了挨批^_^。

实训环境:CentOS-7.5.1804 + Python-3.6.6 + Opencv-3.4.1

做测试用的照片以及数字识别匹配使用的模板(自制)提供给大家,通过查询得到,身份证号码使用的字体格式为OCR-B 10 BT格式,实训中用到的身份证图片为训练测试图片,有一部分是老师当时直接给出的,还有一部分是我自己用自己身份证做的测试和从网上找到了一张,由于部分身份证号码不是标准字体格式,对识别造成影响,所以有部分图片我还提前ps了一下。

Python+Opencv身份证号码区域提取及识别实现

Python+Opencv身份证号码区域提取及识别实现

Python+Opencv身份证号码区域提取及识别实现

Python+Opencv身份证号码区域提取及识别实现

流程图

Python+Opencv身份证号码区域提取及识别实现

前期处理的部分不在描述,流程图和代码注释中都有。其实整个过程并不是很复杂,本来想过在数字识别方面用现成的一些方法,或者想要尝试用到卷积神经网络(CNN)然后做训练集来识别。后来在和老师交流的时候,老师给出建议可以尝试使用特征点匹配或者其他类方法。根据最后数字分割出来单独显示的效果,想到了一个适合于我代码情况的简单方法。

建立一个标准号码库(利用上面自制模板数字分割后获得),然后用每一个号码图片与库中所有标准号码图片做相似度匹配,和哪一个模板相似度最高,则说明该图片为哪一位号码。在将模板号码分割成功后,最关键的一步就是进行相似度匹配。为提高匹配的精确度和效率,首先利用cv.resize()将前面被提取出的每位身份证号码以及标准号码库中的号码做图像大小调整,统一将图像均调整为12x18像素的大小,图像大小的选择是经过慎重的考虑的,如果太大则计算过程耗时,如果过小则可能存在较大误差。匹配的具体方案为:记录需要识别的图片与每个模板图片中有多少位置的像素点相同,相同的越多,说明相似度越高,也就最有可能是某个号码。最终将18位号码都识别完成后,得到的具体的相似度矩阵。

具体代码如下所示:

import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt

# 将身份证号码区域从身份证中提取出
def Extract(op_image, sh_image):

 binary, contours, hierarchy = cv.findContours(op_image,
  cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
 contours.remove(contours[0])
 max_x, max_y, max_w, max_h = cv.boundingRect(contours[0])
 color = (0, 0, 0)
 for c in contours:
 x, y, w, h = cv.boundingRect(c)
 cv.rectangle(op_image, (x, y), (x + w, y + h), color, 1)
 cv.rectangle(sh_image, (x, y), (x + w, y + h), color, 1)
 if max_w < w:
  max_x = x
  max_y = y
  max_w = w
  max_h = h
 cut_img = sh_image[max_y:max_y+max_h, max_x:max_x+max_w]
 cv.imshow("The recognized enlarged image", op_image)
 cv.waitKey(0)
 cv.imshow("The recognized binary image", sh_image)
 cv.waitKey(0)
 return cut_img

# 号码内部区域填充(未继续是用此方法)
def Area_filling(image, kernel):
 # The boundary image
 iterate = np.zeros(image.shape, np.uint8)
 iterate[:, 0] = image[:, 0]
 iterate[:, -1] = image[:, -1]
 iterate[0, :] = image[0, :]
 iterate[-1, :] = image[-1, :]
 while True:
 old_iterate = iterate
 iterate_dilation = cv.dilate(iterate, kernel, iterations=1)
 iterate = cv.bitwise_and(iterate_dilation, image)
 difference = cv.subtract(iterate, old_iterate)
 # if difference is all zeros it will return False
 if not np.any(difference):
  break
 return iterate

# 将身份证号码区域再次切割使得一张图片一位号码
def Segmentation(cut_img, kernel, n):
 #首先进行一次号码内空白填充(效果不佳,放弃)
 #area_img = Area_filling(cut_img, kernel)
 #cv.imshow("area_img", area_img)
 #cv.waitKey(0)
 #dilate = cv.dilate(area_img, kernel, iterations=1)
 #cv.imshow("dilate", dilate)
 #cv.waitKey(0)

 cut_copy = cut_img.copy()
 binary, contours, hierarchy = cv.findContours(cut_copy, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
 contours.remove(contours[0])
 for c in contours:
 x, y, w, h = cv.boundingRect(c)
 for i in range(h):
  for j in range(w):
  # 把首次用findContours()方法识别的轮廓内区域置黑色
  cut_copy[y + i, x + j] = 0
  # cv.rectangle(cut_copy, (x, y), (x + w, y + h), color, 1)
 cv.imshow("Filled image", cut_copy)
 cv.waitKey(0)

 # 尝试进行分割
 binary, contours, hierarchy = cv.findContours(cut_copy, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
 #tmp_img = cut_img.copy()
 # 如果识别的轮廓数量不是n+1位(首先是一个整个区域的轮廓,然后是n位号码各自的轮廓,身份证和匹配模板分割均用此方法)
 while len(contours)!=n+1:
 if len(contours) < n+1:
  # 如果提取的轮廓数量小于n+1, 说明可能有两位数被识别到一个轮廓中,做一次闭运算,消除数位之间可能存在的连接部分,然后再次尝试提取
  #cut_copy = cv.dilate(cut_copy, kernel, iterations=1)
  cut_copy = cv.morphologyEx(cut_copy, cv.MORPH_CLOSE, kernel)
  cv.imshow("cut_copy", cut_copy)
  cv.waitKey(0)
  # 再次尝试提取身份证区域的轮廓并将轮廓内区域用黑色覆盖
  binary, contours, hierarchy = cv.findContours(cut_copy, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
  # 去掉提取出的第一个轮廓(第一个轮廓为整张图片)
  contours.remove(contours[0])
  for c in contours:
  x, y, w, h = cv.boundingRect(c)
  for i in range(h):
   for j in range(w):
   cut_copy[y + i, x + j] = 0
   # cv.rectangle(cut_copy, (x, y), (x + w, y + h), color, 1)
  cv.imshow("Filled image", cut_copy)
  cv.waitKey(0)
  #如果findContours()结果为n,跳出
  if len(contours) == n:
  break

 elif len(contours) > n+1:
  # 如果提取的轮廓数量大于n+1, 说明可能有一位数被识别到两个轮廓中,做一次开运算,增强附近身份证区域部分之间的连接部分,然后再次尝试提取
  #cut_copy = cv.erode(cut_copy, kernel, iterations=1)
  cut_copy = cv.morphologyEx(cut_copy, cv.MORPH_OPEN, kernel2)
  cv.imshow("cut_copy", cut_copy)
  cv.waitKey(0)
  #再次尝试提取身份证区域的轮廓并将轮廓内区域用黑色覆盖
  binary, contours, hierarchy = cv.findContours(cut_copy, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
  #去掉提取出的第一个轮廓(第一个轮廓为整张图片)
  contours.remove(contours[0])
  for c in contours:
  x, y, w, h = cv.boundingRect(c)
  for i in range(h):
   for j in range(w):
   cut_copy[y + i, x + j] = 0
   # cv.rectangle(cut_copy, (x, y), (x + w, y + h), color, 1)
  #cv.imshow("cut_copy", cut_copy)
  #cv.waitKey(0)
  if len(contours) == n:
  break
 # 上述while()中循环完成后,处理的图像基本满足分割要求,进行最后的提取分割
 binary, contours, hierarchy = cv.findContours(cut_copy, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
 contours.remove(contours[0])
 color = (0, 0, 0)
 for c in contours:
 x, y, w, h = cv.boundingRect(c)
 for i in range(h):
  for j in range(w):
  cv.rectangle(cut_copy, (x, y), (x + w, y + h), color, 1)
  cv.rectangle(cut_img, (x, y), (x + w, y + h), color, 1)

 cv.imshow("Filled image", cut_copy)
 cv.waitKey(0)
 cv.imshow("cut_img", cut_img)
 cv.waitKey(0)
 #print('number:', len(contours))
 # Returns the result of the split
 return contours
 #return cut_img

# Sort排序方法,先将图像分割,由于分割的先后顺序不是按照从左往右,根据横坐标大小将每位身份证号码图片进行排序
def sort(contours, image):
 tmp_num = []
 x_all = []
 x_sort = []
 for c in contours:
 x, y, w, h = cv.boundingRect(c)
 # 使用x坐标来确定身份证号码图片的顺序,把个图片坐标的x值放入x_sort中
 x_sort.append(x)
 # 建立一个用于索引x坐标的列表
 x_all.append(x)
 tmp_img = image[y+1:y+h-1, x+1:x+w-1]
 tmp_img = cv.resize(tmp_img, (40, 60))
 cv.imshow("Number", tmp_img)
 cv.waitKey(0)
 # 将分割的图片缩小至12乘18像素的大小,标准化同时节约模板匹配的时间
 tmp_img = cv.resize(tmp_img, (12, 18))
 tmp_num.append(tmp_img)
 # 利用x_sort排序,用x_all索引,对身份证号码图片排序
 x_sort.sort()
 num_img = []
 for x in x_sort:
 index = x_all.index(x)
 num_img.append(tmp_num[index])
 # 返回排序后图片列表
 return num_img

# 图像识别方法
def MatchImage(img_num, tplt_num):
 # IDnum用于存储最终的身份证字符串
 IDnum = ''
 # 身份证号码18位
 for i in range(18):
 # 存储最大相似度模板的索引以及最大相似度
 max_index = 0
 max_simil = 0
  # 模板有1~9,0,X共11个
 for j in range(11):
  # 存储身份证号码图片与模板之间的相似度
  simil = 0
  for y in range(18):
  for x in range(12):
   # 如果身份证号码图片与模板之间对应位置像素点相同,simil 值自加1
   if img_num[i][y,x] == tplt_num[j][y,x]:
   simil+=1
  if max_simil < simil:
  max_index = j
  max_simil = simil
  print(str(simil)+' ',end='')
 if max_index < 9:
  IDnum += str(max_index+1)
 elif max_index == 9:
  IDnum += str(0)
 else:
  IDnum += 'X'
 print()
 return IDnum

# 最终效果展示
def display(IDnum, image):
 image = cv.resize(image, (960, 90))
 plt.figure(num='ID_Number')
 plt.subplot(111), plt.imshow(image, cmap='gray'), plt.title(IDnum, fontsize=30), plt.xticks([]), plt.yticks([])
 plt.show()


if __name__ == '__main__':
 # 一共三张做测试用身份证图像
 path = 'IDcard01.jpg'
 #path = 'IDcard02.png'
 #path = 'IDcard.jpg'
 id_card = cv.imread(path, 0)
 cv.imshow('Original image', id_card)
 cv.waitKey(0)
 # 将图像转化成标准大小
 id_card = cv.resize(id_card,(1200, 820))
 cv.imshow('Enlarged original image', id_card)
 cv.waitKey(0)
 # 图像二值化
 ret, binary_img = cv.threshold(id_card, 127, 255, cv.THRESH_BINARY)
 cv.imshow('Binary image', binary_img)
 cv.waitKey(0)

 # RECTANGULAR
 kernel = cv.getStructuringElement(cv.MORPH_RECT, (3, 3))
 # RECTANGULAR
 kernel2 = cv.getStructuringElement(cv.MORPH_DILATE, (5, 5))
 #close_img = cv.morphologyEx(binary_img, cv.MORPH_CLOSE, kernel)
 # The corrosion treatment connects the ID Numbers
 erode = cv.erode(binary_img, kernel, iterations=10)
 cv.imshow('Eroded image', erode)
 cv.waitKey(0)

 cut_img = Extract(erode, binary_img.copy())
 cv.imshow("cut_img", cut_img)
 cv.waitKey(0)

 # 存储最终分割的轮廓
 contours = Segmentation(cut_img, kernel, 18)
 # 对图像进行分割并排序
 img_num = sort(contours, cut_img)

 # 识别用的模板
 tplt_path = '/home/image/Pictures/template.jpg'
 tplt_img = cv.imread(tplt_path, 0)
 #cv.imshow('Template image', tplt_img)
 #cv.waitKey(0)

 ret, binary_tplt = cv.threshold(tplt_img, 127, 255, cv.THRESH_BINARY)
 cv.imshow('Binary template image', binary_tplt)
 cv.waitKey(0)

 # 与身份证相同的分割方式
 contours = Segmentation(binary_tplt, kernel, 11)
 tplt_num = sort(contours, binary_tplt)
 # 最终识别出的身份证号码
 IDnum = MatchImage(img_num, tplt_num)
 print('\nID_Number is:', IDnum)
 # 图片展示
 display(IDnum, cut_img)

效果展示:

Python+Opencv身份证号码区域提取及识别实现

到此这篇关于Python+Opencv身份证号码区域提取及识别实现的文章就介绍到这了,更多相关Python+Opencv身份证号码区域提取及识别内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
Python中工作日类库Busines Holiday的介绍与使用
Jul 06 Python
基于python(urlparse)模板的使用方法总结
Oct 13 Python
简单实现python数独游戏
Mar 30 Python
python Pandas 读取txt表格的实例
Apr 29 Python
解决pycharm 误删掉项目文件的处理方法
Oct 22 Python
基于python的socket实现单机五子棋到双人对战
Mar 24 Python
Python代码使用 Pyftpdlib实现FTP服务器功能
Jul 22 Python
Python socket模块方法实现详解
Nov 05 Python
python matplotlib如何给图中的点加标签
Nov 14 Python
如何定义TensorFlow输入节点
Jan 23 Python
Python新建项目自动添加介绍和utf-8编码的方法
Dec 26 Python
Python GUI编程之tkinter 关于 ttkbootstrap 的使用详解
Mar 03 Python
Python Selenium实现无可视化界面过程解析
Aug 25 #Python
一文读懂Python 枚举
Aug 25 #Python
详解python变量与数据类型
Aug 25 #Python
python获取百度热榜链接的实例方法
Aug 25 #Python
利用Python如何制作贪吃蛇及AI版贪吃蛇详解
Aug 24 #Python
python实现自动清理重复文件
Aug 24 #Python
anaconda3安装及jupyter环境配置全教程
Aug 24 #Python
You might like
用PHP伪造referer突破网盘禁止外连的代码
2008/06/15 PHP
php array_merge下进行数组合并的代码
2008/07/22 PHP
PHP简单计算两个时间差的方法示例
2017/06/20 PHP
使两个iframe的高度与内容自适应,且相等
2006/11/20 Javascript
关于用Jquery的height()、width()计算动态插入的IMG标签的宽高的问题
2010/12/08 Javascript
jquery插件制作 自增长输入框实现代码
2012/08/17 jQuery
ie8下修改input的type属性报错的解决方法
2014/09/16 Javascript
jquery实现对联广告的方法
2015/02/05 Javascript
JavaScript中的Math.SQRT1_2属性使用简介
2015/06/14 Javascript
JavaScript中使用指数方法Math.exp()的简介
2015/06/15 Javascript
vue.js 图片上传并预览及图片更换功能的实现代码
2018/08/27 Javascript
JavaScript 九种跨域方式实现原理
2019/02/11 Javascript
基于Node.js的大文件分片上传示例
2019/06/19 Javascript
layui数据表格 table.render 报错的解决方法
2019/09/29 Javascript
Vue中实现回车键切换焦点的方法
2020/02/19 Javascript
vue 数据操作相关总结
2020/12/17 Vue.js
Python把csv数据写入list和字典类型的变量脚本方法
2018/06/15 Python
selenium+python 对输入框的输入处理方法
2018/10/11 Python
python获取txt文件词向量过程详解
2019/07/05 Python
python多线程同步之文件读写控制
2021/02/25 Python
python代码中怎么换行
2020/06/17 Python
python字符串的index和find的区别详解
2020/06/20 Python
css3的transition属性详解
2014/12/15 HTML / CSS
CSS3中的clip-path使用攻略
2015/08/03 HTML / CSS
豆腐の盛田屋官网:日本自然派的豆乳面膜、肥皂、化妆水、乳液等
2016/10/08 全球购物
Smashbox官网:美国知名彩妆品牌
2017/01/05 全球购物
植村秀美国官网:Shu Uemura美国
2019/03/19 全球购物
考博自荐信
2013/10/25 职场文书
会计应届生的自荐信
2013/12/13 职场文书
汽车维修专业自荐书
2014/05/26 职场文书
中药学专业毕业生推荐信
2014/07/10 职场文书
检查机关党的群众路线个人整改措施
2014/10/04 职场文书
2014年行政执法工作总结
2014/12/11 职场文书
用Python创建简易网站图文教程
2021/06/11 Python
聊聊CSS粘性定位sticky案例解析
2022/06/01 HTML / CSS
vue实现简易音乐播放器
2022/08/14 Vue.js