Impulse Noise(图像脉冲噪音)的抑制和处理方法(提取自《现代图像处理算法教程》一书并做解释)。
相关参考文章:现代图像处理算法教程(全)
参考书籍:modern-algorithms-for-image-processing-computer-imagery-by-example-using-C#
在上面的英文版书籍中,提出了一种去除脉冲噪音的方法,所谓的脉冲噪声是影响单个的、随机选择的像素或相邻像素的组合,而不是影响图像的所有像素(这个是高斯噪声的特征)。我们传统概念中提到的椒盐噪音其实只是脉冲噪音的一种特例,脉冲噪音可以分暗脉冲噪声和亮脉冲噪声。暗噪声包含亮度低于其邻域亮度的像素或像素组,而亮噪声则相反。
抑制灰度或彩色图像中的脉冲噪声的问题相当复杂,我们以暗噪声为例,则需要自动检测图像中满足以下条件的像素的所有子集 S :
1、子集 S 的所有像素必须具有低于或等于阈值 T 的亮度。
2、子集 S 中的所有像素必须是连接在一起的。
3、像素数(即 S 的面积)低于预定值 M 。
4、不属于 S 但是与 S 的相邻的所有像素必须具有高于 T 的亮度。
5、对于不同的子集 S ,阈值 T 可以不同。
这个问题很难,因为阈值 T 是未知的。理论上,在一个接一个地测试 T 的所有可能值的同时解决问题是可能的。然而,这样的算法会非常慢。
M. I. Schlesinger 在1997年提出了以下快速解决方案的想法:
“We propose such a procedure that the “noise removing” with subsequent thresholding gives the same results as thresholding with subsequent commonly used noise removing in the binary picture. This equivalency holds for every threshold’s value.
我感觉到难以翻译,就直接用原文把。
接着书本里提出了一个具体的算法,我先不说具体的实现,我把我写完算法后对该算法的理解先用文字表达出其核心的思想。
以暗脉冲噪音为例,其实现的方法为:
对图像的所有像素,先从亮度高的像素开始,依次寻找该像素周边领域里亮度小于或等于该像素的连续区域,如果这个区域的面积小于预设的面积M,则把这个区域内的所有像素都设置为和这个领域像素相接触的所有像素中的最小值,显然,这个最小值也要比这个区域内的任何像素都亮。处理完亮度最高的像素后,在继续处理后续亮度逐渐降低的像素,不但重复这个过程,直到所有像素都处理完成。
对于亮脉冲噪音,则处理过程相反。
因此,整个过程只有一个外部参数,即预设的面积,当然,暗脉冲噪音和亮脉冲噪音对应的预设面积可以不同。
从以上的过程来看,所谓的暗脉冲噪音,噪音部位并不一定是很暗的像素,只是相对来说暗一点,比如一团像素值为253的像素如果周边都是255,则只要他的面积小于M,也就会被赋值为255。这个和我们传统的直观理解还是有所不同的,这也是为什么我们去除暗脉冲噪音时为什么要从最亮的像素开始向暗像素逐步进行处理。
从上述过程来看,如果直接实现这样的定义,则还是个非常耗时的过程,因为
(1)、需要从255每次递减一直到0为止,256次遍历整个图像,每次遍历时寻找到像素值等于循环变量的像素位置,然后在该位置进行符合条件的连续区域查找。
(2)、连续区域查找本身是个较为复杂的过程,也是相当耗时。
(3)、如果不进行256次遍历,则需要对图像像素进行排序,而且排序时还要带上图像坐标的X和Y信息。对于大图,排序本身也是非常耗时的。
因此,文章中提出了几个供加速的过程,也是非常有意义的事情。
(1)为了避免排序或者256次递归,首先利用直方图的有关属性,并定义了一个二维数组,来保存图像中各个色阶像素的有关信息,二维数组的第一维范围从0到255,表示色阶值或者说亮度值,第二维的范围是动态的,其大小是图像中具有该色阶或亮度像素的个数,这样,比如数组Index[light][10]是表示亮度等于light的所有像素的集合中的第十个像素的索引(或者位置信息),因此,可以明显的看到这个数组的第二维的里所有元素相加的总和即为图像的像素个数。
这样,当从小亮度开始读取数组Index时(第一维),我们获得以亮度增加的顺序排序的像素的索引,但是当以相反的方向读取时,我们获得以亮度减少的顺序排序的像素的索引。
那个书中的C#代码写的比较混乱,我贴一段C++的代码表示下上述过程的意思:
// 计算图像的直方图,从而间接获取每个色阶在图像中的像素数
for (int Y = 0; Y < Height; Y++)
{
unsigned char* LinePS = Src + Y * Stride;
for (int X = 0; X < Width; X++)
{
Histgram[LinePS[X]]++;
}
}
// 为每个色阶分配合适的内存
for (int Y = 0; Y < 256; Y++)
{
if (Histgram[Y] == 0)
{
PosIndex[Y] = NULL;
}
else
{
// 根据每个色阶的像素数分配合适的大小
PosIndex[Y] = (Point*)malloc(Histgram[Y] * sizeof(Point));
if (PosIndex[Y] == NULL)
{
Status = IM_STATUS_OUTOFMEMORY;
goto FreeMemory;
}
}
} // 再次把直方图数据赋值为0,让其充当在循环中的计数器,并且最终也恢复成正确的直方图数据
memset(Histgram, 0, 256 * sizeof(int)); for (int Y = 0; Y < Height; Y++)
{
unsigned char* LinePS = Src + Y * Stride;
for (int X = 0; X < Width; X++)
{
int Light = LinePS[X];
// 第一个索引Light表示色阶,第二个Histgram[Light]表示在Light色阶中对应的顺序
// PosIndex[100][2]则就记录了图像中第三个色阶值为100的像素的位置,当然这个排序的顺序是按照先行后列的
Point* P = &PosIndex[Light][Histgram[Light]];
P->X = X;
P->Y = Y;
// 这个亮度的索引增加1
Histgram[Light]++;
}
}
首先遍历一次图像进行直方图统计,这样就能知道图像中每个色阶像素的个数,然后根据这个个数分配合理的内存,并再次遍历图像,根据每个位置的色阶把对应的位置信息填充到二维数组中(或者二维指针)。这样就记录了图像中不同色阶的位置信息,并具有一定的统计和排序意义。
(2)、为了避免不必要的计算,我们接着计算下图像的实际最大值和最小值。这个可以直接借用直方图实现。
// 求取图像的最大值和最小值
int MinValue = 255, MaxValue = 0;
for (int Y = 0; Y < 256; Y++)
{
if (Histgram[Y] != 0)
{
MinValue = Y;
break;
}
}
for (int Y = 255; Y >= 0; Y--)
{
if (Histgram[Y] != 0)
{
MaxValue = Y;
break;
}
}
(3)、接下来就是关键了,我们以处理暗脉冲噪音为例,借助前面的处理步骤,我们能轻松的从最亮的像素开始向暗像素开始处理:
// 去除暗噪音,从最大值减开始循环
for (int Light = MaxValue; Light >= MinValue; Light--)
{
// 扫描所有原图中像素值等于Light的所有像素位置
// Histgram[Light]为0的像素则直接会不进行循环的
for (int Y = 0; Y < Histgram[Light]; Y++)
{
Point* P = &PosIndex[Light][Y];
int Pos = P->Y * Stride + P->X;
//......................
}
}
最外一重循环的意思很明显,而第二重Y循环,从下标0开始,一直到Histgram[Light] - 1,因为Histgram[Light]中实际保存的就是图像中色阶为Light的像素的总个数,在PosIndex[Light][Y]中在保存了各个色阶为Light像素的坐标位置。
那么在这个循环内部,可以参考书本中的BreadthFirst_D函数,核心就是以当前点为种子点,按照领域的像素小于等于Light的原则进行区域生长,在生长的过程中,如果遇到了大于Light的领域信息,则要用一个变量记录下这些区域的最小值Min,当生长过程中,发现生长的区域面积已经大于预设值M了,则可以立即停止生长了。否则继续下去,直到周边没有任何像素能继续生长了。
这个生长的过程可以用队列方便的视线,当然也有数组的方式实现的。
对一个像素生长完成后,如果区域面积是满足小于M的要求的,则用前面记录的M值把这些区域的像素都赋值为M,实现噪音的去除。如果大于M了,则可以不管他,继续下一步处理。
但是作者在这里做了一个特别的处理,即区域面积大于M时,我们可以额外多做一个判断,即在这个区域里如果有和Light值相同的像素,则标记相同像素的那个位置,并且下一次处理那个位置时检查这个标记,如果已经有了值,则跳过这个像素不处理,这样就能节省大量的时间,这是因为,如果在同一个联通区域里的两个点,具有相同的色阶值,则分别以他们为中心进行区域生长时的结果肯定是一模一样的,因此,后面就完全没有必要再次进行重复的生长了。
书本作者提供的代码BreadthFirst_D函数里面用了一系列的位操作技巧,个人感觉有点炫技的嫌疑,直接用标记数组起来方便多了。
对于彩色图像,这个算法是不能分通道处理的,因为单独通道处理可能导致每个点的通道不会被同样处理,结果就是会出现一些异常的彩色斑点,一般都是用各个彩色像素的灰度信息来作为特征进行计算,然后统一处理三通道数据。
我们在理解书本原理的基础上,自行进行了C++编码,并对书本提供的两个图片进行了测试,均取得了不错的结果:


一幅灰度图像,一幅彩色图像,暗脉冲和亮脉冲的大小都设置为25像素,右图为去燥后的处理结果,从结果看,非常有效的去除了一些瑕疵,而且,较为关键的一点是,基本上我目前研究过的其他的传统的去燥算法,似乎都达不到类似的较为完美结果。
对于典型的椒盐噪音,因为他本身就属于脉冲噪音,因此,肯定是能完美出来好的, 如下面两图所示:


需要说明的是,即使是这样做了优化,这个去除噪音的速度呢还是不很很快,而且速度除了和图像大小有关外,还和图像的内容有关,也就是说同一样大小的一幅图,处理速度可能会有几倍的差异。
另外,在填充这一块也有一些内容值得考虑,目前是使用领域边缘中最小的值或最大的值填充整个符合条件的领域,那是否可以考虑用领域边缘的平均值呢,或者用这个值和领域内的图像做个融合呢,这样就可以适当的保留领域内的一些边缘信息,而不是都变为同一个值。
这个算法本身不算什么惊艳或者高深算法,但是这种处理的方式和方法和一般的图像处理步骤还是有较大的区别的,也可以说是独树一帜吧。也许这种思路也可以扩展到一些其他的算法应用中去。
该算法目前也已经集成到我的SSE优化的算法集合里:https://files.cnblogs.com/files/Imageshop/SSE_Optimization_Demo.rar?t=1660121429

如果想时刻关注本人的最新文章,也可关注公众号:

Impulse Noise(图像脉冲噪音)的抑制和处理方法(提取自《现代图像处理算法教程》一书并做解释)。的更多相关文章
- vc/mfc获取rgb图像数据后动态显示及保存图片的方法
vc/mfc获取rgb图像数据后动态显示及保存图片的方法 该情况可用于视频通信中获取的位图数据回放显示或显示摄像头捕获的本地图像 第一种方法 #include<vfw.h> 加载 vfw3 ...
- [转]MFC子线程更改图像数据后更新主窗口图像显示方法
程序思路是由外部的输入输出控制卡发出采集图像信号,之后相机采集图像得到图像数据指针,接收图像数据指针创建成图像最后显示到MFC对话框应用程序的Picture Control控件上,同时,为了标定相机位 ...
- PHP生成带logo图像二维码的两种方法
本文主要和大家分享PHP生成带logo图像二维码的两种方法,主要以文字和代码的形式和大家分享,希望能帮助到大家. 一.利用Google API生成二维码Google提供了较为完善的二维码生成接口,调用 ...
- Canny边缘检测及图像缩放之图像处理算法-OpenCV应用学习笔记四
在边缘检测算法中Canny颇为经典,我们就来做一下测试,并且顺便实现图像的尺寸放缩. 实现功能: 直接执行程序得到结果如下:将载入图像显示在窗口in内,同时进行图像两次缩小一半操作将结果显示到i1,i ...
- 利用OpenCV检测图像中的长方形画布或纸张并提取图像内容
基于知乎上的一个答案.问题如下: 也就是在一张照片里,已知有个长方形的物体,但是经过了透视投影,已经不再是规则的长方形,那么如何提取这个图形里的内容呢?这是个很常见的场景,比如在博物馆里看到一幅很喜欢 ...
- Android异步加载图像(含线程池,缓存方法)
研究了android从网络上异步加载图像: (1)由于android UI更新支持单一线程原则,所以从网络上取数据并更新到界面上,为了不阻塞主线程首先可能会想到以下方法. 在主线程中new 一个Han ...
- MFC获取rgb图像数据后动态显示及保存图片的方法
该情况可用于视频通信中获取的位图数据回放显示或显示摄像头捕获的本地图像 第一种方法 #include<vfw.h> 加载 vfw32.lib 链接库 //---------------- ...
- 根据图像路径,创建CBitmap对象的方法
因为项目的关系,需要根据图像路径,创建CBitmap对象.起初查资料找到了LoadBitmap这个函数,根据CSDN得 BOOL LoadBitmap ( LPCTSTR lpszResourceNa ...
- 将一幅图像取平均值缩小N倍实现方法
/// <summary> /// 将图像缩小N倍 /// </summary> /// <param name="source">原图数据&l ...
- 利用matlab求图像均值和方差的几种方法
一.求均值 % 求一副灰度图像的均值 close all; clear; clc; i=imread('d:/lena.jpg'); %载入真彩色图像 i=rgb2gray(i); %转换为灰度图 i ...
随机推荐
- POLIR-Society--Networking- {关系、理论与管理}: 关系网的“边界” + 利益、情感与人性 的矩阵组合: 动机、思考与表达
POLIR-Society-Management-CNO5R+Leading: 管理Leading(引领工作)的几个要点: 立场(目标人群)+金钱价值观+需求+服务产品+管理控制_复杂和不确定性 领导 ...
- sed删除指定行以及前n行
有文本文件,内容如下,需要找出response不为on的设备ip root@dev[15:49:33]$ cat result.txt index[8] ip[8.8.8.8] send respon ...
- Unity组件式思想框架 非常好用
改良了几代版本 组件式设计思想 using System; using System.Collections.Generic; using UnityEngine; public class Enti ...
- linux 笔记 (2)
1:管道:用竖杠表示(|),语法结构:命令1|命令2(把信息从一端传送到另一端) 2:tee:把输出的一个副本输送到标准输出,一般配合管道(|)一起使用 例如:who | tee who.out 3: ...
- CVE-2024-28752 Apache CXF Aegis databinding SSRF漏洞 (复现)
CVE-2024-28752目录终端下执行docker compose up -d开启容器 访问ip:8080 验证该容器是否完全启动成功/查看服务期望的参数类型 访问 http://192.168. ...
- 简单的博客页面客制化 v1
DIY博客的页面 写在前面: 申请了博客第一件事当然是整一个炫酷的界面. 自己水平不够,选了个比较顺眼的皮肤,大部分是套用网上现成的模板完成的. 具体定制的内容: 1.字体的修改 2.版面占比的调整 ...
- [题解]P3952 [NOIP2017 提高组] 时间复杂度
P3952 [NOIP2017 提高组] 时间复杂度 我们把循环的嵌套关系看做树形结构,梳理一下\(3\)种情况: 直接跳过当前子树: \(x,y\in\mathbb{N}\),且\(x>y\) ...
- Python 修改 pip 源为国内源
https://zhuanlan.zhihu.com/p/109939711 在 python 里经常要安装各种这样的包,安装各种包时最常用的就是 pip,pip 默认从官网下载文件,官网位于国外,下 ...
- 万字图文:如何从0到1搭建一套自媒体获客AI Agent
大家好,我是汤师爷,专注AI智能体分享,致力于帮助100W人用智能体创富~ 现在自媒体是不是已经成为每个企业必备的获客渠道了? 但说实话,做短视频获客有三大痛点: 1.没有专业技能:拍摄.剪辑.文案, ...
- GZY.Quartz.MUI(基于Quartz的UI可视化操作组件) 2.8.0发布 新增仪表盘和检索功能
前言 很久没更新这个组件了,主要是没想到加什么东西 后来经过群友提醒,其实可以加个仪表盘的功能,方便在任务比较多的时候监听执行情况. (PS:有些群友的定时任务已经干到了上百条..) 说加就加.. 所 ...