本编博客会谈谈图像滤波,会分别写出滤波过程和原理;
主要可分为以下两大类:
一、线性滤波
1、均值滤波blur()
2、方框滤波 boxFilter()
3、高斯滤波GaussianBlur()
二、非线性滤波
1、中值滤波medianBlur()--当滤波核尺寸大于5时,输入图像只能是CV_8U格式的。
2、双边滤波bilateralFilter()
在前面学习边沿检测时,当时就遇到过滤波的问题,现在开始学习一下主要的原理
图像的空域线性滤波和非线性滤波在空域对图像进行滤波处理无非两种情况,线性滤波和非线性滤波。滤波的意思就是对原图像的每个像素周围一定范围内的像素进行运算,运算的范围就称为掩膜或领域。而运算就分两种了,如果运算只是对各像素灰度值进行简单处理(如乘一个权值)最后求和,就称为线性滤波;而如果对像素灰度值的运算比较复杂,而不是最后求和的简单运算,则是非线性滤波;如求一个像素周围3x3范围内最大值、最小值、中值、均值等操作都不是简单的加权,都属于非线性滤波。
图像滤波既可以在实域进行,也可以在频域进行。图像滤波可以更改或者增强图像。通过滤波,可以强调一些特征或者去除图像中一些不需要的部分。滤波是一个邻域操作算子,利用给定像素周围的像素的值决定此像素的最终的输出值。
(i,j)是像素在图片中的位置;
(m,n)是卷积核中的位置/坐标,其中心点的坐标是(0,0) ;
k(m,n)是卷积核中在(m, n)上的权重参数
I[i+m, j+n]是与k(m,n)相对应的图片像素值
O(i,j)是图片中(i,j)像素的滤波/卷积结果
如下图,当i,j=0时,也就是中心位置是(0,0)。此位置的邻域就是加减m和n, 当m,n=1时(0,0)的邻域就是下面的9个位置。
卷积的操作如下图所示,图片上圈的红框就是卷积核要操作的邻域。例子中卷积核每个位置上的值都为1,因此进行卷积就是把图片上的9个值分别乘上对应卷积核上的值1再求和,4*1+1*1+6*1+7*1+2*1+3*1+9*1+5*1+8*1=45。 其中,45对应卷积核中间1对应的位置;
上面是卷积核移动得到对应的2x2的矩阵结果。
也可以看看这个图理解一下卷积核效果:
边界补充
由上面介绍的卷积操作可以看出,结果比原来图像小了,那么如果要获得同尺寸输出,则要进行边界补充。 因为卷积核越大,得到的结果就越少,所以要补充的就越多。
那进行补充的值为多少呢,下面将介绍4中补充的类型。
我们以7x7卷积:3x3补充成9x9为例。
补零(zero-padding)
补零很简单,就是把缺失的地方都补成0 。
关于卷积核的相关知识可以参考博文:https://blog.csdn.net/zouxy09/article/details/49080029和https://www.cnblogs.com/zgy-personal/p/7227872.html
关于边界补充的相关知识可以参考博文:https://blog.csdn.net/zxfhahaha/article/details/80139430
滤波函数简介
均值滤波:
均值滤波方法是,对待处理的当前像素,选择一个模板,该模板为其邻近的若干个像素组成,用模板的均值来替代原像素的值的方法。
优点:算法简单,计算速度快;
缺点:降低噪声的同时使图像产生模糊,特别是景物的边缘和细节部分。
下面为实现均值滤波的代码和结果
#include "opencv2/imgproc.hpp" #include "opencv2/highgui.hpp" #include<ctime> using namespace cv; using namespace std; //均值滤波 void AverFiltering(const Mat &src, Mat &dst) { if (!src.data) return; //at访问像素点 for (int i = 1; i<src.rows; ++i) for (int j = 1; j < src.cols; ++j) { if ((i - 1 >= 0) && (j - 1) >= 0 && (i + 1)<src.rows && (j + 1)<src.cols) {//边缘不进行处理 dst.at<Vec3b>(i, j)[0] = (src.at<Vec3b>(i, j)[0] + src.at<Vec3b>(i - 1, j - 1)[0] + src.at<Vec3b>(i - 1, j)[0] + src.at<Vec3b>(i, j - 1)[0] + src.at<Vec3b>(i - 1, j + 1)[0] + src.at<Vec3b>(i + 1, j - 1)[0] + src.at<Vec3b>(i + 1, j + 1)[0] + src.at<Vec3b>(i, j + 1)[0] + src.at<Vec3b>(i + 1, j)[0]) / 9; dst.at<Vec3b>(i, j)[1] = (src.at<Vec3b>(i, j)[1] + src.at<Vec3b>(i - 1, j - 1)[1] + src.at<Vec3b>(i - 1, j)[1] + src.at<Vec3b>(i, j - 1)[1] + src.at<Vec3b>(i - 1, j + 1)[1] + src.at<Vec3b>(i + 1, j - 1)[1] + src.at<Vec3b>(i + 1, j + 1)[1] + src.at<Vec3b>(i, j + 1)[1] + src.at<Vec3b>(i + 1, j)[1]) / 9; dst.at<Vec3b>(i, j)[2] = (src.at<Vec3b>(i, j)[2] + src.at<Vec3b>(i - 1, j - 1)[2] + src.at<Vec3b>(i - 1, j)[2] + src.at<Vec3b>(i, j - 1)[2] + src.at<Vec3b>(i - 1, j + 1)[2] + src.at<Vec3b>(i + 1, j - 1)[2] + src.at<Vec3b>(i + 1, j + 1)[2] + src.at<Vec3b>(i, j + 1)[2] + src.at<Vec3b>(i + 1, j)[2]) / 9; } else {//边缘赋值 dst.at<Vec3b>(i, j)[0] = src.at<Vec3b>(i, j)[0]; dst.at<Vec3b>(i, j)[1] = src.at<Vec3b>(i, j)[1]; dst.at<Vec3b>(i, j)[2] = src.at<Vec3b>(i, j)[2]; } } } //图像椒盐化 void salt(Mat &image, int num) { if (!image.data) return;//防止传入空图 int i, j; srand(time(NULL)); for (int x = 0; x < num; ++x) { i = rand() % image.rows; j = rand() % image.cols; image.at<Vec3b>(i, j)[0] = 255; image.at<Vec3b>(i, j)[1] = 255; image.at<Vec3b>(i, j)[2] = 255; } } void main() { Mat image = imread("E:\\VS2015Opencv\\vs2015\\project\\picture\\01.jpg"); Mat Salt_Image; image.copyTo(Salt_Image); salt(Salt_Image, 3000); Mat image1(image.size(), image.type()); Mat image2; AverFiltering(Salt_Image, image1); blur(Salt_Image, image2, Size(3, 3));//openCV库自带的均值滤波函数 imshow("原图", image); imshow("噪声图", Salt_Image); imshow("自定义均值滤波", image1); imshow("openCV自带的均值滤波", image2); waitKey(); }
方框滤波:原理与均值滤波类似
opencv中boxFilter函数的作用是使用方框滤波(box filter)来模糊一张图片。
方框滤波算法的原理很简单,指定一个X*Y的矩阵大小,目标像素的周围X*Y矩阵内的像素全部相加作为目标像素的值,就这么简单.
Boxfilter的初始化过程如下:
1、给定一张图像,宽高为(M,N),确定待求矩形模板的宽高(m,n),如图紫色矩形。图中每个黑色方块代表一个像素,红色方块是假想像素。
2、开辟一段大小为M的数组,记为buff, 用来存储计算过程的中间变量,用红色方块表示
3、将矩形模板(紫色)从左上角(0,0)开始,逐像素向右滑动,到达行末时,矩形移动到下一行的开头(0,1),如此反复,每移动到一个新位置时,计算矩形内的像素和,保存在数组A中。以(0,0)位置为例进行说明:首先将绿色矩形内的每一列像素求和,结果放在buff内(红色方块),再对蓝色矩形内的像素求和,结果即为紫色特征矩形内的像素和,把它存放到数组A中,如此便完成了第一次求和运算。
4、每次紫色矩形向右移动时,实际上就是求对应的蓝色矩形的像素和,此时只要把上一次的求和结果减去蓝色矩形内的第一个红色块,再加上它右面的一个红色块,就是当前位置的和了,用公式表示 sum[i] = sum[i-1] - buff[x-1] + buff[x+m-1]
5、当紫色矩形移动到行末时,需要对buff进行更新。因为整个绿色矩形下移了一个像素,所以对于每个buff[i], 需要加上一个新进来的像素,再减去一个出去的像素,然后便开始新的一行的计算了。
代码如下:
#include <iostream> #include <opencv2/core.hpp> #include <opencv2/highgui.hpp> #include <opencv2/imgproc.hpp> ///////////////////////////////////////// //求积分图-优化方法 //由上方negral(i-1,j)加上当前行的和即可 //对于W*H图像:2*(W-1)*(H-1)次加减法 //比常规方法快1.5倍左右 ///////////////////////////////////////// void Fast_integral(cv::Mat& src, cv::Mat& dst) { int nr = src.rows; int nc = src.cols; int sum_r = 0; dst = cv::Mat::zeros(nr + 1, nc + 1, CV_64F); for (int i = 1; i < dst.rows; ++i) { for (int j = 1, sum_r = 0; j < dst.cols; ++j) { //行累加,因为积分图相当于在原图上方加一行,左边加一列,所以积分图的(1,1)对应原图(0,0),(i,j)对应(i-1,j-1) sum_r = src.at<uchar>(i - 1, j - 1) + sum_r; //行累加 dst.at<double>(i, j) = dst.at<double>(i - 1, j) + sum_r; } } } ////////////////////////////////// //盒子滤波-均值滤波是其特殊情况 ///////////////////////////////// void BoxFilter(cv::Mat& src, cv::Mat& dst, cv::Size wsize, bool normalize) { //图像边界扩充 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; cv::Mat Newsrc; cv::copyMakeBorder(src, Newsrc, hh, hh, hw, hw, cv::BORDER_REFLECT);//以边缘为轴,对称 src.copyTo(dst); //计算积分图 cv::Mat inte; Fast_integral(Newsrc, inte); //BoxFilter double mean = 0; for (int i = hh + 1; i < src.rows + hh + 1; ++i) { //积分图图像比原图(边界扩充后的)多一行和一列 for (int j = hw + 1; j < src.cols + hw + 1; ++j) { double top_left = inte.at<double>(i - hh - 1, j - hw - 1); double top_right = inte.at<double>(i - hh - 1, j + hw); double buttom_left = inte.at<double>(i + hh, j - hw - 1); double buttom_right = inte.at<double>(i + hh, j + hw); if (normalize == true) mean = (buttom_right - top_right - buttom_left + top_left) / wsize.area(); else mean = buttom_right - top_right - buttom_left + top_left; //一定要进行判断和数据类型转换 if (mean < 0) mean = 0; else if (mean>255) mean = 255; dst.at<uchar>(i - hh - 1, j - hw - 1) = static_cast<uchar>(mean); } } } int main() { cv::Mat src = cv::imread("E:\\VS2015Opencv\\vs2015\\project\\picture\\06.jpg"); if (src.empty()) { return -1; } //自编BoxFilter测试 cv::Mat dst1; double t2 = (double)cv::getTickCount(); //测时间 if (src.channels() > 1) { std::vector<cv::Mat> channel; cv::split(src, channel); BoxFilter(channel[0], channel[0], cv::Size(7, 7), true);//盒子滤波 BoxFilter(channel[1], channel[1], cv::Size(7, 7), true);//盒子滤波 BoxFilter(channel[2], channel[2], cv::Size(7, 7), true);//盒子滤波 cv::merge(channel, dst1); } else BoxFilter(src, dst1, cv::Size(7, 7), true);//盒子滤波 t2 = (double)cv::getTickCount() - t2; double time2 = (t2 *1000.) / ((double)cv::getTickFrequency()); std::cout << "FASTmy_process=" << time2 << " ms. " << std::endl << std::endl; //opencv自带BoxFilter测试 cv::Mat dst2; double t1 = (double)cv::getTickCount(); //测时间 cv::boxFilter(src, dst2, -1, cv::Size(7, 7), cv::Point(-1, -1), true, cv::BORDER_CONSTANT);//盒子滤波 t1 = (double)cv::getTickCount() - t1; double time1 = (t1 *1000.) / ((double)cv::getTickFrequency()); std::cout << "Opencvbox_process=" << time1 << " ms. " << std::endl << std::endl; cv::namedWindow("src"); cv::imshow("src", src); cv::namedWindow("ourdst", CV_WINDOW_NORMAL); cv::imshow("ourdst", dst1); cv::namedWindow("opencvdst", CV_WINDOW_NORMAL); cv::imshow("opencvdst", dst2); cv::waitKey(0); }
高斯滤波:
原文:https://www.cnblogs.com/fcfc940503/p/11298250.html