《OpenCV计算机视觉》—— 身份证号码识别案例
文章目录
- 一、案例实现的整体思路
- 二、代码实现
- 1.首先定义两个函数
- 2.模板图像中数字的定位处理
- 3.身份证号码数字的定位处理
- 4.使用模板匹配,计算匹配得分,找到正确结果
一、案例实现的整体思路
- 下面是一个数字0~9的模板图片
- 案例身份证如下:
- 对数字模板的处理
- 通过对模板中的数字进行定位处理,将每个数字的轮廓和外接矩形都一一对应,并由小到大的排序
- 再将每一个数字都对应一个模板,并设置成相同的大小,用于对身份证号码进行匹配并识别
- 对身份证的处理
- 确定出身份证中信息部分的轮廓,确定出每个部分的外接矩形,通过外接矩形的坐标关系确定出身份证号码区域
- 对身份证号码区域的数字与模板数字做相同的处理
- 最后将处理后的模板数字与处理后的身份证号码区域的数字进行模板匹配,识别出对应的号码数字
二、代码实现
- 代码中会运用到轮廓检测与绘制和模板匹配,可以参考以下链接中的内容进行理解
- 轮廓检测与绘制:
- https://blog.csdn.net/weixin_73504499/article/details/141873522?spm=1001.2014.3001.5501
- 模板匹配:
- https://blog.csdn.net/weixin_73504499/article/details/141905861?spm=1001.2014.3001.5501
- 轮廓检测与绘制:
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 = Falsei = 0if method == 'right-to-left' or method == 'bottom-to-top':reverse = Trueif method == 'top-to-bottom' or method == 'bottom-to-top':i = 1boundingBoxes = [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 cv2def cv_show(name, img):cv2.imshow(name, img)cv2.waitKey(0)def sort_contours(cnts, method='left-to-right'):reverse = Falsei = 0if method == 'right-to-left' or method == 'bottom-to-top':reverse = Trueif method == 'top-to-bottom' or method == 'bottom-to-top':i = 1boundingBoxes = [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()
相关文章:

《OpenCV计算机视觉》—— 身份证号码识别案例
文章目录 一、案例实现的整体思路二、代码实现1.首先定义两个函数2.模板图像中数字的定位处理3.身份证号码数字的定位处理4.使用模板匹配,计算匹配得分,找到正确结果 一、案例实现的整体思路 下面是一个数字0~9的模板图片 案例身份证如下: 对…...

如何使用正则表达式替换字符串中的特定位置数字
如何使用正则表达式替换字符串中的特定位置数字 1、效果 把字符串中的第一个123替换掉: 2、代码 使用正则中的sub函数: re.sub(pattern,repl,string,count=0,flags=0) pattern:表示需要匹配的模式,即需要被替换的字符或字符串。 repl:表示替换后的字符串或函数,用于…...

【SQL】在SQL中,行转列
在SQL中,行转列通常是指将数据从水平方向(行)转换为垂直方向(列),这可以通过使用CASE语句或数据库特有的函数如PIVOT(在SQL Server中)来实现。下面我将通过一个具体的例子来说明如何…...

95. UE5 GAS RPG 实现创建多段飞弹攻击敌人
从这篇开始,我们将实现一些技能,比如多段火球术,闪电链等等。 在这一篇里,我们先实现多段火球术,技能可以通过配置发射出多个火球术进行攻击。 创建多段火球函数 首先在我们之前创建的RPGFireBolt.h类里面增加一个生…...

分布式集群下如何做到唯一序列号
优质博文:IT-BLOG-CN 分布式架构下,生成唯一序列号是设计系统常常会遇到的一个问题。例如,数据库使用分库分表的时候,当分成若干个sharding表后,如何能够快速拿到一个唯一序列号,是经常遇到的问题。实现思…...

在 Vue 2 中使用 Axios 发起 POST 和 GET 请求
Axios 是一个基于 Promise 的 HTTP 客户端,用于浏览器和 node.js,它提供了一种非常方便的方式来发送异步 HTTP 请求。在 Vue 2 应用中,Axios 可以帮助我们轻松地与后端 API 进行通信。本文将介绍如何在 Vue 2 项目中引入 Axios,并…...

Linux内核初始化过程中加载TCP/IP协议栈
Linux内核初始化过程中加载TCP/IP协议栈 Linux内核初始化过程中加载TCP/IP协议栈,从start_kernel、kernel_init、do_initcalls、inet_init,找出Linux内核初始化TCP/IP的入口位置,即为inet_init函数。 Linux内核启动过程 之前的实验中我们设…...

Mysql树形结构表-查询所有子集数据
表结构,这里只是个例子,所有的树形结构表均可用: CREATE TABLE zhkt_course_chapter (id bigint NOT NULL COMMENT 唯一id,course_id bigint NOT NULL COMMENT 所属课程id,name varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general…...

Vue 3 Composition API进阶指南
在上一篇文章中,我们介绍了Vue 3的Composition API基础,包括如何使用setup函数、ref和reactive来创建响应式数据,以及使用watchEffect来监控数据变化。本文将继续深入探讨Composition API的高级用法,帮助你更好地理解和利用Vue 3的…...

C++学习,多继承
多继承,一个子类可以有多个父类,它继承了多个父类的特性。这种机制提供了强大的灵活性,但也带来了复杂性,特别是当涉及到基类中的同名成员(包括成员函数和变量)时。 C 类从多个类继承成员,语法如…...

苹果研究人员提出了一种新颖的AI算法来优化字节级表示以自动语音识别(ASR),并将其与UTF-8表示进行比较
端到端(E2E)神经网络已成为多语言自动语音识别(ASR)的灵活且准确的模型。然而,随着支持的语言数量增加,尤其是像中文、日语、韩语(CJK)这样大字符集的语言,输出层的大小显…...

2024年重磅报告!国内AI大模型产业飞速发展!
伴随人工智能技术的加速演进,AI 大模型已成为全球科技竞争的新高地、未来产业的新赛道、经济发展的新引擎,发展潜力大、应用前景广。近年来,我国高度重视人工智能的发展,将其上升为国家战略,出台一系列扶持政策和规划&…...

Sentinel 安装
一、下载jar包 下载地址:Releases alibaba/Sentinel GitHub 二、运行 将jar包放在任意非中文、不包含特殊字符的目录下,启动 启动命令:运行cmd 使用一下命令 java -Dserver.port8090 -Dcsp.sentinel.dashboard.serverlocalhost:8090 -D…...

大佬,简单解释下“嵌入式软件开发”和“嵌入式硬件开发”的区别
在开始前刚好我有一些资料,是我根据网友给的问题精心整理了一份「嵌入式的资料从专业入门到高级教程」, 点个关注在评论区回复“888”之后私信回复“888”,全部无偿共享给大家!!!首先,嵌入式硬…...

04 奇偶分家
题目: 代码: #include<iostream> using namespace std; #include<stdlib.h> #include<stdio.h>int main() {int N;cin>>N;int jicount0,oucount0;for(int i0;i<N;i){int temp;cin>>temp;if(temp%20){oucount;}else if…...

普通人秒变AI专家:李沐创业同款RAG微调实战,打造专属外卖评论大模型
8月14日晚上,李沐发布了一篇关于他创业一年的复盘文章《创业一年,人间三年》,引起了广泛关注。这篇文章中,李沐分享了从创业初期到现在的心路历程,许多读者读后都倍感激动。 创业之初,李沐的团队原本打算利用大语言模型(LLM)开发生产力工具。然而,在张一鸣的建议下,…...

微模块冷通道动环监控:智能化数据中心管理利器@卓振思众
在现代数据中心和机房管理中,微模块冷通道动环监控系统的引入,标志着对冷却和环境管理的新纪元。这一系统不仅提升了数据中心的运维效率,还对设备的安全性和稳定性提供了强有力的保障。本文将详细探讨微模块冷通道动环监控的功能和其在数据中…...

【Linux】进程调度与切换
【Linux】进程调度与切换 1. 基本概念2. 进程切换3. 进程调度3.1运行队列实现优先级设计3.2 处理效率问题3.3 活动队列与过期队列3.4 如何解决饥饿问题3.5 active指针和expired指针 1. 基本概念 竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个&am…...

SAM 2:分割图像和视频中的任何内容
文章目录 摘要1 引言2 相关工作3 任务:可提示视觉分割4 模型5 数据5.1 数据引擎5.2 SA-V数据集6 零样本实验6.1 视频任务6.1.1 提示视频分割6.1.2 半监督视频对象分割6.1.3 公平性评估6.2 图像任务7 与半监督VOS的最新技术的比较8 数据和模型消融8.1 数据消融8.2 模型架构消融…...

【免越狱】iOS任意版本号APP下载
下载地址 https://pan.quark.cn/s/570e928ee2c4 软件介绍 下载iOS旧版应用,简化繁琐的抓包流程。一键生成去更新IPA(手机安装后,去除App Store的更新检测)。 软件界面 使用方法 一、直接搜索方式 搜索APP,双击选…...

告别植物神经紊乱,这5种运动让你身心平衡,活力满满!♀️✨
Hey小伙伴们~👋 最近是不是感觉压力山大,晚上辗转反侧,白天又无精打采?😴😔 这可能是植物神经紊乱在悄悄作祟哦!别怕,今天就来给大家种草几个超有效的运动方式,帮你找回那…...

又一个iPhone时代开始
今年的苹果秋季发布会在昨晚召开了,今天早上我们也看到了很多相关的新闻。我猜你看完后的感觉可能是,这不过又是一次普普通通的参数升级。又是提升了百分之多少,又是增加了多少倍——非常简单的一些更新。比如说芯片升级了、相机的摄像头一会…...

在 CentOS 中永久关闭防火墙的步骤
在 CentOS 中永久关闭防火墙的步骤 在 CentOS 系统中,防火墙通常由 firewalld 服务管理。如果你希望在系统中永久关闭防火墙,可以按照以下步骤操作: 1. 停止防火墙服务 首先,你需要停止当前正在运行的防火墙服务。可以使用以下…...

【数据库】详解基本SQL语句用法
一、SELECTING DATA FROM TABLES【查询数据】 SELECT命令是表上所有查询的基础,因此给出它的完整描述以显示它的功能。在描述之后提供各种格式的示例。 1.1 整体描述 SELECT column1, column2, ... FROMtable1 [INNER | LEFT | RIGHT] JOIN table2 on conditions…...

R语言地理加权回归、主成份分析、判别分析等空间异质性数据分析
在自然和社会科学领域有大量与地理或空间有关的数据,这一类数据一般具有严重的空间异质性,而通常的统计学方法并不能处理空间异质性,因而对此类型的数据无能为力。以地理加权回归为基础的一系列方法:经典地理加权回归,…...

数学建模笔记—— 非线性规划
数学建模笔记—— 非线性规划 非线性规划1. 模型原理1.1 非线性规划的标准型1.2 非线性规划求解的Matlab函数 2. 典型例题3. matlab代码求解3.1 例1 一个简单示例3.2 例2 选址问题1. 第一问 线性规划2. 第二问 非线性规划 非线性规划 非线性规划是一种求解目标函数或约束条件中…...

JavaScript中的控制流语句:break、continue、return、throw
在JavaScript编程中,控制流语句是控制代码执行流程的重要工具。这些语句包括break、continue、return和throw,它们可以在循环、函数以及其他代码块中使用,以改变正常的执行顺序。下面我们将逐一探讨这些语句的用途和示例。 break break语句…...

移动通信为啥要用双极化天线?
❝本文简单介绍下移动通信为啥要用双极化天线及其简单概述。 移动通信为啥要用双极化天线? - RFASK射频问问❝本文简单介绍下移动通信为啥要用双极化天线及其简单概述。什么是极化?电磁波的极化通常是用其电场矢量的空间指向来描述:在空间某…...

C语言从头学59——学习头文件math.h(二)
继续学习头文件<math.h>,编号接续前文。 六、三角函数 math.h中的三角函数包括: acos():反余弦,参数范围-1至1,返回值double类型:0~PI asin():反正弦,参…...

Leetcode 3283. Maximum Number of Moves to Kill All Pawns
Leetcode 3283. Maximum Number of Moves to Kill All Pawns 1. 解题思路2. 代码实现 题目链接:3283. Maximum Number of Moves to Kill All Pawns 1. 解题思路 这一题坦率地说没有想到什么好的思路,因此只能非常暴力地按照题意进行了一下构造。 显然…...