一、cv.threshold()
该函数是openCV中永远忽图像二值化的核心函数,其完整签名为:
ret,dst = cv2.threshold(src,thresh,maxval,type)
参数说明:
src :输入图像(图像为灰度图或者单通道的图像) 一般都是BGR2GRAY图像: cv2.cvtColor(BGR图像,cv2.COLOR_BGR2GRAY)
thresh:阈值(如10)
maxval:当像素值超过阈值时的输出值(如255)
type:阈值类型(如:cv2.THRESH_BINARY_INV)
返回值
ret: 实际使用的阈值 (可能与输入阈值不同)
dst: 处理后的二值图像
具体参数解析:
cv2.THRESH_BINARY_INV:
作用:反二值处理,将像素大于thresh阈值的点设为0【黑色】,,小于等于阈值的点设为maxval255【白色】
数学表达式:
-
示例
(阈值thresh: 10):
- 像素值 > 10 → 输出 0(黑色)。
- 像素值 ≤ 10 → 输出 255(白色)。
-
应用场景:背景减除、噪声抑制、特征提取等。
二、cv2.findContours()
1.函数基础定义方式
cv.findContours 是openCV中的用于检测图像轮廓的核心函数
contours,hierarchy = cv2.findContours(image,mode,method)
参数说明:
image :输入图像(需要二值化图像或者单通道灰度图)
mode:轮廓检索模式
method:轮廓近似方法
返回值:
contours:轮廓列表,每个元素为一个轮廓点集
hierarchy:轮廓层次结构信息
2. 具体参数解析
-
ref.copy():- 作用:复制输入图像
ref,避免原图被修改(cv2.findContours会修改输入图像)12。
- 作用:复制输入图像
- cv2.RETR_EXTERNAL`:
- 作用:仅检测最外层轮廓(不包含嵌套轮廓)24。
- 应用场景:适用于目标检测、形状分析等场景。
- cv2.CHAIN_APPROX_SIMPLE`:
- 作用:压缩轮廓点集,仅保留关键点(如矩形只需4个点)24。
- 优点:减少数据量,提高处理效率。
3. 完整解析
contours, hierarchy = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
- **
ref.copy()**:复制输入图像ref,避免原图被修改。 - **
cv2.RETR_EXTERNAL**:仅检测最外层轮廓(不包含嵌套轮廓)。 - **
cv2.CHAIN_APPROX_SIMPLE**:压缩轮廓点集,仅保留关键点(如矩形只需4个点)。 - 返回值:
contours:轮廓列表,每个元素为一个轮廓点集(如矩形轮廓为4个点)。hierarchy:轮廓层次结构信息(可选)。
4. 实际效果
- 输入:二值图像
ref(如通过cv2.threshold处理后的图像)。 - 输出:
contours:包含所有最外层轮廓的点集列表。hierarchy:描述轮廓的父子关系(可选)。
- 应用场景:目标检测、形状分析、物体分割等。
三、cv2.drawContours()
1.函数定义方式
cv2.drawContours()是openCV中用于图像上绘制轮或的函数
cv2.drawContours(image, contours, contourIdx, color, thickness=None, lineType=None, hierarchy=None, maxLevel=None, offset=None)
参数说明:
-
image:目标图像,一般都是原图(在该图像上直接绘制轮廓)。 -
contours:轮廓列表(由cv2.findContours返回)。 -
contourIdx:要绘制的轮廓索引(-1 表示绘制所有轮廓)。 -
color:轮廓颜色(BGR 格式)【如(0,0,255)红色】。 -
thickness:轮廓线宽(就是红色线的粗细)(负值表示填充轮廓)。 -
lineType:线型(默认为 8 连接)。 -
hierarchy:轮廓层次结构(可选)。 -
maxLevel:绘制的轮廓层次深度(可选)。 -
offset:轮廓偏移量(可选)。
2. 具体参数解析
cv2.drawContours(img, refCnts, -1, (0,0,255), 3)
- **
img**:目标图像(在该图像上直接绘制轮廓)。 - **
refCnts**:轮廓列表(由cv2.findContours返回)。 - **
-1**:绘制所有轮廓(contourIdx为负值表示绘制所有轮廓)。 - **
(0,0,255)**:轮廓颜色(BGR 格式,红色)。 - **
3**:轮廓线宽(像素值)。
3. 实际效果
- 输入:目标图像
img和轮廓列表refCnts。 - 输出:在
img上绘制所有轮廓(红色,线宽 3 像素)。 - 应用场景:目标检测、形状分析、物体分割等。
四、cv2.boundingRect(c)
1、函数的定义方式:
cv2.boundingRect()适应openCV中用于计算轮廓的最小外接矩形的函数
(x,y,w,h)= cv2.boundingRect(contour)
2、参数:contour (轮廓点击说白就是一个一个的轮廓)
返回值:(x,y,w,h),其中:
(x,y)矩形的左上角坐标
w:矩形的宽度
h;矩形的高度
列表循环遍历
boundingBoxes = [cv2.boundingRect(c) for c in 轮廓列表]
作用:遍历轮廓列表 轮廓列表,对每个轮廓 c 计算最小外接矩形,并将结果存储在 boundingBoxes 列表中。
- 返回值:
boundingBoxes是一个包含多个(x, y, w, h)元组的列表,每个元组对应一个轮廓的最小外接矩形。
$$
$$
3. 实际效果
- 输入:轮廓列表(由
cv2.findContours返回)。 - 输出:矩形列表,每个矩形描述一个轮廓的边界框。
- 应用场景:目标检测、形状分析、物体分割等。
4、完整实例
import cv2
import numpy as np
# 创建示例图像(灰度图)
img = np.zeros((100, 100), dtype=np.uint8)
cv2.rectangle(img, (10, 10), (40, 40), 255, -1) # 绘制白色矩形
# 查找轮廓
contours, _ = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 计算边界框
boundingBoxes = [cv2.boundingRect(c) for c in contours]
print("边界框列表:", boundingBoxes)
5. 注意事项
- 输入要求:轮廓列表必须由
cv2.findContours返回,且图像需为二值图(如通过cv2.threshold处理后的图像)。 - 坐标系:
(x, y)为矩阵左上角坐标,坐标系原点在图像左上角。 - 目标中心:矩形中心为
(x+w//2, y+h//2)。
详细解析:
zip(*sorted(zip(cnts, boundingBoxes), key=lambda b: b[1][i], reverse=reverse)) 的含义
-
函数基础
zip(*sorted(zip(cnts, boundingBoxes), key=lambda b: b[1][i], reverse=reverse)) 是 Python 中用于对轮廓和边界框进行排序的复合操作,结合了 zip、sorted 和 lambda 函数。其核心作用是根据边界框的特定维度(如 x 或 y 坐标)对轮廓和边界框进行同步排序。
-
参数解析
(cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes), key=lambda b: b[1][i], reverse=reverse))
zip(cnts, boundingBoxes):
作用:将轮廓列表 cnts 和边界框列表 boundingBoxes 打包成元组对,形成一个新的列表 [(cnt1, bbox1), (cnt2, bbox2), ...]。 示例:
cnts = [c1, c2, c3]
boundingBoxes = [(x1, y1, w1, h1), (x2, y2, w2, h2), (x3, y3, w3, h3)]
打包后:
[(c1, (x1, y1, w1, h1)), (c2, (x2, y2, w2, h2)), (c3, (x3, y3, w3, h3))]
sorted(..., key=lambda b: b[1][i], reverse=reverse):
作用:对打包后的元组对进行排序,排序依据是边界框 bbox 的第 i 个元素(如 x 或 y 坐标)。 参数说明: key=lambda b: b[1][i]:b 是元组对 (cnt, bbox),b[1] 是边界框 (x, y, w, h),b[1][i] 是边界框的第 i 个元素(如 x 或 y)。 reverse=reverse:是否降序排序(True 降序,False 升序)。 示例(按 x 坐标升序排序): python Copy Code
输入:
[(c1, (x1, y1, w1, h1)), (c2, (x2, y2, w2, h2)), (c3, (x3, y3, w3, h3))]
排序后:
[(c1, (x1, y1, w1, h1)), (c2, (x2, y2, w2, h2)), (c3, (x3, y3, w3, h3))]
假设 x1 < x2 < x3
zip(*...):
作用:将排序后的元组对 解包成两个独立列表,分别对应轮廓和边界框。 示例:
# 排序后:[(c1, (x1, y1, w1, h1)), (c2, (x2, y2, w2, h2)), (c3, (x3, y3, w3, h3))]
# 解包后:cnts = [c1, c2, c3]
# boundingBoxes = [(x1, y1, w1, h1), (x2, y2, w2, h2), (x3, y3, w3, h3)]
- 实际效果 输入:轮廓列表 cnts 和边界框列表 boundingBoxes。 输出:按边界框的第 i 个元素(如 x 或 y 坐标)排序后的轮廓和边界框列表。 应用场景:目标检测、形状分析、物体分割等,确保轮廓和边界框按特定顺序排列(如从左到右、从上到下)。
- 完整示例
3. import cv2
import numpy as np
# 创建示例图像(灰度图)
img = np.zeros((100, 100), dtype=np.uint8)
cv2.rectangle(img, (10, 10), (40, 40), 255, -1) # 绘制白色矩形
cv2.rectangle(img, (60, 60), (90, 90), 255, -1) # 绘制白色矩形
# 查找轮廓
contours, _ = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 计算边界框
boundingBoxes = [cv2.boundingRect(c) for c in contours]
# 按 x 坐标升序排序(i=0)
i = 0 # 0 表示 x 坐标
reverse = False # 升序
contours, boundingBoxes = zip(*sorted(zip(contours, boundingBoxes),
key=lambda b: b[1][i],
reverse=reverse))
print("排序后的轮廓:", contours)
print("排序后的边界框:", boundingBoxes)
输出:
排序后的轮廓: (<轮廓1>, <轮廓2>)
排序后的边界框: ((10, 10, 30, 30), (60, 60, 30, 30))
解释:按 x 坐标升序排序后,轮廓和边界框按从左到右顺序排列。
- 注意事项 输入要求:cnts 和 boundingBoxes 必须长度相同,且 boundingBoxes 中每个元素为 (x, y, w, h) 格式。 排序维度:i=0 表示按 x 坐标排序,i=1 表示按 y 坐标排序,i=2 表示按宽度排序,i=3 表示按高度排序。 降序排序:reverse=True 时,按降序排序(如从右到左、从下到上)。
五、ROI提取和尺寸调整
详细解析:
1.cv2.boundingRect(c)的作用
(x,y,w,h)= cv2.boundingRect(c)
功能:计算轮廓c de 最小外籍矩形(直边界矩形),返回矩形的左上角坐标(x,y)和尺寸(w , h)
参数:c 是轮廓点集,(cv2.findContours()函数返回)
返回值:
-
(x, y):矩形左上角坐标(矩阵坐标系,原点在左上角)。w:矩形宽度。h:矩形高度。
- 应用场景:目标检测、形状分析、ROI(感兴趣区域)提取等。
2.ROI(感兴趣区域)提取
roi = ref[y:y + h, x:x + w]
功能:从图像ref中截取矩形区域roi
坐标:ref[y:y+h,x:x+w] 表示从 (x,y)坐标开始,截取宽w,高度h的子图像
效果:将轮廓 c 包围的区域从原图中分离出来,形成独立的子图像 roi
六、ROI调整大小
roi = cv2.resize(roi,(57,88))
功能:将roi调整为固定尺寸(57,88)
参数(57,88)是目标尺寸(宽57,高88)
应用场景:数据增强、特征提取,模型输入预处理
七、cv2.getStructuringElement
1.函数定义
cv2.getStructuringElement()是opencv用于生成形态学操作结构元素(卷积核)的核心函数
kernel = cv2.getStructuringElement(shape,ksize,anchor=None)
2、‘参数说明:
shape:结构元素的形状,cv2.MORPH_RECT表示矩形
ksize:结构元素的大小 9*3矩阵
anchor:结构元素的锚点位置,默认中心点
返回值:返回一个 指定形状和大小的结构元素【二维数组】
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
-
rectKernel:- 形状:
cv2.MORPH_RECT(矩形)。 - 大小:
(9, 3)(9 列,3 行)。 - 效果:生成一个 9x3 的全 1 矩阵(如
[[1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1]])。
- 形状:
-
sqKernel:- 形状:
cv2.MORPH_RECT(矩形)。 - 大小:
(5, 5)(5 列,5 行)。 - 效果:生成一个 5x5 的全 1 矩阵(如
[[1,1,1,1,1],[1,1,1,1,1],[1,1,1,1,1],[1,1,1,1,1],[1,1,1,1,1]])。
- 形状:
3、实际效果
rectKernel:9*3的矩形结构元素,适合水平方向的形态学操作(如边缘检测)
sqKernel:5*5矩形结构元素,适合通用的形态学操作(如去噪,填充空洞)
3、形态学操作原理
腐蚀(Erosion):结构元素和图像逐像素比较,只有当结构元素完全覆盖前景区域时,输出像素采薇前景(rectKernel:可消除细小的水平线)
膨胀(DIlation):结构元素与图像逐像素比较,只要结构元素与前景区域有交集,那么输出的像素即为前景,【如sqKernel可以填充小孔洞】
开运算:先腐蚀在膨胀,消除噪声
闭运算:先膨胀后腐蚀,填充孔洞。
八、cv2.morphologyEx
1.函数的定义
cv2.morphologyEx()是OpenCV中用于执行形态学操作的核心函数
dst = cv2.morphologyEx(src,op,kernel)
参数说明:
src:输入图像(需要灰度图或者单通道图像)
op:操作类型(如,cv2.MORPH_TOPHAT,表示顶帽运算)
kernel:结构元素(卷积核 如 cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3)))
返回值:处理后的图像dst
顶帽运算原理:
顶帽运算通过以下步骤实现:
1.开运算:先腐蚀后膨胀(去除噪声,就是噪点)
差值计算:原图减去开运算结果,,突出比邻居区域更亮的细小区域
数学公式:TopHat(f) = f - (f ∘ b) ,其中f 输入图像,b是结构元素,∘ 表示开运算。
. 实际效果
- 输入:灰度图像
gray和结构元素rectKernel(如 9x3 矩形核)。 - 输出:顶帽运算结果
tophat,突出比邻域亮的细小区域(如边缘、纹理)。 - 应用场景:光照校正、微弱目标增强、细小结构检测。
- 光照校正:常用于解决光照不均匀问题,增强微弱目标特。
九、cv2.Sobel()
1,定义方式
cv2.Sobel是 opencv用于计算图像梯度(边缘检测)的函数
dst = cv2.Sobel(src,ddepth,dx,dy,ksize)
参数说明:
src:输入图像(需为灰度图,或者单通道图像,如通过 cv2.morphologyEx 处理后的图像)
ddepth:输出图像的深度(如 cv2.CV_32F 表数32位浮点数,避免信息丢失)
dx:x方向的导数阶数(dx=1表示计算x方向的梯度)
dy: y方向的导数阶数(dy=0表示不计算y方向的梯度) 因为数字的垂直边缘很明显
ksize : 卷积核大小 (如 ksize=-1 表示使用 3x3 的 Scharr 算子)。
返回值:处理后的梯度图像dst
代码 的实际效果:
输入:灰度的图像tophat 如通过顶帽运算来增强图像
输出: 水平方向的梯度图像 gradX,突出边缘(如水平线)
应用场景:边缘检测,特征提取,微弱目标有增强的效果
数值处理:计算结果为浮点数,需取绝对值并归一化(如 np.absolute 和归一化)。
十、梯度图像归一化处理流程
gradX = np.absolute(gradX) # 取绝对值
(minVal, maxVal) = (np.min(gradX), np.max(gradX)) # 获取最小值和最大值
gradX = (255 * ((gradX - minVal) / (maxVal - minVal))) # 归一化
gradX = gradX.astype("uint8") # 转换为8位无符号整数
1、步骤:
1.取绝对值,将梯度值装换位正数(消除负值梯度)
2.获取极限值
3.归一化处理:将梯度值线性映射到 [0, 255] 范围(如 minVal=10,maxVal=100 时,gradX=20 映射为 255*(20-10)/(100-10)=25)。
4.类型转换: 将浮点数转换为 8 位无符号整数(0-255 范围)。
2. 实际效果
- 输入:梯度图像
gradX(如通过cv2.Sobel计算的水平梯度)。 - 输出:归一化后的梯度图像(0-255 范围,8 位无符号整数)。
- 应用场景:增强边缘特征、图像处理可视化、特征提取。
3.功能:
- 消除负梯度:
np.absolute将负梯度值转换为正数。 - 线性映射:
(255 * ((gradX - minVal) / (maxVal - minVal)))将梯度值线性映射到 [0, 255] 范围。 - 类型转换:
astype("uint8")将浮点数转换为 8 位无符号整数(0-255 范围)。
4. 注意事项
- 数值范围:归一化后梯度值范围为 0-255,适合图像处理(如边缘检测、特征提取)。
- 极值处理:若
minVal == maxVal(如全黑图像),需添加容错处理(如maxVal = maxVal if maxVal != minVal else 1)。 - 性能优化:可使用
cv2.normalize替代手动归一化(如gradX = cv2.normalize(gradX, None, 0, 255, cv2.NORM_MINMAX))。
十一、形态学闭运算
代码:gradX = cv2.morphologyEx(gradX, cv2.MORPH_CLOSE, rectKernel)
1.形态学闭运算
功能:执行闭运算(先膨胀后腐蚀),填充小孔洞,连接断裂边缘
- 参数:
gradX:输入梯度图像(如通过 Sobel 计算的水平梯度)。cv2.MORPH_CLOSE:闭运算模式。rectKernel:结构元素(如 9x3 矩形核)。
- 效果:增强边缘连续性,消除细小孔洞(如数字内部空洞)。
2. 二值化处理
thresh = cv2.threshold(gradX, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
- 功能:将图像转换为二值图(黑白图像),自动选择阈值。
- 参数:
gradX:输入图像(闭运算后的梯度图像)。0:初始阈值(自动调整)。255:最大值(白色)。cv2.THRESH_BINARY | cv2.THRESH_OTSU:二值化模式(OTSU 自动阈值)。
- 效果:将图像分割为前景(白色)和背景(黑色),突出边缘特征。
. 注意事项
- 闭运算:
rectKernel形状为矩形(9x3),适合水平方向的增强。 - 二值化:***
cv2.THRESH_OTSU自动选择阈值,适合复杂图像(如光照不均)***。 - 数值处理:闭运算后需重新归一化(如
gradX.astype("uint8"))。
3. 形态学闭运算
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel)
- 功能:执行闭运算(先膨胀后腐蚀),填充小孔洞、连接断裂边缘。
- 参数:
thresh:输入二值图像(如通过 Otsu 阈值处理后的图像)。cv2.MORPH_CLOSE:闭运算模式。sqKernel:结构元素(如 5x5 矩形核)。
- 效果:增强边缘连续性,消除细小孔洞(如数字内部空洞)。
- 应用场景:修复二值化图像中的断裂边缘、填充小孔洞(如数字内部空洞)。
4. 轮廓检测
threshCnts, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
- 功能:检测图像中的轮廓(封闭区域边界)。
- 参数:
thresh.copy():输入图像(需复制避免修改原图)。cv2.RETR_EXTERNAL:仅检测最外层轮廓(不包含嵌套轮廓)。cv2.CHAIN_APPROX_SIMPLE:压缩轮廓点集(仅保留关键点)。
- 返回值:
threshCnts:轮廓列表,每个元素为一个轮廓点集(如矩形轮廓为4个点)。hierarchy:轮廓层次结构信息(可选)。
- 应用场景:目标检测、形状分析、物体分割等。
import cv2
import numpy as np
# 读取图像并转换为灰度图
img = cv2.imread('test.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 生成结构元素
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
# 执行顶帽运算
tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rectKernel)
# 计算水平梯度
gradX = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=-1)
gradX = np.absolute(gradX) # 取绝对值
gradX = (255 * ((gradX - np.min(gradX)) / (np.max(gradX) - np.min(gradX)))).astype("uint8") # 归一化
# 形态学闭运算
gradX = cv2.morphologyEx(gradX, cv2.MORPH_CLOSE, rectKernel)
# 二值化处理
thresh = cv2.threshold(gradX, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
# 形态学闭运算(增强边缘)
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel)
# 轮廓检测
threshCnts, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
效果:
thresh中边缘特征(如数字边缘)被突出显示,背景被抑制。threshCnts包含所有最外层轮廓的点集列表(如数字轮廓)。hierarchy描述轮廓的父子关系(可选)。
. 注意事项
- 闭运算:
rectKernel(9x3)适合水平方向增强,sqKernel(5x5)适合通用增强。- 闭运算后需重新归一化(如
thresh.astype("uint8"))。
- 轮廓检测:
cv2.RETR_EXTERNAL仅检测最外层轮廓,cv2.RETR_TREE可检测嵌套轮廓。cv2.CHAIN_APPROX_SIMPLE压缩点集,减少数据量(如矩形轮廓只需4个点)。
- 数值处理:闭运算后需重新归一化(如
thresh.astype("uint8"))。
详细解读:信用卡数字组识别与模板匹配
这段代码是整个信用卡OCR识别系统的核心部分,负责对每个定位到的数字组进行数字识别。让我一步步详细解释:
一、整体功能概述
功能:识别每个数字组中的4个数字
输入:定位到的4个数字组区域(位置:gX, gY, gW, gH)
输出:识别出的16位信用卡数字,并在图像上绘制结果
二、代码逐行详解
1. 遍历每个数字组区域
for (i, (gX, gY, gW, gH)) in enumerate(locs):
locs: 包含4个元组的列表,每个元组代表一个数字组区域- 每个元组包含:
(gX, gY, gW, gH)gX: 区域左上角的x坐标gY: 区域左上角的y坐标gW: 区域宽度gH: 区域高度
enumerate(): 同时获取索引i和区域信息
示例:
locs = [
(30, 100, 50, 15), # 第1组:4000
(110, 100, 50, 15), # 第2组:1234
(190, 100, 50, 15), # 第3组:5678
(270, 100, 50, 15) # 第4组:9010
]
2. 初始化数字组输出列表
groupOutput = []
- 为当前数字组创建一个空列表
- 用来存储这一组4个数字的识别结果
3. 提取数字组区域并预处理
group = gray[gY - 5:gY + gH + 5, gX - 5:gX + gW + 5]
-
从灰度图像
gray中提取数字组区域 -
为什么要±5像素?(填充边界)
原始区域: [gY:gY+gH, gX:gX+gW] 填充后区域: [gY-5:gY+gH+5, gX-5:gX+gW+5] 目的: 1. 确保数字边缘完整(轮廓检测需要封闭区域) 2. 防止数字紧贴边界时被截断 3. 提供一些背景上下文,有助于二值化
可视化:
填充前: 填充后:
██████ ░░░░░░░░
██████ ░░████░░
██████ → ░░████░░
██████ ░░████░░
░░░░░░░░
(边界可能截断数字) (确保数字完整)
4. 二值化数字组区域
group = cv2.threshold(group, 0, 255,
cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
cv2.threshold(): 将图像转换为黑白二值图像- 参数说明:
group: 输入图像(数字组区域)0: 阈值(这里设为0,因为使用OTSU自动确定)255: 二值化后的最大值(白色)cv2.THRESH_BINARY: 二值化类型cv2.THRESH_OTSU: 使用OTSU算法自动确定最佳阈值
[1]: 取函数返回的第二个值(二值图像)
OTSU算法原理:
假设数字组区域包含两种像素:数字(前景)和背景
OTSU会自动找到一个阈值,使得前景和背景的类内方差最小
即:T = argmax(σ²_between(T))
效果:将数字变成白色(255),背景变成黑色(0)
5. 检测数字组内的数字轮廓
digitCnts, hierarchy = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
group.copy(): 复制图像,避免修改原图cv2.RETR_EXTERNAL: 只检测最外层轮廓cv2.CHAIN_APPROX_SIMPLE: 压缩冗余点,节省内存- 返回值:
digitCnts: 轮廓列表(应该包含4个轮廓,对应4个数字)hierarchy: 轮廓层次结构(这里不使用)
轮廓检测结果:
数字组图像"4000":
原始:█ █ █ █(4个白色区域)
轮廓检测:找到4个独立轮廓
每个轮廓对应一个数字
6. 对数字轮廓进行排序
digitCnts = contours.sort_contours(digitCnts,
method="left-to-right")[0]
- 使用
imutils.contours.sort_contours()对轮廓排序 method="left-to-right": 从左到右排序[0]: 取排序后的轮廓列表
为什么需要排序?
检测到的轮廓顺序可能是随机的:[数字0, 数字4, 数字0, 数字0]
排序后保证顺序正确:[数字4, 数字0, 数字0, 数字0] → "4000"
7. 遍历每个数字轮廓进行识别
for c in digitCnts:
# 处理每个数字...
- 对排序后的4个数字轮廓逐个处理
8. 提取单个数字区域
(x, y, w, h) = cv2.boundingRect(c)
roi = group[y:y + h, x:x + w]
cv2.boundingRect(c): 获取轮廓的外接矩形(x, y, w, h): 相对于数字组区域的坐标
roi: 提取单个数字图像区域
示例:提取数字"4"
数字组区域"4000"的二值图像:
[ 4 0 0 0 ]
█ █ █ █
提取第一个数字"4":
roi = group[y:y+h, x:x+w]
得到只有数字"4"的小图像
9. 调整数字大小以匹配模板
roi = cv2.resize(roi, (57, 88))
-
将提取的数字图像调整为57×88像素
-
为什么需要调整大小?
模板大小:57×88像素 提取的数字可能大小不一 调整到相同大小才能进行模板匹配
10. 模板匹配:识别当前数字
scores = []
for (digit, digitROI) in digits.items():
result = cv2.matchTemplate(roi, digitROI,
cv2.TM_CCOEFF)
(_, score, _, _) = cv2.minMaxLoc(result)
scores.append(score)
scores = []: 创建空列表存储匹配得分digits.items(): 遍历模板库中的10个数字(0-9)cv2.matchTemplate(): 模板匹配roi: 当前数字图像digitROI: 模板数字图像cv2.TM_CCOEFF: 相关系数匹配法
cv2.minMaxLoc(): 获取匹配结果的最小值和最大值score: 最高匹配得分
scores.append(score): 将得分加入列表
匹配过程示例(数字"4"):
当前数字roi: 数字"4"的图像
与模板库匹配:
- 与模板"0"匹配 → 得分: 0.35
- 与模板"1"匹配 → 得分: 0.42
- 与模板"2"匹配 → 得分: 0.55
- 与模板"3"匹配 → 得分: 0.60
- 与模板"4"匹配 → 得分: 0.95 ← 最高分
- ...
scores = [0.35, 0.42, 0.55, 0.60, 0.95, ...]
11. 确定识别结果
groupOutput.append(str(np.argmax(scores)))
np.argmax(scores): 找到最高得分的索引str(): 转换为字符串groupOutput.append(): 添加到当前数字组的输出列表
继续上面的示例:
scores = [0.35, 0.42, 0.55, 0.60, 0.95, 0.50, 0.45, 0.40, 0.30, 0.25]
np.argmax(scores) = 4(第5个元素,索引从0开始)
识别结果为:"4"
groupOutput = ["4"]
12. 在图像上绘制识别结果
cv2.rectangle(image, (gX - 5, gY - 5),
(gX + gW + 5, gY + gH + 5), (0, 0, 255), 1)
- 在原始彩色图像上绘制红色矩形框
- 参数说明:
image: 原始信用卡图像(gX-5, gY-5): 矩形左上角(考虑填充)(gX+gW+5, gY+gH+5): 矩形右下角(考虑填充)(0, 0, 255): 颜色(BGR格式,红色)1: 线宽
13. 在矩形框上方显示识别结果
cv2.putText(image, "".join(groupOutput), (gX, gY - 15),
cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2)
"".join(groupOutput): 将数字列表合并为字符串- 例如:
["4","0","0","0"]→"4000"
- 例如:
- 参数说明:
image: 原始图像"4000": 要显示的文本(gX, gY-15): 文本位置(矩形框上方15像素)cv2.FONT_HERSHEY_SIMPLEX: 字体0.65: 字体大小(0, 0, 255): 颜色(红色)2: 线宽
三、完整流程示例(以第一组数字"4000"为例)
步骤1:提取数字组区域
输入:locs[0] = (30, 100, 50, 15)
提取:group = gray[95:120, 25:85] # 填充5像素
步骤2:预处理和二值化
灰度区域 → OTSU二值化 → 黑白图像
数字变成白色,背景变成黑色
步骤3:轮廓检测和排序
检测到4个轮廓 → 从左到右排序
轮廓顺序:[数字4, 数字0, 数字0, 数字0]
步骤4:逐个识别数字
数字1("4"):
提取roi → 调整到57×88 → 与模板匹配
匹配得分:[0.35, 0.42, 0.55, 0.60, 0.95, ...]
最高分索引:4 → 识别为"4"
groupOutput = ["4"]
数字2("0"):
匹配得分:[0.92, 0.35, 0.40, 0.45, 0.50, ...]
最高分索引:0 → 识别为"0"
groupOutput = ["4", "0"]
数字3("0"):
识别为"0"
groupOutput = ["4", "0", "0"]
数字4("0"):
识别为"0"
groupOutput = ["4", "0", "0", "0"]
步骤5:绘制结果
绘制红色矩形框:(25,95)-(85,120)
显示文本:"4000" 在位置(30,85)
四、关键参数说明
1. 填充值(±5)的选择
# 为什么是5像素?
# 基于经验值,考虑因素:
# 1. 信用卡数字高度约10-20像素
# 2. 填充应足够大以包含完整数字
# 3. 又不能太大以免包含其他干扰
# 4. 5像素约为数字高度的25-50%
2. 模板匹配方法的选择
# 为什么用TM_CCOEFF?
# 优点:
# 1. 对光照变化不敏感(零均值化)
# 2. 匹配得分范围大,容易区分
# 3. 速度快,适合实时应用
3. 数字识别逻辑
# 简化版识别逻辑:
def recognize_digit(roi, digits):
best_score = -float('inf')
best_digit = None
for digit, template in digits.items():
score = match_template(roi, template)
if score > best_score:
best_score = score
best_digit = digit
return best_digit
五、可能的问题和优化
1. 问题:轮廓检测可能找不到4个轮廓
原因:
1. 数字连接在一起(闭操作过度)
2. 数字断裂(二值化阈值不合适)
3. 噪声干扰
解决方案:
1. 调整形态学操作的核大小
2. 调整二值化阈值
3. 添加轮廓面积筛选
2. 优化建议
# 1. 添加轮廓验证
if len(digitCnts) != 4:
print(f"警告:第{i+1}组找到{len(digitCnts)}个轮廓,期望4个")
# 可以尝试其他处理方法
# 2. 添加置信度阈值
if max(scores) < confidence_threshold:
print(f"警告:第{i+1}个数字识别置信度过低")
# 标记为未知或重新处理
# 3. 使用多种匹配方法融合
methods = [cv2.TM_CCOEFF, cv2.TM_CCOEFF_NORMED]
# 综合多个方法的得分
六、总结
这段代码实现了信用卡数字识别的核心功能:
- 区域提取:从定位到的数字组区域中提取图像
- 预处理:二值化处理,准备轮廓检测
- 轮廓分析:找到每个数字的轮廓并排序
- 模板匹配:将每个数字与模板库匹配,找出最相似的
- 结果绘制:在原始图像上显示识别结果
关键特点:
- 使用填充确保数字完整
- 使用OTSU自动确定二值化阈值
- 使用模板匹配进行数字识别
- 按顺序处理,确保数字顺序正确
这个算法虽然简单,但对于标准字体(OCR-A)的信用卡数字识别非常有效,准确率可达95%以上。
回复