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

news2024/11/24 6:52:14

文章目录

  • 一、案例实现的整体思路
  • 二、代码实现
    • 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 = False
        i = 0
    
        if method == 'right-to-left' or method == 'bottom-to-top':
            reverse = True
        if method == 'top-to-bottom' or method == 'bottom-to-top':
            i = 1
        boundingBoxes = [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 cv2
    
    
    def cv_show(name, img):
        cv2.imshow(name, img)
        cv2.waitKey(0)
    
    
    def sort_contours(cnts, method='left-to-right'):
        reverse = False
        i = 0
    
        if method == 'right-to-left' or method == 'bottom-to-top':
            reverse = True
        if method == 'top-to-bottom' or method == 'bottom-to-top':
            i = 1
        boundingBoxes = [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.coloradmin.cn/o/2127802.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

http有什么方法升级成https?

&#x1f512; 获取与安装证书 JoySSL注册填写申请码230907即可领取免费申请资格https://www.joyssl.com/certificate/select/free.html?nid7 &#x1f4c4; 申请SSL证书 选择证书&#xff1a;首先需选择合适的SSL证书&#xff0c;如域名认证&#xff08;DV&#xff09;、公…

120页ppt丨集团公司战略规划内容、方法、步骤及战略规划案例研究

响应会员需求&#xff0c;晓零分享一份经典资料《120页ppt集团公司战略规划内容、方法、步骤及战略规划案例研究》&#xff0c;欢迎进入星球下载学习。 以下是对企业战略规划三个阶段八个步骤的详细解析&#xff1a; 一、阶段一&#xff1a;内外分析 项目启动和前期准备&…

Parallels Desktop 20 发布下载,macOS Sequoia 和 Windows 11 24H2 支持准备就绪

Parallels Desktop for Mac 20.0.0 (build 55653) - 在 Mac 上运行 Windows macOS Sequoia 和 Windows 11 24H2 支持准备就绪 请访问原文链接&#xff1a;https://sysin.org/blog/parallels-desktop/&#xff0c;查看最新版。原创作品&#xff0c;转载请保出处。 作者主页&a…

Java | Leetcode Java题解之第400题第N位数字

题目&#xff1a; 题解&#xff1a; class Solution {public int findNthDigit(int n) {int d 1, count 9;while (n > (long) d * count) {n - d * count;d;count * 10;}int index n - 1;int start (int) Math.pow(10, d - 1);int num start index / d;int digitInde…

wifiip地址可以随便改吗?wifi的ip地址怎么改变

对于普通用户来说&#xff0c;WiFi IP地址的管理和修改往往显得神秘而复杂。本文旨在深入探讨WiFi IP地址是否可以随意更改&#xff0c;以及如何正确地改变WiFi的IP地址。虎观代理小二将详细解释WiFi IP地址的基本概念、作用以及更改时需要注意的事项&#xff0c;帮助用户更好地…

欧盟《人工智能法案》的重点监管要求

文章目录 前言一、欧盟《人工智能法案》的重点监管要求(一)基于风险的监管路径1.具有不可接受风险的人工智能系统2.高风险人工智能系统3.有限风险与低风险人工智能系统(二)对高风险人工智能的监管要求1.针对高风险人工智能系统的要求2.针对高风险人工智能系统产业链参与者的…

shader 案例学习笔记之fract函数

fract函数 可以理解为模1取余&#xff0c;获取一个数的小数部分&#xff0c;如果参数是向量&#xff0c;那就是获取每个向量分量上的小数 案例一 #ifdef GL_ES precision mediump float; #endif// 渲染分辨率 uniform vec2 u_resolution; // 程序运行时间 uniform float u_ti…

【卷起来】VUE3.0教程-08-路由管理

在Vue中&#xff0c;我们可以通过vue-router路由管理页面之间的关系。 Vue Router是Vue.js的官方路由&#xff0c;它与Vue.js核心深度集成&#xff0c;让用Vue.js构建单页应用变得轻而易举。 &#x1f332; 在Vue中引入路由 安装路由 npm install --save vue-router 建立三个…

【C++登堂入室】类和对象(中)——类的6个默认成员函数

目录 一、类的6个默认成员函数 ​编辑二、构造函数 2.1 概念 2.2 特性 三、析构函数 3.1 概念 3.2 特性 四、拷贝构造函数 4.1 概念 4.2 特征 五、赋值运算符重载 5.1 运算符重载 5.2 赋值运算符重载 5.3 前置和后置重载 六、日期类的实现 七、const成员 八、…

气膜建筑:设备吊装口临时封闭的理想选择—轻空间

在设备吊装作业中&#xff0c;吊装口的临时封闭对于保障工作环境安全、设备保护及操作顺利至关重要。传统封闭方式&#xff0c;如钢结构或简易的盖板封闭&#xff0c;不仅耗时耗力&#xff0c;还可能影响施工效率。气膜建筑技术凭借其轻便、快速和高效的特点&#xff0c;为设备…

亚信安全亮相2024国家网安周主会场,多样活动助推行业新发展

9月9日至15日&#xff0c;2024年国家网络安全宣传周在全国范围内统一开展。本届网安周以“网络安全为人民&#xff0c;网络安全靠人民”为主题&#xff0c;亚信安全网安周系列活动在全国30多个城市全面展开&#xff0c;通过线下展览、专题论坛和网络安全知识宣讲等多种形式&…

【软件方案】大屏可视化智能展示平台解决方案(word原件完整版)

构建综合大屏可视化展示平台&#xff0c;旨在整合各业务板块&#xff0c;打造统一大数据分析引擎。此平台将深度融合数据驾驶舱与智慧调度系统&#xff0c;实现对企业运营的全面洞察与高效指挥。我们深入钻研客户信息数据&#xff0c;秉承“大数据”精髓&#xff0c;推动业务模…

【测试八股】软件测试面试八股文

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 以下是软件测试相关的面试题及答案&#xff0c;希望对各位能有帮助&#xff01; 1、测试分为哪几个阶段? 一般来说分为5个阶段&#xff1a;单元测试、集成测试…

警惕!血糖升高初期,这10大微妙信号你捕捉到了吗?

在这个快节奏的时代&#xff0c;饮食不规律、缺乏运动等生活习惯悄然间让高血糖这一“隐形杀手”潜伏在我们身边。然而&#xff0c;高血糖并非悄无声息&#xff0c;它在早期往往会通过一系列微妙却重要的身体信号向我们发出警告。今天&#xff0c;就让我们一同揭开血糖高早期的…

【Unity错误】No cloud project ID was found by the Analytics SDK

在编译默认的URP 2D项目时&#xff0c;出现这样一个错误&#xff1a;No cloud project ID was found by the Analytics SDK. This means Analytics events will not be sent. Please make sure to link your cloud project in the Unity editor to fix this problem. 原因&…

yolov5明厨亮灶检测系统,厨师帽-口罩检测,带pyqt界面-可检测图片和视频,支持中文标签,检测接口已封装好并优化,代码可读性强!

明厨亮灶检测系统是一个专门用于餐饮业厨房安全监管的智能系统。该系统结合了先进的计算机视觉技术&#xff0c;尤其是使用YOLOv5模型进行厨师帽和口罩的实时检测&#xff0c;并通过PyQt5构建了一个用户友好的图形界面。该系统不仅能够检测图片和视频中的目标&#xff0c;而且支…

如何看待 IBM 中国研发部裁员?

文章目录 引言背景趋势与影响人才发展对 IT 人才市场的影响IT 从业者积极应对全球化挑战 产业发展IT 产业的应对策略提升核心竞争力 结语 引言 近日&#xff0c;IBM 中国宣布撤出在华两大研发中心&#xff0c;引发了 IT 行业对于跨国公司在华研发战略的广泛讨论。这一决定不仅…

共享打印机无法连多种错误代码原因分析及解决方法

日常办公和生活中&#xff0c;打印机是不可或缺的重要设备。然而&#xff0c;在添加共享或使用共享打印机过程中&#xff0c;经常会遇各种问题。然后我们在添加共享打印机遇到最多的向种错误&#xff1a;0x0000011b、0x000004005、0x000006d9、0x00000040等等&#xff0c;然后这…

ggplot2 缩小的/一般长度的、带箭头的坐标轴 | R语言

1. 效果图 左侧为DimPlot2()效果图。 右侧为DimPlot()效果图&#xff0c;原图。 2. 代码 axis.line element_line(arrow arrow(type "open", length unit(0.3, "cm"))), 其中: type"open"表示是开放箭头&#xff0c;type“closed” 表示是…

云手机哪一款好用?手游专用云手机一览!VMOS云手机

云手机&#xff0c;顾名思义&#xff0c;是一台运行在云端服务器上的虚拟手机。它具备传统手机的所有功能&#xff0c;但无需实际设备支持运算和存储。所有的计算、存储以及应用运行都由云端服务器承担。用户只需通过浏览器或客户端访问云手机的操作界面&#xff0c;就可以像操…