图像平滑滤波

卷积与滤波概念

离散卷积

丢两个骰子,求点数加起来为 t t t 的概率是多少?
两个骰子加起来为4的概率:
f ( 1 ) g ( 3 ) + f ( 2 ) g ( 2 ) + f ( 3 ) g ( 1 ) f(1)g(3) + f(2)g(2) + f(3)g(1) f(1)g(3)+f(2)g(2)+f(3)g(1)
写成卷积标准形式为:
( f ∗ g ) ( 4 ) = ∑ i = 1 3 f ( i ) g ( 4 − i ) (f *g)(4) = \sum_{i=1}^3f(i)g(4-i) (fg)(4)=i=13f(i)g(4i)
进一步,两个骰子加起来为 t t t 的概率,就是二个骰子的概率密度函数的卷积:
( f ∗ g ) ( t ) = ∑ i = 1 t − 1 f ( i ) g ( t − i ) (f *g)(t) = \sum_{i=1}^{t-1}f(i)g(t-i) (fg)(t)=i=1t1f(i)g(ti)
与1维卷积类似,图像(二维)卷积定义:
( f ∗ g ) ( x , y ) = 1 N M ∑ i = 0 N − 1 ∑ j = 0 M − 1 f ( i , j ) g ( x − i , y − j ) (f *g)(x,y) = \frac{1}{NM}\sum_{i=0}^{N-1}\sum_{j=0}^{M-1}f(i,j)g(x-i,y-j) (fg)(x,y)=NM1i=0N1j=0M1f(i,j)g(xi,yj)
g g g 称为滤波器

图像滤波如何计算

在这里插入图片描述

  • 滤波器 g ( x , y ) g(x,y) g(x,y) 左右、上下反转:得到 g ( − x , − y ) g(-x,-y) g(x,y)
  • 按照前述公式计算卷积(先乘后累加)

图像平滑滤波与去噪

图像滤波由卷积定义,基本知识:

  • 4-领域与8-领域

图像平滑:

  • 平均滤波:在一个小区域内(通常3*3)像素值平均
    g ( x , y ) = 1 M ∑ i , j f ( i , j ) g(x,y) = \frac{1}{M}\sum_{i,j}f(i,j) g(x,y)=M1i,jf(i,j)
    其中 M M M表示像素值个数,滤波器采用4-领域时 M = 5 M = 5 M=5;滤波器采用8-领域时 M = 9 M = 9 M=9;如果不考虑中间像素 M = 8 M = 8 M=8
  • 加权平均滤波:在一个小区域内像素值加权平均
    g ( x , y ) = ∑ i , j w i j f ( i , j ) g(x,y) = \sum_{i,j}w_{ij}f(i,j) g(x,y)=i,jwijf(i,j)
    其中 w i j w_{ij} wij表示所有像素值之和的倒数。
  • 高斯滤波器:元素值分布根据位置呈现高斯函数的特点
    1 16 [ 1 2 1 2 4 2 1 2 1 ] \frac{1}{16} \left[ \begin{array}{ccc} 1 & 2 & 1\\\\ 2 & 4 & 2\\\\ 1 & 2 & 1\\\\ \end{array} \right] 161121242121
  • 双边滤波器:元素值不仅与位置有关,还和源图像在该位置的像素值有关。其是空域核和值域核的叠加,通过双边滤波器,可以在使图像平滑的同时,比较好的保留边缘。
  • 中值滤波:确定窗口及位置(含有奇数个像素),窗口内像素按灰度大小排序,取中间值代替原窗口中心像素值。对椒盐噪声有效

总结:

  1. 平滑滤波包括平均滤波、高斯滤波、中值滤波等方法,其中高斯滤波最为常用。
  2. 平滑滤波具有去除噪声效果,不同滤波方法具有对不同噪声的适应性。

数学形态学滤波

数学形态学基本操作——膨胀和腐蚀

A ⨁ B A\bigoplus B AB表示集合 A A A 用结构元素 B B B 膨胀(dilate),定义为:
A ⨁ B = ∪ ( A ) b ( b ∈ B ) A\bigoplus B = \cup(A)_b (b\in B) AB=(A)b(bB)
表示,将 A A A 按照 B B B 中的所有元素进行平移,将所有平移的结果取并集。
A ⨁ B A\bigoplus B AB表示集合 A A A 用结构元素 B B B 腐蚀(erode),定义为:
A ⨀ B = ∩ ( A ) − b ( b ∈ B ) A\bigodot B = \cap(A)_{-b} (b\in B) AB=(A)b(bB)
表示,将 A A A 按照 B B B 中的所有元素进行反方向平移,将所有平移的结果取并集。

直观表现是,腐蚀使黑色区域变大了,膨胀使白色区域变大了。

图像形态学操作——开闭运算

  • 膨胀和腐蚀并不互为逆运算,二者级联使用可生成新的形态学操作
  • 开运算:先腐蚀后膨胀(岛屿分开了):
    ( A ⨀ B ) ⨁ B (A\bigodot B)\bigoplus B (AB)B
  • 闭运算:先膨胀后腐蚀(岛屿闭合了):
    ( A ⨁ B ) ⨀ B (A\bigoplus B)\bigodot B (AB)B
  • 先开后闭:可有效去除噪声

数学形态学去噪方法

实战演练:图像平滑滤波对比

OpenCV实现滤波及平滑去噪各函数

  • 图像滤波
dst = cv2.filter2D(src, ddepth, kernel [, dst[, anchor[, delta[, borderType]]]])
#dst:目标图像,其与原图像尺寸和通道数相同
#ddepth:目标图像的所需深度,包括CV_16S/CV_32F/CV_64F等
#kernel:卷积核(或相当于相关核),单通道浮点矩阵;如果要将不同的内核应用与不同的通道,请使用拆分,再单独处理各通道
#anchor:内核的锚点,指示内核中过滤点的相对位置;锚应位于内核中;默认值(-1, -1)表示锚位于内核中心。
#detal:在将它们存储在dst中之前,将可选值添加到已过滤的像素中。类似于偏置。
#borderType:卷积填充方式,包括BORDER_CONSTANT, BORDER_REPLICATE, BORDER_REFLECT等。
  • 平均滤波
dst = cv2.blur(src, ksize[, dst[,anchor[, borderType]]])
#ksize:滤波器大小,如(5, 5)
  • 高斯平滑滤波
dst = cv2.GaussianBlur(src, ksize, sigmaX[, dst[, sigmaY[, borderType]]])
  • 中值滤波
dst = cv2.medianBlur(src, ksize[, dst])
  • 双边滤波
dst = cv2.bilateralFilter(src, d, sigmaColor, sigmaSpace[, dst[, borderType]])
#d:过滤期间使用的各像素领域的直径
#sigmaColor:色彩空间的sigma参数,该参数较大时,各像素领域内相距较远的颜色会被混合到一起,从而造成更大范围的半相等颜色
#sigmaSpace:色彩空间的sigma参数,该参数较大时,只要颜色相近,越远的像素会相互影响

python代码:

import cv2 as cv
import numpy as np

def gauss_noise(image, mean = 0, var = 0.001):
    """
    添加高斯噪声
    mean:  均值
    var: 方差
    """
    image = np.array(image/255, dtype = float)
    noise = np.random.normal(mean, var ** 0.5, image.shape)
    out = image + noise
    if out.min() < 0:
        low_clip = -1.
    else:
        low_clip = 0.
    out = np.clip(out, low_clip, 1.0)
    out = np.uint8(out*255)
    # cv.imshow("Gauss_noise", out)
    return out
    
filename = 'C:/python/img/lena.jpg'
img = cv.imread(filename)
img = gauss_noise(img) #原图像加入高斯噪声

blur = cv.blur(img, (5, 5))  #平均滤波
gauss = cv.GaussianBlur(img, (5, 5), 0) #高斯滤波
median = cv.medianBlur(img, 5)  #中值滤波
bilateral = cv.bilateralFilter(img, 5, 150, 150) #双边滤波

cv.imshow('Image', img)
cv.imshow('Blurred', blur)
cv.imshow('Gauss', gauss)
cv.imshow('Median filtered', median)
cv.imshow('Bilateral filtered', bilateral)

cv.waitKey()
cv.destroyAllWindows()

c++添加高斯噪声:


#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
#include <cstdlib>
#include <limits>
#include <cmath>
using namespace cv;
using namespace std;
double generateGaussianNoise(double mu, double sigma)
{
	//定义一个特别小的值
	const double epsilon = numeric_limits<double>::min();
	//返回目标数据类型能表示的最逼近1的正数和1的差的绝对值
	static double z;		
	//局部变量,这样可以在不同的文件中定义同名函数和同名变量,而不用担心命名冲突
	double u1, u2;
	//构造随机变量

	do
	{
		u1 = rand()*(1.0 / RAND_MAX);
		u2 = rand()*(1.0 / RAND_MAX);
	} while (u1 <= epsilon);
	//构造高斯随机变量X
	z = sqrt(-2.0*log(u1))*sin(2 * CV_PI * u2);
	return z * sigma + mu;
}
//为图像添加高斯噪声
Mat addGaussianNoise(Mat& srcImage)
{
	Mat resultImage = srcImage.clone();    //深拷贝,克隆
	int channels = resultImage.channels();    //获取图像的通道
	int nRows = resultImage.rows;    //图像的行数

	int nCols = resultImage.cols*channels;   //图像的总列数
											 //判断图像的连续性
	if (resultImage.isContinuous())    //判断矩阵是否连续,若连续,我们相当于只需要遍历一个一维数组 
	{
		nCols *= nRows;
		nRows = 1;
	}
	for (int i = 0; i < nRows; i++)
	{
		for (int j = 0; j < nCols; j++)
		{	//添加高斯噪声
			int val = resultImage.ptr<uchar>(i)[j] + generateGaussianNoise(2, 0.8) * 32;
			if (val < 0)
				val = 0;
			if (val > 255)
				val = 255;
			resultImage.ptr<uchar>(i)[j] = (uchar)val;
		}
	}
	return resultImage;
}
int main()
{
	Mat srcImage = imread("lena.jpg", 1);
	if (!srcImage.data)
		return -1;
	imshow("srcImage", srcImage);
	Mat resultImage = addGaussianNoise(srcImage);
	imshow("resultImage", resultImage);
	waitKey(0);
	return 0;
}
  1. 关于代码里出现的OpenCV isContinuous()函数:
bool cv::Mat::isContinuous() const

该方法用于报告矩阵是否连续。
如果矩阵元素在每行末尾连续存储而没有间隙,则方法返回true。 否则,它返回false。 显然,对于1x1或1xN矩阵总是连续的。一般 用Mat :: create创建的矩阵总是连续的。
2. Mat 数据类型指针ptr 的使用
这里的 depth_.ptr(y)[x] 就是指向depth _ 的第y 行的第x个数据,数据类型为无符号的短整型。
char 是有符号的 ,uchar(unsigned char) 是无符号的,8-bit无符号整形数据,里面全是正数 。
两者都作为字符用的话是没有区别的,但当整数用时有区别:
char 整数范围为-128到127( 0x80__0x7F) ;
而unsigned char 整数范围为0到255( 0__0xFF ) 有时候想把整数数值限在255范围内,也用unsigned char。
而本例中resultImage.ptr<uchar>(i)[j]是指向resultImage第 i 行第 j 个元素,限制数值位0-255的整数。

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

using namespace std;
using namespace cv;

typedef struct _imgPair {
	Mat *src;
	Mat *dst;
	void * Param;
	char* winName;
}ImgPair;

typedef struct _gaussianParam {
	int kernelSize;
	int sigma;
}GaussianParam;

typedef struct _bilateralParam {
	int kernelSize;
	int sigmaColor;
	int sigmaSpace;
}BilateralParam;

void on_gaussiankernelBar(int ksize, void *userdata)
{
	ImgPair* pImgPair = (ImgPair*)userdata;
	GaussianParam *gPair = (GaussianParam *)(pImgPair->Param); 
	//->用于指向结构体的指针,表示结构体内的元素
	gPair->kernelSize = ksize;

	GaussianBlur(*(pImgPair->src), *(pImgPair->dst), Size(gPair->kernelSize / 2 * 2 + 1, gPair->kernelSize / 2 * 2 + 1), gPair->sigma, gPair->sigma);
	//Mat类型的对象不直接做函数参数,而是用指向它的指针
	//gPair->kernelSize / 2 * 2 + 1可以确保卷积核尺寸为奇数,这样可以随意拖动bar,不用担心拖到偶数
	//sigma.X,sigma.Y分别为X,Y方向卷积核的方差,其中sigma.Y默认设置为0
	imshow(pImgPair->winName, *(pImgPair->dst));
}


void on_gaussianSigmaBar(int sigma, void *userdata)
{
	ImgPair* pImgPair = (ImgPair*)userdata;
	GaussianParam *gPair = (GaussianParam *)(pImgPair->Param);
	gPair->sigma = double(sigma);

	GaussianBlur(*(pImgPair->src), *(pImgPair->dst), Size(gPair->kernelSize / 2 * 2 + 1, gPair->kernelSize / 2 * 2 + 1), gPair->sigma, gPair->sigma);
	//无论拖动ksize的条,还是sigma的条,都要重新运行GuassianBlur函数,重新计算。
	imshow(pImgPair->winName, *(pImgPair->dst));
}




void on_medianSigmaBar(int ksize, void *userdata)
{
	ImgPair* pImgPair = (ImgPair*)userdata;

	medianBlur(*(pImgPair->src), *(pImgPair->dst), ksize / 2 * 2 + 1);
	//中值滤波只有三个参数
	imshow(pImgPair->winName, *(pImgPair->dst));

}


void on_bilateralDBar(int ksize, void *userdata)
{
	//拖动双边滤波ksize的bar
	ImgPair* pImgPair = (ImgPair*)userdata;
	BilateralParam *param = (BilateralParam *)(pImgPair->Param);
	bilateralFilter(*(pImgPair->src), *(pImgPair->dst), ksize / 2 * 2 + 1, param->sigmaColor, param->sigmaSpace);
	//灰度距离核和空间距离核的sigma
	param->kernelSize = ksize;
	imshow(pImgPair->winName, *(pImgPair->dst));

}



void on_bilateralSigmaSpaceBar(int sigmaSpace, void *userdata)
{
	ImgPair* pImgPair = (ImgPair*)userdata;
	BilateralParam *param = (BilateralParam *)(pImgPair->Param);
	bilateralFilter(*(pImgPair->src), *(pImgPair->dst), param->kernelSize / 2 * 2 + 1, param->sigmaColor, sigmaSpace);
	param->sigmaSpace = sigmaSpace;
	imshow(pImgPair->winName, *(pImgPair->dst));
}

void on_bilateralSigmaColorBar(int sigmaColor, void *userdata)
{
	ImgPair* pImgPair = (ImgPair*)userdata;
	BilateralParam *param = (BilateralParam *)(pImgPair->Param);
	bilateralFilter(*(pImgPair->src), *(pImgPair->dst), param->kernelSize / 2 * 2 + 1, sigmaColor, param->sigmaSpace);
	param->sigmaColor = sigmaColor;
	imshow(pImgPair->winName, *(pImgPair->dst));
}


int main()
{
	Mat src = imread("lena.jpg");

	namedWindow("src");
	imshow("src", src);


	/*-------GaussianBlur-----------*/
	Mat GaussianBlurImg;
	namedWindow("GaussianBlurImg");
	GaussianParam gaussianParam = { 5, 1.0 };
	GaussianBlur(src, GaussianBlurImg, Size(5, 5), 1, 1);
	GaussianParam gparam = { 5, 1.0 };
	ImgPair  gaussianPair = { &src, &GaussianBlurImg, &gparam,  "GaussianBlurImg" };

	imshow("GaussianBlurImg", GaussianBlurImg);
	createTrackbar("kernelsize", "GaussianBlurImg", &(gparam.kernelSize), 30, on_gaussiankernelBar, &gaussianPair);
	createTrackbar("sigma", "GaussianBlurImg", &(gparam.sigma), 10, on_gaussianSigmaBar, &gaussianPair);
//createTrackbar是Opencv中的API,其可在显示图像的窗口中快速创建一个滑动控件
//形式参数一、trackbarname:滑动空间的名称;
//形式参数二、winname:滑动空间用于依附的图像窗口的名称;
//形式参数三、value:初始化阈值;
//形式参数四、count:滑动控件的刻度范围;
//形式参数五、TrackbarCallback是回调函数,其定义如下
	/*-------medianBlur-----------*/
	Mat MedianBlurImg;
	int kernelSize = 5; //初始化的kerbelSize
	ImgPair  medianPair = { &src, &MedianBlurImg, nullptr,  "MedianBlurImg" };
	medianBlur(src, MedianBlurImg, 5);
	imshow("MedianBlurImg", MedianBlurImg);
	createTrackbar("kernelsize", "MedianBlurImg", &(kernelSize), 30, on_medianSigmaBar, &medianPair);


	/*---Bilateral-----------------*/
	Mat BilateralFilterImg;
	bilateralFilter(src, BilateralFilterImg, 5, 2, 2);
	BilateralParam bparam = { 5,1,1 };
	ImgPair  bilateralPair = { &src, &BilateralFilterImg, &bparam,  "BilateralFilterImg" };
	imshow("BilateralFilterImg", BilateralFilterImg);
	createTrackbar("kernelsize", "BilateralFilterImg", &(bparam.kernelSize), 30, on_bilateralDBar, &bilateralPair);
	createTrackbar("sigmaspace", "BilateralFilterImg", &(bparam.sigmaSpace), 30, on_bilateralSigmaSpaceBar, &bilateralPair);
	createTrackbar("sigmacolor", "BilateralFilterImg", &(bparam.sigmaColor), 30, on_bilateralSigmaColorBar, &bilateralPair);
	waitKey(0);
}

补充一个关于介绍滑动条的小程序:使用滑动条做调色板

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

using namespace cv;

//参数1:滑动条位置;参数2:传入的参数
void nothing(int pos, void *)
{
}

int main()
{
	Mat img = Mat::zeros(300, 512, CV_8UC3);
	namedWindow("image");
	//createTrackbar函数:为窗口创建滑动条
	createTrackbar("B", "image", 0, 255, nothing);
	createTrackbar("G", "image", 0, 255, nothing);
	createTrackbar("R", "image", 0, 255, nothing);
	createTrackbar("0:off\n1:on", "image", 0, 1, nothing);
	while (1)
	{
		imshow("image", img);
		if (waitKey(1) == 27)
			break;

		//getTrackbarPos函数作用:获取滑动条的位置的值
		int b = getTrackbarPos("B", "image");
		int g = getTrackbarPos("G", "image");
		int r = getTrackbarPos("R", "image");
		int s = getTrackbarPos("0:off\n1:on", "image");

		if (s == 0)
			img = Scalar(0, 0, 0);
		else
			//opencv中颜色按BGR排列,numpy、matplotlib常用的第三方库RGB排列
			img = Scalar(b, g, r); //修改Mat类对象颜色的方法
	}
	waitKey(0);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值