Digital signal process
Digital signal process (DSP)
DSP 图像信号处理,主要介绍基于opencv图像处理中常用的滤波、仿射变换、形态学操作和图像增强算法
1 Filtering
数字滤波器用于模糊和锐化数字图像。滤波可以通过以下方式进行
- 在空间域与专门设计的核(滤波器阵列)卷积
- 在频率(傅立叶)域屏蔽特定频率区域
滑动窗口
下面的这个例子中选择了一个ksize=3×3的滑动窗口(或称滤波器模板、kernel),如黄色部分所示。用这个ksize=3×3的窗口作用于原始图像上的每一个像素,如下图的绿色部分所示,被这个窗口覆盖的9个像素点都参与计算,这样在该像素点上就会得到一个新的像素值,当窗口沿着图像逐个像素进行计算,就会得到一幅新的图像。
均值平滑
均值平滑的滑动窗口所有系数为1/(窗口高X窗口宽),新生成的像素值就是窗口中心点以及周围所有像素值相加后的平均值。比如选择一个ksize=5×5的窗口,新图像的(x,y)点的像素值用numpy表示为 np.sum(i[x-2:i+3,y-2:y+3])/(5*5)。
python
dst = cv2.blur(src, ksize[, dst[, anchor[, borderType]]])
参数含义:
- src:源图像,通道数不限,数据类型必须为CV_8U, CV_16U, CV_16S, CV_32F or CV_64F;
- ksize:kernel尺寸、窗口大小,二元组类型,元素值可以是偶数或奇数;
- anchor:锚点,默认为(-1,-1),作用于滑动窗口的中心点;
- borderType:边界处理类型;
- 从运行效果可以看到,ksize越大,图像越模糊,清晰度越低
中值平滑
中值平滑和均值平滑一样也用到了滑动窗口,但是它并不是计算滑动窗口中的某种加权和,而是使用原图像滑动窗口中所有像素值排序后的中值作为新图像的像素值。
python
dst = cv2.medianBlur(src, ksize[, dst])
参数含义:
- src:源图像,通道数可以是1,3或4,当ksize为3或者5时,数据类型可以是CV_8U, CV_16U, CV_32F,当使用更大的ksize时,数据类型只能是CV_8U;
- ksize:kernel尺寸、窗口大小,整数型,大于1的奇数值;
从上面的对比可以看到绿色的原始图像,其像素值变化的非常"剧烈",有很多波峰或波谷,但是经过平滑处理后的像素值(红色和蓝色)则显得平滑的多。
小结:平滑处理是图像滤波的一种,可以看做是低通滤波,它会消除图像的高频"信号",让图像看起来更模糊、平滑,通过将变化前后的图像像素值绘制曲线可以更形象地观察到这种平滑效果。
高斯平滑
二维高斯分布,其中x表示邻域像素距离中心点水平方向的间距,y表示垂直方向的间距
- 从这个5×5大小的矩阵也能看到,位于正中心的点取值为0.2491172是最大值的点,紧邻着正中心距离为1的周围4个点的值0.11405416是第2大的点,依次越往外取值越小。
python
dst = cv2.GaussianBlur(src, ksize, sigmaX[, dst[, sigmaY[, borderType]]])
参数含义:
-
src:通道数任意,实际处理是分通道处理;图像深度只能是CV_8U, CV_16U, CV_16S, CV_32F or CV_64F;
-
ksize:元组类型,窗口大小,宽度和高度可以不一样,但是必须是正的奇数;如果设置为0,则根据sigma计算得到。
-
sigmaX:图像X方向的标准差,对应前述二维高斯分布的σ1;
-
sigmaY:图像Y方向的标准差,对应前述二维高斯分布的σ2,如果传入0,会等于sigmaX,如果sigmaX和sigmaY都传入0,sigmaX和sigmaX则根据ksize计算;
-
borderType:边界处理方式;
-
ksize越大,图像越模糊
- 对应到高斯分布曲线的特性,当sigma越大时,原点的取值越小,周围点的取值更大,对应到图像上中心点的权重越低,周围点权重越高,所以sigma越大图像越模糊。
双边平滑
均值、中值、高斯平滑的去躁是一种"无差别攻击",所有的像素都受到同一个加权系数的影响,所以在平滑过程中也会影响到图像的边沿(像素值突变的地方),接下来要介绍的双边滤波则可以在去除噪声的同时又能保持图像的边沿,也就是传说中的"去噪保边"。
第1个exp()函数是空间距离加权系数的简化,1)前面的1/2πσ1σ2系数没有了,因为该系数在σ1和σ2确定后最后归一化的时候是会被消除掉的;2)双边平滑x和y方向用的σ1=σ2,所以用一个σs代替;3)u值在高斯平滑中也一直设置为0,这里也直接去掉了。其中x和y分别表示邻域像素和中心点X和Y方向的距离。
第2个exp()函数也是高斯函数,v(x0,y0)表示中心点的像素值,v(x,y)表示距离中心点距离为(x,y)的像素值,所以这部分的值就和像素差值有关。如果像素差值为0,这部分系数的值就为1,所以就等价于高斯平滑的系数;如果2个像素差值非常大,这个部分exp()的值就向0靠拢,最后的f(x,y)就向0靠拢,这时该像素值对中心点的影响就非常小。所以如果2个像素值差异非常大时,这时新生成图像的像素值就不会被这个差异极大的像素受影响,从而保持该差异,达到"保边"的效果。
2个exp()函数就是双边平滑的"双边"的含义,它并不是指图像的X和Y(行、列)2个方向,而是指在像素差和空间距离的2个"边"。
python
dst = cv2.bilateralFilter(src, d, sigmaColor, sigmaSpace[, dst[, borderType]])
参数含义:
- src:8bit或浮点类型;1或3通道;
- d:窗口大小,如果为非正数,根据sigmaSpace计算;d>5时速度会比较慢,当噪声比较严重时可以选择d>=9,但是此时不适合对时间敏感的处理;
- sigmaColor:亮度差的sigma参数;
- sigmaSpace:空间距离的sigma参数,同时作用于图像的X和Y(行、列)2个方向;
- borderType:边界处理方式;
- 从运行结果可看到,在颜色突然变化的地方(边沿),高斯平滑只保留了一半左右的边界,而双边平滑几乎将所有的边界保留下来,而且高斯平滑的边界亮度平均值也没有双边平滑高。
小结:高斯平滑对比均值和中值平滑其取值更符合"惯例",在空间距离上距离越近的像素用来计算新像素的值其权重越大。均值平滑、中值平滑和高斯平滑会对整幅图像实现无差别的平滑,一个固定系数的滑动窗口作用于整个图像,所以平滑后的图像虽然处理掉了噪声,但是边沿部分也会被削弱。而双边平滑在高斯平滑使用的系数基础上乘以像素差值的高斯函数,和中心点像素差值越大整个系数值越小,最后就能达到去躁保边的效果。
2 Affine transformations
Transformation Name | Affine Matrix | Example |
---|---|---|
Identity 恒等 | ||
Reflection 反射 | ||
Scale 缩放 | ||
Rotate 旋转 | where θ = π/6 =30° | |
Shear 倾斜 |
3 Morphological transformation
腐蚀
腐蚀操作可以将边界的白色(前景)像素"腐蚀"掉,但仍能保持大部分白色。类似平滑处理的滑动窗口,用某种结构元在图像上滑动,当结构元覆盖原始图像中的所有像素都为"1"时,新图像中该像素点的值才为"1"(CV8U为255)。腐蚀可以用来去除噪声、去掉"粘连"。
python
cv2.erode(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]]) ->dst
参数含义:
- src:通道数任意;图像深度只能是CV_8U, CV_16U, CV_16S, CV_32F or CV_64F;
- kernel:可以由getStructuringElement()构建;
- dst:输出图像,通道数和数据类型同src;
- anchor:锚点,默认使用(-1,-1)表示中心点;
- iterations:腐蚀次数;
- borderType:边界类型;
- borderValue:边界值;
- 从运行结果可以看到kernel的ksize越大,iterations次数越多,图像看起来越"廋"
膨胀
膨胀是腐蚀的逆操作,可以将边界的白色(前景)像素"生长"扩大。滑动窗口经过白色像素时,只要结构元中有1个像素为"1"时,新图像中该像素点的值就为"1"(CV8U为255)。膨胀可以用来增强连接、填充凹痕
python
cv2.dilate(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]]) ->dst
参数含义:
- src:通道数任意;图像深度只能是CV_8U, CV_16U, CV_16S, CV_32F or CV_64F;
- kernel:可以由getStructuringElement()构建;
- dst:输出图像,通道数和数据类型同src;
- anchor:锚点,默认使用(-1,-1)表示中心点;
- iterations:膨胀次数;
- borderType:边界类型;
- borderValue:边界值;
- 从运行结果可以看到kernel的ksize越大,iterations次数越多,图像看起来越"胖"
结构元生成
结构元生成函数用来生成形态学变换的kernel参数
python
cv2.getStructuringElement(shape, ksize[, anchor]) ->retval
参数含义:
- shape:结构元(kernel)的形状;
- ksize:结构元(kernel)的大小;
- anchor:锚点,默认使用(-1,-1)表示中心点;
shape | |
---|---|
cv2.MORPH_CROSS | 十字交叉形,在锚点坐标的水平和竖直方向的元素为1,其他为0 |
cv2.MORPH_ELLIPSE | 椭圆形 |
cv2.MORPH_RECT | 方形,所有的数值均为1 |
python
MORPH_RECT kernel:
[[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]
[1 1 1 1 1 1 1]
[1 1 1 1 1 1 1]
[1 1 1 1 1 1 1]]
MORPH_ELLIPSE kernel:
[[0 0 0 1 0 0 0]
[0 1 1 1 1 1 0]
[1 1 1 1 1 1 1]
[1 1 1 1 1 1 1]
[1 1 1 1 1 1 1]
[0 1 1 1 1 1 0]
[0 0 0 1 0 0 0]]
MORPH_CROSS kernel:
[[0 0 0 1 0 0 0]
[0 0 0 1 0 0 0]
[0 0 0 1 0 0 0]
[1 1 1 1 1 1 1]
[0 0 0 1 0 0 0]
[0 0 0 1 0 0 0]
[0 0 0 1 0 0 0]]
- 从运行结果可以看到当使用方形MORPH_RECT的结构元时,新图像的边界看起来仍然是方方正正的,但是使用十字形MORPH_CROSS和椭圆形MORPH_ELLIPSE的结构元时,边界要显得"圆滑"的多
cv2.morphologyEx
python
cv2.morphologyEx(src, op, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]]) ->dst
参数含义:
- src:源图像,通道数任意;图像深度只能是CV_8U, CV_16U, CV_16S, CV_32F or CV_64F;其中op为cv2.MORPH_HITMISS时仅支持CV_8UC1;
- op:变换方式;
- kernel:可以由getStructuringElement()构建,op为cv2.MORPH_HITMISS时则由子图构建;
- dst:输出图像,通道数和数据类型同src;
- anchor:锚点,默认使用(-1,-1)表示中心点;
- iterations:迭代次数;
- borderType:边界类型;
- borderValue:边界值;
形态学变换 | op标志位 | 与erode和dilate关系 |
---|---|---|
腐蚀 | cv2.MORPH_ERODE | dst=erode(src,element) |
膨胀 | cv2.MORPH_DILATE | dst=dilate(src,element) |
开操作 | cv2.MORPH_OPEN | dst=dilate(erode(src,element)) |
闭操作 | cv2.MORPH_CLOSE | dst=erode(dilate(src,element)) |
梯度 | cv2.MORPH_GRADIENT | dst=dilate(src,element)−erode(src,element) |
顶帽 | cv2.MORPH_TOPHAT | dst=src−open(src,element) |
黑帽 | cv2.MORPH_BLACKHAT | dst=close(src,element)−src |
击中击不中 | cv2.MORPH_HITMISS | dst=erode(src,element) & erode(src,element) |
开操作
开操作的实质是先进行腐蚀再膨胀,可以用来消除小于结构元大小的细小区域
闭操作
闭操作实际上是先进行膨胀再腐蚀,因为膨胀可以用来填充孔洞、修复缺失的连接,但是同时也会导致白色轮廓增大,当用同样的结构元(kernel)再进行一次腐蚀操作后,就可以保持外形轮廓和原来的一致
形态学梯度
形态学梯度操作是用膨胀图像减去腐蚀图像的结果,因为膨胀可以增大边沿,腐蚀会缩小边沿,所以形态学梯度变换就能将轮廓提取出来
顶帽变换
顶帽变换是用原图减去开操作图像,因为开操作会去除小于结构元的小区域,原图减去开操作图像后,会将开操作去除的小区域保留下来
黑帽变换
黑帽变换和顶帽变换则相反,是将闭操作后的图像减去原图,因为闭操作会填充孔洞(小的黑色区域),孔洞部分变成白色,而原图中仍然为黑色,这样就会将原图中的孔洞保留下来并变为白色区域。
击中击不中变换
击中击不中变换可以用来在原图中查找子图,假设要查找的图像中包含了多种子图,可以利用某个子图构造出kernel,经过击中击不中变换就能在该子图中心保留一个非零的点。注意这里构造kernel不再是使用getStructuringElement(),而是需要用子图构造。一个构造kernel的例子如下,首先从子图中读取图像,然后和要做变换的原图做一样的阈值化,接下来构造一个和子图大小一样类型为np.int8型的kernel,其中子图阈值化后值为255的位置设置为1,阈值化后值为0的位置设置为-1
小结:开操作,闭操作,顶帽,黑帽,形态学梯度,击中击不中变换都是以膨胀、腐蚀为基础。其中击中击不中变换需要从子图构造kernel,而其他几种形态学变换则用getStructuringElement()构造。
4 Contrast enhancement
直方图均衡
equalizeHist()可以实现图像的直方图均衡,它是一种全局直方图均衡,考量的对象是整幅图像。
接口形式:
python
cv2.equalizeHist(src[, dst]) ->dst
参数含义:
- src:输入图像,8bit单通道;
- dst:均衡后的输出图像,类型同src;
- 从运行结果看,原来整体较暗的图像,经过直方图均衡后,整体要亮一些。这点也和直方图显示的效果是一样的,原图的直方图整体要偏左,而经过直方图均衡后的直方图在x轴上分布更均匀。
自适应直方图均衡
equalizeHist()方法是一种全局直方图均衡方法,在某些场合可能会导致原本对比度高的区域反而变得更低,像下面这个例子中原图上半部分的树叶对比度高,但是下半部分对比度低,如果使用equalizeHist()方法得到的图片如下图右下部分所示:
CLAHE是对比度抑制自适应直方图均衡(Contrast Limited Adaptive Histogram Equalization)的简称,不同于普通的直方图均衡,它是一种局部直方图均衡方法。
python
cv2.createCLAHE([, clipLimit[, tileGridSize]]) ->retval
dst = retval.apply(src)
参数含义:
- clipLimit:对比对限制阈值,默认为40;
- tileGridSize:直方图均衡的栅格尺寸,输入图像将会按照该尺寸分隔后进行局部直方图均衡,默认是8×8大小;
- src:输入图像,8bit单通道;
- dst:均衡后的输出图像,类型同src;
- 从运行结果可以看到,使用自适应直方图均衡后,不但树叶下半部分的细节呈现出来了 ,而且上半部分的细节也得到了保留。
Gamma Correction
python
# import the necessary packages
from __future__ import print_function
import numpy as np
import argparse
import cv2
def adjust_gamma(image, gamma=1.0):
# build a lookup table mapping the pixel values [0, 255] to
# their adjusted gamma values
invGamma = 1.0 / gamma
table = np.array([((i / 255.0) ** invGamma) * 255
for i in np.arange(0, 256)]).astype("uint8")
# apply gamma correction using the lookup table
return cv2.LUT(image, table)
References
- en.wikipedia.org/wiki/Digita... 简介参考
- www.juzicode.com/opencvpytho... opencv-python API参考