GaussianBlur对分

简介

高斯滤波是一种线性平滑滤波,用于消除高斯噪声。高斯滤波的中心点权重更大,原理中心的权重较小,其维度为奇数。高斯核函数用于将有限维数据映射到高维空间。通常定义为空间中任意一点 x 到某一中心点 μ 之间的欧式距离的单调函数。

高斯核的计算公式为:

一维高斯核:
G ( x ) = 1 2 π σ e − ( x − μ ) 2 2 σ 2 G(x)=\frac{1}{\sqrt{2\pi }\sigma } e^{-\frac{(x-\mu )^{2}}{2\sigma^{2} } } G(x)=2π σ1e2σ2(xμ)2

二维高斯核:
G ( x , y ) = 1 2 π σ 1 σ 2 e − ( ( ( x − μ 1 ) 2 2 σ 1 2 ) + ( ( y − μ 2 ) 2 2 σ 2 2 ) ) G(x, y)=\frac{1}{\sqrt{2\pi }\sigma_{1} \sigma_{2} } e^{-((\frac{(x-\mu_{1} )^{2}}{2\sigma_{1}^{2} })+(\frac{(y-\mu_{2})^{2} }{2\sigma_{2}^{2} })) } G(x,y)=2π σ1σ21e((2σ12(xμ1)2)+(2σ22(yμ2)2))

其中,

x 为空间中的点;

μ \mu μ为核函数中心;

控制高斯核函数的作用范围,其值越大,高斯核函数的局部影响范围就越大,其值不要选太小,否则在分类任务中容易过拟合。

计算高斯核

根据上述公式可以计算二维高斯核,高斯核最终需要归一化,在归一化过程中常熟部分
1 2 π σ 1 σ 2 \frac{1}{\sqrt{2\pi }\sigma_{1} \sigma_{2} } 2π σ1σ21
会被消除,所以在实际计算时只需计算指数部分即可。因为高斯滤波可以进行核分离计算,假设高斯核宽 2a + 1, 高 2b + 1,其推导公式如下:
g ( x , y ) = ∑ s = − a a ∑ t = − b b [ G ( x , y ) × f ( x + s , y + t ) ] = ∑ s = − a a ∑ t = − b b [ 1 2 π σ 1 σ 2 e − ( ( s − μ 1 ) 2 2 σ 1 2 + ( t − μ 2 ) 2 2 σ 2 2 ) × f ] = ∑ s = − a a ∑ t = − b b [ 1 2 π σ 1 e − ( s − μ 1 ) 2 2 σ 1 2 1 2 π σ 2 e − ( t − μ 2 ) 2 2 σ 2 2 × f ] = ∑ s = − a a { 1 2 π σ 1 e − ( s − μ 1 ) 2 2 σ 1 2 ∑ t = − b b [ 1 2 π σ 2 e − ( t − μ 2 ) 2 2 σ 2 2 × f ] ⏟ 水平卷积 } ⏟ 竖直卷积 \begin{matrix} g(x,y)& = \sum_{s=-a}^{a}\sum_{t=-b}^{b} [G(x,y)\times f(x+s,y+t)]\\ & = \sum_{s=-a}^{a}\sum_{t=-b}^{b} [\frac{1}{2\pi\sigma_{1} \sigma{2}}e^{-(\frac{(s-\mu_{1})^{2}}{2\sigma_{1}^{2}} + \frac{(t-\mu_{2})^{2}}{2\sigma_{2}^{2}})} \times f ]\\ & =\sum_{s=-a}^{a}\sum_{t=-b}^{b} [\frac{1}{\sqrt{2\pi} \sigma_{1}}e^{-\frac{(s-\mu_{1})^{2}}{2\sigma_{1}^{2}}} \frac{1}{\sqrt{2\pi} \sigma_{2}} e^{-\frac{(t-\mu_{2})^{2}}{2\sigma_{2}^{2}}} \times f ]\\ & = \underbrace{\sum_{s=-a}^{a}\{\frac{1}{\sqrt{2\pi} \sigma_{1}}e^{-\frac{(s-\mu_{1})^{2}}{2\sigma_{1}^{2}}} \underbrace{\sum_{t=-b}^{b}[\frac{1}{\sqrt{2\pi} \sigma_{2}} e^{-\frac{(t-\mu_{2})^{2}}{2\sigma_{2}^{2}}} \times f]}_{水平卷积} \}}_{竖直卷积} \end{matrix} g(x,y)=s=aat=bb[G(x,y)×f(x+s,y+t)]=s=aat=bb[2πσ1σ21e(2σ12(sμ1)2+2σ22(tμ2)2)×f]=s=aat=bb[2π σ11e2σ12(sμ1)22π σ21e2σ22(tμ2)2×f]=竖直卷积 s=aa{2π σ11e2σ12(sμ1)2水平卷积 t=bb[2π σ21e2σ22(tμ2)2×f]}
因此高斯卷积可以进行分离卷积。所以只需计算一维的高斯核:

cv::Mat window;
	window.create(1 , wsize, CV_32F);

	int center = (wsize - 1) / 2;
	float sum = 0.0;
	for (int i = 0; i < wsize; ++i){
		float g = exp(-(pow(i - center, 2)) / (2 * sigma*sigma));
		window.at<float>(0, i) = g;
		sum += g;
	}
	window = window / sum;

    std::vector<uint16_t> kernel(wsize);
    for(int i = 0; i < wsize; ++i){
        kernel[i] = window.at<float>(0, i) < 1e-5 ? 0 : (uint16_t)round(window.at<float>(0, i) * float((int32_t)(1 << 8)));

在计算一维高斯核的时候使用定点优化

卷积

接下来就是卷积操作,卷积操作需要我们注意边界问题,opencv的编辑采用的镜像反射,即 abcdefg 反射为 gfedcb| abcdefg | fedcba 。我们在计算时需要注意边界问题。

int boder = (wsize - 1) / 2;
	uint16_t buf[src.rows][src.channels() * src.cols];  
	//高斯滤波--水平方向
	for (int i = 0; i < src.rows; ++i){
		for (int j = 0; j < src.cols; ++j){
			uint16_t sum[3] = { 0 };

			for (int r = -boder; r <= boder; ++r){
                    int new_j = j + r < 0 ? -(j + r) : (j + r < src.cols ? j + r : 2 * (src.cols - 1) - j - r);
                    
                    if (src.channels() == 1){
						sum[0] = saturate_cast<uint16_t>(sum[0] + (uint8_t)src.at<uchar>(i, new_j) * kernel[r + boder]); //行不变列变
					}
					else if (src.channels() == 3){
						cv::Vec3b rgb = src.at<cv::Vec3b>(i, new_j);
						sum[0] = saturate_cast<uint16_t>(sum[0] + (uint32_t)(rgb[0]) * kernel[r + boder]); //B
						sum[1] = saturate_cast<uint16_t>(sum[1] + (uint32_t)(rgb[1]) * kernel[r + boder]); //G
						sum[2] = saturate_cast<uint16_t>(sum[2] + (uint32_t)(rgb[2]) * kernel[r + boder]); //R
				}
			}

			if (src.channels() == 1){
				buf[i][j] = sum[0];
			}
			else if (src.channels() == 3){
				buf[i][j * 3 + 0] = sum[0];
				buf[i][j * 3 + 1] = sum[1];
				buf[i][j * 3 + 2] = sum[2];
			}
		}
	}

	// 高斯滤波--垂直方向
	// 对水平方向处理后的dst边界填充
    // cv::Size 的参数为 宽,高,注意顺序
	dst = cv::Mat::zeros(src.size(), src.type());
	for (int j = 0; j < src.cols; ++j){
		for (int i = 0; i < src.rows; ++i){
			uint32_t sum[3] = { 0 };

			for (int r = -boder; r <= boder; ++r){
                int new_i = i + r < 0 ? -(i + r) : (i + r < src.rows ? i + r : 2 * (src.rows - 1) - i - r);
				
                if (src.channels() == 1){
                    sum[0] = sum[0] + (uint32_t)buf[new_i][j * 3 + 0] * (uint32_t)kernel[r + boder]; //列不变行变
                }
                else if (src.channels() == 3){
                    sum[0] = sum[0] + (uint32_t)buf[new_i][j * 3 + 0] * (uint32_t)kernel[r + boder];//B
                    sum[1] = sum[1] + (uint32_t)buf[new_i][j * 3 + 1] * (uint32_t)kernel[r + boder];//G
                    sum[2] = sum[2] + (uint32_t)buf[new_i][j * 3 + 2] * (uint32_t)kernel[r + boder];//R
                }
            }

			// std::cout << cv::saturate_cast<uint8_t>((sum[0] + ((1 << 16) >> 1)) >> 16) << std::endl;
			// std::cin.get();

			if (src.channels() == 1){
				dst.at<uchar>(i, j) = cv::saturate_cast<uint8_t>(sum[0]);
			}
			else if (src.channels() == 3){
				cv::Vec3b rgb = {  cv::saturate_cast<uint8_t>((sum[0] + ((1 << 16) >> 1)) >> 16), 
									 cv::saturate_cast<uint8_t>((sum[1] + ((1 << 16) >> 1)) >> 16), 
									 	 cv::saturate_cast<uint8_t>((sum[2] + ((1 << 16) >> 1)) >> 16) };
				dst.at<cv::Vec3b>(i, j) = rgb;
			}
	   	}	
	}

卷积时进行了定点优化,需要注意饱和度转换,因为计算的中间结果很可能大于255。

定点转换的原理

浮点数转换为定点数

val = _val.getSign() ? 0 : (uint16_t)cvRound(_val * cv::softdouble((int32_t)(1 << fixedShift)));

1、如果浮点数 val 为 0,则将定点数设为 0;

2、1 << fixedShift 是定点数表示中的缩放因子,它将浮点数乘以 2^fixedShift,即对应于将小数部分的 fixedShift 位移到整数部分。

3、接下来,使用 cvRound 函数对结果进行四舍五入,将其转换为整数。

4、将转换后的整数值赋给 val

定点数转换为 uint8_t, 即 CV_8U

static CV_ALWAYS_INLINE uint16_t fixedround(const uint16_t& _val) { return (_val + ((1 << fixedShift) >> 1)); }

cv::saturate_cast<ET>(fixedround(val) >> fixedShift);

fixedround函数将定点数 _val 进行四舍五入取整,并返回一个 uint16_t 类型的结果。在定点数表示中,小数部分的位数由 fixedShift 定义。这个函数的目的是在 _val 的小数部分上加上一个适当的偏移量,以实现四舍五入的效果。具体来说,假设 fixedShift 为 n,那么 (1 << fixedShift) 表示 2^n,即定点数的精度。该函数中的 (1 << fixedShift) >> 1 表示将精度减半,也就是将小数部分的最高位右移一位,这相当于将原来定点数的小数部分加上0.5的效果。函数将 _val 加上这个半精度偏移量,并返回结果,从而实现四舍五入的效果。

第二行代码负责将定点数转换我 CV_8U 类型,因为 fixedShift 为小数部分,所有右移 fixedShift 位,即将小数部分丢弃,只保留整数部分。然后使用饱和度算法将定点数转换为指定的目标类型,即CV_8U。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值