目录
在计算机视觉领域,证件信息的自动识别是一个非常实用的技术方向。本文将详细介绍如何使用 OpenCV 库实现身份证号的自动识别系统,通过模板匹配的方法精准提取身份证上的数字信息。
项目概述
本项目旨在通过计算机视觉技术,自动识别身份证图片中的身份证号码。系统主要分为三个核心模块:自定义工具函数、模板图像预处理、身份证号定位与识别。整个流程基于 OpenCV 库实现,不需要复杂的深度学习框架,适合入门级学习者理解和实践。
技术栈:
- Python 3.x
- OpenCV (cv2) 库
- NumPy 库
一、核心工具函数实现
首先,我们需要实现两个核心工具函数:图像展示函数和轮廓排序函数,这两个函数将在整个项目中反复使用。
import cv2
import numpy as np
def cv_show(name, image):
"""
展示图像的通用函数
:param name: 窗口名称
:param image: 要展示的图像
"""
cv2.imshow(name, image)
cv2.waitKey(0) # 等待按键输入
cv2.destroyWindow(name) # 关闭当前窗口
def sort_contours(cnts, method='left-to-right'):
"""
对轮廓进行排序
:param cnts: 轮廓列表
:param method: 排序方式,可选值:'left-to-right'、'right-to-left'、'top-to-bottom'、'bottom-to-top'
:return: 排序后的轮廓列表和对应的边界框列表
"""
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 # 按y坐标排序
# 计算每个轮廓的边界框并排序
bounding_boxes = [cv2.boundingRect(c) for c in cnts]
(cnts, bounding_boxes) = zip(*sorted(zip(cnts, bounding_boxes),
key=lambda b: b[1][i],
reverse=reverse))
return cnts, bounding_boxes
函数说明:
cv_show()
:封装了 OpenCV 的图像显示功能,自动等待用户按键并关闭窗口sort_contours()
:根据指定方式对轮廓进行排序,这在处理数字序列时非常重要,确保数字顺序正确
二、模板图像预处理
模板匹配的核心是先建立数字模板库,我们需要从包含 0-9 数字的模板图像中提取每个数字的特征。
模版如下:
1. 模板图像二值化处理
# 读取模板图像
img = cv2.imread("shuzi.png")
cv_show('模板原图', img)
# 转换为灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv_show('模板灰度图', gray)
# 二值化处理(反色处理,使数字为白色,背景为黑色)
ref = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY_INV)[1]
cv_show('模板二值化图', ref)
运行结果如下:
处理说明:
- 首先读取包含 0-9 数字的模板图像
- 转换为灰度图以简化处理
- 应用反相二值化,将数字变为白色 (255),背景变为黑色 (0),便于后续轮廓检测
2. 提取并排序数字轮廓
# 寻找轮廓
_, refCnts, hierarchy = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 在原图上绘制轮廓
cv2.drawContours(img, refCnts, -1, (0, 255, 0), 2)
cv_show('带轮廓的模板图', img)
# 按从左到右顺序排序轮廓
refCnts = sort_contours(refCnts, 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)
# 显示每个提取的数字
cv_show(f'数字{i}', roi)
# 存储到字典中
digits[i] = roi
cv2.destroyAllWindows()
处理说明:
- 使用
cv2.findContours()
函数检测数字轮廓,只检测外轮廓 - 对轮廓进行从左到右排序,确保数字顺序正确
- 裁剪每个数字区域并调整为统一尺寸 (57x88),便于后续匹配
- 将处理好的数字模板存储在字典中,建立索引与数字的对应关系
三、身份证号识别流程
1. 身份证图像预处理
# 读取身份证图像
img = cv2.imread('./shenfen.jpg')
imgg = img.copy() # 保存副本用于后续绘制结果
cv_show('身份证原图', img)
# 转换为灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv_show('身份证灰度图', gray)
# 二值化处理
ref = cv2.threshold(gray, 120, 255, cv2.THRESH_BINARY_INV)[1]
cv_show('身份证二值化图', ref)
2. 定位身份证号区域
# 寻找身份证图像中的所有轮廓
_, refCnts, hierarchy = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 绘制所有轮廓,观察轮廓检测效果
a = cv2.drawContours(img.copy(), refCnts, -1, (0, 255, 0), 2)
cv_show('带轮廓的身份证', a)
# 筛选身份证号区域的轮廓
locs = []
for (i, c) in enumerate(refCnts):
# 计算轮廓的边界框
(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])
定位说明:
- 首先检测身份证图像中的所有外轮廓
- 根据身份证号的大致位置(通常在身份证下方)设置坐标筛选条件
- 对筛选出的轮廓按 x 坐标排序,确保数字从左到右排列
3. 提取并识别每个数字
output = []
# 遍历每个数字的位置信息
for (i, (gX, gY, gW, gH)) in enumerate(locs):
groupOutput = []
# 提取数字区域(适当扩展边界)
group = gray[gY-2:gY+gH+2, gX-2:gX+gW+2]
cv_show(f'数字区域{i}', group)
# 二值化处理(使用OTSU自动阈值)
group = cv2.threshold(group, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
cv_show(f'二值化数字{i}', group)
# 调整为与模板相同的尺寸
roi = cv2.resize(group, (57, 88))
cv_show(f'标准化数字{i}', roi)
# 模板匹配计算得分
scores = []
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(imgg, (gX-5, gY-5), (gX+gW+5, gY+gH+5), (0, 0, 255), 1)
cv2.putText(imgg, "".join(groupOutput), (gX, gY-15),
cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2)
output.extend(groupOutput)
# 显示最终结果
print("识别到的身份证号: {}".format("".join(output)))
cv_show("识别结果", imgg)
cv2.destroyAllWindows()
识别说明:
- 逐个提取身份证号区域的每个数字
- 对每个数字进行二值化和尺寸标准化,使其与模板保持一致
- 使用
cv2.matchTemplate()
进行模板匹配,计算与每个数字模板的匹配得分 - 选择得分最高的数字作为识别结果,并在原图上标记
- 最终将所有识别结果拼接成完整的身份证号
五、总结
本项目通过 OpenCV 实现了基于模板匹配的身份证号识别系统,主要流程包括:
- 自定义工具函数辅助图像处理
- 从模板图像中提取数字特征并建立模板库
- 对身份证图像进行预处理和轮廓分析
- 定位并提取身份证号区域的每个数字
- 通过模板匹配识别数字并输出结果
该方法不需要复杂的深度学习模型,实现简单且易于理解,适合作为计算机视觉入门实践项目。对于精度要求更高的场景,可以考虑结合深度学习方法(如 CNN)进行优化。