图像平滑的目的之一是消除噪声,二是模糊图像。
从信号频谱的角度来看,信号缓慢变化的部分在频率域表现为低频,迅速变化的部分表现为高频。图像在获取、储存、处理、传输过程中,会受到电气系统和外界干扰而存在一定程度的噪声,图像噪声使图像模糊,甚至淹没图像特征,给分析带来困难。
模板卷积是数字图像处理常用的一种邻域运算方式,模板卷积可以实现图像平滑、图像锐化、边缘检测等功能。
模板可以是一小幅图像,也可以是一个滤波器。
模板卷积的基本步骤:
(1)模板在输入图像上移动,让模板中心依次与输入图像的每个像素重合;
(2)模板系数与跟模板重合的输入图像的对应像素相乘,再将乘积相加;
(3)把结果赋予输图像,其像素位置与模板中心在输入图像上的位置一致;
具体过程如下图所示(原图像进行了边界扩展,目的是保持卷积后图像与原图像大小相同)
图像边界问题:
(1)当模板超出图像边界时不做处理;
(2)扩充图像,可以复制原图像边界像素或利用常数填充图像边界。
计算结果可能超出灰度范围,对于8位灰度图像,当计算结果超出 [0, 255] 时,可以简单的将其值置为0或255.
常用模板有Box模板和高斯模板
以下为均值滤波与高斯滤波代码
#include
#include
#include
#include
#include
int main()
{
//smooth with GAUSS and BOX
const char *filename = "building.bmp";
IplImage *inputimage = cvLoadImage(filename, -1); //载入图片
IplImage *smoothimage = cvCreateImage(cvSize(inputimage->width, inputimage->height), IPL_DEPTH_8U, inputimage->nChannels);//声明去噪图片
IplImage *extensionimage = cvCreateImage(cvSize(inputimage->width+2, inputimage->height+2), IPL_DEPTH_8U, inputimage->nChannels);//声明边界扩展图片
cvCopyMakeBorder(inputimage, extensionimage, cvPoint(1, 1), IPL_BORDER_REPLICATE); //进行边界扩展,原图像在新图像的(1,1)位置,IPL_BORDER_REPLICATE表示扩展为边界像素
int gauss[3][3] = { 1,2,1,2,4,2,1,2,1 }; //声明高斯模板
int box[3][3] = { 1,1,1,1,1,1,1,1,1 }; //声明Box模板
//进行模板卷积
for (int i = 0; i < inputimage->height; i++)
{
for (int j = 0; j < inputimage->width; j++)
{
for (int k = 0; k < inputimage->nChannels; k++)
{
float temp = 0;
for (int m = 0; m < 3; m++)
{
for (int n = 0; n < 3; n++)
{
temp += gauss[m][n] * (unsigned char)extensionimage->imageData[(i + m)*extensionimage->widthStep + (j + n)* extensionimage->nChannels + k];
//temp += box[m][n] * (unsigned char)extensionimage->imageData[(i + m)*extensionimage->widthStep + (j + n)* extensionimage->nChannels + k];
}
}
smoothimage->imageData[i*smoothimage->widthStep + j * smoothimage->nChannels + k] = temp / 16.0; //gauss模板
//smoothimage->imageData[i*smoothimage->widthStep + j * smoothimage->nChannels + k] = temp / 9.0; //box模板
}
}
}
cv::Mat smooth = cv::cvarrToMat(smoothimage);
cv::imwrite("buildingsmooth.bmp", smooth); //将去噪后的图片保存到当前目录下
cvNamedWindow("inputimage", 1);
cvNamedWindow("smoothimage", 1);
cvShowImage("inputimage", inputimage);
cvShowImage("smoothimage", smoothimage);
cvWaitKey(0);
cvDestroyWindow("inputimage");
cvDestroyWindow("smoothimage");
cvReleaseImage(&inputimage);
cvReleaseImage(&smoothimage);
}
以下为程序运行结果,从左到右依次为:原噪声图像---均值滤波图像---高斯滤波图像
中值滤波是一种非线性滤波,它能在滤除噪声的同时很好的保持图像边缘
中值滤波的原理:把以当前像素为中心的小窗口内的所有像素的灰度按从小到大排序,取排序结果的中间值作为该像素的灰度值。
以下是具体实现代码:
#include
#include
#include
#include
#include
int median(int *input) //中值函数,借助了简单的排序(起泡法)
{
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 8 - i; j++)
{
if (input[j] > input[j + 1])
{
int k;
k = input[j];
input[j] = input[j + 1];
input[j + 1] = k;
}
}
}
return input[4];
}
int main()
{
//smooth with median filter
const char *filename = "dinner.bmp";
IplImage *inputimage = cvLoadImage(filename, -1);
IplImage *smoothimage = cvCreateImage(cvSize(inputimage->width, inputimage->height), IPL_DEPTH_8U, inputimage->nChannels);
IplImage *extensionimage = cvCreateImage(cvSize(inputimage->width + 2, inputimage->height + 2), IPL_DEPTH_8U, inputimage->nChannels);
cvCopyMakeBorder(inputimage, extensionimage, cvPoint(1, 1), IPL_BORDER_REPLICATE); //进行边界扩展,原图像在新图像的(1,1)位置,IPL_BORDER_REPLICATE表示扩展为边界像素
for (int i = 0; i < smoothimage->height; i++)
{
for (int j = 0; j < smoothimage->width; j++)
{
for (int k = 0; k < smoothimage->nChannels; k++)
{
int temp[9] = {};
int s = 0;
for (int m = 0; m < 3; m++)
{
for (int n = 0; n < 3; n++)
{
temp[s++] = (unsigned char)extensionimage->imageData[(i + m)*extensionimage->widthStep + (j + n)* extensionimage->nChannels + k];
}
}
int m;
m = median(temp); //得到中值
smoothimage->imageData[i*smoothimage->widthStep + j * smoothimage->nChannels + k] = m;
}
}
}
cv::Mat smooth = cv::cvarrToMat(smoothimage);
cv::imwrite("dinnersmooth.jpg", smooth); //将去噪后的图片保存到当前目录下
cvNamedWindow("inputimage", 1);
cvNamedWindow("smoothimage", 1);
cvShowImage("inputimage", inputimage);
cvShowImage("smoothimage", smoothimage);
cvWaitKey(0);
cvDestroyWindow("inputimage");
cvDestroyWindow("smoothimage");
cvReleaseImage(&inputimage);
cvReleaseImage(&smoothimage);
}
以下是实现结果,从左到右依次是 原噪声图像---中值滤波后图像
双边滤波不仅考虑了像素信息,也考虑到了像素位置信息
具体公式如下:
Wij为当前像素权值,Pij为当前像素信息,Pi为当前像素邻域均值;Cij为当前像素位置信息,Ci为当前像素平均位置信息,与分别为当前像素信息、当前像素位置的标准差。
具体算法过程与上述算法类似,具体代码如下:
#include
#include
#include
#include
#include
#include "opencv2/opencv.hpp"
float avg(float *a) //计算平均数
{
float temp = 0.0;
for (int m = 0; m < 9; m++)
{
temp += a[m];
}
return temp;
}
float var(float *b,float c) //计算方差
{
float temp = 0.0;
for (int m = 0; m < 9; m++)
{
float a = 0;
a = b[m] > c ? (b[m] - c) : (c - b[m]);
temp = temp + a * a;
}
return temp / 9.0;
}
int main()
{
// bilateral filter
const char *filename = "building.bmp";
IplImage *inputimage = cvLoadImage(filename, -1);
IplImage *smoothimage = cvCreateImage(cvSize(inputimage->width, inputimage->height), IPL_DEPTH_8U, inputimage->nChannels);
IplImage *extensionimage = cvCreateImage(cvSize(inputimage->width + 2, inputimage->height + 2), IPL_DEPTH_8U, inputimage->nChannels);
//边界扩展
int z = 0;
while (z <30) //进行30次循环,进行一次双边滤波效果不大
{
//IplImage *extensionimage = cvCreateImage(cvSize(inputimage->width + 2, inputimage->height + 2), IPL_DEPTH_8U, inputimage->nChannels);
//IplImage *smoothimage = cvCreateImage(cvSize(inputimage->width, inputimage->height), IPL_DEPTH_8U, inputimage->nChannels);
cvCopyMakeBorder(inputimage, extensionimage, cvPoint(1, 1), IPL_BORDER_REPLICATE); //进行边界扩展,原图像在新图像的(1,1)位置,IPL_BORDER_REPLICATE表示扩展为边界像素
for (int i = 0; i < smoothimage->height; i++)
{
for (int j = 0; j < smoothimage->width; j++)
{
for (int k = 0; k < smoothimage->nChannels; k++)
{
float average, variance = 0;
float shuzu[9] = {};
int pos = 0;
for (int m = -1; m < 2; m++)
{
for (int n = -1; n < 2; n++)
{
shuzu[pos++] = (unsigned char)extensionimage->imageData[(i + 1 + m)*extensionimage->widthStep + (j + 1 + n) * extensionimage->nChannels + k] / 9;
}
}
average = avg(shuzu); //计算均值
variance = var(shuzu, average); //计算方差
float root = sqrt(variance);
if (variance < 1) //防止相似像素间的方差过小,造成错误
{
smoothimage->imageData[i*smoothimage->widthStep + j * smoothimage->nChannels + k]
= (unsigned char)extensionimage->imageData[(i + 1)*extensionimage->widthStep + (j + 1) * extensionimage->nChannels + k];
//printf("%f\n", variance);
}
else
{
float temp = 0.0;
float w = 0.0;
float num1[9] = { 1.414,1,1.414,1,0,1,1.414,1,1.414 }; //位置信息
int s = 0;
for (int m = -1; m < 2; m++)
{
for (int n = -1; n < 2; n++)
{
float e, e1, e2, den1, num2, den2;
//num1 = (m ^ 2 + n ^ 2) ^ (1/2);
den1 = 0.182;
e1 = exp(-num1[s++] / den1);
unsigned char x, y;
x = (unsigned char)extensionimage->imageData[(i + 1)*extensionimage->widthStep + (j + 1) * extensionimage->nChannels + k];
y = (unsigned char)extensionimage->imageData[(i + 1 + m)*extensionimage->widthStep + (j + 1 + n)* extensionimage->nChannels + k];
num2 = x < y ? (y - x) : (x - y); //像素信息转换为无符号字符型0-255之间,保证差值为整数
num2 = pow(num2, 2);
den2 = variance;
e2 = exp(-num2 / (2 * den2));
e = e1 * e2;
w += e;
temp += e * (unsigned char)extensionimage->imageData
[(i + 1 + m)*extensionimage->widthStep + (j + 1 + n)* extensionimage->nChannels + k];
}
}
smoothimage->imageData[i*smoothimage->widthStep + j * smoothimage->nChannels + k] = temp / w;
}
}
}
}
for (int i = 0; i < smoothimage->height; i++)
{
for (int j = 0; j < smoothimage->width; j++)
{
for (int k = 0; k < smoothimage->nChannels; k++)
{
inputimage->imageData[i*inputimage->widthStep + j * inputimage->nChannels + k]
= smoothimage->imageData[i*smoothimage->widthStep + j * smoothimage->nChannels + k];
}
}
}
z++;
}
cvNamedWindow("inputimage", 1);
cvNamedWindow("smoothimage", 1);
cvShowImage("inputimage", inputimage);
cvShowImage("smoothimage", smoothimage);
cvWaitKey(0);
cv::Mat smooth = cv::cvarrToMat(smoothimage);
cv::imwrite("buildingsmooth.bmp", smooth);
/*const char *file = "G:\\flower_filter.bmp";
cvSaveImage(file, smoothimage);*/
cvDestroyWindow("inputimage");
cvDestroyWindow("smoothimage");
cvReleaseImage(&inputimage);
cvReleaseImage(&smoothimage);
}
具体实现效果如下,从左到右依次为 原噪声图像---双边滤波15次后的图像---双边滤波后30次的图像---双边滤波后40次的图像