图像去噪

内容

  • 均值滤波

    利用 OpenCV 对灰度图像像素进行操作,分别利用算术均值滤波器、几何均值滤波器、谐波和逆谐波均值滤波器进行图像去噪。模板大小为 5*5。(分别为图像添加高斯噪声、胡椒噪声、盐噪声和椒盐噪声,并观察滤波效果)

  • 中值滤波

    利用 OpenCV 对灰度图像像素进行操作,利用 5*5 尺寸的模板对图像进行中值滤波。(分别为图像添加高斯噪声、胡椒噪声、盐噪声和椒盐噪声,并观察滤波效果)

  • 自适应均值滤波

    利用 OpenCV 对灰度图像像素进行操作,设计自适应局部降低噪声滤波器去噪算法。模板大小7*7。(对比均值滤波器的效果)

  • 自适应中值滤波

    利用 OpenCV 对灰度图像像素进行操作,设计自适应中值滤波算法对椒盐图像进行去噪。模板大小 7*7(对比中值滤波器的效果)

添加噪声

高斯噪声

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void addGaussianNoise(Mat& srcImage, Mat& dstImage, double mean, double stddev)
{
dstImage = srcImage.clone();

// 使用均值为mean、标准差为stddev的高斯分布生成噪声
std::default_random_engine generator;
std::normal_distribution<double> distribution(mean, stddev);

for (int i = 0; i < srcImage.rows; i++) {
for (int j = 0; j < srcImage.cols; j++) {
for (int c = 0; c < srcImage.channels(); c++) {
if (srcImage.channels() == 1) {
dstImage.at<uchar>(i, j) = saturate_cast<uchar>(srcImage.at<uchar>(i, j) + distribution(generator));
}
else if (srcImage.channels() == 3) {
dstImage.at<Vec3b>(i, j)[c] = saturate_cast<uchar>(srcImage.at<Vec3b>(i, j)[c] + distribution(generator));
}
}
}
}
}

胡椒噪声

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void addPepperNoise(Mat& srcImage, Mat& dstImage, double pepperRatio)
{
dstImage = srcImage.clone();
int totalPixels = srcImage.rows * srcImage.cols;
int numPepperPixels = static_cast<int>(totalPixels * pepperRatio);

std::default_random_engine generator;
std::uniform_int_distribution<int> distribution(0, totalPixels - 1);

for (int i = 0; i < numPepperPixels; i++) {
int randomIndex = distribution(generator);
int row = randomIndex / srcImage.cols;
int col = randomIndex % srcImage.cols;
if (srcImage.channels() == 1) {
dstImage.at<uchar>(row, col) = 0; // 设置为黑色
}
else if (srcImage.channels() == 3) {
dstImage.at<Vec3b>(row, col) = Vec3b(0, 0, 0); // 设置为黑色
}
}
}

盐噪声

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void addSaltNoise(Mat& srcImage, Mat& dstImage, double saltRatio)
{
dstImage = srcImage.clone();
int totalPixels = srcImage.rows * srcImage.cols;
int numSaltPixels = static_cast<int>(totalPixels * saltRatio);

std::default_random_engine generator;
std::uniform_int_distribution<int> distribution(0, totalPixels - 1);

for (int i = 0; i < numSaltPixels; i++) {
int randomIndex = distribution(generator);
int row = randomIndex / srcImage.cols;
int col = randomIndex % srcImage.cols;
if (srcImage.channels() == 1) {
dstImage.at<uchar>(row, col) = 255; // 设置为白色
}
else if (srcImage.channels() == 3) {
dstImage.at<Vec3b>(row, col) = Vec3b(255, 255, 255); // 设置为白色
}
}
}

椒盐噪声

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void addSaltPepperNoise(Mat& srcImage, Mat& dstImage, double saltRatio, double pepperRatio)
{
dstImage = srcImage.clone();
int totalPixels = srcImage.rows * srcImage.cols;
int numSaltPixels = static_cast<int>(totalPixels * saltRatio);
int numPepperPixels = static_cast<int>(totalPixels * pepperRatio);

std::default_random_engine generator;
std::uniform_int_distribution<int> distribution(0, totalPixels - 1);

// 添加盐噪声
for (int i = 0; i < numSaltPixels; i++) {
int randomIndex = distribution(generator);
int row = randomIndex / srcImage.cols;
int col = randomIndex % srcImage.cols;
if (srcImage.channels() == 1) {
dstImage.at<uchar>(row, col) = 255; // 设置为白色
}
else if (srcImage.channels() == 3) {
dstImage.at<Vec3b>(row, col) = Vec3b(255, 255, 255); // 设置为白色
}
}

// 添加胡椒噪声
for (int i = 0; i < numPepperPixels; i++) {
int randomIndex = distribution(generator);
int row = randomIndex / srcImage.cols;
int col = randomIndex % srcImage.cols;
if (srcImage.channels() == 1) {
dstImage.at<uchar>(row, col) = 0; // 设置为黑色
}
else if (srcImage.channels() == 3) {
dstImage.at<Vec3b>(row, col) = Vec3b(0, 0, 0); // 设置为黑色
}
}
}

噪声效果

高斯噪声(mean=0, stddev=30) 高斯噪声(mean=0, stddev=30)
胡椒噪声(p=0.1) 胡椒噪声(p=0.1)
盐噪声(p=0.1) 盐噪声(p=0.1)
椒盐噪声(p1=0.1, p2=0.1) 椒盐噪声(p1=0.1, p2=0.1)

均值滤波(size = 5)

算术均值滤波

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void arithmetic_mean_filter(Mat& img, Mat& result, int size) {
result = img.clone();
int height = img.rows;
int width = img.cols;
int k = size / 2;
double sum = 0;
for (int i = k; i < height - k; i++) {
for (int j = k; j < width - k; j++) {
for (int c = 0; c < img.channels(); c++) {
int sum = 0;
for (int m = -k; m <= k; m++) {
for (int n = -k; n <= k; n++) {
if (img.channels() == 1) {
sum += img.at<uchar>(i + m, j + n);
}
else if (img.channels() == 3) {
sum += img.at<Vec3b>(i + m, j + n)[c];
}
}
}
if (img.channels() == 1) {
result.at<uchar>(i, j) = sum / (size * size);
}
else if (img.channels() == 3) {
result.at<Vec3b>(i, j)[c] = sum / (size * size);
}
}
}
}
}

算术均值滤波效果

高斯噪声 高斯噪声
胡椒噪声 胡椒噪声
盐噪声 盐噪声
椒盐噪声 椒盐噪声

几何均值滤波

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void geometric_mean_filter(Mat& img, Mat& result, int size) {
result = img.clone();
int height = img.rows;
int width = img.cols;
int k = size / 2;
double sum = 0;
for (int i = k; i < height - k; i++) {
for (int j = k; j < width - k; j++) {
for (int c = 0; c < img.channels(); c++) {
sum = 1;
for (int m = -k; m <= k; m++) {
for (int n = -k; n <= k; n++) {
if (img.channels() == 1) {
sum *= img.at<uchar>(i + m, j + n);
}
else if (img.channels() == 3) {
sum *= img.at<Vec3b>(i + m, j + n)[c];
}
}
}
if (img.channels() == 1) {
result.at<uchar>(i, j) = pow(sum, 1.0 / (size * size));
}
else if (img.channels() == 3) {
result.at<Vec3b>(i, j)[c] = pow(sum, 1.0 / (size * size));
}
}
}
}
}

几何均值滤波效果

高斯噪声 高斯噪声
胡椒噪声 胡椒噪声
盐噪声 盐噪声
椒盐噪声 椒盐噪声

谐波均值滤波

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void harmonic_mean_filter(Mat& img, Mat& result, int size) {
result = img.clone();
int height = img.rows;
int width = img.cols;
int k = size / 2;
double sum = 0;
for (int i = k; i < height - k; i++) {
for (int j = k; j < width - k; j++) {
for (int c = 0; c < img.channels(); c++) {
sum = 0;
for (int m = -k; m <= k; m++) {
for (int n = -k; n <= k; n++) {
if (img.channels() == 1) {
sum += 1.0 / img.at<uchar>(i + m, j + n);
}
else if (img.channels() == 3) {
sum += 1.0 / img.at<Vec3b>(i + m, j + n)[c];
}
}
}
if (img.channels() == 1) {
result.at<uchar>(i, j) = (size * size) / sum;
}
else if (img.channels() == 3) {
result.at<Vec3b>(i, j)[c] = (size * size) / sum;
}
}
}
}
}

谐波均值滤波效果

高斯噪声 高斯噪声
胡椒噪声 胡椒噪声
盐噪声 盐噪声
椒盐噪声 椒盐噪声

逆谐波均值滤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void contraharmonic_mean_filter(Mat& img, Mat& result, int size, double Q) {
result = img.clone();
int height = img.rows;
int width = img.cols;
int k = size / 2;
double sum1 = 0;
double sum2 = 0;
for (int i = k; i < height - k; i++) {
for (int j = k; j < width - k; j++) {
for (int c = 0; c < img.channels(); c++) {
sum1 = 0;
sum2 = 0;
for (int m = -k; m <= k; m++) {
for (int n = -k; n <= k; n++) {
if (img.channels() == 1) {
sum1 += pow(img.at<uchar>(i + m, j + n), Q + 1);
sum2 += pow(img.at<uchar>(i + m, j + n), Q);
}
else if (img.channels() == 3) {
sum1 += pow(img.at<Vec3b>(i + m, j + n)[c], Q + 1);
sum2 += pow(img.at<Vec3b>(i + m, j + n)[c], Q);
}
}
}
if (img.channels() == 1) {
result.at<uchar>(i, j) = sum1 / sum2;
}
else if (img.channels() == 3) {
result.at<Vec3b>(i, j)[c] = sum1 / sum2;
}
}
}
}
}

逆谐波均值滤波效果

高斯噪声(filter.Q = 1.5) 高斯噪声(filter.Q = 1.5)
胡椒噪声(filter.Q = 1.5) 胡椒噪声(filter.Q = 1.5)
盐噪声(filter.Q = -1.5) 盐噪声(filter.Q = -1.5)
椒盐噪声(filter.Q = 1.5) 椒盐噪声(filter.Q = 1.5)

自适应均值滤波

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
void adaptive_mean_filter(Mat& srcImage, Mat& dstImage, int size)
{
dstImage = srcImage.clone();
int height = srcImage.rows;
int width = srcImage.cols;
int k = size / 2;

// 估算
//cv::Scalar image_mean, image_stddev;
//if (srcImage.channels() == 1) { // 灰度图像
// cv::meanStdDev(srcImage, image_mean, image_stddev);
//}
//else if (srcImage.channels() == 3) { // 彩色图像
// cv::Mat image_channels[3];
// cv::split(srcImage, image_channels);
// cv::Scalar channel_means[3];
// cv::Scalar channel_stddevs[3];

// for (int i = 0; i < 3; i++) {
// cv::meanStdDev(image_channels[i], channel_means[i], channel_stddevs[i]);
// }

// double total_mean = (channel_means[0][0] + channel_means[1][0] + channel_means[2][0]) / 3.0;
// double total_stddev = sqrt((channel_stddevs[0][0] * channel_stddevs[0][0] + channel_stddevs[1][0] * channel_stddevs[1][0] + channel_stddevs[2][0] * channel_stddevs[2][0]) / 3.0);

// image_mean = cv::Scalar(total_mean);
// image_stddev = cv::Scalar(total_stddev);
//}
//double variance = pow(image_stddev[0], 2);
double variance = 900; // 直接采用与高斯噪声方差匹配的值
for (int i = k; i < height - k; i++) {
for (int j = k; j < width - k; j++) {
for (int c = 0; c < srcImage.channels(); c++) {
if (srcImage.channels() == 1) {
double gxy = srcImage.at<uchar>(i, j);
double xy_mean = 0;
for (int m = -k; m <= k; m++) {
for (int n = -k; n <= k; n++) {
xy_mean += srcImage.at<uchar>(i + m, j + n);
}
}
xy_mean /= (size * size);
double xy_variance = 0;
for (int m = -k; m <= k; m++) {
for (int n = -k; n <= k; n++) {
xy_variance += pow(srcImage.at<uchar>(i + m, j + n) - xy_mean, 2);
}
}
xy_variance /= (size * size);
if (variance / xy_variance > 1.0) {
dstImage.at<uchar>(i, j) = saturate_cast<uchar>(xy_mean);
}
else {
dstImage.at<uchar>(i, j) = saturate_cast<uchar>(gxy - (variance / xy_variance) * (gxy - xy_mean));
}
}
else if (srcImage.channels() == 3) {
double gxy = srcImage.at<Vec3b>(i, j)[c];
double xy_mean = 0;
for (int m = -k; m <= k; m++) {
for (int n = -k; n <= k; n++) {
xy_mean += srcImage.at<Vec3b>(i + m, j + n)[c];
}
}
xy_mean /= (size * size);
double xy_variance = 0;
for (int m = -k; m <= k; m++) {
for (int n = -k; n <= k; n++) {
xy_variance += pow(srcImage.at<Vec3b>(i + m, j + n)[c] - xy_mean, 2);
}
}
xy_variance /= (size * size);
if (variance / xy_variance > 1.0) {
dstImage.at<Vec3b>(i, j)[c] = saturate_cast<uchar>(xy_mean);
}
else {
dstImage.at<Vec3b>(i, j)[c] = saturate_cast<uchar>(gxy - (variance / xy_variance) * (gxy - xy_mean));
}
}
}
}
}
}

自适应均值滤波效果

高斯噪声 stddev=30 算术均值滤波
自适应均值滤波 stddev=30 自适应均值滤波(stddev由全局方差估算)

均值滤波总结

  • 算术均值滤波效果相近,会模糊图像
  • 几何均值滤波适合处理盐噪声,但不适合有胡椒的噪声,即灰度是接近0的像素点产生影响较大
  • 谐波滤波适合处理盐噪声,但不适合有胡椒的噪声,即灰度是接近0的像素点产生影响较大
  • 逆谐波滤波不适合椒盐噪声,对于胡椒噪声和盐噪声应该选择不同符号的Q值
  • 自适应中值滤波相比算术均值滤波更加清晰,但是需要知道噪声的方差。如果估算的方差和噪声方差相差较大,则效果接近算术均值滤波,效果不明显

中值滤波

中值滤波

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void median_filter(Mat& img, Mat& result, int size) {
result = img.clone();
int height = img.rows;
int width = img.cols;
int k = size / 2;
vector<int> v;
for (int i = k; i < height - k; i++) {
for (int j = k; j < width - k; j++) {
for (int c = 0; c < img.channels(); c++) {
v.clear();
for (int m = -k; m <= k; m++) {
for (int n = -k; n <= k; n++) {
if (img.channels() == 1) {
v.push_back(img.at<uchar>(i + m, j + n));
}
else if (img.channels() == 3) {
v.push_back(img.at<Vec3b>(i + m, j + n)[c]);
}
}
}
sort(v.begin(), v.end());
if (img.channels() == 1) {
result.at<uchar>(i, j) = v[v.size() / 2];
}
else if (img.channels() == 3) {
result.at<Vec3b>(i, j)[c] = v[v.size() / 2];
}
}
}
}
}

中值滤波效果(size = 5)

高斯噪声 高斯噪声
胡椒噪声 胡椒噪声
盐噪声 盐噪声
椒盐噪声 椒盐噪声

自适应中值滤波(size = 7)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
void adaptive_median_filter(Mat& srcImage, Mat& dstImage, int size)
{
dstImage = srcImage.clone();
int height = srcImage.rows;
int width = srcImage.cols;

int kstart = size / 2;
int Smax = size;
vector<int> v;
for(int i = kstart; i < height - kstart; i++) {
for (int j = kstart; j < width - kstart; j++) {
for (int c = 0; c < srcImage.channels(); c++) {
int Sxy = 3;
int k = Sxy / 2;
while (Sxy < Smax) {
v.clear();
for (int m = -k; m <= k; m++) {
for (int n = -k; n <= k; n++) {
if (srcImage.channels() == 1) {
v.push_back(srcImage.at<uchar>(i + m, j + n));
}
else if (srcImage.channels() == 3) {
v.push_back(srcImage.at<Vec3b>(i + m, j + n)[c]);
}
}
}
sort(v.begin(), v.end());
int zmin = v[0];
int zmax = v[v.size() - 1];
int zmed = v[v.size() / 2];
int zxy = 0;
if (srcImage.channels() == 1) {
zxy = srcImage.at<uchar>(i, j);
}
else if (srcImage.channels() == 3) {
zxy = srcImage.at<Vec3b>(i, j)[c];
}
if (zmed > zmin && zmed < zmax) {
if (zxy > zmin && zxy < zmax) {
if (srcImage.channels() == 1) {
dstImage.at<uchar>(i, j) = zxy;
}
else if (srcImage.channels() == 3) {
dstImage.at<Vec3b>(i, j)[c] = zxy;
}
}
else {
if (srcImage.channels() == 1) {
dstImage.at<uchar>(i, j) = zmed;
}
else if (srcImage.channels() == 3) {
dstImage.at<Vec3b>(i, j)[c] = zmed;
}
}
break;
}
else {
Sxy += 2;
k = Sxy / 2;
}
}
}
}
}
}

自适应中值滤波效果

椒盐噪声(p1=0.1,p2=0.1) 椒盐噪声(p1=0.25,p2=0.25)
中值滤波(size=7) 中值滤波(size=7)
中值滤波(size=3) 中值滤波(size=3)
自适应中值滤波(Smax=7) 自适应中值滤波(Smax=7)

中值滤波总结

  • 中值滤波更加适合处理带椒盐噪声
  • 自适应中值滤波一般而言,在p>0.2时效果更好。如果用普通的中值滤波,采用size7则图片模糊,采用size3则处理不干净。在p<0.2时,则用size3的普通中值滤波效果更好