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 相关文章推荐
Django中STATIC_ROOT和STATIC_URL及STATICFILES_DIRS浅析
May 08 Python
linux安装Python3.4.2的操作方法
Sep 28 Python
Python 做曲线拟合和求积分的方法
Dec 29 Python
Python设计模式之装饰模式实例详解
Jan 21 Python
详解python读取image
Apr 03 Python
Pythony运维入门之Socket网络编程详解
Apr 15 Python
Django单元测试工具test client使用详解
Aug 02 Python
python不相等的两个字符串的 if 条件判断为True详解
Mar 12 Python
Python代码中如何读取键盘录入的值
May 27 Python
Python celery原理及运行流程解析
Jun 13 Python
总结Python使用过程中的bug
Jun 18 Python
Python first-order-model实现让照片动起来
Jun 25 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检测用户是否关闭浏览器的方法
2016/02/14 PHP
免费空间广告万能消除代码
2006/09/04 Javascript
jquery动画4.升级版遮罩效果的图片走廊--带自动运行效果
2012/08/24 Javascript
JavaScript的setAttribute兼容性问题解决方法
2013/11/11 Javascript
jQuery的cookie插件实现保存用户登陆信息
2014/04/15 Javascript
jQuery取得设置清空select选择的文本与值
2014/07/08 Javascript
输入框过滤非数字的js代码
2014/09/18 Javascript
js计算时间差代码【包括计算,天,时,分,秒】
2016/04/26 Javascript
Javascript字符串拼接小技巧(推荐)
2016/06/02 Javascript
概述如何实现一个简单的浏览器端js模块加载器
2016/12/07 Javascript
解决AngualrJS页面刷新导致异常显示问题
2017/04/20 Javascript
浅谈react+es6+webpack的基础配置
2017/08/09 Javascript
Vue使用vue-cli创建项目
2017/09/01 Javascript
Jquery异步上传文件代码实例
2019/11/13 jQuery
JS动态图片的实现方法完整示例
2020/01/13 Javascript
微信小程序整个页面的自动适应布局的实现
2020/07/12 Javascript
python使用Plotly绘图工具绘制气泡图
2019/04/01 Python
Django 开发环境与生产环境的区分详解
2019/07/26 Python
通过 Django Pagination 实现简单分页功能
2019/11/11 Python
关于Tensorflow使用CPU报错的解决方式
2020/02/05 Python
jenkins+python自动化测试持续集成教程
2020/05/12 Python
pycharm sciview的图片另存为操作
2020/06/01 Python
Python 在函数上添加包装器
2020/07/28 Python
详解python实现可视化的MD5、sha256哈希加密小工具
2020/09/14 Python
Python+OpenCV图像处理——打印图片属性、设置存储路径、调用摄像头
2020/10/22 Python
欧洲有机婴儿食品最大的市场:Organic Baby Food(供美国和加拿大)
2018/03/28 全球购物
同步和异步有何异同,在什么情况下分别使用他们?
2012/12/28 面试题
本科毕业生求职自荐信
2014/02/03 职场文书
数控专业大学毕业生职业规划范文
2014/02/06 职场文书
施工员岗位职责
2014/03/16 职场文书
生产助理岗位职责
2014/06/18 职场文书
党的生日演讲稿
2014/09/10 职场文书
教师个人考察材料
2014/12/16 职场文书
初中团支书竞选稿
2015/11/21 职场文书
2016教师暑期培训学习心得体会
2016/01/09 职场文书
全网非常详细的pytest配置文件
2022/07/15 Python