【算法随记三】小半径中值模糊的急速实现(16MB图7.5ms实现) + Photoshop中蒙尘和划痕算法解读。
在本人的博客里,分享了有关中值模糊的O(1)算法,详见:任意半径中值滤波(扩展至百分比滤波器)O(1)时间复杂度算法的原理、实现及效果 ,这里的算法的执行时间和参数是无关的。整体来说,虽然速度也很快,但是在某些特殊情况下我们还是需要更快的速度。特别是对于小半径的中值,我们有理由去对其进一步的优化的。本文我们进一步探讨这个问题。
一、3*3中值模糊
首先我们来看看半径为1的中值,此时涉及到的领域为3*3,共9个像素,那么最传统的实现方式就是对9个像素直接进行排序,这里我们直接使用系统的排序函数qsort,一种简单的代码如下所示:
int __cdecl ComparisonFunction(const void *X, const void *Y) // 一定要用__cdecl这个标识符
{
unsigned char Dx = *(unsigned char *)X;
unsigned char Dy = *(unsigned char *)Y;
if (Dx < Dy)
return -;
else if (Dx > Dy)
return +;
else
return ;
} void MedianBlur3X3_Ori(unsigned char *Src, unsigned char *Dest, int Width, int Height, int Stride)
{
int Channel = Stride / Width;
if (Channel == )
{
unsigned char Array[];
for (int Y = ; Y < Height - ; Y++)
{
unsigned char *LineP0 = Src + (Y - ) * Stride + ;
unsigned char *LineP1 = LineP0 + Stride;
unsigned char *LineP2 = LineP1 + Stride;
unsigned char *LinePD = Dest + Y * Stride + ;
for (int X = ; X < Width - ; X++)
{
Array[] = LineP0[X - ]; Array[] = LineP0[X]; Array[] = LineP0[X + ];
Array[] = LineP1[X - ]; Array[] = LineP1[X]; Array[] = LineP2[X + ];
Array[] = LineP2[X - ]; Array[] = LineP2[X]; Array[] = LineP2[X + ];
qsort(Array, , sizeof(unsigned char), &ComparisonFunction);
LinePD[X] = Array[];
}
}
}
else
{
unsigned char ArrayB[], ArrayG[], ArrayR[];
for (int Y = ; Y < Height - ; Y++)
{
unsigned char *LineP0 = Src + (Y - ) * Stride + ;
unsigned char *LineP1 = LineP0 + Stride;
unsigned char *LineP2 = LineP1 + Stride;
unsigned char *LinePD = Dest + Y * Stride + ;
for (int X = ; X < Width - ; X++)
{
ArrayB[] = LineP0[-]; ArrayG[] = LineP0[-]; ArrayR[] = LineP0[-];
ArrayB[] = LineP0[]; ArrayG[] = LineP0[]; ArrayR[] = LineP0[];
ArrayB[] = LineP0[]; ArrayG[] = LineP0[]; ArrayR[] = LineP0[]; ArrayB[] = LineP1[-]; ArrayG[] = LineP1[-]; ArrayR[] = LineP1[-];
ArrayB[] = LineP1[]; ArrayG[] = LineP1[]; ArrayR[] = LineP1[];
ArrayB[] = LineP1[]; ArrayG[] = LineP1[]; ArrayR[] = LineP1[]; ArrayB[] = LineP2[-]; ArrayG[] = LineP2[-]; ArrayR[] = LineP2[-];
ArrayB[] = LineP2[]; ArrayG[] = LineP2[]; ArrayR[] = LineP2[];
ArrayB[] = LineP2[]; ArrayG[] = LineP2[]; ArrayR[] = LineP2[]; qsort(ArrayB, , sizeof(unsigned char), &ComparisonFunction);
qsort(ArrayG, , sizeof(unsigned char), &ComparisonFunction);
qsort(ArrayR, , sizeof(unsigned char), &ComparisonFunction); LinePD[] = ArrayB[];
LinePD[] = ArrayG[];
LinePD[] = ArrayR[]; LineP0 += ;
LineP1 += ;
LineP2 += ;
LinePD += ;
}
}
}
}
代码很简洁和清晰,我们没有处理边缘的那一圈像素,这无关精要,我们编译后测试,结果如下所示:
1920*1080大小的24位图像,平均用时1280ms,灰度图像平均用时460ms,这相当的慢,无法接受。
下面我们稍微对其进行下提速。
对于9个数据的排序,我们可以对其进行特殊的处理,因为数据的个数是确定的,按照理论分析,没有必要进行大规模的比较,实际只需要进行19次比较就可以了。修改后算法如下所示:
inline void Swap(int &X, int &Y)
{
X ^= Y;
Y ^= X;
X ^= Y;
} void MedianBlur3X3_Faster(unsigned char *Src, unsigned char *Dest, int Width, int Height, int Stride)
{
int Channel = Stride / Width;
if (Channel == )
{ for (int Y = ; Y < Height - ; Y++)
{
unsigned char *LineP0 = Src + (Y - ) * Stride + ;
unsigned char *LineP1 = LineP0 + Stride;
unsigned char *LineP2 = LineP1 + Stride;
unsigned char *LinePD = Dest + Y * Stride + ;
for (int X = ; X < Width - ; X++)
{
int Gray0, Gray1, Gray2, Gray3, Gray4, Gray5, Gray6, Gray7, Gray8;
Gray0 = LineP0[X - ]; Gray1 = LineP0[X]; Gray2 = LineP0[X + ];
Gray3 = LineP1[X - ]; Gray4 = LineP1[X]; Gray5 = LineP2[X + ];
Gray6 = LineP2[X - ]; Gray7 = LineP2[X]; Gray8 = LineP2[X + ]; if (Gray1 > Gray2) Swap(Gray1, Gray2);
if (Gray4 > Gray5) Swap(Gray4, Gray5);
if (Gray7 > Gray8) Swap(Gray7, Gray8);
if (Gray0 > Gray1) Swap(Gray0, Gray1);
if (Gray3 > Gray4) Swap(Gray3, Gray4);
if (Gray6 > Gray7) Swap(Gray6, Gray7);
if (Gray1 > Gray2) Swap(Gray1, Gray2);
if (Gray4 > Gray5) Swap(Gray4, Gray5);
if (Gray7 > Gray8) Swap(Gray7, Gray8);
if (Gray0 > Gray3) Swap(Gray0, Gray3);
if (Gray5 > Gray8) Swap(Gray5, Gray8);
if (Gray4 > Gray7) Swap(Gray4, Gray7);
if (Gray3 > Gray6) Swap(Gray3, Gray6);
if (Gray1 > Gray4) Swap(Gray1, Gray4);
if (Gray2 > Gray5) Swap(Gray2, Gray5);
if (Gray4 > Gray7) Swap(Gray4, Gray7);
if (Gray4 > Gray2) Swap(Gray4, Gray2);
if (Gray6 > Gray4) Swap(Gray6, Gray4);
if (Gray4 > Gray2) Swap(Gray4, Gray2); LinePD[X] = Gray4;
}
} }
else
{ for (int Y = ; Y < Height - ; Y++)
{
unsigned char *LineP0 = Src + (Y - ) * Stride + ;
unsigned char *LineP1 = LineP0 + Stride;
unsigned char *LineP2 = LineP1 + Stride;
unsigned char *LinePD = Dest + Y * Stride + ;
for (int X = ; X < Width - ; X++)
{
int Blue0, Blue1, Blue2, Blue3, Blue4, Blue5, Blue6, Blue7, Blue8;
int Green0, Green1, Green2, Green3, Green4, Green5, Green6, Green7, Green8;
int Red0, Red1, Red2, Red3, Red4, Red5, Red6, Red7, Red8;
Blue0 = LineP0[-]; Green0 = LineP0[-]; Red0 = LineP0[-];
Blue1 = LineP0[]; Green1 = LineP0[]; Red1 = LineP0[];
Blue2 = LineP0[]; Green2 = LineP0[]; Red2 = LineP0[]; Blue3 = LineP1[-]; Green3 = LineP1[-]; Red3 = LineP1[-];
Blue4 = LineP1[]; Green4 = LineP1[]; Red4 = LineP1[];
Blue5 = LineP1[]; Green5 = LineP1[]; Red5 = LineP1[]; Blue6 = LineP2[-]; Green6 = LineP2[-]; Red6 = LineP2[-];
Blue7 = LineP2[]; Green7 = LineP2[]; Red7 = LineP2[];
Blue8 = LineP2[]; Green8 = LineP2[]; Red8 = LineP2[]; if (Blue1 > Blue2) Swap(Blue1, Blue2);
if (Blue4 > Blue5) Swap(Blue4, Blue5);
if (Blue7 > Blue8) Swap(Blue7, Blue8);
if (Blue0 > Blue1) Swap(Blue0, Blue1);
if (Blue3 > Blue4) Swap(Blue3, Blue4);
if (Blue6 > Blue7) Swap(Blue6, Blue7);
if (Blue1 > Blue2) Swap(Blue1, Blue2);
if (Blue4 > Blue5) Swap(Blue4, Blue5);
if (Blue7 > Blue8) Swap(Blue7, Blue8);
if (Blue0 > Blue3) Swap(Blue0, Blue3);
if (Blue5 > Blue8) Swap(Blue5, Blue8);
if (Blue4 > Blue7) Swap(Blue4, Blue7);
if (Blue3 > Blue6) Swap(Blue3, Blue6);
if (Blue1 > Blue4) Swap(Blue1, Blue4);
if (Blue2 > Blue5) Swap(Blue2, Blue5);
if (Blue4 > Blue7) Swap(Blue4, Blue7);
if (Blue4 > Blue2) Swap(Blue4, Blue2);
if (Blue6 > Blue4) Swap(Blue6, Blue4);
if (Blue4 > Blue2) Swap(Blue4, Blue2); if (Green1 > Green2) Swap(Green1, Green2);
if (Green4 > Green5) Swap(Green4, Green5);
if (Green7 > Green8) Swap(Green7, Green8);
if (Green0 > Green1) Swap(Green0, Green1);
if (Green3 > Green4) Swap(Green3, Green4);
if (Green6 > Green7) Swap(Green6, Green7);
if (Green1 > Green2) Swap(Green1, Green2);
if (Green4 > Green5) Swap(Green4, Green5);
if (Green7 > Green8) Swap(Green7, Green8);
if (Green0 > Green3) Swap(Green0, Green3);
if (Green5 > Green8) Swap(Green5, Green8);
if (Green4 > Green7) Swap(Green4, Green7);
if (Green3 > Green6) Swap(Green3, Green6);
if (Green1 > Green4) Swap(Green1, Green4);
if (Green2 > Green5) Swap(Green2, Green5);
if (Green4 > Green7) Swap(Green4, Green7);
if (Green4 > Green2) Swap(Green4, Green2);
if (Green6 > Green4) Swap(Green6, Green4);
if (Green4 > Green2) Swap(Green4, Green2); if (Red1 > Red2) Swap(Red1, Red2);
if (Red4 > Red5) Swap(Red4, Red5);
if (Red7 > Red8) Swap(Red7, Red8);
if (Red0 > Red1) Swap(Red0, Red1);
if (Red3 > Red4) Swap(Red3, Red4);
if (Red6 > Red7) Swap(Red6, Red7);
if (Red1 > Red2) Swap(Red1, Red2);
if (Red4 > Red5) Swap(Red4, Red5);
if (Red7 > Red8) Swap(Red7, Red8);
if (Red0 > Red3) Swap(Red0, Red3);
if (Red5 > Red8) Swap(Red5, Red8);
if (Red4 > Red7) Swap(Red4, Red7);
if (Red3 > Red6) Swap(Red3, Red6);
if (Red1 > Red4) Swap(Red1, Red4);
if (Red2 > Red5) Swap(Red2, Red5);
if (Red4 > Red7) Swap(Red4, Red7);
if (Red4 > Red2) Swap(Red4, Red2);
if (Red6 > Red4) Swap(Red6, Red4);
if (Red4 > Red2) Swap(Red4, Red2); LinePD[] = Blue4;
LinePD[] = Green4;
LinePD[] = Red4; LineP0 += ;
LineP1 += ;
LineP2 += ;
LinePD += ;
}
}
}
}
看上去代码的行数多了,但是实际上执行速度会更快,我们测试的结果如下:
1920*1080大小的24位图像,平均用时155ms,灰度图像平均用时45ms,比之前的原始实现速度要快了近10倍。
而在任意半径中值滤波(扩展至百分比滤波器)O(1)时间复杂度算法的原理、实现及效果一文中的算法,采用了SSE优化,同样大小的图耗时为:
1920*1080大小的24位图像,平均用时260ms,灰度图像平均用时160ms,比上述的C语言版本要慢。
早期有朋友曾提示我在手机上使用Neon可以做到16MB的图像半径为1的中值模糊可以做到20ms,我真的一点也不敢相信。总觉得不太可思议。16MB可是4000*4000的大小啊,我用上述C的代码处理起来要242ms,比手机端还慢了10倍。
经过朋友提醒,在https://github.com/ARM-software/ComputeLibrary/blob/master/src/core/NEON/kernels/NEMedian3x3Kernel.cpp#L113上看到了相关的Neon代码,惊奇的发现他和我上面的C代码几乎完全一样。但是就是这一点代码提醒了我。
inline void sort(uint8x8_t &a, uint8x8_t &b)
{
const uint8x8_t min = vmin_u8(a, b);
const uint8x8_t max = vmax_u8(a, b);
a = min;
b = max;
}
真是一语惊醒梦中人啊,这么简单的优化我怎么没想到呢。
我们自己看看上面的C代码,每个像素的9次比较虽然不能用SIMD指令做,但是多个像素的比较之间是相互不关联的,因此,这样我就可以一次性处理16个像素了,改成SSE优化的方式也就很简单了:
inline void _mm_sort_ab(__m128i &a, __m128i &b)
{
const __m128i min = _mm_min_epu8(a, b);
const __m128i max = _mm_max_epu8(a, b);
a = min; b = max;
}
void MedianBlur3X3_SSE(unsigned char *Src, unsigned char *Dest, int Width, int Height, int Stride)
{
int Channel = Stride / Width;
int BlockSize = , Block = ((Width - )* Channel) / BlockSize;
for (int Y = ; Y < Height - ; Y++)
{
unsigned char *LineP0 = Src + (Y - ) * Stride + Channel;
unsigned char *LineP1 = LineP0 + Stride;
unsigned char *LineP2 = LineP1 + Stride;
unsigned char *LinePD = Dest + Y * Stride + Channel;
for (int X = ; X < Block * BlockSize; X += BlockSize, LineP0 += BlockSize, LineP1 += BlockSize, LineP2 += BlockSize, LinePD += BlockSize)
{
__m128i P0 = _mm_loadu_si128((__m128i *)(LineP0 - Channel));
__m128i P1 = _mm_loadu_si128((__m128i *)(LineP0 - ));
__m128i P2 = _mm_loadu_si128((__m128i *)(LineP0 + Channel));
__m128i P3 = _mm_loadu_si128((__m128i *)(LineP1 - Channel));
__m128i P4 = _mm_loadu_si128((__m128i *)(LineP1 - ));
__m128i P5 = _mm_loadu_si128((__m128i *)(LineP1 + Channel));
__m128i P6 = _mm_loadu_si128((__m128i *)(LineP2 - Channel));
__m128i P7 = _mm_loadu_si128((__m128i *)(LineP2 - ));
__m128i P8 = _mm_loadu_si128((__m128i *)(LineP2 + Channel)); _mm_sort_ab(P1, P2); _mm_sort_ab(P4, P5); _mm_sort_ab(P7, P8);
_mm_sort_ab(P0, P1); _mm_sort_ab(P3, P4); _mm_sort_ab(P6, P7);
_mm_sort_ab(P1, P2); _mm_sort_ab(P4, P5); _mm_sort_ab(P7, P8);
_mm_sort_ab(P0, P3); _mm_sort_ab(P5, P8); _mm_sort_ab(P4, P7);
_mm_sort_ab(P3, P6); _mm_sort_ab(P1, P4); _mm_sort_ab(P2, P5);
_mm_sort_ab(P4, P7); _mm_sort_ab(P4, P2); _mm_sort_ab(P6, P4);
_mm_sort_ab(P4, P2); _mm_storeu_si128((__m128i *)LinePD, P4);
}
for (int X = Block * BlockSize; X < (Width - ) * Channel; X++, LinePD++)
{
// DO Something
}
}
}
注意到上面我已经把灰度和彩色图像的代码写成同一个方式处理了,这是因为对于彩色图像,3个通道之间的处理时毫无联系的。同时我们前面的Swap2个变量的过程时完全可以通过Min和Max两个算子实现的,我们按下F5测试运行,惊人的速度出现了:
1920*1080大小的24位图像,平均用时3ms,灰度图像平均用时1ms,比上述的C语言版本快了近40倍。
顺便也测试了下16MB的图像,结果平均只需要7.5ms。真是太厉害了。
二、5*5中值模糊
对于5*5的中值模糊,优化的方式还是一样的,但是5*5共计25个像素,理论上需要131次比较,其他的过程类似,测试基于SSE的方式,5*5的中值1920*1080大小的24位图像,平均用时40ms,灰度图像平均用时20ms,虽慢了很多,但是还是O(1)那里的速度快。
三、蒙尘和划痕
在这里提Photoshop的这个算法也许并不是很合适,但是我也是在研究中值模糊时顺便把这个算法给攻破的,当我们打开蒙尘和划痕界面时,发现其有半径和阈值两个参数,细心比较,如果阈值设置为0,则相同半径设置时其结果图像和杂色里的中间值算法的结果一模一样,这也可以从蒙尘和划痕算法和中间值同样都放在杂色菜单下可以看出端倪。
通过上述分析,我们可以肯定蒙尘和划痕算法是基于中值模糊的,实际上,PS里很多算法都是基于中值模糊的,特别是那些有平滑度参数的算法^_^。经过多次测试,我们得到的该算法的结果就是如下:
if Abs(Median - Src) > Threshold
Dest = Median
else
Dest = Src
对于彩色图像,不是用彩色图的中值,而是用其亮度值作为唯一的判断标准,如果用彩色的中值作为标准来判断每个分量的,很容易出现过多的噪点,因为有可能会出现Blue分量改变,而Red不变的情况,或其他类似现象。
蒙尘和划痕的一个作用是去除噪点,特别的,我觉得他在小半径的时候更为有用,小半径中值不会改变原图太多,加上这个阈值则可以很容易去除噪点,同时,基本不会出现新的模糊问题。比如下面这个图。
原图 半径为1的中值模糊
半径为1,阈值取20时的蒙尘和划痕 半径为2,阈值取20时的蒙尘和划痕
由以上几图,可以明显的看出,带阈值的蒙尘和划痕在抑制了噪音的同时对原图其他细节基本没有破坏,因此,是一种比较合适的初级的预处理算法,既然是预处理,那么其效率就非常重要了,因此本文的快速3*3模糊的作用也就是相当有用。
还举个例子,下面这个照片中有很多白色的小点点,如果直接用中值确实可以将白点去除,但是可能要半径为四左右才可以去除,但是此时图像整体也变得模糊了,如果使用蒙版和划痕,则处理后的效果非常完美。
原图 半径4,阈值100
本文相关算法代码下载地址:https://files.cnblogs.com/files/Imageshop/MedianBlur3X3.rar。
本人的SSE算法优化合集DEMO:测试Demo:http://files.cnblogs.com/files/Imageshop/SSE_Optimization_Demo.rar。
【算法随记三】小半径中值模糊的急速实现(16MB图7.5ms实现) + Photoshop中蒙尘和划痕算法解读。的更多相关文章
- 最快的3x3中值模糊
10.1国庆后,知名博主:laviewpbt http://www.cnblogs.com/Imageshop/ 发起了一个优化3x3中值模糊的小活动. 俺也参加其中,今天博主laviewpbt ...
- Emgu-WPF学习使用-中值模糊
原文:Emgu-WPF学习使用-中值模糊 实现效果: 实现途径: 前提:Image File-> System.Drawing.Bitmap->Image<Bgr, byte> ...
- 【算法随记七】巧用SIMD指令实现急速的字节流按位反转算法。
字节按位反转算法,在有些算法加密或者一些特殊的场合有着较为重要的应用,其速度也是一个非常关键的应用,比如一个byte变量a = 3,其二进制表示为00000011,进行按位反转后的结果即为110000 ...
- opencv-11-中值滤波及自适应中值滤波
开始之前 在上一篇我们实现了读取噪声图像, 然后 进行三种形式的均值滤波得到结果, 由于我们自己写的均值滤波未作边缘处理, 所以效果有一定的下降, 但是总体来说, 我们得到的结果能够说明我们的算法执行 ...
- 关于中值滤波算法,以及C语言实现(转)
源:关于中值滤波算法,以及C语言实现 1.什么是中值滤波? 中值滤波是对一个滑动窗口内的诸像素灰度值排序,用其中值代替窗口中心象素的原来灰度值,它是一种非线性的图像平滑法,它对脉冲干扰级椒盐噪声的抑制 ...
- OpenCV计算机视觉学习(4)——图像平滑处理(均值滤波,高斯滤波,中值滤波,双边滤波)
如果需要处理的原图及代码,请移步小编的GitHub地址 传送门:请点击我 如果点击有误:https://github.com/LeBron-Jian/ComputerVisionPractice &q ...
- OpenCV笔记(1)(图片读取与现实、色彩空间、基础运算、均值方差、逻辑运算、泛洪填充、均值中值及自定义平滑)
一.图片读取和显示 import cv2 as cv # 图片读取cv.imread(img_path) car_img = cv.imread("car1.png") # 图片显 ...
- 【算法随记五】使用FFT变换自动去除图像中严重的网纹。
这个课题在很久以前就已经有所接触,不过一直没有用代码去实现过.最近买了一本<机器视觉算法与应用第二版>书,书中再次提到该方法:使用傅里叶变换进行滤波处理的真正好处是可以通过使用定制的滤波器 ...
- 基于FPGA的中值滤波算法实现
在这一篇开篇之前,我需要解决一个问题,上一篇我们实现了基于FPGA的均值滤波算法的实现,最后的显示效果图上发现有一些黑白色的斑点,我以为是椒盐噪声,然后在做基于FPGA的中值滤波算法的实验时,我发现黑 ...
随机推荐
- android点击屏幕隐藏小键盘
原文:android点击屏幕隐藏小键盘 fragment 下隐藏点击空白处隐藏小键盘 view.setOnTouchListener(new OnTouchListener() { @Overri ...
- Swift程式语言(中国版)(8.8 %)
前言 今天Apple宣布了一项新的编程语言Swift.还提供了一个近400页The Swift Programming Language(Swift程式语言). 虽然我没有开发者账户.不能实际锻炼机S ...
- Opencv中SVM样本训练、归类流程及实现
支持向量机(SVM)中最核心的是什么?个人理解就是前4个字--"支持向量",一旦在两类或多累样本集中定位到某些特定的点作为支持向量,就可以依据这些支持向量计算出来分类超平面,再依据 ...
- 创建可按比例调整的布局的 Windows 窗体
能够正确调整大小的窗体可以提高您的用户界面的易用性. 此演练演示了如何创建当用户调整窗体大小时按比例调整的布局. 您将使用 TableLayoutPanel 控件实现一个接收联系人信息的数据输入窗体. ...
- 办ZigBee实验SmartRF Flash Programmer软件界面无法打开
开SmartRF Flash Programmer: 打开任务管理器.在任务管理器里右键点击.将其最大化: 将最大化的界面拖动到屏幕中间: 然后关闭SmartRF Flash Programmer,之 ...
- 创建一个显示所有预定义系统颜色的ListBox
原文 Creating a ListBox that Shows All Predefined System Colors 该System.Windows.SystemColors类包含了一系列揭露当 ...
- crossplatform---Nodejs in Visual Studio Code 01.简单介绍Nodejs
1.开始 作者自己:开发人员,Asp.Net , html / js , restful , memcached , oracle ,windows , iis 目标读者:供自己以后回顾 2.我看No ...
- 用游戏杆控制WPF中三维模型
原文:用游戏杆控制WPF中三维模型 用游戏杆控制WPF中三维模型 今天心情比较好,不写WF的文章了,换个主题.写一个我最最最擅长的内容. 例子下载: http://files.cnblogs. ...
- 第四十天 阿乐在其中—Android小游戏的飞机(四)加入敌人
8月9日,晴. "江城如画里,山晓望晴空. 雨水夹明镜.双桥落彩虹. 人烟寒橘柚,秋色老梧桐." 上篇已经让飞机载入子弹和音效及背景音乐,本篇主要加入敌机. 本篇要用到的几个函数解 ...
- 基于Linux C的socketEthereal程序和Package分析 (一个)
执行测试平台:CentOS 6.5发行版,内核版本号3.11 1. Linux抓包源程序 在OSI七层模型中.网卡工作在物理层和数据链路层的MAC子层. 进行网络通信时.源主机通过socket( ...