《OpenCV计算机视觉》—— 身份证号码识别案例

news/2024/9/19 18:41:58/ 标签: 计算机视觉, 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()
    

http://www.ppmy.cn/news/1524183.html

相关文章

containerd二进制安装

文章目录 安装版本&#xff08;在2024年9月10日是新版&#xff09;方式1&#xff1a;yum安装&#xff08;不推荐&#xff09;设置主机名设置IP获取阿里YUM源查询containerd安装验证与启动服务测试命令 方式2&#xff1a;二进制安装&#xff08;推荐&#xff09;安装之前先了解c…

每周心赏|教师节“AI大礼包”:3款教学神器让你AI不释手

教师节“AI大礼包”真的来了&#xff01; 家人们谁懂啊&#xff0c;当学生时不想上课&#xff0c;当老师后不想上班…… 3款超会整活的教师必备AI神器&#xff0c;终于让我给挖到了&#xff1a;一键拥有金牌名师教学经验&#xff0c;助力撰写教案、高效赋能学生、总结工作成果…

流浪地球发动机

代码来自此处 if __name__ __main__:# 发动机的总个数, 计划手动启动的发动机总个数n, e map(int, input().split())# 记录每个发动机的最终启动时刻, 初始化为极大值&#xff0c;方便后面取最早启动时刻launches [1001] * nfor _ in range(e):# 发动机的手动启动时刻, 发动…

通信工程学习:什么是FDM频分复用、TDM时分复用、WDM波分复用、CDM码分复用

FDM频分复用、TDM时分复用、WDM波分复用、CDM码分复用 FDM频分复用、TDM时分复用、WDM波分复用、CDM码分复用是通信领域中常见的四种复用技术&#xff0c;它们各自具有不同的特点和应用场景。以下是对这四种复用技术的详细解释&#xff1a; 一、FDM频分复用&#xff08;Frequ…

Vue 中的 Vuex:全面解析与使用教程

什么是 Vuex&#xff1f; Vuex 是 Vue.js 官方提供的状态管理模式&#xff0c;专为 Vue.js 应用设计。它通过集中式存储管理应用中所有的组件状态&#xff0c;允许组件之间更方便地共享数据&#xff0c;并提供了一套规则来确保状态以可预测的方式发生变化。Vuex 对大型应用尤其…

Inspector里面可以编辑的变量相关

1.私有和保护变量无法在Inspector中编辑 2.给私有和保护变量加个特性[SerializeField]&#xff08;强制序列化字段特性&#xff09;就可以在inspector中看到和修改了 序列化就是把一个对象保存到一个文件或数据库字段中去 3.公共的也不让其显示编辑 给变量前加特性[HideInIn…

【Leetcode学习笔记】路径总和

【题目描述】给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径&#xff0c;这条路径上所有节点值相加等于目标和 targetSum 。如果存在&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 输入&#x…

Linux 信息安全:构建坚固的防御体系

摘要&#xff1a; 本文围绕 Linux 信息安全展开。阐述了 Linux 在信息技术中的重要地位&#xff0c;强调信息安全的重要性以及 Linux 信息安全面临复杂网络环境、演变攻击手段与内部威胁等挑战。详细介绍了 Linux 系统的安全架构与机制&#xff0c;包括用户与权限管理、文件系统…

HarmonyOS开发实战( Beta5.0)自定义组件冻结功能规范

自定义组件处于非激活状态时&#xff0c;状态变量将不响应更新&#xff0c;即Watch不会调用&#xff0c;状态变量关联的节点不会刷新。通过freezeWhenInactive属性来决定是否使用冻结功能&#xff0c;不传参数时默认不使用。支持的场景有&#xff1a;页面路由&#xff0c;TabCo…

vue3安装sass时报错:Embedded Dart Sass couldn‘t find the embedded compiler executable

vue3安装sass&#xff1a; npm install sass --save-dev 引用 <template><div class"c1"><h1>hello</h1></div> </template> <style lang"scss">.c1{background-color:red;h1{color:yellow;}} </style>报…

Nestjs微服务简单案例

相信大家&#xff0c;来看这篇博客&#xff0c;就应该知道微服务的概念。只是不太知道实用方法而已。下面我通过最简单的案例&#xff0c;来教会大家。 首先这是我的项目目录&#xff1a; nestwfw/ ├── app/ ├── project-microsericesapp 是web服务&#xff0c;用来接收…

HAL库学习目录查询表

日期内容2024.09.11基于STM32C8T6的CubeMX&#xff1a;HAL库点亮LED2024.09.11STMCuBeMX新建项目的两种匪夷所思的问题2024.09.11STMCubeMX文件下载后会出现其他项目无法下载的问题

如何在Vue实例上挂载自己定义的工具类

在实际的Vue开发中&#xff0c;我们经常需要在多个组件之间共享一些工具函数或类&#xff0c;比如格式化日期、处理字符串、操作数组等。这些工具类可以封装到一个独立的模块中&#xff0c;然后挂载到Vue实例上&#xff0c;方便在任何地方使用。本文将详细介绍如何在Vue实例上挂…

如何利用免费工具轻松设计出专业Logo?

Logo 作为品牌的象征和视觉核心&#xff0c;承载了品牌的价值和理念。无论是创业公司还是个人品牌&#xff0c;拥有一个独特的 Logo 都显得尤为重要。然而&#xff0c;设计一个专业的 Logo 通常需要高昂的设计费用&#xff0c;许多人因此望而却步。幸运的是&#xff0c;随着互联…

【平渊科技】项目拆解:小说推文项目 | 经验分享

目录 项目介绍 直接上操作教程 第一步&#xff1a;选文 第二步&#xff1a;改文 第三步&#xff1a;配音 第四步&#xff1a;剪辑 项目介绍 小说推文项目&#xff0c;可以说是市场上最常见的项目了。 我是去年十月份开始接触的&#xff0c;接触的比较晚了&#xff0c;我…

【H2O2|全栈】更多关于HTML(1)HTML进阶(一)

目录 HTML进阶知识 前言 准备工作 标签的扩展&#xff08;一&#xff09; 本文中的标签在什么位置使用&#xff1f; title标签 meta标签 name viewport referrer http-equiv charset content link标签 实际案例 可视部分 代码分析 其他标签 base标签 styl…

【Hot100算法刷题集】哈希-03-最长连续序列(含排序、哈希、并查集法未正确使用哈希表导致算法效率降低的分析)

&#x1f3e0;关于专栏&#xff1a;专栏用于记录LeetCode中Hot100专题的所有题目 &#x1f3af;每日努力一点点&#xff0c;技术变化看得见 题目转载 题目描述 &#x1f512;link->题目跳转链接 给定一个未排序的整数数组 nums &#xff0c;找出数字连续的最长序列&#x…

hiricacp 连接池校验机制

一、背景 项目发生告警&#xff0c;但是并没有影响业务&#xff0c;看了下日志&#xff0c;红框里面有循环调用了3次 &#xff0c;一直以为是外部的重试在重试&#xff0c;但是外部确没有重试记录&#xff0c;就深扒了代码 二、想法 我知道hikaricp获取连接之后会校验连接的有…

一文读懂在线学习凸优化技术

一文读懂在线学习凸优化技术 在当今的数据驱动时代&#xff0c;机器学习算法已成为解决复杂问题的关键工具。在线学习凸优化作为机器学习中的一项核心技术&#xff0c;不仅在理论研究上具有重要意义&#xff0c;还在实际应用中展现出巨大的潜力。本文将深入浅出地介绍在线学习…

【CanMV K230 AI视觉】 人体检测

【CanMV K230 AI视觉】 人体检测 人体检测 动态测试效果可以去下面网站自己看。 B站视频链接&#xff1a;已做成合集 抖音链接&#xff1a;已做成合集 人体检测 人体检测是判断摄像头画面中有无出现人体&#xff0c;常用于人体数量检测&#xff0c;人流量监控以及安防监控等。…