空域滤波增强采用模板处理方法对图像进行滤波,去除图像噪声或增强图像的细节。
①卷积的离散表达式,基本上可以理解为模板运算的数学表达方式:
②由此,卷积的冲击相应函数h(x,y),称为空域卷积模板。
使用空域模板进行的图像处理,被称为空域滤波。
模板本身被称为空域滤波器。
①滤波器的分类
· 数学形态分类
· 处理效果分类
①定义
线性滤波器是线性系统和频域滤波概念在空域的自然延伸。其特征是结果像素值的计算由下列公式定义:
其中:wi i=1,2,…,n是模板的系数;
zi i=1,2,…,n是被计算像素及其邻域像素的值。
②分类
低通滤波器:平滑图像,去除噪音;
高通滤波器:边缘增强,边缘提取;
带通滤波器:删除特定频率,增强中很少用。
①定义
使用模板进行结果像素值的计算,结果值直接取决于像素邻域的值,而不使用乘积和的计算。
②分类
中值滤波:平滑图像,去除噪音;
最大值滤波:寻找最亮点;
最小值滤波:寻找最暗点。
①对大图像处理前,删去无用的细小细节;
②连接中断的线段和曲线;
③降低噪音;
④平滑处理,恢复过分锐化的图像;
⑤图像创意(阴影、软边、朦胧效果)。
(1)原理
用模板区域内像素的中值,作为结果值:
(2)效果
强迫突出的亮点(暗点)更像它周围的值,以消除孤立的亮点(暗点)。
(3)算法实现
将模板区域内的像素排列,求出中值。
对于同值像素,连续排列后取中值。
(4)Opencv中的实现
void medianBlur(InputArray src, OutputArray dst, int kszie);
参数说明:
·src:InputArray类型的src,输入图像,Mat类的对象。该函数对通道是独立处理的,且可以处理1、3或4得到的Mat图像,但是待处理的图像深度应该是CV_8U,CV_16U、CV_32F,但对于较大孔径尺寸的图像,只能是CV_8U。
·dst:OutputArray类型的dst,目标图像,需要和输入图像有相同的尺寸和类型。
·ksize:int类型参数,孔径的线性尺寸,必须是大于1的奇数。
//中值滤波
Mat dst;
medianBlur(noise, dst, 5); //5×5模板
imshow("中值滤波", dst);
(5)注意点
①用n*n的中值滤波器去除那些相对于其邻域像素更亮或更暗,并且其区域小于滤波器区域一半的孤立像素集。
②适合于滤除椒盐噪声和干扰脉冲,尤其适合于目标物体形状是块状的图像滤波。
③具有丰富尖角几何结构的图像,一般采用十字形滤波窗,且窗口大小最好不要超过图像中最小目标物的尺寸,否则会丢失目标物的细小几何特征。
④需要保持细线状及尖顶角目标细节时,最好不要采用中值滤波。
(6)Python实现
def middle_filter(img, size):
"""
中值滤波器
:param img:
:param size:
:return:
"""
rows, cols, channels = img.shape
# 扩充图像
pad = int((size - 1) / 2)
dst = np.zeros((rows + 2 * pad, cols + 2 * pad, channels), np.uint8)
dst[pad:pad + rows, pad:pad + cols] = img.copy()
out = dst.copy()
for i in range(rows):
for j in range(cols):
dst[pad + i, pad + j, 0] = np.median(out[i:i + size, j:j + size, 0])
dst[pad + i, pad + j, 1] = np.median(out[i:i + size, j:j + size, 1])
dst[pad + i, pad + j, 2] = np.median(out[i:i + size, j:j + size, 2])
dst = dst[pad:pad + rows, pad:pad + cols]
return dst
(1)原理
均值滤波也称为线性滤波,其采用的主要方法为邻域平均法。
线性滤波的基本原理是用均值代替原图像中的各个像素值,即对待处理的当前像素点(x,y),选择一个模板,该模板由其近邻的若干像素组成,求模板中所有像素的均值,再把该均值赋予当前像素点(x,y),作为处理后图像在该点上的灰度g(x,y),即g(x,y)=∑f(x,y)/m,m为该模板中包含当前像素在内的像素总个数。
(2)缺点
存在边缘模糊的问题。
(3)OpenCV中的实现
void blur(InputArray src,
OutputArray dst,
Size ksize,
Point anchor=Point(-1,-1),
int borderType=BORDER_DEFAULT )
参数说明:
·src:InputArray类型的src,输入图像,Mat类的对象。该函数对通道是独立处理的,且可以处理1、3或4得到的Mat图像,但是待处理的图像深度应该是CV_8U,CV_16U、CV_32F、CV_16S、CV_64F。
·dst:OutputArray类型的dst,目标图像,需要和输入图像有相同的尺寸和类型。
·ksize:Size类型的ksize,内核的大小。一般这样写Size(w,h)来表示内核的大小(其中,w为像素宽度,h为像素高度);Size(3,3)就表示3*3的核大小。
·anchor:Point类型的anchor,表示锚点(即被平滑的那个点),默认值为Point(-1,-1);如果这个点坐标是负值的话,就表示取核的中心为锚点,所以默认值Point(-1,-1)表示这个锚点在核的中心。
·borderType:int类型,用于推断图像外部像素的某种边界模式;默认值为BORDER_DEFAULT,一般不去管它。
//均值滤波
Mat dst;
blur(src, dst, Size(5, 5));
imshow("均值滤波--blur", dst);
(4)C++实现
/*邻域平均法平滑*/
void smooth(Mat& src, Mat& dst, Size wsize)
{
//窗口大小,需为奇数
if (wsize.height % 2 == 0 || wsize.width % 2 == 0)
{
fprintf(stderr, "Please enter odd size");
exit(-1);
}
int hh = (wsize.height - 1) / 2;
int hw = (wsize.width - 1) / 2;
Mat newsrc;
//扩充图片的边缘
copyMakeBorder(src, newsrc, hh, hh, hw, hw, BORDER_REFLECT_101); //以边缘为轴,对称
dst.create(src.size(), src.type());
//均值滤波
int sum = 0, sumr = 0, sumg = 0, sumb = 0;
int mean = 0, meanr = 0, meang = 0, meanb = 0;
for (int i = hh; i < src.rows + hh; i++)
{
for (int j = hw; j < src.cols + hw; j++)
{
if (src.channels() == 1) //灰度图像
{
for (int r = i - hh; r < i + hh; r++) //卷积核矩阵
{
for (int c = j - hw; c < j + hw; c++)
{
sum = newsrc.at<uchar>(r, c) + sum;
}
}
mean = sum / (wsize.area());
dst.at<uchar>(i - hh, j - hw) = mean;
sum = 0;
mean = 0;
}
else //彩色图像
{
for (int r = i - hh; r < i + hh; r++) //卷积核矩阵
{
for (int c = j - hw; c < j + hw; c++)
{
sumb = newsrc.at<Vec3b>(r, c)[0] + sumb;
sumg = newsrc.at<Vec3b>(r, c)[1] + sumg;
sumr = newsrc.at<Vec3b>(r, c)[2] + sumr;
}
}
meanb = sumb / (wsize.area());
meang = sumg / (wsize.area());
meanr = sumr / (wsize.area());
dst.at<Vec3b>(i - hh, j - hw)[0] = meanb;
dst.at<Vec3b>(i - hh, j - hw)[1] = meang;
dst.at<Vec3b>(i - hh, j - hw)[2] = meanr;
sumr = 0, sumg = 0, sumb = 0;
meanr = 0, meang = 0, meanb = 0;
}
}
}
}
这里发现图像变暗,猜测是因为不同通道选取的中值为不同像素点的,故最后融合后图像的色彩效果不好,整体亮度变暗。
(5)Python实现(以下代码为彩色图像的实现)
def mean_filter(img, size):
"""
均值滤波器
:param img:输入图片
:param size:滤波器窗口大小
:return:
"""
rows, cols, channels = img.shape
# 扩充图像
pad = int((size - 1) / 2)
dst = np.zeros((rows + 2 * pad, cols + 2 * pad, channels), np.uint8)
dst[pad:pad + rows, pad:pad + cols] = img.copy()
out = dst.copy()
for i in range(rows):
for j in range(cols):
dst[pad + i, pad + j, 0] = np.sum(out[i:i + size, j:j + size, 0]) / (size * size)
dst[pad + i, pad + j, 1] = np.sum(out[i:i + size, j:j + size, 1]) / (size * size)
dst[pad + i, pad + j, 2] = np.sum(out[i:i + size, j:j + size, 2]) / (size * size)
dst = dst[pad:pad + rows, pad:pad + cols]
return dst
(1)原理
方形滤波器是一种矩形的且滤波器中的所有值全部相等。
(2)OpenCV中的实现
CV_EXPORTS_W void boxFilter( InputArray src,
OutputArray dst,
int ddepth,
Size ksize,
Point anchor = Point(-1,-1),
bool normalize = true,
int borderType = BORDER_DEFAULT );
参数说明:
·src:输入图像,Mat类的对象。
·dst:目标图像,需要和源图像有一样的尺寸和类型。
·ddepth:输出图像的深度,-1代表使用源图像深度,即src.depth()。
·ksize:Size类型,内核的大小。
·anchor:Point类型的anchor,表示锚点,即被平滑的那个点,默认值Point(-1,-1),表示这个锚点在该核的中心。
·normalize:bool类型,默认值是true,表示内核是否被其区域归一化了。
·borderType:int类型,用于推断图像外部像素的某种边界模式,默认值BORDER_DEFAULT,一般不去管它。
//方形滤波
Mat dst6;
boxFilter(noise, dst6, -1, Size(5, 5), Point(-1, -1),true);
③高斯滤波和均值滤波一样,都是利用一个掩膜和图像进行卷积求解。
不同之处在于:均值滤波器的模板系数都是相同的,为1;
而高斯滤波器的模板系数,随着距离模板中心距离的增大,系数减小(服从二维高斯分布)。
所以,高斯滤波器相比于均值滤波器而言,对图形模糊程度较小,更能保持图像的整体细节。
④对二维高斯分布函数的说明:
·(x,y):掩膜内任一点的坐标;
·(x0,y0):掩膜内中心点的坐标;
·σ:标准差。
⑤模板样例:
·对于整数形式的模板,需要进行归一化处理,将模板左上角的值归一化为1。
·使用整数模板时,需要在模板的前面加一个系数,系数为模板中元素和的倒数。
⑥标准差σ的意义及选取
·标准差代表着数据的离散程度;
·如果σ较小,那么生成的模板的中心系数较大,而周围的系数较小,这样对图像的平滑效果就不是很明显;
·如果σ较大,则生成的模板的各个系数相差就不是很大,比较类似均值模板,对图像的平滑效果比较明显。
·高斯分布的概率分布密度图:
可以看到:σ越小分布越高瘦,σ越大分布越矮胖。
·由于图像的长宽可能不是滤波器大小的整数倍,因此我们需要在图像的边缘补0,这种方法叫做zero padding。
(2)OpenCv中的实现
void GaussianBlur( InputArray src,
OutputArray dst,
Size ksize,
double sigmaX,
double sigmaY = 0,
int borderType = BORDER_DEFAULT )
参数说明:
·src:输入图像;
·dst:输出图像,大小和类型与src相同;
·ksize:高斯核大小,必须为正奇数;
·sigmaX:X方向上的高斯核标准差;
·sigmaY:Y方向上的高斯核标准差,如果sigmaY为零,则将其设置为等于sigmaX,如果sigmaX和sigmaY均为零,则分
别根据ksize.width个ksize.height进行计算;
·borderType:int类型,用于推断图像外部像素的某种边界模式,默认值BORDER_DEFAULT,一般不去管它。
·
//高斯滤波
Mat dst7;
GaussianBlur(noise, dst7, Size(5, 5), 0.8, 0.8);
(3)C++实现
//x,y方向联合实现获取高斯模板
void generateGaussMask(Mat& Mask, Size wsize, double sigma)
{
Mask.create(wsize, CV_64F);
int h = wsize.height;
int w = wsize.width;
int center_h = (h - 1) / 2;
int center_w = (w - 1) / 2;
double sum = 0.0;
double x, y;
for (int i = 0; i < h; i++)
{
y = pow(i - center_h, 2);
for (int j = 0; j < w; j++)
{
x = pow(j - center_w, 2);
double g = exp(-(x + y) / (2 * sigma * sigma));
Mask.at<double>(i, j) = g;
sum += g;
}
}
Mask = Mask / sum;
}
void Gauss_Filter(Mat& src, Mat& dst, Mat window)
{
int hh = (window.rows - 1) / 2;
int hw = (window.cols - 1) / 2;
dst.create(src.size(), src.type());
//边界填充
Mat Newsrc;
copyMakeBorder(src, Newsrc, hh, hh, hw, hw, BORDER_REPLICATE); //边界复制
//高斯滤波
for (int i = hh; i < src.rows + hh; i++)
{
for (int j = hw; j < src.cols + hw; j++)
{
double sum[3] = { 0 };
for (int r = -hh; r <= hh; r++)
{
for (int c = -hw; c <= hw; c++)
{
if (src.channels() == 1)
{
sum[0] = sum[0] + Newsrc.at<uchar>(i + r, j + c) * window.at<double>(r + hh, c + hw);
}
else
{
Vec3b rgb = Newsrc.at<Vec3b>(i + r, j + c);
sum[0] = sum[0] + rgb[0] * window.at<double>(r + hh, c + hw);
sum[1] = sum[1] + rgb[1] * window.at<double>(r + hh, c + hw);
sum[2] = sum[2] + rgb[2] * window.at<double>(r + hh, c + hw);
}
}
}
for (int k = 0; k < src.channels(); k++)
{
if (sum[k] < 0)
{
sum[k] = 0;
}
else if (sum[k] > 255)
{
sum[k] = 255;
}
}
if (src.channels() == 1)
{
dst.at<uchar>(i - hh, j - hw) = static_cast<uchar>(sum[0]);
}
else
{
Vec3b rgb = { static_cast<uchar>(sum[0]),static_cast<uchar>(sum[1]),static_cast<uchar>(sum[2]) };
dst.at<Vec3b>(i - hh, j - hw) = rgb;
}
}
}
}
(4)Python实现
def gaussian_filter(img, size, sigma):
"""
高斯滤波
:param img: 输入图像
:param size: 窗口大小
:param sigma: 标准差
:return:
"""
rows, cols, channels = img.shape
# zero padding
pad = int((size - 1) / 2)
dst = np.zeros((rows + pad * 2, cols + pad * 2, channels), np.float32)
dst[pad:pad + rows, pad:pad + cols] = img.copy().astype(np.float32)
kernel = np.zeros((size, size), np.float32)
for i in range(-pad, -pad + size):
for j in range(-pad, -pad + size):
kernel[j + pad, i + pad] = np.exp(-(i ** 2 + j ** 2) / (2 * (sigma ** 2)))
kernel = kernel / (2 * np.pi * sigma * sigma)
kernel = kernel / kernel.sum()
out = dst.copy()
# filter
for i in range(rows):
for j in range(cols):
dst[pad + i, pad + j, 0] = np.sum(kernel * out[i:i + size, j:j + size, 0])
dst[pad + i, pad + j, 1] = np.sum(kernel * out[i:i + size, j:j + size, 1])
dst[pad + i, pad + j, 2] = np.sum(kernel * out[i:i + size, j:j + size, 2])
dst = np.clip(dst, 0, 255)
dst = dst[pad:pad + rows, pad:pad + cols].astype(np.uint8)
return dst
·椒盐噪声:
·高斯噪声:
(1)原理
①自适应中值滤波器:根据预先设定好的条件,在滤波过程中,动态地改变滤波器的窗口尺寸大小,还会根据一定条件判断当前像素是不是噪声,如果是则用邻域中值替换掉当前像素,不是则不做改变。
②相关符号:
· Zmin=Sxy中的最小灰度值;
· Zmax=Sxy中的最大灰度值;
· Zmed=Sxy中的灰度值的中值;
· Zxy表示坐标(x,y)处的灰度值;
·Smax=Sxy允许的最大窗口尺寸。
③两个处理过程,分别记为A和B:
A:
A1=Zmed-Zmin
A2=Zmed-Zmax
如果A1>0且A2<0,则跳转到B;
否则,增大窗口尺寸;
如果增大后窗口的尺寸≤Smax,则重复A过程;
否则,输出Zmed。
B:
B1=Zxy-Zmin
B2=Zxy-Zmax
如果B1>0且B2<0,则输出Zxy
否则输出Zmed。
④过程A的目的是确定当前窗口内得到中值Zmed是否是噪声。
如果Zmin
如果Zmin 如果不满足上述条件,则可判定Zxy是噪声,这是输出中值Zmed(在A中已经判断出Zmed不是噪声)。
⑤如果在过程A中,得到的Zmed不符合条件Zmin ·在这种情况下,需要增大滤波器的窗口尺寸,在一个更大的范围内寻找一个非噪声点的中值,直到找到一个非噪声的中值,跳转到B; (2)Python代码实现 (1)原理 ·f(x,y)表示要处理的图像,f(x,y)表示图像在点(x,y)处的像素值; (2)优点双边滤波的好处是可以较好地保存边缘,使用维纳滤波或高斯滤波降噪时都会明显地模糊边缘,对于高频细节保护效果不明显。 参数说明: 参考文章:
·或者,窗口的尺寸达到了最大值,这时返回找到的中值,退出。def AdaptProcess(img, i, j, minsize, maxsize):
'''
处理当前像素点
:return:
'''
filter_size = minsize
kernel_size = filter_size // 2
rio = img[i - kernel_size:i + kernel_size + 1, j - kernel_size:j + kernel_size + 1]
minpix = np.min(rio)
maxpix = np.max(rio)
medpix = np.median(rio)
zxy = img[i, j]
if (medpix > minpix) and (medpix < maxpix):
if (zxy > minpix) and (zxy < maxpix):
return zxy
else:
return medpix
else:
filter_size = filter_size + 2
if filter_size <= maxsize:
return AdaptProcess(img, i, j, filter_size, maxsize)
else:
return medpix
def adapt_median_filter(img, minsize, maxsize):
"""
自适应中值滤波器
:return:
"""
bordersize = maxsize // 2
src = cv2.copyMakeBorder(img, bordersize, bordersize, bordersize, bordersize, cv2.BORDER_REFLECT)
for m in range(bordersize, src.shape[0] - bordersize):
for n in range(bordersize, src.shape[1] - bordersize):
src[m, n, 0] = AdaptProcess(src[:, :, 0], m, n, minsize, maxsize)
src[m, n, 1] = AdaptProcess(src[:, :, 1], m, n, minsize, maxsize)
src[m, n, 2] = AdaptProcess(src[:, :, 2], m, n, minsize, maxsize)
dst = src[bordersize:bordersize + img.shape[0], bordersize:bordersize + img.shape[1]]
return dst
8.双边滤波(bilateral filtering)
①双边滤波是一种非线性滤波器,它可以达到保持边缘、降噪平滑的效果。
和其他滤波原理一样,双边滤波也是采用加权平均的方法,用周边像素亮度值的加权平均代表某个像素的强度,所用的加权平均基于高斯分布。
②双边滤波的基本思路是同时考虑将要被滤波的像素点的空域信息(domain)和值域信息(range)。
③双边滤波将高斯滤波中,由两个像素间的空间距离(空间临近度)计算得到的高斯权重,优化为空间临近度计算的权值和像素值相似度(图像边缘处像素值变化较大)计算的权值的乘积,优化后的权重再与图像卷积计算,得到的图像既很好地保留了边缘又实现了去噪。
④公式
·像素值相似度:
·空间临近度:
·其中(i,j)代表要处理的像素点的坐标,(k,l)则是其周围一定范围内,可能影响到其值的像素点的坐标。
·总体公式:
·(k,l)为模板窗口的中心坐标;
·(i,j)为模板窗口的其它系数的坐标;
·σr为高斯函数的标准差。
(3)Opencv的实现: void bilateralFilter(InputArray src,
OutputArray dst,
int d,
double sigmaColor,
double sigmaSpace,
int borderType=BORDER_DEFAULT)
//双边滤波
Mat dst;
bilateralFilter(src, dst, 5, 100, 15);
·src:输入图像;
·dst:输出图形,图像大小和类型与src相同;
·d:过滤期间使用的各像素邻域的直径;
·sigmaColor:色彩空间的sigma参数,该参数较大时,各像素邻域内相距较远的颜色会被混合到一起,从而造成更大范围的半相等颜色;
·sigmaSpace:坐标空间的sigma参数,该参数较大时,只要颜色相近,越远的像素会相互影响;
·borderType:边界类型。
https://blog.csdn.net/Ibelievesunshine/article/details/104881204
https://www.cnblogs.com/wangguchangqing/p/6379646.html
https://blog.csdn.net/rocketeerLi/article/details/87933644?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2aggregatepagefirst_rank_ecpm_v1~rank_v31_ecpm-1-87933644.pc_agg_new_rank&utm_term=%E5%8F%8C%E8%BE%B9%E6%BB%A4%E6%B3%A2%E7%AE%97%E6%B3%95python&spm=1000.2123.3001.4430