目录
1 变量定义
在进行算法分析前,首先对各变量进行定义:
2 各类模板卷积介绍
2.1 均值滤波
为抵消突变的数值,最容易想到的便是均值方法,均值滤波是一种特殊的模板卷积其邻域的权重均为1,公式描述为:
最常见的对于最常见3x3大小box均值滤波,其实际作用效果如下:
另:均值滤波模板并不一定为全1矩阵,在此展示几种常见均值滤波模板,模板的选取不同会导致滤波效果不同:
2.2 高斯滤波
高斯滤波是一种以高斯函数为基底的模板卷积,为了方便导出高斯模板,在此引入高斯函数,其中标准差
σ
\sigma
σ取值不同时,高斯函数的作用范围也不同:
取其中整数坐标数点函数值并将矩阵内数值表示为分数,即可获取高斯模板,由于标准差的取值不同,模板大小不同,约分方式不同,高斯模板多种多样。由于高斯函数的性质,模板取值范围一般不超过三倍标准差,一般模板大小可取
f
l
o
o
r
(
6
σ
−
1
)
∗
f
l
o
o
r
(
6
σ
−
1
)
floor(6\sigma-1)*floor(6\sigma-1)
floor(6σ−1)∗floor(6σ−1) ,如下即为一标准差为1,模板大小为5*5高斯模板:
标准差与作用范围联系:
实际高斯滤波公式可描述为:
2.3 双边滤波
双边滤波是一种非线性滤波方法,相较于只考虑位置信息的高斯滤波方法,双边滤波,双边滤波在计算像素权重时同时将位置信息及颜色信息考虑在内,双边滤波的思想很简单,将原本全部分给空间信息的分量部分分给像素颜色信息,像素颜色差异越小其权重越大:
将空间权重记为
G
s
(
s
p
a
c
e
−
w
e
i
g
h
t
)
G_s(space-weight)
Gs(space−weight) ,像素值权重记为
G
r
(
r
a
n
g
e
−
w
e
i
g
h
t
)
G_r(range-weight)
Gr(range−weight),
则有:
σ
s
2
,
σ
r
2
\sigma_s^2,\sigma_r^2
σs2,σr2为
{
∣
∣
P
p
−
P
q
∣
∣
}
\{|| P_p-P_q||\}
{∣∣Pp−Pq∣∣}集合与
{
∣
I
p
−
I
q
∣
}
\{|I_p-I_q|\}
{∣Ip−Iq∣}集合的标准差。
以下展示空间权重及像素值权重分别造成的影响,取
i
,
j
∈
[
−
25
,
25
]
,
i
,
j
∈
i
n
t
i,j\in[-25,25],i,j\in int
i,j∈[−25,25],i,j∈int范围经
c
u
b
i
c
cubic
cubic插值得出的曲面,可以轻易算得其以(0,0)点为中心各位置像素值权重如图所示:
像素值权重影响:
不同中心点的像素值权重影响:
空间权重影响:
空间-像素值权重的共同影响:
我们将两种权值综合考虑,发现当所取中心点位于边缘附近时,边缘颜色值权重较高,而在边缘一侧时,该侧权重值较高:
2.4 中值滤波
中值滤波可以非常好的处理突现的椒盐类噪声,但对于较细的深色或浅色薄边缘处理能力有限。同时对于前景区域角点具有腐蚀效果。中值滤波的公式描述为:
对于
3
∗
3
3*3
3∗3大小中值滤波可以表述为:
实际作用效果如图所示:
3 各类滤波特性
3.1 均值,高斯处理椒盐噪声
均值滤波对于颜色较为不均匀图像的平滑能力较强,但面对颜色较为均匀区域的突变点的消去效果并不是很好,由图所示,对于椒盐噪声,使用均值滤波消去噪声至少要进行4次以上模板卷积,同时会模糊其他区域(注,数值越大颜色越贴近暖色,数值越小越贴近冷色):
高斯滤波对中心位置数值赋予了更大的权重,所以会更加难以处理单点类椒盐噪声。由图所示,使用高斯滤波需要进行更多次数的滤波才能达到与中值滤波相似的水平。
3.2 中值滤波处理椒盐噪声
经试验可以看出,对于薄边缘较少图片的椒盐噪声,中值滤波可以非常完美对其进行消除,且消除后图像较为稳定,不会因为中值滤波的次数较多而导致图像过度模糊,下图为对于椒盐噪声图片的一次中值滤波结果:
使用中值滤波处理薄边缘较少的图片,图片各像素数值会在几次之内趋于稳定,并在多次中值滤波的过程中不发生显著变化:
3.3 多次中值滤波的薄边缘腐蚀
当进行中值滤波时,由于模板形状的影响,图像中较细的深色或浅色薄边缘会被腐蚀:
对于在均匀背景下,前景区域的锐角、直角角点,由于角点邻域背景区域所占面积较大,因此容易产生角点腐蚀的现象:
试验效果:
3.4 高斯滤波处理高斯噪声
高斯滤波对于高斯噪音有着较好的处理能力,在噪声斑点较少时,可以较快的平滑噪声突变,在噪声斑点较多时,虽平滑后虽色彩会有少部分偏移,但仍能在保留良好的轮廓的基础上平滑噪声区域:
3.5 双边滤波对边缘的保留性
双边滤波在边缘一侧时会提高该侧区域权值,在边缘线上时会提高边缘处权值,基于此原理,双边滤波对于连续边缘有着极强的保留性,如下图所示,在进行较多次双边滤波(50次)后,图像仍能保留基本边缘:
3.6 双边滤波对磨皮的应用
基于双边滤波对边缘细节的保留性,可以产生类似磨皮的效果:
3.7 基于双边滤波的指纹图除杂
双边滤波的特性较为适用于保留边缘的指纹小面积联通区域去除,如图所示,我们将指纹图像进行多次双边滤波并二值化,可以较为完美的得到清晰指纹图
4 代码
4.1 图像扩张
expandImage.h
#pragma once
#include<cv.h>
#include<cxcore.h>
#include<highgui.h>
IplImage *expandImage(IplImage *inputimage);
IplImage *expandImage_nTimes(IplImage *inputimage, int n);//
//==============================================================
//功能:对图像进行边缘扩张
//输入:
// *expandimage :需进行扩张处理图像
// n :扩张像素数目
//输出:边缘后图像
//--------------------------------------------------------------
//注:
//单次扩张请使用 expandImage
//多次扩张请使用 expandImage_nTimes
//==============================================================
expandImage.cpp
#include"expandImage.h"
//单次边缘扩张
IplImage *expandImage(IplImage *inputimage)
{
IplImage *expandimage = cvCreateImage(cvSize(inputimage->width + 2, inputimage->height + 2), IPL_DEPTH_8U, inputimage->nChannels);
//中心区域复制
for (int i = 0; i < inputimage->height; i++)
{
for (int j = 0; j < inputimage->width; j++)
{
for (int k = 0; k < inputimage->nChannels; k++)
{
expandimage->imageData[(i + 1)*expandimage->widthStep + (j + 1)* expandimage->nChannels + k] =
inputimage->imageData[i*inputimage->widthStep + j * inputimage->nChannels + k];
}
}
}
//上下边缘复制
for (int i = 0; i < inputimage->width; i++)
{
for (int k = 0; k < inputimage->nChannels; k++)
{
expandimage->imageData[(i + 1)* expandimage->nChannels + k] =
inputimage->imageData[i * inputimage->nChannels + k];
expandimage->imageData[(expandimage->height - 1)*expandimage->widthStep + (i + 1)* expandimage->nChannels + k] =
inputimage->imageData[(inputimage->height - 1)*inputimage->widthStep + i * inputimage->nChannels + k];
}
}
//左右边缘复制
for (int i = 0; i < inputimage->height; i++)
{
for (int k = 0; k < inputimage->nChannels; k++)
{
expandimage->imageData[(i + 1)*expandimage->widthStep + k] =
inputimage->imageData[i * inputimage->widthStep + k];
expandimage->imageData[(i + 1)*expandimage->widthStep + (expandimage->width - 1)* expandimage->nChannels + k] =
inputimage->imageData[i * inputimage->widthStep + (inputimage->width - 1)* inputimage->nChannels + k];
}
}
//四角复制
for (int k = 0; k < inputimage->nChannels; k++)
{
expandimage->imageData[k] = inputimage->imageData[k];
expandimage->imageData[(expandimage->height - 1)*expandimage->widthStep + k] =
inputimage->imageData[(inputimage->height - 1)*inputimage->widthStep + k];
expandimage->imageData[(expandimage->width - 1)* expandimage->nChannels + k] =
inputimage->imageData[(inputimage->width - 1)* inputimage->nChannels + k];
expandimage->imageData[(expandimage->height - 1)*expandimage->widthStep + (expandimage->width - 1)* expandimage->nChannels + k] =
inputimage->imageData[(inputimage->height - 1)*inputimage->widthStep + (inputimage->width - 1)* inputimage->nChannels + k];
}
return expandimage;
}
//n次边缘扩张
IplImage *expandImage_nTimes(IplImage *inputimage, int n) {
IplImage *expandimage_n;
expandimage_n = expandImage(inputimage);
for (int i = 0; i < n - 1; i++) {
expandimage_n = expandImage(expandimage_n);
}
return expandimage_n;
}
4.2 图像滤波
filterImage.h
IplImage *filterImage(IplImage *expandimage, int *mask, int n);//
//==============================================================
//功能:利用任意大小二维模板对图像进行图像滤波
//输入:
// *expandimage :需进行图像滤波的图像
// *mask :mask[]类型模板
// n :模板大小
//输出:滤波后图像
//--------------------------------------------------------------
//注:
//当mask定义为: int mask[n][n]={{...},{...},...}时
//filterImage=filterImage(expandimage, *mask, int n);
//
//当mask定义为: int mask[]={...}时
//filterImage=filterImage(expandimage, mask, int n);
//==============================================================
IplImage *midFilterImage(IplImage *expandimage, int n);//中值滤波
IplImage *bilateralFilterImage(IplImage *expandimage, int n, double sigamaS, double sigamaR);//双边滤波
//
//
//
filterImage.cpp
#include"filterImage.h"
//nxn模板滤波
IplImage *filterImage(IplImage *expandimage, int *mask, int n)
{
int mid = (n + 1) / 2 - 1;
double tempValue;
double weightSum = 0;
//计算权重总和
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
weightSum = weightSum + mask[i*n + j];
}
}
//滤波主要部分
IplImage *filterimage = cvCreateImage(cvSize(expandimage->width - 2 * mid, expandimage->height - 2 * mid), IPL_DEPTH_8U, expandimage->nChannels);
for (int i = 0; i < filterimage->height; i++)
{
for (int j = 0; j < filterimage->width; j++)
{
for (int k = 0; k < filterimage->nChannels; k++)
{
tempValue = 0;
for (int ii = 0; ii < n; ii++)
{
for (int jj = 0; jj < n; jj++)
{
tempValue = tempValue + (double)mask[ii*n + jj] * (unsigned char)expandimage->imageData[(i + ii)*expandimage->widthStep + (j + jj)* expandimage->nChannels + k] / weightSum;
}
}
filterimage->imageData[i*filterimage->widthStep + j * filterimage->nChannels + k] = (unsigned char)tempValue;
}
}
}
return filterimage;
}
//中值滤波
IplImage *midFilterImage(IplImage *expandimage, int n) {
int mid = (n + 1) / 2 - 1;
double *valueList;
double tempValue;
valueList = (double *)malloc(n*n * sizeof(double));
IplImage *filterimage = cvCreateImage(cvSize(expandimage->width - 2 * mid, expandimage->height - 2 * mid), IPL_DEPTH_8U, expandimage->nChannels);
for (int i = 0; i < filterimage->height; i++)
{
for (int j = 0; j < filterimage->width; j++)
{
for (int k = 0; k < filterimage->nChannels; k++)
{
//将nxn邻域内数据赋予double类型指针中
for (int ii = 0; ii < n; ii++)
{
for (int jj = 0; jj < n; jj++)
{
valueList[ii*n + jj] = (unsigned char)expandimage->imageData[(i + ii)*expandimage->widthStep + (j + jj)* expandimage->nChannels + k];
}
}
//冒泡排序
for (int s1 = 0; s1 < n*n - 1; s1++)
{
for (int s2 = s1 + 1; s2 < n*n; s2++)
{
if (valueList[s2] < valueList[s1])
{
tempValue = valueList[s1];
valueList[s1] = valueList[s2];
valueList[s2] = tempValue;
}
}
}
//赋予中值
filterimage->imageData[i*filterimage->widthStep + j * filterimage->nChannels + k] = (unsigned char)valueList[(n*n - 1) / 2];
}
}
}
return filterimage;
}
IplImage *bilateralFilterImage(IplImage *expandimage, int n, double sigamaS, double sigamaR) {
int mid = (n + 1) / 2 - 1;
double Ds2, Gs, Dr2, Gr;
double Gsr;
double wiedthSum;
double tempValue;
IplImage *filterimage = cvCreateImage(cvSize(expandimage->width - 2 * mid, expandimage->height - 2 * mid), IPL_DEPTH_8U, expandimage->nChannels);
for (int i = 0; i < filterimage->height; i++)
{
for (int j = 0; j < filterimage->width; j++)
{
for (int k = 0; k < filterimage->nChannels; k++)
{
tempValue = 0;
wiedthSum = 0;
for (int ii = 0; ii < n; ii++)
{
for (int jj = 0; jj < n; jj++)
{
Ds2 = (double)(ii - mid)*(ii - mid) + (double)(jj - mid)*(jj - mid);
Dr2 = (unsigned char)expandimage->imageData[(i + ii)*expandimage->widthStep + (j + jj)* expandimage->nChannels + k] -
(unsigned char)expandimage->imageData[(i + mid)*expandimage->widthStep + (j + mid)* expandimage->nChannels + k];
Dr2 = Dr2 * Dr2;
Gs = exp(-(Ds2) / (2 * sigamaS*sigamaS));
Gr = exp(-(Dr2) / (2 * sigamaR*sigamaR));
Gsr = Gs * Gr;
wiedthSum = wiedthSum + Gsr;
tempValue = tempValue + Gsr * (unsigned char)expandimage->imageData[(i + ii)*expandimage->widthStep + (j + jj)* expandimage->nChannels + k];
}
}
filterimage->imageData[i*filterimage->widthStep + j * filterimage->nChannels + k] = (unsigned char)(tempValue / wiedthSum);
}
}
}
return filterimage;
}
4.3 噪声添加
addNoise.h
#pragma once
#include<cv.h>
#include<cxcore.h>
#include<highgui.h>
#include<time.h>
IplImage* AddGuassianNoise(IplImage* src);//添加高斯噪声
IplImage* AddPepperSaltNoise(IplImage* src, double ratio);//添加椒盐噪声,随机黑白点
addNoise.cpp
#include"addNoise.h"
//添加高斯噪声
IplImage* AddGuassianNoise(IplImage* src)
{
IplImage* dst = cvCreateImage(cvGetSize(src), src->depth, src->nChannels);
IplImage* noise = cvCreateImage(cvGetSize(src), src->depth, src->nChannels);
CvRNG rng = cvRNG(-1);
cvRandArr(&rng, noise, CV_RAND_NORMAL, cvScalarAll(0), cvScalarAll(80));
cvAdd(src, noise, dst);
return dst;
}
//添加椒盐噪声,随机黑白点
IplImage* AddPepperSaltNoise(IplImage* src, double ratio)
{
IplImage* dst = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, src->nChannels);
cvCopy(src, dst);
srand((unsigned)time(NULL));
int Num = floor((double)(src->height*src->width)*ratio);
printf("%d", Num);
int psi;
int psj;
int c;
for (int n = 0; n < Num; n++) {
psi = rand() % (int)src->height;
psj = rand() % (int)src->width;
c = rand() % 2;
if (c == 0) {
for (int k = 0; k < src->nChannels; k++) {
dst->imageData[psi*dst->widthStep + psj * dst->nChannels + k] = (unsigned char)255;
}
}
else if (c == 1) {
for (int k = 0; k < src->nChannels; k++) {
dst->imageData[psi*dst->widthStep + psj * dst->nChannels + k] = (unsigned char)0;
}
}
}
return dst;
}
4.4 图像批量存储
按理说直接cvSaveImage就好,但我的opencv不知道哪里出了毛病,于是写了个saveImage:
#pragma once
#include<cv.h>
#include<cxcore.h>
#include<highgui.h>
#include<opencv2/opencv.hpp>
using namespace cv;
using namespace std;
void saveImage(const char *filename, IplImage *file) {
Mat savemat = cvarrToMat(file);
imwrite(filename, savemat);
}
对于图像批量存储我是这样干的:
#include<string.h>
#include"expandImage.h"
#include"filterImage.h"
#include"saveImage.h"
int main(){
const char *filename = "想读取图片名字";
IplImage *inputimage = cvLoadImage(filename, -1);
IplImage *expandimage;
IplImage *GFimage;
int mask_GF[3][3] = { {1,2,1}, {2,4,2}, {1,2,1} };
GFimage = copyImage(inputimage);
for (int i = 0; i < 50; i++) {
expandimage = expandImage_nTimes(GFimage, 1);
GFimage = filterImage(expandimage, *mask_GF, 3);
string filesavename = "存储图片名字" + to_string(i + 1) + ".bmp";
saveImage(filesavename.c_str(), GFimage);
}
return 0;
}