OpenCV4-直方图与傅里叶变换-项目实战-信用卡数字识别

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • [1. 直方图与傅里叶变换](#1. 直方图与傅里叶变换)
    • [1.1 直方图定义](#1.1 直方图定义)
    • [1.2 mask操作](#1.2 mask操作)
    • [1.3 直方图均衡化](#1.3 直方图均衡化)
    • [1.4 自适应直方图均衡化](#1.4 自适应直方图均衡化)
    • [1.5 傅里叶变换](#1.5 傅里叶变换)
    • [1.6 频域变换结果](#1.6 频域变换结果)
    • [1.7 低通和高通滤波](#1.7 低通和高通滤波)
  • [2. 项目实战-信用卡数字识别](#2. 项目实战-信用卡数字识别)
    • [2.1 代码](#2.1 代码)
  • 总结

前言

1. 直方图与傅里叶变换

1.1 直方图定义

就是统计每一个像素值有多少个点

java 复制代码
cv2.calcHist(images,channels,mask,histSize,ranges)
	images: 原图像图像格式为 uint8 或 float32。当传入函数时应 用中括号 [] 括来例如[img]
	channels: 同样用中括号括来它会告函数我们统幅图 像的直方图。如果入图像是灰度图它的值就是 [0],如果是彩色图像 的传入的参数可以是 [0][1][2] 它们分别对应着 BGR。
	mask: 掩模图像。统整幅图像的直方图就把它为 None。但是如 果你想统图像某一部分的直方图的你就制作一个掩模图像并 使用它。
	histSize:BIN 的数目。也应用中括号括来,就是0~256,还是0~10,11~20这样划分
	ranges: 像素值范围常为 [0~256]
java 复制代码
img = cv2.imread('./cat.jpg',0) #0表示灰度图
hist = cv2.calcHist([img],[0],None,[256],[0,256])
hist.shape

256表示有256个取值,其实就是0~255的每个值

1表示每个值出现个数

java 复制代码
plt.hist(img.ravel(),256); 
plt.show()
java 复制代码
img = cv2.imread('./cat.jpg')
color = ('b','g','r')
for i,col in enumerate(color): 
    histr = cv2.calcHist([img],[i],None,[256],[0,256]) 
    plt.plot(histr,color = col) 
    plt.xlim([0,256]) 

i就是0,1,2,col就是bgr

1.2 mask操作

这个就是掩码的作用

java 复制代码
# 创建mast
mask = np.zeros(img.shape[:2], np.uint8)
print (mask.shape)
mask[100:300, 100:400] = 255
cv_show(mask,'mask')

mask的形状要和img的形状一样

java 复制代码
img = cv2.imread('./cat.jpg', 0)
cv_show(img,'img')
java 复制代码
masked_img = cv2.bitwise_and(img, img, mask=mask)#与操作
cv_show(masked_img,'masked_img')

and0的话,那么就是0,就是黑色了

java 复制代码
hist_full = cv2.calcHist([img], [0], None, [256], [0, 256])
hist_mask = cv2.calcHist([img], [0], mask, [256], [0, 256])
java 复制代码
plt.subplot(221), plt.imshow(img, 'gray')
plt.subplot(222), plt.imshow(mask, 'gray')
plt.subplot(223), plt.imshow(masked_img, 'gray')
plt.subplot(224), plt.plot(hist_full), plt.plot(hist_mask)
plt.xlim([0, 256])
plt.show()

黄色的线是hist_mask,因为要矮一点

1.3 直方图均衡化

这个就是不均衡

这样就变均衡了

就是这样计算就变均衡了

java 复制代码
img = cv2.imread('./cat.jpg',0) #0表示灰度图 #clahe
plt.hist(img.ravel(),256); 
plt.show()
java 复制代码
equ = cv2.equalizeHist(img) 
plt.hist(equ.ravel(),256)
plt.show()

变胖了

java 复制代码
res = np.hstack((img,equ))
cv_show(res,'res')

感觉均衡化之后,就变黑一点了

感觉变得更显眼了

感觉变亮了,也变模糊了

比如人脸就变模糊了,那么如果分模块均衡化呢,这样就不会把脸的像素均衡化到其他地方了

1.4 自适应直方图均衡化

java 复制代码
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8)) 
java 复制代码
res_clahe = clahe.apply(img)
res = np.hstack((img,equ,res_clahe))
cv_show(res,'res')

最后一个图就是局部均衡化

第二个是全局均衡化

但是局部均衡化的话,边界感比较明显

1.5 傅里叶变换

我们生活在时间的世界中,早上7:00起来吃早饭,8:00去挤地铁,9:00开始上班。。。以时间为参照就是时域分析。

但是在频域中一切都是静止的!

按照频域描述就是每隔一分钟得一个三分,每隔一分钟得一个两分

任何一个周期函数都可以用很多个正弦波堆积出来


1.6 频域变换结果

高频:变化剧烈的灰度分量,例如边界

低频:变化缓慢的灰度分量,例如一片大海

滤波

低通滤波器:只保留低频,没有边界了,会使得图像模糊

高通滤波器:只保留高频,保留所有边界,边界锐化,会使得图像细节增强

opencv中主要就是cv2.dft()和cv2.idft(),输入图像需要先转换成np.float32 格式,得到的结果中频率为0的部分会在左上角,通常要转换到中心位置,通过shift变换

java 复制代码
import numpy as np
import cv2
from matplotlib import pyplot as plt

img = cv2.imread('../img/lena.jpg',0)

img_float32 = np.float32(img)

dft = cv2.dft(img_float32, flags = cv2.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)

magnitude_spectrum = 20*np.log(cv2.magnitude(dft_shift[:,:,0],dft_shift[:,:,1]))

plt.subplot(121),plt.imshow(img, cmap = 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(magnitude_spectrum, cmap = 'gray')
plt.title('Magnitude Spectrum'), plt.xticks([]), plt.yticks([])
plt.show()

dft表示执行傅里叶变换

fftshift表示把低频值放在数组中间

cv2.dft() 是 OpenCV 中实现离散傅里叶变换的函数。

flags=cv2.DFT_COMPLEX_OUTPUT 表示输出结果为复数形式(傅里叶变换的结果是复数,包含实部和虚部)。

输出 dft 是一个三维数组,形状为 (高度, 宽度, 2),其中最后一个维度的两个元素分别对应复数的实部和虚部。

傅里叶变换的原始结果中,低频分量(对应图像的平滑区域)分布在频谱的角落,高频分量(对应边缘、噪声等细节)分布在四周。

np.fft.fftshift() 可以将低频分量移至频谱的中心,方便后续观察和处理(如滤波)。

最终显示的效果是:左图为原始灰度图,右图为傅里叶变换后的幅度谱(中心亮斑为低频分量,越亮表示该频率成分越强)。

中间比较亮---》低频,越往外,越高频,越暗

1.7 低通和高通滤波

java 复制代码
import numpy as np
import cv2
from matplotlib import pyplot as plt

img = cv2.imread('lena.jpg',0)

img_float32 = np.float32(img)

dft = cv2.dft(img_float32, flags = cv2.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)

rows, cols = img.shape
crow, ccol = int(rows/2) , int(cols/2)     # 中心位置

# 低通滤波
mask = np.zeros((rows, cols, 2), np.uint8)
mask[crow-30:crow+30, ccol-30:ccol+30] = 1

# IDFT
fshift = dft_shift*mask
f_ishift = np.fft.ifftshift(fshift)
img_back = cv2.idft(f_ishift)
img_back = cv2.magnitude(img_back[:,:,0],img_back[:,:,1])

plt.subplot(121),plt.imshow(img, cmap = 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(img_back, cmap = 'gray')
plt.title('Result'), plt.xticks([]), plt.yticks([])

plt.show()        

rows, cols = img.shape # 获取图像尺寸(高度、宽度)

crow, ccol = int(rows/2), int(cols/2) # 计算频谱中心坐标(整数)

#创建低通滤波器掩码(mask)

mask = np.zeros((rows, cols, 2), np.uint8) # 初始化全0掩码,形状与dft_shift一致(最后一维为2,对应复数的实部和虚部)

mask[crow-30:crow+30, ccol-30:ccol+30] = 1 # 将中心30x30区域设为1(保留低频)

这个mask就只有中心区域为1,其他为0

java 复制代码
# 应用滤波器:只保留掩码为1的频率成分
fshift = dft_shift * mask  # 逐元素相乘,过滤高频,全部为0了

# 将频谱移回原始位置(与fftshift相反)
f_ishift = np.fft.ifftshift(fshift)

# 执行逆傅里叶变换(从频率域转回空间域)
img_back = cv2.idft(f_ishift)

# 计算逆变换结果的幅度(复数转实数)
img_back = cv2.magnitude(img_back[:, :, 0], img_back[:, :, 1])


低通滤波---》变模糊了

java 复制代码
img = cv2.imread('../img/lena.jpg',0)

img_float32 = np.float32(img)

dft = cv2.dft(img_float32, flags = cv2.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)

rows, cols = img.shape
crow, ccol = int(rows/2) , int(cols/2)     # 中心位置

# 高通滤波
mask = np.ones((rows, cols, 2), np.uint8)
mask[crow-30:crow+30, ccol-30:ccol+30] = 0

# IDFT
fshift = dft_shift*mask
f_ishift = np.fft.ifftshift(fshift)
img_back = cv2.idft(f_ishift)
img_back = cv2.magnitude(img_back[:,:,0],img_back[:,:,1])

plt.subplot(121),plt.imshow(img, cmap = 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(img_back, cmap = 'gray')
plt.title('Result'), plt.xticks([]), plt.yticks([])

plt.show()    

高通滤波就是把低通变为0,高通*1不变了

只留下边界了

2. 项目实战-信用卡数字识别

左边的是模版,挨个挨个判断检测就可以了

轮廓检测---》内轮廓,外轮廓(要这个)------》,轮廓外接矩形

预处理---》灰度--》过滤数字----》轮廓长宽比例判断,先框4000,在细致操作

2.1 代码

java 复制代码
# 导入工具包
from imutils import contours
import numpy as np
import cv2
import myutils

# 指定信用卡类型
FIRST_NUMBER = {
	"3": "American Express",
	"4": "Visa",
	"5": "MasterCard",
	"6": "Discover Card"
}
# 绘图展示
def cv_show(name,img):
	cv2.imshow(name, img)
	cv2.waitKey(0)
	cv2.destroyAllWindows()
# 读取一个模板图像
img = cv2.imread("./images/ocr_a_reference.png")
cv_show('img',img)
# 灰度图
ref = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv_show('ref',ref)
# 二值图像
ref = cv2.threshold(ref, 10, 255, cv2.THRESH_BINARY_INV)[1]
# 10:阈值。所有灰度值小于10的像素将被设置为0(黑色),所有灰度值大于或等于10的像素将被设置为255(白色)。
# 255:最大值。这里设置为255,表示白色。
# cv2.THRESH_BINARY_INV:阈值类型。THRESH_BINARY_INV 表示反转二值化,即小于阈值的像素设置为255,大于或等于阈值的像素设置为0。这通常用于将背景设置为白色,目标对象设置为黑色。
cv_show('ref',ref)

# 计算轮廓
#cv2.findContours()函数接受的参数为二值图,即黑白的(不是灰度图),cv2.RETR_EXTERNAL只检测外轮廓,cv2.CHAIN_APPROX_SIMPLE只保留终点坐标
#返回的list中每个元素都是图像中的一个轮廓


refCnts, hierarchy = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# ref.copy():对二值图像 ref 进行拷贝,因为 cv2.findContours 会修改输入图像。
# cv2.RETR_EXTERNAL:轮廓检索模式,表示只检索最外层的轮廓。
# cv2.CHAIN_APPROX_SIMPLE:轮廓近似方法,表示只保留轮廓的端点,减少数据量。
# refCnts:检测到的轮廓列表。
# hierarchy:轮廓的层次结构信息。
cv2.drawContours(img,refCnts,-1,(0,0,255),3)
# refCnts:轮廓列表。
# -1:表示绘制所有轮廓。
# (0, 0, 255):轮廓的颜色,这里设置为红色。
# 3:轮廓的线条宽度。
cv_show('img',img)
print (len(refCnts))
refCnts = myutils.sort_contours(refCnts, method="left-to-right")[0] #排序,从左到右,从上到下,返回的是排完序的轮廓
digits = {}

# 遍历每一个轮廓,i 是索引,c 是当前轮廓。
for (i, c) in enumerate(refCnts):
	# 计算外接矩形并且resize成合适大小
	(x, y, w, h) = cv2.boundingRect(c)
	roi = ref[y:y + h, x:x + w]
	roi = cv2.resize(roi, (57, 88))

	# 每一个数字对应每一个模板
	digits[i] = roi

# 初始化卷积核,卷积核通常用于图像形态学操作,如膨胀(dilation)、腐蚀(erosion)、开运算(opening)和闭运算(closing)
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))

#读取输入图像,预处理
image = cv2.imread("./images/credit_card_01.png")
cv_show('image',image)
image = myutils.resize(image, width=300)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv_show('gray',gray)

#礼帽操作,突出更明亮的区域,用于突出图像中比周围区域更明亮的部分。礼帽操作通常用于增强图像中的小亮点或细节。
tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rectKernel) 
cv_show('tophat',tophat) 
# 计算图像的水平梯度(gradX),cv2.Sobel:计算图像的梯度。
gradX = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0, #ksize=-1相当于用3*3的
	ksize=-1)
# dx=1:计算水平方向的梯度(x 方向)。
# dy=0:不计算垂直方向的梯度(y 方向)。
# ksize=-1:使用默认的 3x3 滤波器

# 计算梯度的绝对值,确保所有值都是非负的。
gradX = np.absolute(gradX)
(minVal, maxVal) = (np.min(gradX), np.max(gradX))
# np.min(gradX) 和 np.max(gradX):计算梯度图像的最小值和最大值。
# 归一化:将梯度图像的值范围调整到 0 到 255 之间。
gradX = (255 * ((gradX - minVal) / (maxVal - minVal)))
gradX = gradX.astype("uint8")

print (np.array(gradX).shape)
cv_show('gradX',gradX)

#通过闭操作(先膨胀,再腐蚀)将数字连在一起
gradX = cv2.morphologyEx(gradX, cv2.MORPH_CLOSE, rectKernel) 
cv_show('gradX',gradX)
#THRESH_OTSU会自动寻找合适的阈值,适合双峰,需把阈值参数设置为0
thresh = cv2.threshold(gradX, 0, 255,
	cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] 
cv_show('thresh',thresh)

#再来一个闭操作,先膨胀,在腐蚀

thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel) #再来一个闭操作
cv_show('thresh',thresh)

# 计算轮廓

threshCnts, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
	cv2.CHAIN_APPROX_SIMPLE)
# 把经过很多次操作之后的轮廓,画在原始图像之中
cnts = threshCnts
cur_img = image.copy()
cv2.drawContours(cur_img,cnts,-1,(0,0,255),3) 
cv_show('img',cur_img)
locs = []

# 遍历轮廓
for (i, c) in enumerate(cnts):
	# 计算矩形
	(x, y, w, h) = cv2.boundingRect(c)
    # 计算长宽比例
	ar = w / float(h)

	# 选择合适的区域,根据实际任务来,这里的基本都是四个数字一组
	if ar > 2.5 and ar < 4.0:

		if (w > 40 and w < 55) and (h > 10 and h < 20):
			#符合的留下来
			locs.append((x, y, w, h))

# 将符合的轮廓从左到右排序
locs = sorted(locs, key=lambda x:x[0])
output = []

# 遍历每一个轮廓中的数字
for (i, (gX, gY, gW, gH)) in enumerate(locs):
	# initialize the list of group digits
	groupOutput = []

	# 根据坐标提取每一个组
	group = gray[gY - 5:gY + gH + 5, gX - 5:gX + gW + 5]
	cv_show('group',group)
	# 预处理
	group = cv2.threshold(group, 0, 255,
		cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
	cv_show('group',group)
	# 计算每一组的轮廓
	digitCnts,hierarchy = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL,
		cv2.CHAIN_APPROX_SIMPLE)
	digitCnts = contours.sort_contours(digitCnts,
		method="left-to-right")[0]

	# 计算每一组中的每一个数值
	for c in digitCnts:
		# 找到当前数值的轮廓,resize成合适的的大小
		(x, y, w, h) = cv2.boundingRect(c)
		roi = group[y:y + h, x:x + w]
		roi = cv2.resize(roi, (57, 88))
		cv_show('roi',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(image, (gX - 5, gY - 5),
		(gX + gW + 5, gY + gH + 5), (0, 0, 255), 1)
	cv2.putText(image, "".join(groupOutput), (gX, gY - 15),
		cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2)

	# 得到结果
	output.extend(groupOutput)

# 打印结果
print("Credit Card Type: {}".format(FIRST_NUMBER[output[0]]))
print("Credit Card #: {}".format("".join(output)))
cv2.imshow("Image", image)
cv2.waitKey(0)


这个就是最终的结果

总结

相关推荐
luoganttcc3 小时前
在 orin 上 安装了 miniconda 如何使用 orin 内置的 opencv
人工智能·opencv·计算机视觉
JinchuanMaster3 小时前
cv_bridge和openCV不兼容问题
人工智能·opencv·计算机视觉
Rhys..3 小时前
python自动化中(包括UI自动化和API自动化)env的作用和使用
python·ui·自动化
我的xiaodoujiao3 小时前
从 0 到 1 搭建完整 Python 语言 Web UI自动化测试学习系列 17--测试框架Pytest基础 1--介绍使用
python·学习·测试工具·pytest
Bellafu6663 小时前
selenium对每种前端控件的操作,python举例
前端·python·selenium
将车2444 小时前
自动化测试脚本环境搭建
python·测试工具·自动化
海祁4 小时前
【python学习】文件操作
python·学习
jianqiang.xue4 小时前
单片机图形化编程:课程目录介绍 总纲
c++·人工智能·python·单片机·物联网·青少年编程·arduino
heisd_14 小时前
在编译opencv出现的问题
人工智能·opencv·计算机视觉