Opencv图像处理(全)

2023年6月14日08:05:46

文章目录

备注:以下源码均可运行,不同项目涉及的函数均有详细分析说明。

环境配置下载地址(注意版本对应)

- Anaconda
- opencv_python.whl
- IDE:Pycharm

11、图像项目实战

(一)银行卡号识别 —— sort_contours()、resize()

【信用卡检测流程详解】
11、提取模板的每个数字
   1111、读取模板图像、转换成灰度图、转换成二值图
   1122、轮廓检测、绘制轮廓、对得到的所有轮廓进行排序(编号)
   1133、提取模板的所有轮廓 - 每一个数字
22、提取信用卡的所有轮廓
   2211、读取待检测图像、转换为灰度图、顶帽操作、sobel算子操作、闭操作、二值化、二次膨胀+腐蚀
   2222、轮廓检测、绘制轮廓
33、提取银行卡《四个数字一组》轮廓,然后每个轮廓与模板的每一个数字进行匹配,得到最大匹配结果
   3311、在所有轮廓中,识别出《四个数字一组》的轮廓(共有四个),并进行阈值化、轮廓检测和轮廓排序
   3322、在《四个数字一组》中,提取每个数字的轮廓以及坐标,并进行模板匹配得到最大匹配结果
44、在原图像上,用矩形画出《四个数字一组》,并在原图上显示所有的匹配结果
Opencv图像处理(全)
Opencv图像处理(全)
Opencv图像处理(全)

import cv2                              # opencv读取的格式是BGR
import matplotlib.pyplot as plt # Matplotlib是RGB
import numpy as np

def sort_contours(cnt_s, method="left-to-right"):
reverse = False
ii_myutils = 0
if method == "right-to-left" or method == "bottom-to-top":
reverse = True
if method == "top-to-bottom" or method == "bottom-to-top":
ii_myutils = 1
bounding_boxes = [cv2.boundingRect(cc_myutils) for cc_myutils in cnt_s] # 用一个最小的矩形,把找到的形状包起来x,y,h,w
(cnt_s, bounding_boxes) = zip(*sorted(zip(cnt_s, bounding_boxes), key=lambda b: b[1][ii_myutils], reverse=reverse))
return cnt_s, bounding_boxes

def resize(image, width=None, height=None, inter=cv2.INTER_AREA):
dim_myutils = None
(h_myutils, w_myutils) = image.shape[:2]
if width is None and height is None:
return image
if width is None:
r_myutils = height / float(h_myutils)
dim_myutils = (int(w_myutils * r_myutils), height)
else:
r_myutils = width / float(w_myutils)
dim_myutils = (width, int(h_myutils * r_myutils))
resized = cv2.resize(image, dim_myutils, interpolation=inter)
return resized

######################################################################
# 11、提取模板的每个数字
######################################################################
# 读取模板图像(银行卡对应0~9的数字模板)
img = cv2.imread(r'ocr_a_reference.png')
ref_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 转换成灰度图
ref_BINARY = cv2.threshold(ref_gray, 10, 255, cv2.THRESH_BINARY_INV)[1] # 转换成二值图像
#######################################
# 轮廓检测:contours, hierarchy = cv2.findContours(img, mode, method)
# 输入参数 mode: 轮廓检索模式
# (1)RETR_EXTERNAL: 只检索最外面的轮廓;
# (2)RETR_LIST: 检索所有的轮廓,但检测的轮廓不建立等级关系,将其保存到一条链表当中,
# (3)RETR_CCOMP: 检索所有的轮廓,并建立两个等级的轮廓。顶层是各部分的外部边界,内层是的边界信息;
# (4)RETR_TREE: 检索所有的轮廓,并建立一个等级树结构的轮廓;(最常用)
# method: 轮廓逼近方法
# (1)CHAIN_APPROX_NONE: 存储所有的轮廓点,相邻的两个点的像素位置差不超过1。 例如:矩阵的四条边。(最常用)
# (2)CHAIN_APPROX_SIMPLE: 压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标。 例如:矩形的4个轮廓点。
# 输出参数 contours:所有的轮廓
# hierarchy:每条轮廓对应的属性
# 备注0:轮廓就是将连续的点(连着边界)连在一起的曲线,具有相同的颜色或者灰度。轮廓在形状分析和物体的检测和识别中很有用。
# 备注1:函数输入图像是二值图,即黑白的(不是灰度图)。所以读取的图像要先转成灰度的,再转成二值图。
# 备注2:函数在opencv2只返回两个值:contours, hierarchy。
# 备注3:函数在opencv3会返回三个值:img, countours, hierarchy
#######################################
refCnts, hierarchy = cv2.findContours(ref_BINARY.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#######################################
# 绘制轮廓:v2.drawContours(image, contours, contourIdx, color, thickness) ———— (在图像上)画出图像的轮廓
# 输入参数 image: 需要绘制轮廓的目标图像,注意会改变原图
# contours: 轮廓点,上述函数cv2.findContours()的第一个返回值
# contourIdx: 轮廓的索引,表示绘制第几个轮廓。-1表示绘制所有的轮廓
# color: 绘制轮廓的颜色(RGB)
# thickness: (可选参数)轮廓线的宽度,-1表示填充
# 备注:图像需要先复制一份copy(), 否则(赋值操作的图像)与原图会随之一起改变。
#######################################
img_Contours = img.copy()
cv2.drawContours(img_Contours, refCnts, -1, (0, 0, 255), 3)
# print(np.array(refCnts).shape)

# 画图(图像处理并得到轮廓的图形化显示)
plt.subplot(221), plt.imshow(img, 'gray'), plt.title('(0)ref')
plt.subplot(222), plt.imshow(ref_gray, 'gray'), plt.title('(1)ref_gray')
plt.subplot(223), plt.imshow(ref_BINARY, 'gray'), plt.title('(2)ref_BINARY')
plt.subplot(224), plt.imshow(img_Contours, 'gray'), plt.title('(3)img_Contours')
plt.show()
#######################################
# 对得到的所有轮廓进行排序(编号):从左到右,从上到下
refCnts = sort_contours(refCnts, method="left-to-right")[0]
#######################################
# 提取(模板的)所有轮廓 - 数字
digits = {} # 保存每个模板的数字 - 元组初始化
for (i, c) in enumerate(refCnts):
(x, y, w, h) = cv2.boundingRect(c) # 得到轮廓(数字)的外接矩形的左上角的(x, y)坐标、宽度和长度
roi = ref_BINARY[y:y + h, x:x + w] # 获得外接矩形的坐标
roi = cv2.resize(roi, (57, 88)) # 将感兴趣区域的图像(数字)resize相同的大小
digits[i] = roi # 保存每个模板(数字)
######################################################################
# 22、提取信用卡的所有轮廓
######################################################################
# 初始化卷积核:getStructuringElement(shape, ksize)
# 输入参数: shape:形状
# (1) MORPH_RECT 矩形
# (2) MORPH_CROSS 十字型
# (3) MORPH_ELLIPSE 椭圆形
# ksize:卷积核大小。例如:(3, 3)表示3*3的卷积核
######################################
rect_Kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))
square_Kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
######################################
# 读取输入图像(待检测信用卡图像),并进行预处理
image_card = cv2.imread(r'images\credit_card_01.png')
image_resize = resize(image_card, width=300)
image_gray = cv2.cvtColor(image_resize, cv2.COLOR_BGR2GRAY)
#######################################
# 形态学变化函数:cv2.morphologyEx(src, op, kernel)
# 参数说明:src传入的图片,op进行变化的方式, kernel表示方框的大小
# op变化的方式有五种:
# 开运算(open): cv2.MORPH_OPEN 先腐蚀,再膨胀。 开运算可以用来消除小黑点。
# 闭运算(close): cv2.MORPH_CLOSE 先膨胀,再腐蚀。 闭运算可以用来突出边缘特征。
# 形态学梯度(morph-grad): cv2.MORPH_GRADIENT 膨胀后图像(减去)腐蚀图像。 可以突出团块(blob)的边缘,保留物体的边缘轮廓。
# 顶帽(top-hat): cv2.MORPH_TOPHAT 原始输入(减去)开运算结果。 将突出比原轮廓亮的部分。
# 黑帽(black-hat): cv2.MORPH_BLACKHAT 闭运算结果(减去)原始输入 将突出比原轮廓暗的部分。
#######################################
image_tophat = cv2.morphologyEx(image_gray, cv2.MORPH_TOPHAT, rect_Kernel) # 礼帽操作,突出更明亮的区域
#######################################
# Sobel算子是一种常用的边缘检测算子。对噪声具有平滑作用,提供较为精确的边缘方向信息,但是边缘定位精度不够高。
# 边缘就是像素对应的灰度值快速变化的地方。如:黑到白的边界
# 图像是二维的。Sobel算子在x,y两个方向求导,故有不同的两个卷积核(Gx, Gy),且Gx的转置等于Gy。分别反映了每一点像素在水平方向和在垂直方向上的亮度变换情况.
########################################
# dst = cv2.Sobel(src, ddepth, dx, dy, ksize)
# 输入参数 src 输入图像
# ddepth 图像的深度,-1表示采用的是与原图像相同的深度。目标图像的深度必须大于等于原图像的深度;
# dx和dy 表示的是求导的阶数,0表示这个方向上没有求导,一般为0、1、2。
# ksize 卷积核大小,一般为3、5。
# 同时对x和y进行求导,会导致部分信息丢失。(不建议)- 分别计算x和y,再求和(效果好)
########################################
# (1)cv2.CV_16S的说明
# (1)Sobel函数求完导数后会有负值,还有会大于255的值。
# (2)而原图像是uint8,即8位无符号数。所以Sobel建立图像的位数不够,会有截断。
# (3)因此要使用16位有符号的数据类型,即cv2.CV_16S。
# (2)cv2.convertScaleAbs(): 给图像的所有像素加一个绝对值
# 通过该函数将其转回原来的uint8形式。否则将无法显示图像,而只是一副灰色的窗口。
########################################
# 进行sobel算子操作 ksize=-1相当于用3*3的卷积核进行筛选ll(内置的卷积核)
image_gradx = cv2.Sobel(image_tophat, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=-1) # 检索图像的边界,gradx是经过Sobel算子处理后的图像的像素点矩阵
image_gradx = np.absolute(image_gradx) # 对数组中每一个元素求绝对值
(minVal, maxVal) = (np.min(image_gradx), np.max(image_gradx)) # 找到最大边界差值和最小边界插值
image_gradx = (255 * ((image_gradx - minVal) / (maxVal - minVal))) # 归一化公式,将图像像素数据限制在0-1之间,便于后续的操作
image_gradx = image_gradx.astype("uint8") # 将gradx的矩阵元素改为数据类型uint8,一般图像的像素点类型都是uint8
"""sobel_Gx1 = cv2.Sobel(image_tophat, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=3) # 3*3卷积核
sobel_Gx_Abs1 = cv2.convertScaleAbs(sobel_Gx1) # (1)左边减右边(2)白到黑是正数,黑到白就是负数,且所有的负数会被截断成0,所以要取绝对值。
sobel_Gy1 = cv2.Sobel(image_tophat, cv2.CV_64F, 0, 1, ksize=3)
sobel_Gy_Abs1 = cv2.convertScaleAbs(sobel_Gy1)
sobel_Gx_Gy_Abs1 = cv2.addWeighted(sobel_Gx_Abs1, 0.5, sobel_Gy_Abs1, 0.5, 0) # 权重值x + 权重值y +偏置b"""

########################################
# 闭操作(先膨胀,再腐蚀)将银行卡分成四个部分,每个部分的四个数字连在一起
image_CLOSE = cv2.morphologyEx(image_gradx, cv2.MORPH_CLOSE, square_Kernel)
########################################
# 图像阈值 ret, dst = cv2.threshold(src, thresh, max_val, type)
# 输入参数 dst: 输出图
# src: 输入图,只能输入单通道图像,通常来说为灰度图
# thresh: 阈值
# max_val: 当像素值超过了阈值(或者小于阈值,根据type来决定),所赋予的值
# type: 二值化操作的类型,包含以下5种类型:
# (1) cv2.THRESH_BINARY 超过阈值部分取max_val(最大值),否则取0
# (2) cv2.THRESH_BINARY_INV THRESH_BINARY的反转
# (3) cv2.THRESH_TRUNC 大于阈值部分设为阈值,否则不变
# (4) cv2.THRESH_TOZERO 大于阈值部分不改变,否则设为0
# (5) cv2.THRESH_TOZERO_INV THRESH_TOZERO的反转
########################################
# THRESH_OTSU 会自动寻找合适的阈值,适合双峰,需把阈值参数设置为0。
image_thresh = cv2.threshold(image_CLOSE, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
# (二次)闭操作,将四个连在一起的数字进行填充形成一个整体。
image_2_dilate = cv2.dilate(image_thresh, square_Kernel, iterations=2) # 膨胀(迭代次数2次)
image_1_erode = cv2.erode(image_2_dilate, square_Kernel, iterations=1) # 腐蚀(迭代次数1次)
image_2_CLOSE = image_1_erode
# 计算轮廓
threshCnts, hierarchy = cv2.findContours(image_2_CLOSE.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# cnts = threshCnts # 代表图像轮廓的点集
image_Contours = image_resize.copy()
cv2.drawContours(image_Contours, threshCnts, -1, (0, 0, 255), 3)

plt.subplot(241), plt.imshow(image_card, 'gray'), plt.title('(0)image_card')
plt.subplot(242), plt.imshow(image_gray, 'gray'), plt.title('(1)image_gray')
plt.subplot(243), plt.imshow(image_tophat, 'gray'), plt.title('(2)image_tophat')
plt.subplot(244), plt.imshow(image_gradx, 'gray'), plt.title('(3)image_gradx')
plt.subplot(245), plt.imshow(image_CLOSE, 'gray'), plt.title('(4)image_CLOSE')
plt.subplot(246), plt.imshow(image_thresh, 'gray'), plt.title('(5)image_thresh')
plt.subplot(247), plt.imshow(image_2_CLOSE, 'gray'), plt.title('(6)image_2_CLOSE')
plt.subplot(248), plt.imshow(image_Contours, 'gray'), plt.title('(7)image_Contours')
plt.show()

######################################################################
# 33、提取银行卡" 四个数字一组 "轮廓,然后每个轮廓与模板的每一个数字进行匹配,得到最大匹配结果
######################################################################
# 3311、识别出四个数字一组的所有轮廓(理论上是四个)
########################################
locs = [] # 保存四个数字一组的轮廓坐标 - 列表初始化
for (i, c) in enumerate(threshCnts): # 遍历轮廓
(x, y, w, h) = cv2.boundingRect(c) # 计算矩形
ar = w / float(h) # (四个数字一组)的长宽比
# 匹配(四个数字为一组)轮廓的大小 —— 以实际图像大小进行调整
if (2.0 < ar and ar < 4.0):
if (35 < w < 60) and (10 < h < 20):
locs.append((x, y, w, h)) # 符合的留下来
# 将符合的轮廓从左到右排序
locs = sorted(locs, key=lambda x:x[0])
########################################
# 3322、在四个数字一组中,提取每个数字的轮廓坐标并进行模板匹配
########################################
output = [] # 保存银行卡中每个数字的轮廓坐标 - 列表初始化
# 遍历银行卡的每一个数字
for (ii, (gX, gY, gW, gH)) in enumerate(locs): # ii 应为四组
groupOutput = [] # 信用卡每个数字的最后匹配结果存储

group_digit = image_gray[gY - 5:gY + gH + 5, gX - 5:gX + gW + 5] # 根据坐标提取每一个组(将每个轮廓的结果放大一些,避免信息丢失)
group_digit_th = cv2.threshold(group_digit, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] # 二值化
digitCnts, hierarchy = cv2.findContours(group_digit_th.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 获取轮廓
digitCnts = sort_contours(digitCnts, method="left-to-right")[0] # 对获得的轮廓进行编号
# 计算每一组中的每一个数值
for jj in digitCnts: # jj 应为四个数字
(x, y, w, h) = cv2.boundingRect(jj) # 获取当前数值的轮廓
roi = group_digit[y:y + h, x:x + w] # 获取当前数值的坐标
roi = cv2.resize(roi, (57, 88)) # 修改尺寸大小(该大小应与模板数字的大小相同)
cv2.imshow("Image", roi)
cv2.waitKey(200) # 延迟200ms
########################################
# 模板匹配:cv2.matchTemplate(image, template, method)
# 输入参数 image 待检测图像
# template 模板图像
# method 模板匹配方法:
# (1)cv2.TM_SQDIFF: 计算平方差。 计算出来的值越接近0,越相关
# (2)cv2.TM_CCORR: 计算相关性。 计算出来的值越大,越相关
# (3)cv2.TM_CCOEFF: 计算相关系数。 计算出来的值越大,越相关
# (4)cv2.TM_SQDIFF_NORMED: 计算(归一化)平方差。 计算出来的值越接近0,越相关
# (5)cv2.TM_CCORR_NORMED: 计算(归一化)相关性。 计算出来的值越接近1,越相关
# (6)cv2.TM_CCOEFF_NORMED: 计算(归一化)相关系数。 计算出来的值越接近1,越相关
# (最好选择有归一化操作,效果好)
########################################
# 获取匹配结果函数:min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(ret)
# 其中: ret是cv2.matchTemplate函数返回的矩阵;
# min_val, max_val, min_loc, max_loc分别表示最小值,最大值,最小值与最大值在图像中的位置
# 如果模板方法是平方差或者归一化平方差,要用min_loc; 其余用max_loc
########################################
scores = [] # 计算【轮廓中的数字: roi】和【模板中的数字: digitROI】的匹配分数
for (kk, digitROI) in digits.items(): # kk 应为10数字(对应模板的十个数)
result = cv2.matchTemplate(roi, digitROI, cv2.TM_CCOEFF)
(_, max_score, _, _) = cv2.minMaxLoc(result) # max_score表示最大值
scores.append(max_score) # 将对象max_score添加到列表scores后面
groupOutput.append(str(np.argmax(scores))) # 将最大匹配分数对应的数字保存下来
########################################
# 添加文字及修改格式函数:cv2.putText(img, str(i), (123, 456)), font, 2, (0, 255, 0), 3)
# 输入参数依次是:图片,添加的文字,左上角坐标,字体,字体大小,颜色,字体粗细
########################################
# 在原图像上,用矩形将" 四个数字一组 "画出来(应共有四个矩形,对应四个组)
cv2.rectangle(image_resize, (gX - 5, gY - 5), (gX + gW + 5, gY + gH + 5), (0, 0, 255), 1)
cv2.putText(image_resize, "".join(groupOutput), (gX, gY - 15), cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0,

  • 作者:胖墩会武术
  • 原文链接:https://blog.csdn.net/shinuone/article/details/126022763
    更新时间:2023年6月14日08:05:46 ,共 12862 字。