一、圖像平滑
圖像平滑的目的之一是消除噪聲,二是模糊圖像。
從信号頻譜的角度來看,信号緩慢變化的部分在頻率域表現為低頻,迅速變化的部分表現為高頻。圖像在擷取、儲存、處理、傳輸過程中,會受到電氣系統和外界幹擾而存在一定程度的噪聲,圖像噪聲使圖像模糊,甚至淹沒圖像特征,給分析帶來困難。
二、模闆卷積
模闆卷積是數字圖像處理常用的一種鄰域運算方式,模闆卷積可以實作圖像平滑、圖像銳化、邊緣檢測等功能。
模闆可以是一小幅圖像,也可以是一個濾波器。
模闆卷積的基本步驟:
(1)模闆在輸入圖像上移動,讓模闆中心依次與輸入圖像的每個像素重合;
(2)模闆系數與跟模闆重合的輸入圖像的對應像素相乘,再将乘積相加;
(3)把結果賦予輸圖像,其像素位置與模闆中心在輸入圖像上的位置一緻;
具體過程如下圖所示(原圖像進行了邊界擴充,目的是保持卷積後圖像與原圖像大小相同)
圖像邊界問題:
(1)當模闆超出圖像邊界時不做處理;
(2)擴充圖像,可以複制原圖像邊界像素或利用常數填充圖像邊界。
計算結果可能超出灰階範圍,對于8位灰階圖像,當計算結果超出 [0, 255] 時,可以簡單的将其值置為0或255.
常用模闆有Box模闆和高斯模闆
以下為均值濾波與高斯濾波代碼
#include <cv.h>
#include <cxcore.h>
#include <highgui.h>
#include <stdio.h>
#include <opencv2/opencv.hpp>
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 <cv.h>
#include <cxcore.h>
#include <highgui.h>
#include <stdio.h>
#include <opencv2/opencv.hpp>
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 <cv.h>
#include <cxcore.h>
#include <highgui.h>
#include <stdio.h>
#include<math.h>
#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次的圖像