基于 OpenCV 的身份证号码识别系统详解

文章目录


一、核心工具函数解析

首先,我们来看系统依赖的工具函数,这些函数为整个识别过程提供了基础支持:

python 复制代码
import cv2

def sort_contours(cnts, method='left-to-right'):
    """
    对轮廓进行排序
    
    参数:
        cnts: 轮廓列表
        method: 排序方法,支持以下四种:
            'left-to-right' - 从左到右(默认)
            'right-to-left' - 从右到左
            'top-to-bottom' - 从上到下
            'bottom-to-top' - 从下到上
    
    返回值:
        cnts: 排序后的轮廓列表
        boundingBoxes: 对应的边界框列表
    """
    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]
    
    # 根据边界框的x或y坐标进行排序
    (cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes),
                                        key=lambda b: b[1][i], reverse=reverse))
    return cnts, boundingBoxes


def resize(image, width=None, height=None, inter=cv2.INTER_AREA):
    """
    按比例调整图像大小
    
    参数:
        image: 输入图像
        width: 目标宽度(可选)
        height: 目标高度(可选)
        inter: 插值方法,默认为cv2.INTER_AREA
    
    返回值:
        resized: 调整后的图像
    """
    dim = None
    (h, w) = image.shape[:2]
    
    # 如果未指定尺寸,直接返回原图
    if width is None and height is None:
        return image
    
    # 根据指定的宽度或高度计算缩放比例
    if width is None:
        r = height / float(h)
        dim = (int(w * r), height)
    else:
        r = width / float(w)
        dim = (width, int(h * r))
    
    # 执行缩放操作
    resized = cv2.resize(image, dim, interpolation=inter)
    return resized


def cv_show(name, img):
    """
    显示图像窗口
    
    参数:
        name: 窗口名称
        img: 要显示的图像
    """
    cv2.imshow(name, img)
    cv2.waitKey(0)


# 枚举函数示例
for i, j in enumerate([5, 6, 7]):
    print(i, j)

# 列表推导式示例
a = [i for i in range(5)]
print(a)

# 字符串连接示例
a = ''.join(['4', '0', '0', '0'])
print(a)

关键函数解析:

  1. sort_contours() :此函数的核心功能是依据轮廓的空间位置进行排序。在身份证号码识别中,我们需要确保数字按照正确的阅读顺序(从左到右)被处理。函数通过cv2.boundingRect()获取每个轮廓的边界框,然后根据指定的排序方法对边界框的x或y坐标进行排序。

  2. resize() :这是一个灵活的缩放函数,可以根据宽度或高度按比例调整图像大小,保持原始宽高比不变。cv2.INTER_AREA插值方法特别适合缩小图像,能够减少锯齿效应。

  3. cv_show() :简化了图像显示流程,调用cv2.waitKey(0)使得窗口保持打开直到用户按键。

二、数字模板提取模块

模板匹配的前提是准备好标准的数字模板。以下是模板提取的完整实现:

python 复制代码
import cv2
import numpy as np
from myutils import sort_contours, cv_show


def process_template():
    """处理模板图像,提取数字模板"""
    
    # 读取模板图像文件
    img = cv2.imread("ID_Card_tem.png")
    cv_show('Template Original', img)

    # 将图像转换为灰度图
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    # 对灰度图进行二值化处理
    ref = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY_INV)[1]
    cv_show('Template Binary', ref)

    # 查找二值图像中的所有轮廓
    _, contours, hierarchy = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    
    # 绘制所有轮廓
    img_copy = img.copy()
    cv2.drawContours(img_copy, contours, -1, (0, 255, 0), 2)
    cv_show('Template Contours', img_copy)

    # 对轮廓进行从左到右排序
    refCnts = sort_contours(contours, method='left-to-right')[0]
    
    # 创建字典存储数字模板
    digits = {}
    for i, c in enumerate(refCnts):
        # 获取轮廓的边界矩形
        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))
        # 黑白反转(使数字为白色,背景为黑色)
        roi = cv2.bitwise_not(roi)
        # 存储到字典
        digits[i] = roi

    print(f"Extracted {len(digits)} digit templates")
    return digits

模板提取关键步骤:

  1. 图像预处理 :首先将彩色模板转换为灰度图像,然后使用固定阈值(150)进行二值化。THRESH_BINARY_INV参数表示反色二值化,使数字区域变为白色,背景变为黑色。

  2. 轮廓检测cv2.findContours()函数检测所有外部轮廓。RETR_EXTERNAL只检测最外层轮廓,CHAIN_APPROX_SIMPLE使用简单的轮廓近似,减少点的数量。

  3. 轮廓排序 :使用自定义的sort_contours()函数确保数字按正确顺序排列。这对于建立索引(0对应数字0,1对应数字1等)至关重要。

  4. 模板标准化:将每个数字裁剪出来,统一缩放到57×88像素,并进行黑白反转,为后续的模板匹配做准备。

三、身份证号码识别模块

有了数字模板后,就可以识别实际身份证图像中的号码:

python 复制代码
def recognize_id_card(digits):
    """识别身份证图像中的号码"""
    
    # 读取身份证图像
    img = cv2.imread("ID_Card.jpg")
    original_img = img.copy()
    cv_show('ID Card Original', img)

    # 灰度化处理
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    cv_show('ID Card Gray', gray)

    # 二值化处理
    binary = cv2.threshold(gray, 120, 255, cv2.THRESH_BINARY_INV)[1]
    cv_show('ID Card Binary', binary)

    # 查找所有轮廓
    _, contours, hierarchy = cv2.findContours(binary.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    
    # 绘制所有轮廓
    img_with_contours = img.copy()
    cv2.drawContours(img_with_contours, contours, -1, (0, 255, 0), 2)
    cv_show('All Contours', img_with_contours)

    # 筛选身份证号码区域
    locs = []
    for i, c in enumerate(contours):
        x, y, w, h = cv2.boundingRect(c)
        # 根据位置筛选(需要根据实际图像调整参数)
        if (y > 330 and y < 360) and x > 220:
            locs.append((x, y, w, h))

    # 按x坐标排序
    locs = sorted(locs, key=lambda x: x[0])
    
    # 如果没有找到数字区域,输出调试信息
    if len(locs) == 0:
        print("Error: No ID number region found")
        for i, c in enumerate(contours):
            x, y, w, h = cv2.boundingRect(c)
            print(f"Contour{i}: x={x}, y={y}, w={w}, h={h}")
        return

    print(f"Found {len(locs)} digit regions")

    # 识别每个数字区域
    output = []
    for i, (gX, gY, gW, gH) in enumerate(locs):
        # 裁剪数字区域
        group = gray[gY - 2:gY + gH + 2, gX - 2:gX + gW + 2]
        
        # 使用OTSU方法进行自适应二值化
        group = cv2.threshold(group, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
        
        # 调整到模板大小
        roi = cv2.resize(group, (57, 88))
        
        # 模板匹配
        scores = []
        for digit, digitROI in digits.items():
            result = cv2.matchTemplate(roi, digitROI, cv2.TM_CCOEFF)
            _, score, _, _ = cv2.minMaxLoc(result)
            scores.append(score)
        
        # 选择最佳匹配
        digit_index = np.argmax(scores)
        groupOutput = str(digit_index)
        output.append(groupOutput)
        
        # 在图像上绘制结果
        cv2.rectangle(original_img, (gX - 5, gY - 5),
                      (gX + gW + 5, gY + gH + 5), (0, 0, 255), 2)
        cv2.putText(original_img, groupOutput, (gX, gY - 15),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2)

    # 组合识别结果
    id_number = "".join(output)
    print(f"识别到的身份证号码: {id_number}")
    
    # 显示最终结果
    cv2.putText(original_img, "".join(groupOutput), (gX, gY - 15),
                cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2)
    output.extend(groupOutput)
    
    cv_show('Recognition Result', original_img)
    return id_number

识别过程核心环节:

  1. 预处理与轮廓检测:与模板处理类似,但对身份证图像使用较低的阈值(120)进行二值化,以适应实际图像中可能存在的亮度变化。

  2. 区域筛选 :这是识别成功的关键。代码通过位置条件(y > 330 and y < 360) and x > 220筛选出身份证号码区域。实际应用中,这些参数需要根据具体图像进行调整。

  3. 自适应二值化 :对于裁剪出的每个数字区域,使用THRESH_OTSU方法进行自适应阈值处理。OTSU算法能自动计算最佳阈值,特别适合光照不均的图像。

  4. 模板匹配 :使用cv2.matchTemplate()函数将当前数字区域与所有模板进行比较。TM_CCOEFF(相关系数匹配)方法计算模板与图像区域的相关系数,值越大表示匹配度越高。

  5. 结果可视化:识别过程中,在原始图像上用红色矩形框标出每个数字区域,并在上方显示识别结果,提供直观的反馈。

相关推荐
笨鸟先飞,勤能补拙4 小时前
AI 赋能网络安全:技术全景、成熟度评估与实战案例
人工智能·python·安全·web安全·网络安全·sqlite·github
长和信泰光伏储能5 小时前
京津冀光伏发电:绿色能源的未来之路
python·能源
江畔柳前堤5 小时前
信号的本质——从模拟波形到AI中的数据张量
人工智能·深度学习·机器学习·计算机视觉·数据挖掘·语音识别
浦信仿真大讲堂5 小时前
从重复操作到自动化闭环:如何让 CST 与 Python 真正协同起来
python·自动化·cst·仿真软件·达索软件
Gu Gu Study5 小时前
ScoutLoop开放域深度研究引擎(agent的初步设计想法)
人工智能·python
卷无止境6 小时前
写代码这件事,到底该讲究点什么?
后端·python
卷无止境6 小时前
循环复杂度到底在算什么,Python 代码怎么才能写得让人一看就懂
后端·python
lpfasd1236 小时前
MediaCrawler 项目深度分析
chrome·python·chrome devtools
江畔柳前堤7 小时前
系统的本质——从LTI系统到神经网络
人工智能·深度学习·神经网络·机器学习·计算机视觉·数据挖掘·语音识别
Dxy12393102167 小时前
Python项目打包成EXE完整教程(PyInstaller实战避坑)
开发语言·python