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

article/2025/9/27 11:25:17

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


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

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

01
02
03
模板

流程图

流程图

前期处理的部分不在描述,流程图和代码注释中都有。其实整个过程并不是很复杂,本来想过在数字识别方面用现成的一些方法,或者想要尝试用到卷积神经网络(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 = xmax_y = ymax_w = wmax_h = hcut_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 imageiterate = 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 = iterateiterate_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 Falseif not np.any(difference):breakreturn 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:breakelif 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 splitreturn 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 = 0max_simil = 0# 模板有1~9,0,X共11个for j in range(11):# 存储身份证号码图片与模板之间的相似度simil = 0for y in range(18):for x in range(12):# 如果身份证号码图片与模板之间对应位置像素点相同,simil 值自加1if img_num[i][y,x] == tplt_num[j][y,x]:simil+=1if max_simil < simil:max_index = jmax_simil = similprint(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)# RECTANGULARkernel = cv.getStructuringElement(cv.MORPH_RECT, (3, 3))# RECTANGULARkernel2 = cv.getStructuringElement(cv.MORPH_DILATE, (5, 5))#close_img = cv.morphologyEx(binary_img, cv.MORPH_CLOSE, kernel)# The corrosion treatment connects the ID Numberserode = 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)

效果展示:

 


http://chatgpt.dhexx.cn/article/KLDZ1N5g.shtml

相关文章

身份证号码案例

我国的居民身份证号码&#xff0c;由由十七位数字本体码和一位数字校验码组成。请定义方法判断用户输入的身份证号码是否合法&#xff0c;并在主方法中调用方法测试结果。规则为&#xff1a;号码为18位&#xff0c;不能以数字0开头&#xff0c;前17位只可以是数字&#xff0c;最…

Python之身份证号码的校验

该文章已同步收录到我的博客网站&#xff0c;欢迎浏览我的博客网站&#xff0c;xhang’s blog 问题描述&#xff1a; 中华人民共和国居民身份证号码由17 位数字和1位校验码组成。 其中&#xff0c;前6位为所在地编号&#xff0c;第7~14 位为出生年月日&#xff0c;第15~17位为登…

JAVA 身份证号码的验证

一、身份证结构和形式 在通用的身份证号码有15位的和18位的&#xff1b; 15位身份证号码各位的含义: 1、1-2位省、自治区、直辖市代码&#xff1b; 2、3-4位地级市、盟、自治州代码&#xff1b; 3、5-6位县、县级市、区代码&#xff1b; 4、7-12位出生年月日,比如670401代…

等价类划分测试—身份证

目录 0.题目要求&#xff1a; 注意&#xff1a;一个测试用例只能覆盖一个无效等价类&#xff0c;但可以覆盖尽可能多的前面未覆盖到的有效等价类。 1.划分等价类 1.1有效等价类 1.2无效等价类 2.测试用例&#xff1a; 0.题目要求&#xff1a; 针对国内18位身份证号码验证…

二维码文件分析

将二维码保存&#xff0c;进行解码 进行base64解码,网址&#xff1a;https://www.sojson.com/base64.html。第一次解码&#xff1a;6LZ5Liq5bCx5piv6aqM6KB56CB77yaQkozNVVCNVNZNg 第二次解码&#xff1a;这个就是验证码&#xff1a;BJ35UB5SY6 得到key

Base64[再谈Base64] -- 附练习源代码

我打赌当你见到Base64这个词的时候你会觉得在哪里见过&#xff0c;因为在你能够上网看到这篇文章的时候你已经在后台使用它了。如果您对二进制数有所了解&#xff0c;你就可以开始读它了。 打开一封Email&#xff0c;查看其原始信息&#xff08;您可以通过收取、导出该邮件用文…

KgoUI(3) 之 vue + Sass

框架源代码&#xff1a;码云 直接上步骤&#xff1a; 第一&#xff1a;sass需要的模块 &#xff08;没有安装cnpm 就用 npm咯&#xff09; cnpm install --save-dev sass-loader cnpm install --save-dev node-sass cnpm install --save-dev sass-resources-loader…

安卓项目实战之强大的网络请求框架okGo使用详解(四):Cookie的管理

Cookie概念相关 具体来说cookie机制采用的是在客户端保持状态的方案&#xff0c;而session机制采用的是在服务器端保持状态的方案。同时我们也看到&#xff0c;由于采用服务器端保持状态的方案在客户端也需要保存一个标识&#xff0c;所以session机制是需要借助于cookie机制来…

Windows下安装Ginkgo测试框架

终端执行命令 #切换至GO安装路径 cd F:\Go\goProject\src\github.com F:#安装Ginkgo及其附加库 go get github.com/onsi/ginkgo/ginkgo go get github.com/onsi/gomega结果安装其附加库时开始报错 package golang.org/x/net/html/charset: unrecognized import path "g…

HTTPSConnectionPool(host=‘files.pythonhosted.org‘, port=443): Read timed out.

python 下载第三方库的时候一直报错怎么办&#xff0c; pip --default-timeout1000 install -U pip 尝试了延时操作依然不行&#xff0c;不能获取 这种情况有可能是你开了VPN或者是其他的软件&#xff0c;退出下载即可

Go:构建应用程序的10大框架

文章目录 简介一、CLI 命令(spf13/cobra)二、配置读取器(spf13/viper)三、Web 框架(labstack/echo)四、依赖注入(uber-go/fx)五、Swagger Generator, UI 和 Validation1. Swagger generator (swaggo/swag)2. Swagger UI (swaggo/echo-swagger)3. Swagger validation (go-swagge…

KgoUI(2) 之 vue + layui

框架源代码&#xff1a;码云 我接触layui 已经快2年多了&#xff08;Layui刚出的时候我就开始关注了&#xff09;&#xff0c;vue 和 layui并不是天生一对&#xff0c;elementui 和vue配合更加亲密无间。我认为layui的弹层layer是当前前端最好的弹层。 这里依旧选择layu…

2021年全国大学生网络安全邀请赛暨第七届“东华杯“上海市大学生网络安全大赛Writeup

2021年全国大学生网络安全邀请赛暨第七届"东华杯"上海市大学网格全大赛Writeup Misc checkin 题目给了AGYAbABhAGcAewBkAGgAYgBfADcAdABoAH0- 是UTF-7编码&#xff0c;解码得到flag flag为&#xff1a; flag{dhb_7th}project 下载附件&#xff0c;解压之后发现这…

玩转Kafka—SpringGo整合Kafka

玩转Kafka—Spring整合Kafka 1 新建Spring Boot项目&#xff0c;增加依赖 <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependenc…

视图单行子查询返回mysql,Oracle命令整理 - osc_sj1kgo4z的个人空间 - OSCHINA - 中文开源技术交流社区...

常用命令 1 sqlplus scott/tiger192.168.47.10:1521/orcl后面不要加&#xff1b; sqlplus sys/oracle as sysdba 【密码认证】 sqlplus用户名任意/密码任意as sysdba 【主机认证】 2 spool d:\基本查询.txt 录屏开始 spool off …

MLX90316KGO-BDG-100-RE传感器 旋转位置 角度测量

介绍 MLX90316是Tria⊗is旋转位置传感器&#xff0c;提供在设备表面旋转的小偶极磁铁(轴端磁铁)的绝对角位置。 得益于其表面的集成磁集中器(IMC)&#xff0c;单片设备以非接触式方式感知应用磁通量密度的水平分量。 这种独特的传感原理应用于旋转位置传感器&#xff0c;可在机…

图形语言 Kgo

http://www.ferlysoft.com/product-kgo.asp 始于2008 无代码开发技术&#xff0c;以图形取代编程 Kgo 介绍Kgo是无代码开发管理信息系统的特定领域语言。 Kgo语言由语法语义、图形设计云工具和运行时环境三个部分构成。Kgo 语法语义采用XSD&#xff08;XML Schemas Definition&…

KgoUI 页面展示

前端框架 vue layui sass 框架源代码&#xff1a;码云

微软账号登陆不上_微软待办(todo)如何跟Outlook任务同步?

小生我之前也遇到这个问题了&#xff0c;后来参考了知友的一个答案&#xff0c;自此得到了解决 但是也还是由很多人不太明白&#xff0c;所以我尝试着把我解决这个问题的关键点描述一下&#xff0c;希望能给大家一些帮助 关键点只有一个&#xff1a; 三个软件的账户一定要一样 …