图像增强---空域滤波之平滑

一.空域滤波基础

1.定义

空域滤波增强采用模板处理方法对图像进行滤波,去除图像噪声或增强图像的细节。

2.卷积理论

①卷积的离散表达式,基本上可以理解为模板运算的数学表达方式:
在这里插入图片描述
②由此,卷积的冲击相应函数h(x,y),称为空域卷积模板

3.空域滤波及滤波器的定义

使用空域模板进行的图像处理,被称为空域滤波。
模板本身被称为空域滤波器
①滤波器的分类
· 数学形态分类
在这里插入图片描述
· 处理效果分类

  • 平滑滤波器;
  • 锐化滤波器。

4.线性滤波器

①定义
线性滤波器是线性系统和频域滤波概念在空域的自然延伸。其特征是结果像素值的计算由下列公式定义:
在这里插入图片描述
其中:wi i=1,2,…,n是模板的系数;
zi i=1,2,…,n是被计算像素及其邻域像素的值。
②分类
低通滤波器:平滑图像,去除噪音;
高通滤波器:边缘增强,边缘提取;
带通滤波器:删除特定频率,增强中很少用。

5.非线性滤波器

①定义
使用模板进行结果像素值的计算,结果值直接取决于像素邻域的值,而不使用乘积和的计算。
在这里插入图片描述
②分类
中值滤波:平滑图像,去除噪音;
在这里插入图片描述
最大值滤波:寻找最亮点;
在这里插入图片描述
最小值滤波:寻找最暗点。
在这里插入图片描述

二.平滑滤波器

1.主要用途

①对大图像处理前,删去无用的细小细节;
②连接中断的线段和曲线;
③降低噪音;
④平滑处理,恢复过分锐化的图像;
⑤图像创意(阴影、软边、朦胧效果)。

2.设计

在这里插入图片描述

3.中值滤波

(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

4.均值滤波

(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

在这里插入图片描述

5.方形滤波

(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);

在这里插入图片描述

6.高斯滤波

(1)原理
①一维高斯分布函数:
在这里插入图片描述
②二维高斯分布函数:
在这里插入图片描述
在这里插入图片描述

③高斯滤波和均值滤波一样,都是利用一个掩膜和图像进行卷积求解。
不同之处在于:均值滤波器的模板系数都是相同的,为1;
而高斯滤波器的模板系数,随着距离模板中心距离的增大,系数减小(服从二维高斯分布)。
所以,高斯滤波器相比于均值滤波器而言,对图形模糊程度较小,更能保持图像的整体细节。

④对二维高斯分布函数的说明:
·(x,y):掩膜内任一点的坐标;
·(x0,y0):掩膜内中心点的坐标;
·σ:标准差。
⑤模板样例:
在这里插入图片描述
·对于整数形式的模板,需要进行归一化处理,将模板左上角的值归一化为1。
·使用整数模板时,需要在模板的前面加一个系数,系数为模板中元素和的倒数。
⑥标准差σ的意义及选取
·标准差代表着数据的离散程度;
·如果σ较小,那么生成的模板的中心系数较大,而周围的系数较小,这样对图像的平滑效果就不是很明显;
·如果σ较大,则生成的模板的各个系数相差就不是很大,比较类似均值模板,对图像的平滑效果比较明显。
·高斯分布的概率分布密度图:
在这里插入图片描述
可以看到:σ越小分布越高瘦,σ越大分布越矮胖。
·由于图像的长宽可能不是滤波器大小的整数倍,因此我们需要在图像的边缘补0,这种方法叫做zero padding。
(2)OpenCv中的实现

void GaussianBlur( InputArray src,
                    OutputArray dst,
                    Size ksize,
                    double sigmaX,
                    double sigmaY = 0int 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

·椒盐噪声:
在这里插入图片描述
·高斯噪声:
在这里插入图片描述

7.自适应中值滤波器

(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<Zmed<Zmax,则中值Zmed不是噪声,这时转到过程B测试,当前窗口的中心位置的像素Zxy是否是一个噪声点。
如果Zmin<Zxy<Zmax,则Zxy不是一个噪声,此时滤波器输出Zxy;
如果不满足上述条件,则可判定Zxy是噪声,这是输出中值Zmed(在A中已经判断出Zmed不是噪声)。

⑤如果在过程A中,得到的Zmed不符合条件Zmin<Zmed<Zmax,则可判断得到的中值Zmed是一个噪声。

·在这种情况下,需要增大滤波器的窗口尺寸,在一个更大的范围内寻找一个非噪声点的中值,直到找到一个非噪声的中值,跳转到B;
·或者,窗口的尺寸达到了最大值,这时返回找到的中值,退出。

(2)Python代码实现

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)

(1)原理
①双边滤波是一种非线性滤波器,它可以达到保持边缘、降噪平滑的效果。
和其他滤波原理一样,双边滤波也是采用加权平均的方法,用周边像素亮度值的加权平均代表某个像素的强度,所用的加权平均基于高斯分布
②双边滤波的基本思路是同时考虑将要被滤波的像素点的空域信息(domain)和值域信息(range)。
在这里插入图片描述
③双边滤波将高斯滤波中,由两个像素间的空间距离(空间临近度)计算得到的高斯权重,优化为空间临近度计算的权值像素值相似度(图像边缘处像素值变化较大)计算的权值的乘积,优化后的权重再与图像卷积计算,得到的图像既很好地保留了边缘又实现了去噪。
④公式
·像素值相似度:
在这里插入图片描述
·空间临近度:
在这里插入图片描述
·其中(i,j)代表要处理的像素点的坐标,(k,l)则是其周围一定范围内,可能影响到其值的像素点的坐标。
·总体公式:
在这里插入图片描述

·f(x,y)表示要处理的图像,f(x,y)表示图像在点(x,y)处的像素值;
·(k,l)为模板窗口的中心坐标;
·(i,j)为模板窗口的其它系数的坐标;
·σr为高斯函数的标准差。

(2)优点双边滤波的好处是可以较好地保存边缘,使用维纳滤波或高斯滤波降噪时都会明显地模糊边缘,对于高频细节保护效果不明显。
(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

  • 4
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

llurran

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值