2.图像增强算子
2.1 几何变换算子
? 图像的几何变换又称为图像空间变换, 它将一幅图像中的坐标位置映射到另一幅图像中的新坐标位置。
2.1.1 图像缩放
? 缩放只是调整图像的大小。为此,OpenCV 附带了一个函数cv.resize()。图像的大小可以手动指定,也可以指定缩放因子,使用了不同 的插值方法。首选的插值方法是cv2.INTER_AREA 用于缩小,cv.INTER_CUBIC(慢)和cv.INTER_LINEAR 用于缩放。默认情况下, 出于所有调整大小的目的,使用的插值方法为cv.INTER_LINEAR。您可以使用以下方法调整输入图像的大小:
? INTER_AREA基于区域像素关系的一种重采样或者插值方式.该方法是图像抽取的首选方法,它可以产生更少的波纹.
? INTER_LINEAR是双线性插值,默认情况下使用该方式进行插值;INTER_CUBIC是用3次方函数差值.
import numpy as np
import cv2 as cv
img = cv.imread('1.jpg')
res = cv.resize(img,None,fx=2, fy=2, interpolation = cv.INTER_CUBIC)
height, width = img.shape[:2]
res = cv.resize(img,(2*width, 2*height), interpolation = cv.INTER_CUBIC)
2.1.2 图像翻转
? opencv翻转图像有三种方式,分别时上下翻转、左右翻转和对角线翻转。
import cv2 as cv
import numpy as np
img = cv.imread(image_path, cv.IMREAD_COLOR)
dst1 = cv.flip(img, 0)
res1 = np.vstack((img, dst1))
dst2 = cv.flip(img, 1)
res2 = np.vstack((img, dst2))
dst3 = cv.flip(img, -1)
res3 = np.vstack((img, dst3))
2.1.3 仿射变换
? 仿射变换基本上是两个图像之间的关系。使用矩阵乘法(线性变换)的形式表示的转换,后跟向量加法(转换)关于这种关系的信息大概可以通过两种方式来实现:
-
我们知道X和T我们也知道它们是相关的。那么我们的任务就是找M -
我们知道M和X。为了获得T,我们只需要计算T= M? X。其中M的是确定的,或者它可以作为点之间的几何关系。 在OpenCV中,仿射变换可以通过函数warpAffine来支持,当然部分单独的函数也可以进行某个特定的变换,如缩放和旋转就有单独的变换函数。 img=cv2.imread('yun.jpg',cv2.IMREAD_GRAYSCALE)
cv2.imwrite('yun.jpg',img)
h,w=img.shape[:2]
A1=np.array([[0.5,0,0],[0,0.5,0]],np.float32)
A2=cv2.warpAffine(img,A1,(w,h),borderValue=126)
B1=np.array([[0.5,0,w/4],[0,0.5,h/4]],np.float32)
B2=cv2.warpAffine(img,B1,(w,h),borderValue=126)
C1=cv2.getRotationMatrix2D((w/2.0,h/2.0),30,1)
C2=cv2.warpAffine(img,C1,(w,h),borderValue=126)
2.2 图像金字塔算子
? 它的本质是对图像进行放缩变换。一般情况下,我们要处理是一副具有固定分辨率的图像。但是有些情况下,我们需要对同一图像的不同分辨率的子图像进行处理。比如,我们要在一幅图像中查找某个目标,比如脸,我们不知道目标在图像中的尺寸大小。这种情况下,我们需要创建一组图像,这些图像是具有不同分辨率的原始图像。我们把这组图像叫做图像金字塔(简单来说就是同一图像的不同分辨率的子图集合)。如果我们把最大的图像放在底部,最小的放在顶部,看起来像一座金字塔,故而得名图像金字塔。
? 图像金字塔最初用于机器视觉和图像压缩,一幅图像的金字塔是一系列以金字塔形状排列的分辨率逐步降低,且来源于同一张原始图的图像集合。其通过梯次向下采样获得,直到达到某个终止条件才停止采样。金字塔的底部是待处理图像的高分辨率表示,而顶部是低分辨率的近似。我们将一层一层的图像比喻成金字塔,层级越高,则图像越小,分辨率越低。
2.2.1 拉普拉斯金字塔
? 用来从金字塔低层图像重建上层未采样图像,在数字图像处理中也即是预测残差,可以对图像进行最大程度的还原,配合高斯金字塔一起使用。两者的简要区别:高斯金字塔用来向下降采样图像,而拉普拉斯金字塔则用来从金字塔底层图像中向上采样重建一个图像。要从金字塔第i层生成第i+1层(我们表示第i+1层为G_i+1),我们先要用高斯核对G_1进行卷积,然后删除所有偶数行和偶数列。当然的是,新得到图像面积会变为源图像的四分之一。按上述过程对输入图像G_0执行操作就可产生出整个金字塔。
当图像向金字塔的上层移动时,尺寸和分辨率就降低。OpenCV中,从金字塔中上一级图像生成下一级图像的可以用PryDown。而通过PryUp将现有的图像在每个维度都放大两遍。
图像金字塔中的向上和向下采样分别通过OpenCV函数 pyrUp 和 pyrDown 实现。
对图像向上采样:pyrUp函数
对图像向下采样:pyrDown函数
这里的向下与向上采样,是对图像的尺寸而言的(和金字塔的方向相反),向上就是图像尺寸加倍,向下就是图像尺寸减半。而如果我们按上图中演示的金字塔方向来理解,金字塔向上图像其实在缩小,这样刚好是反过来了。
但需要注意的是,PryUp和PryDown不是互逆的,即PryUp不是降采样的逆操作。这种情况下,图像首先在每个维度上扩大为原来的两倍,新增的行(偶数行)以0填充。然后给指定的滤波器进行卷积(实际上是一个在每个维度都扩大为原来两倍的过滤器)去估计“丢失”像素的近似值。
PryDown( )是一个会丢失信息的函数。为了恢复原来更高的分辨率的图像,我们要获得由降采样操作丢失的信息,这些数据就和拉普拉斯金字塔有关系了。
import cv2 as cv
def pyramid_demo(image):
level = 3
temp = image.copy()
pyramid_images = []
for i in range(level):
dst = cv.pyrDown(temp)
pyramid_images.append(dst)
cv.imshow('pyramid_down' + str(i), dst)
temp = dst.copy()
return pyramid_images
def lapalian_demo(image):
pyramid_images = pyramid_demo(image)
level = len(pyramid_images)
for i in range(level - 1, -1, -1):
if (i - 1) < 0:
expand = cv.pyrUp(pyramid_images[i], dstsize=image.shape[:2])
lpls = cv.subtract(image, expand)
cv.imshow("lapalian_down" + str(i), lpls)
else:
expand = cv.pyrUp(pyramid_images[i], dstsize=pyramid_images[i - 1].shape[:2])
lpls = cv.subtract(pyramid_images[i - 1], expand)
cv.imshow("lapalian_down_" + str(i), lpls)
2.3 图像平滑算子
? 在处理和传输数字图像的过程中可能会受到不同的噪声的干扰,图像噪声会引起图像质量降低、图像变得模糊,使图像的特征被淹没、而通过除去噪声来达到图像增强的目的的操作被称为图像平滑处理
2.3.1 均值滤波
? 假设被噪声污染的数字图像空间含有 N*N 个像素点,而这幅数字图像中每一个像素点值可以通过领域内几个像素点的平均值计算出来,采用平滑图像处理方法就可以得到一幅新的图像,这个过程就被称为均值滤波。
import cv2
import numpy as np
k = 5
img0 = cv2.imread("1.jpg")
img1 = cv2.cvtColor(img0, cv2.COLOR_BGR2GRAY)
h, w = img0.shape[:2]
r5=cv2.blur(img1,(k,k))
2.3.2 方框滤波
? 方框滤波是均值滤波的一种形式。在均值滤波中,滤波结果的像素值是任意一个点的邻域平均值,等于各邻域像素值之和的均值,而在方框滤波中,可以自由选择是否对均值滤波的结果进行归一化,即可以自由选择滤波结果是邻域像素值之和的平均值,还是邻域像素值之和。
import cv2
import matplotlib.pyplot as plt
img = cv2.imread('1.jpg')
result = cv2.boxFilter(source, -1, (5, 5), normalize=False)
2.3.3 高斯滤波
? 为了克服简单局部平均法的弊端(图像模糊),目前已提出许多保持边缘、细节的局部平滑算法。它们的出发点都集中在如何选择邻域的大小、形状和方向、参数加平均及邻域各店的权重系数等。
? 图像高斯平滑也是邻域平均的思想对图像进行平滑的一种方法,在图像高斯平滑中,对图像进行平均时,不同位置的像素被赋予了不同的权重。高斯平滑与简单平滑不同,它在对邻域内像素进行平均时,给予不同位置的像素不同的权值.
import cv2
k = 5
img=cv2.imread('1.jpg')
blur=cv2.GaussianBlur(img,(k,k),0)
2.3.4 中值滤波
? 中值滤波器遍历信号的每个元素(在这种情况下为图像),并用其相邻像素的中位数(位于估计像素周围的正方形邻域)中替换每个像素。
import cv2
k = 5
img=cv2.imread('1.jpg')
blur=cv2.medianBlur(img,k)
2.3.5 双边滤波
? 双边滤波是一种非线性的滤波方法,是结合图像的空间邻近度和像素值相似度的一种折衷处理,同时考虑空间与信息和灰度相似性,达到保边去噪的目的,具有简单、非迭代、局部处理的特点。之所以能够达到保边去噪的滤波效果是因为滤波器由两个函数构成:一个函数是由几何空间距离决定滤波器系数,另一个是由像素差值决定滤波器系数.
? 相对高斯滤波去降噪,会较明显地模糊边缘,对于高频细节的保护效果并不明显。
?
import cv2
img=cv2.imread('1.jpg')
blur = cv2.bilateralFilter(img,9,75,75)
参数解释:
2.4 傅立叶变换算子
? 表示能将满足一定条件的某个函数表示成三角函数(正弦和/或余弦函数)或者它们的积分的线性组合。在不同的研究领域,傅立叶变换具有多种不同的变体形式,如连续傅立叶变换和离散傅立叶变换。傅里叶变换是一种分析信号的方法,它可分析信号的成分,也可用这些成分合成信号。许多波形可作为信号的成分,比如正弦波、方波、锯齿波等,傅里叶变换用正弦波作为信号的成分。对于图像,将时域转化为频域。
2.4.1 傅立叶变换算子
? 在频域范围内可以研究图像增强,可以利用频率成分和图像外表之间的对应关系。一些在空间域表述困难的增强任务,在频率域中变得非常普通滤波在频率域更为直观,它可以解释空间域滤波的某些性质,可以在频率域指定滤波器,做反变换,然后在空间域使用结果滤波器作为空间域滤波器的指导,一旦通过频率域试验选择了空间滤波,通常实施都在空间域进行。
? 意义:1、图像的频率是表征图像中灰度变化剧烈程度的指标,是灰度在平面空间上的梯度**(灰度变化得快频率就高,灰度变化得慢 频率就低)**
? 2、图像上某一点与邻域点灰度值差异的强弱,即梯度的大小,也即该点的频率的大小(差异/梯度越大,频率越高,能量越低, 在频谱图上就越暗。差异/梯度越小,频率越低,能量越高,在频谱图上就越亮。换句话说,频率谱上越亮能量越高,频率 越低,图像差异越小/平缓)。一般来讲,梯度大则该点的亮度强,否则该点亮度弱。频谱图,也叫功率图。
dft = cv2.dft(np.float32(img2), flags = cv2.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)
magnitude_spectrum1 = 20*np.log(cv2.magnitude(dft_shift[:, :, 0], dft_shift[:, :, 1]))
plt.subplot(121), plt.imshow(img2, cmap = 'gray')
plt.title('原图'), plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(magnitude_spectrum1, cmap = 'gray')
plt.title('频谱图'), plt.xticks([]), plt.yticks([])
plt.show()
2.5 形态学操作算子
? 形态学算子的主要思想是用一定形状的结构元素在图像中抽取出相应的某些结构,通常可以用于图像的滤波、分割、分类等处理。形态学算子有腐蚀、膨胀、开和闭四种。
2.5.1 图像腐蚀
? 腐蚀是一种消除边界点,使边界向内部收缩的过程。可以用来消除小且无意义的物体。腐蚀的算法: 用3x3的结构元素,扫描图像的每一个像素 用结构元素与其覆盖的二值图像做“与”操作 如果都为1,结果图像的该像素为1。否则为0。 结果:使二值图像减小一圈
import cv2
img0 = cv2.imread("1.jpg")
img1 = cv2.cvtColor(img0, cv2.COLOR_BGR2GRAY)
erosion1 = cv2.erode(img1, kernel1)
2.5.2 图像膨胀
? 膨胀是将与物体接触的所有背景点合并到该物体中,使边界向外部扩张的过程。可以用来填补物体中的空洞。膨胀的算法: 用3x3的结构元素,扫描图像的每一个像素 用结构元素与其覆盖的二值图像做“与”操作 如果都为0,结果图像的该像素为0。否则为1 结果:使二值图像扩大一圈
import cv2
img0 = cv2.imread("1.jpg")
img1 = cv2.cvtColor(img0, cv2.COLOR_BGR2GRAY)
dilation1 = cv2.dilate(img1, kernel1)
2.5.3 开运算
? 虽然腐蚀可以将粘连的目标进行分离,膨胀可以将断续的目标进行连接,但是无论是腐蚀还是膨胀处理后,目标的尺寸都会产生变化。 开运算,闭运算就是为了解决这个问题。
? 先腐蚀后膨胀的过程称为开运算。用来消除小物体、在纤细点处分离物体、平滑较大物体的边界的同时并不明显改变其面积。
import cv2
import numpy as np
ori = cv2.imread(r"1.jpg")
k = np.ones((10, 10), np.uint8)
opening = cv2.morphologyEx(ori, cv2.MORPH_OPEN,k)
2.5.4 闭运算
? 先膨胀后腐蚀的过程称为闭运算。用来填充物体内细小空洞、连接邻近物体、平滑其边界的同时并不明显改变其面积。
import cv2
import numpy as np
ori = cv2.imread(r"1.jpg")
k1 = np.ones((8, 8), np.uint8)
k2 = np.ones((15, 15), np.uint8)
closing1 = cv2.morphologyEx(ori, cv2.MORPH_CLOSE, k1)
closing2 = cv2.morphologyEx(ori, cv2.MORPH_CLOSE, k2)
2.5.5 梯度运算
? 图像的梯度操作就是将图像膨胀的结果减去腐蚀的结果,以得到图像的边缘。将形态学运算函数中第二个参数改为cv2.MORPH_GRADIENT即可。即cv2.morphologyEx(img,cv2.MORPH_GRADIENT,kernel)
?
import cv2
import numpy as np
ori = cv2.imread(r"1.jpg")
result=cv2.morphologyEx(ori, cv2.MORPH_GRADIENT, kernel)
2.5.6 礼帽运算
? 礼帽操作就是用原图减去开运算的图像,以得到前景图外面的毛刺噪声,因为开运算可以消除小物体,所以通过做差就可以将消除掉的小物体提取出来,使用时修改形态学运算函数参数为cv2.MORPH_TOPHAT即可
import cv2
import numpy as np
ori = cv2.imread(r"1.jpg")
result=cv2.morphologyEx(ori, cv2.MORPH_TOPHAT, kernel)
2.5.7 黑帽运算
? 黑帽就是用原图减去闭运算的图像,以得到前景图像内部的小孔等噪声。使用时修改形态学运算函数参数为cv2.MORPH_BLACKHAT即可。
import cv2
import numpy as np
ori = cv2.imread(r"1.jpg")
result=cv2.morphologyEx(ori, cv2.MORPH_BLACKHAT, kernel)
2.6 直方图均衡化
? 图像的空域处理是一种重要的图像处理技术,这类方法直接以图像的像素操作为基础,主要分为灰度变换和空域滤波两大类,直方图均衡化(Histogram equalization)就是一种常用的灰度变换方法。
? 通常,暗图像直方图的分量集中在灰度较低的一端,而亮图像直方图分量偏向于灰度较高的一端,如下图
2.6.1 直方图均衡化
? 从图中可以得到这样的结论:如果一幅图像的灰度直方图几乎覆盖了整个灰度的取值范围,并且除了个别灰度值的个数较为突出,整个灰度值分布近似于均匀分布,那么这幅图像就具有较大的灰度动态范围和较高的对比度,同时图像的细节更为丰富。已经证明,仅仅依靠输入图像的直方图信息,就可以得到一个变换函数,利用该变换函数可以将输入图像达到上述效果,该过程就是直方图均衡化。
import cv2
import numpy as np
from matplotlib import pyplot as plt
'''
calcHist-计算图像直方图
函数原型:calcHist(images,channels,mask,histSize,ranges,hist=None,accumulate=None)
images:图像矩阵,例如:[image]
channels:通道数,例如:0
mask:掩膜,一般为:None
histSize:直方图大小,一般等于灰度级数
ranges:横轴范围
'''
img = cv2.imread("1.jpg", 0)
hist = cv2.calcHist([img],[0],None,[256],[0,256])
plt.figure()
plt.title("Grayscale Histogram")
plt.xlabel("Bins")
plt.ylabel("# of Pixels")
plt.plot(hist)
plt.xlim([0,256])
plt.show()
'''
equalizeHist—直方图均衡化
函数原型: equalizeHist(src, dst=None)
src:图像矩阵(单通道图像)
dst:默认即可
'''
dst = cv2.equalizeHist(img)
hist = cv2.calcHist([dst],[0],None,[256],[0,256])
plt.figure()
plt.hist(dst.ravel(), 256)
plt.show()
cv2.imshow("Histogram Equalization",np.hstack([img, dst]))
cv2.waitKey(0)
img = cv2.imread("1.jpg", 1)
cv2.imshow("src", img)
(b, g, r) = cv2.split(img)
bH = cv2.equalizeHist(b)
gH = cv2.equalizeHist(g)
rH = cv2.equalizeHist(r)
result = cv2.merge((bH, gH, rH))
cv2.imshow("dst_rgb", result)
cv2.waitKey(0)
2.7 Gamma变换
? 伽马变换就是用来图像增强,其提升了暗部细节,简单来说就是通过非线性变换,让图像从暴光强度的线性响应变得更接近人眼感受的响应,即将漂白(相机曝光)或过暗(曝光不足)的图片,进行矫正。
2.7.1 gamma变换
? Gamma变换是对输入图像灰度值进行的非线性操作,使输出图像灰度值与输入图像灰度值呈指数关系:
? 这个指数即为Gamma。
? 经过Gamma变换后的输入和输出图像灰度值关系如图1所示:横坐标是输入灰度值,纵坐标是输出灰度值,蓝色曲线是gamma值小于1时的输入输出关系,红色曲线是gamma值大于1时的输入输出关系。可以观察到,当gamma值小于1时(蓝色曲线),图像的整体亮度值得到提升,同时低灰度处的对比度得到增加,更利于分辩低灰度值时的图像细节。
? Gamma大于1时,对图像的灰度分布直方图具有拉伸作用(使灰度向高灰度值延展),而小于1时,对图像的灰度分布直方图具有收缩作用(是使灰度向低灰度值方向靠拢).
import cv2
img0 = cv2.imread('12.jpg')
hist_b = cv2.calcHist([img0],[0],None,[256],[0,256])
hist_g = cv2.calcHist([img0],[1],None,[256],[0,256])
hist_r = cv2.calcHist([img0],[2],None,[256],[0,256])
def gamma_trans(img,gamma):
gamma_table = [np.power(x/255.0,gamma)*255.0 for x in range(256)]
gamma_table = np.round(np.array(gamma_table)).astype(np.uint8)
return cv2.LUT(img0,gamma_table)
img0_corrted = gamma_trans(img0, 0.5)
cv2.imshow('img0',img0)
cv2.imshow('gamma_image',img0_corrted)
cv2.imwrite('gamma_image.png',img0_corrted)
hist_b_c =cv2.calcHist([img0_corrted],[0],None,[256],[0,256])
hist_g_c =cv2.calcHist([img0_corrted],[1],None,[256],[0,256])
hist_r_c =cv2.calcHist([img0_corrted],[2],None,[256],[0,256])
fig = plt.figure('gamma')
pix_hists = [[hist_b, hist_g, hist_r],
[hist_b_c, hist_g_c, hist_r_c]]
pix_vals = range(256)
for sub_plt, pix_hist in zip([121, 122], pix_hists):
ax = fig.add_subplot(sub_plt, projection='3d')
for c, z, channel_hist in zip(['b', 'g', 'r'], [20, 10, 0], pix_hist):
cs = [c] * 256
ax.bar(pix_vals, channel_hist, zs=z, zdir='y', color=cs, alpha=0.618, edgecolor='none', lw=0)
ax.set_xlabel('Pixel Values')
ax.set_xlim([0, 256])
ax.set_ylabel('Count')
ax.set_zlabel('Channels')
plt.show()
cv2.waitKey()
|