1.图像直方图
1.1 直方图计算
直方图是一种用于可视化数据分布的图表形式,它显示了数据在各个数值范围内的频率或数量。直方图可以帮助我们了解数据的分布情况、寻找异常值和识别数据模式。在计算机视觉中,直方图也经常用于图像处理和分析。通过统计图像中不同灰度级别的像素数量,可以获得图像的灰度分布,进而进行图像增强、对比度调整、颜色校正等操作。
首先每个图像单独拿出来其实就是一堆像素点,范围在0-255
之间,而直方图就是统计这一张图片各个像素点值的个数,如下图所示,横坐标代表不同像素点取值,纵坐标代表出现的次数。
在Opencv
中,可以使用cv2.calcHist()
函数来计算图像的直方图。语法如下:
css
cv2.calcHist(images,channels,mask,histSize,ranges)
cv2.calcHist()
函数的各个参数解释:
images
: 需要计算直方图的图像,以方括号的形式传递,表示可以处理多个图像。例如[img]channels:
指定计算直方图的通道索引,对于灰度图像,只有一个通道,因此传递0。如果是彩色图像,则传入的参数可以是 0,1,2,它们分别对应着 BGR。mask
: 用于指定掩码图像,如果不需要,则设置为None
,即统计整幅图像的直方图Size
: 表示直方图的大小,即有多少个bins,例如设置为256,表示将图像像素值分成256个区间进行统计。ranges
表示像素值的范围。常为 [0, 256]
接下来我们来看一下具体的案例:
我们照样是读取小狗洋气
的照片,来看一下它的直方图分布
ini
import cv2 #opencv读取的格式是BGR
# 定义图像显示函数
def cv_show(img,name):
cv2.imshow(name,img)
cv2.waitKey()
cv2.destroyAllWindows()
img = cv2.imread('yangqi.jpg',0) #0表示读取灰度图
# 直方图计算
hist = cv2.calcHist([img],[0],None,[256],[0,256])
hist.shape
scss
(256, 1)
hist
返回每个0-255共256个像素值出现的个数,我们打印出来看一下它的分布情况
scss
plt.hist(img.ravel(),256);
plt.show()
1.2 分通道读取
接下来我们以彩色图为例,统计每一个通道的直方图。这里要注意的是,opencv的颜色顺序是b、g、r。
接下来我们首先读取彩色图,然后利用一个for循环来遍历每个通道得到对应的直方图并打印。
ini
# 读取彩色图
img = cv2.imread('yangqi.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])
1.3 mask操作(掩码操作)
直方图掩码操作是一种通过应用掩码图像来选择特定区域,并计算该区域的直方图的方法。它在图像处理和计算机视觉领域中非常常用,用于分析图像中感兴趣区域的像素分布情况。
在直方图掩码操作中,我们使用两个图像:原始图像和掩码图像。
- 原始图像:这是我们要进行操作和分析的图像。
- 掩码图像:这是一个二值图像,用于指定原始图像中我们感兴趣的区域。掩码图像通常是灰度图像,其中具有我们感兴趣的区域的像素值设为255,而其他区域的像素值为零。
我们先来创建一个掩码图像
css
# 创建mast
mask = np.zeros(img.shape[:2], np.uint8)
print (mask.shape)
mask[100:200, 50:150] = 255
cv_show(mask,'mask')
接下来我们应用掩码:
使用OpenCV的cv2.bitwise_and()
函数将原始图像和掩码图像进行操作。产生一个新的图像,其中只有掩码图像非零像素对应的原始图像像素会被保留下来,其余像素置为零,相当于起到了一个捕捉特定区域的作用
css
masked_img = cv2.bitwise_and(img, img, mask=mask)#掩码操作
cv_show(masked_img,'masked_img')
计算直方图:使用OpenCV的cv2.calcHist()
函数计算掩码后图像的直方图。可以指定通道索引,直方图大小和像素值范围等参数。
css
hist_full = cv2.calcHist([img], [0], None, [256], [0, 256])
hist_mask = cv2.calcHist([img], [0], mask, [256], [0, 256])
绘制直方图:
使用Matplotlib库绘制直方图,并通过plt.show()
显示图像。
scss
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()
通过直方图掩码操作,我们可以获取特定区域的像素分布情况,从而对图像进行更精细的分析和处理。这种方法常用于目标检测、图像分割、背景建模等应用中,使得我们可以专注于感兴趣的区域并获得相关的统计信息。
2.傅里叶变换
傅里叶变换(Fourier Transform)
是一种将信号在时域(时间域)
和频域(频率域)
之间进行转换的数学工具,具体的数学理论不做详细探讨。它的基本思想是将一个信号分解成一组不同频率的正弦和余弦函数的叠加。
在图像处理中,傅里叶变换常被用于分析和处理图像的频域信息,通过将图像从空间域转换到频域,我们可以了解图像中各个频率成分的贡献,针对不同频率,我们有以下作用:
- 高频:变化剧烈的灰度分量,例如边界
- 低频:变化缓慢的灰度分量
2.1 频率转换结果
对于二维图像而言,我们常用离散傅里叶变换(Discrete Fourier Transform, DFT)
离散傅里叶变换(DFT)是将图像从空间域转换到频域的一种方法,它通过计算图像中每个像素点的频谱来描述图像的频率特征。DFT输出的结果是一个复数数组,其中每个元素表示该频率分量的振幅和相位。因此,我们需要将这个输出结果转换成图像格式。
在opencv中我们调用cv2.dft()函数实现dft转换,输入图形要先转换成np.float32的格式,此外,cv2.dft()返回的结果是双通道的(实部,虚部),通常还需要转换成图像格式才能展示(0,255)。
ini
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
dft_shift = np.fft.fftshift(dft)#shift转换,将频率为0的部分转换到中心位置。
# 得到灰度图能表示的形式
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()
右图是经过频率转换之后的结果,可以看到中心位置比较亮,呈现一个中心向外发散的情况,越在中心频率越低,越在外边频率越高
2.2 高通和低通滤波器
低通滤波器和高通滤波器是信号处理中常见的两种滤波器类型,用于在频域上选择特定频率范围内的信号分量。
低通滤波器(Low-pass Filter)
的作用是保留信号中较低频率的分量,并削弱或消除高频分量,相当于把边界抹除了,使得图像变得模糊。低通滤波器可以用来平滑信号、去除噪声或降低信号频率。 低通滤波器的频率响应通常是一个平坦的直线,直到截止频率,截止频率之后,频率响应开始逐渐下降。
我们下面来看一下如何实现一个经过低通滤波器之后再返回源图像
ini
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结果
dft_shift = np.fft.fftshift(dft) #使用shift将0转换到中心位置
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 #只在中心位置为1,其余位置都为0
# IDFT:DFT的逆变换,将dft的结果变换的源图像
fshift = dft_shift*mask #通过掩码操作只保留中心位置
f_ishift = np.fft.ifftshift(fshift)# 做shift的逆变换
img_back = cv2.idft(f_ishift)# 做dft的逆变换
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()
左图是我们的原始图像,右图是经过低通道后得到的图像结果,可以看到图像变得模糊,并且边界点不是很明显,主要集中在中心人脸位置。
高通滤波器(High-pass Filter): 高通滤波器允许通过频率范围内的高频信号,而抑制低频信号,相当于将边界锐化,会使得图像细节增强。它的作用是保留信号中的高频分量,并减弱或消除低频信号。高通滤波器可以用来强调信号中的快速变化部分,滤除基线漂移或去除低频噪声。
ini
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结果
dft_shift = np.fft.fftshift(dft) #使用shift将0转换到中心位置
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 #只在中心位置为0,其余位置都为1
# IDFT:DFT的逆变换,将dft的结果变换的源图像
fshift = dft_shift*mask #通过掩码操作只保留中心位置
f_ishift = np.fft.ifftshift(fshift)# 做shift的逆变换
img_back = cv2.idft(f_ishift)# 做dft的逆变换
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()
可以看到图像只保留了一些边界信息,主要是Lena
的身体轮廓。
最后我们来思考一下为什么要使用傅里叶变换呢,为什么我们要把图像转换到一个频率当中做处理呢?其实最主要的原因就是这样做更简单高效。经过傅里叶变换,这个频率当中分为低频和高频,层次分明,此时做各种变换都特别容易。如果在原始图像中进行变换,会非常复杂,不利于我们工程去实现迭代。因此,我们通常都会把图像先映射到频率当中,在频率当中做处理。