《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,双击选…...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...

Unity3D中Gfx.WaitForPresent优化方案
前言 在Unity中,Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染(即CPU被阻塞),这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案: 对惹,这里有一个游戏开发交流小组&…...
R语言AI模型部署方案:精准离线运行详解
R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...

什么是Ansible Jinja2
理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...

Linux nano命令的基本使用
参考资料 GNU nanoを使いこなすnano基础 目录 一. 简介二. 文件打开2.1 普通方式打开文件2.2 只读方式打开文件 三. 文件查看3.1 打开文件时,显示行号3.2 翻页查看 四. 文件编辑4.1 Ctrl K 复制 和 Ctrl U 粘贴4.2 Alt/Esc U 撤回 五. 文件保存与退出5.1 Ctrl …...
苹果AI眼镜:从“工具”到“社交姿态”的范式革命——重新定义AI交互入口的未来机会
在2025年的AI硬件浪潮中,苹果AI眼镜(Apple Glasses)正在引发一场关于“人机交互形态”的深度思考。它并非简单地替代AirPods或Apple Watch,而是开辟了一个全新的、日常可接受的AI入口。其核心价值不在于功能的堆叠,而在于如何通过形态设计打破社交壁垒,成为用户“全天佩戴…...

手机平板能效生态设计指令EU 2023/1670标准解读
手机平板能效生态设计指令EU 2023/1670标准解读 以下是针对欧盟《手机和平板电脑生态设计法规》(EU) 2023/1670 的核心解读,综合法规核心要求、最新修正及企业合规要点: 一、法规背景与目标 生效与强制时间 发布于2023年8月31日(OJ公报&…...
嵌入式常见 CPU 架构
架构类型架构厂商芯片厂商典型芯片特点与应用场景PICRISC (8/16 位)MicrochipMicrochipPIC16F877A、PIC18F4550简化指令集,单周期执行;低功耗、CIP 独立外设;用于家电、小电机控制、安防面板等嵌入式场景8051CISC (8 位)Intel(原始…...

pgsql:还原数据库后出现重复序列导致“more than one owned sequence found“报错问题的解决
问题: pgsql数据库通过备份数据库文件进行还原时,如果表中有自增序列,还原后可能会出现重复的序列,此时若向表中插入新行时会出现“more than one owned sequence found”的报错提示。 点击菜单“其它”-》“序列”,…...