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

相关推荐
CaracalTiger5 小时前
什么是Clawdbot?Clawdbot下载、安装、配置教程(最新版Moltbot)
python·编辑器·aigc·idea·ai编程·intellij idea·agi
WJX_KOI9 小时前
Open Notebook 一个开源的结合AI的记笔记软件
python
0思必得010 小时前
[Web自动化] 反爬虫
前端·爬虫·python·selenium·自动化
2301_8223827610 小时前
Python上下文管理器(with语句)的原理与实践
jvm·数据库·python
喵手11 小时前
Python爬虫实战:从零搭建字体库爬虫 - requests+lxml 实战采集字体网字体信息数据(附 CSV 导出)!
爬虫·python·爬虫实战·零基础python爬虫教学·csv导出·采集字体库数据·字体库字体信息采集
2301_7903009611 小时前
Python深度学习入门:TensorFlow 2.0/Keras实战
jvm·数据库·python
管牛牛11 小时前
图像的卷积操作
人工智能·深度学习·计算机视觉
程序员敲代码吗12 小时前
用Python生成艺术:分形与算法绘图
jvm·数据库·python