在您阅读本文前,先需要告诉你的是:即使是本文优化过的算法,DCT去噪的计算量依旧很大,请不要向这个算法提出实时运行的苛刻要求。

  言归正传,在IPOL网站中有一篇基于DCT的图像去噪文章,具体的链接地址是:http://www.ipol.im/pub/art/2011/ys-dct/,IPOL网站的最大特点就是他的文章全部提供源代码,而且可以基于网页运行相关算法,得到结果。不过其里面的代码本身是重实现论文的过程,基本不考虑速度的优化,因此,都相当的慢。

这篇文章的原理也是非常简单的,整个过程就是进行dct变换,然后在dct域进行硬阈值操作,再反变换回来。但是DCT变换不是针对整幅图进行处理,而是基于每个像素点的领域(这里使用的8领域或者16领域),每次则移动一个像素。IPOL上提供的代码函数也很少,但是一个关键的问题就是内存占用特别夸张,我们贴他的部分代码:

// Transfer an image im of size width x height x channel to sliding patches of
// size width_p x height_p xchannel.
// The patches are stored in patches, where each ROW is a patch after being
// reshaped to a vector.
void Image2Patches(vector<float>& im,
vector< vector< vector< vector< float > > > >& patches,
int width, int height, int channel, int width_p,
int height_p)
{
int size1 = width * height; int counter_patch = ; // Loop over the patch positions
for (int j = ; j < height - height_p + ; j ++)
for (int i = ; i < width - width_p + ; i ++) {
int counter_pixel = ;
// loop over the pixels in the patch
for (int kp = ; kp < channel; kp++)
for (int jp = ; jp < height_p; jp ++)
for (int ip = ; ip < width_p; ip ++) {
patches[counter_patch][kp][jp][ip] =
im[kp*size1 + (j+jp)*width + i + ip];
counter_pixel ++;
}
counter_patch ++;
}
}

  看到这里的patches了,他的作用是保存每个点周边的8*8领域的DCT变换的结果的,即使使用float类型的变量,也需要约Width * Height * 8 * 8 * sizeof(float)个字节的数组,假定宽度和高度都为1024的灰度图,这个数据量为256MB,其实256MB的内存在现在机器来说毫无压力,可这里要求是连续分布内存,则很有可能分配失败,在外部表现的错误则是内存溢出。我们首先来解决这个问题。

  我们来看看原作者的代码中patches的主要作用,见下面这部分代码:

    // Loop over the patch positions
for (int j = ; j < height - height_p + ; j ++)
for (int i = ; i < width - width_p + ; i ++) {
int counter_pixel = ;
// loop over the pixels in the patch
for (int kp = ; kp < channel; kp++)
for (int jp = ; jp < height_p; jp ++)
for (int ip = ; ip < width_p; ip ++) {
im[kp*size1 + (j+jp)*width + i + ip] +=
patches[counter_patch][kp][jp][ip];
im_weight[kp*size1 + (j+jp)*width + i + ip] ++;
counter_pixel ++;
}
counter_patch ++;
} // Normalize by the weight
for (int i = ; i < size; i ++)
im[i] = im[i] / im_weight[i];

  可见patches主要是为了保存每个点领域的DCT变换的数据,然后累加,上述四重循环外围两个是图像的宽度和高度方向,内部两重则是DCT的变换数据的行列方向,如果我们把DCT的行列方向的循环提到最外层,而把图像的宽度和高度方向的循环放到内存,其一就是整个过程只需要一个8*8*sizeof(float)大小的重复利用的内存,第二就是正好符号了内部放大循环,外部放小循环的优化准则,在速度和内存占用上都有重大提高。

我们来继续优化,在获取每个点的领域时,传统的方式需要8*8个循环,那整个图像就需要Width * Height * 8 * 8次了, 这个数据量太可观了,在图像处理中任意核卷积(matlab中conv2函数)的快速实现 一文中共享的代码里提到了一种方式,大约只需要Width * Height * 8次读取,并且其cache命中率还要高很多,具体的方式可参考本文附带的代码。

继续可以优化的地方就是8*8点的浮点DCT变换了。原文提供一维DCT变换的代码如下:

for (int j = ; j < ; j ++)
{
out[j] = ;
for (int i = ; i < ; i ++)
{
out[j] += in[i] * DCTbasis[j][i];
}
}

就是两个循环,共64次乘法和64次加法,上面的DCTbasis为固定的DCT系数矩阵。

  而一维的IDCT的变换代码为:

for (int j = ; j < PATCHSIZE; j ++)
{
out[j] = ;
for (int i = ; i < PATCHSIZE; i ++)
{
out[j] += in[i] * DCTbasis[i][j];
}
}

和DCT唯一的区别仅仅是DCTbasis的行列坐标相反。

这种代码一看就想到了有SSE进行优化,PATCHSIZE为8 正好是两个SSE浮点数m128的大小,乘法和加法都有对应的SSE函数,一次性可进行4个浮点加法和浮点乘法,效率当然会高很多,优化后的代码如下所示:

/// <summary>
/// 8*8的一维DCT变换及其逆变换。
/// </summary>
/// <param name="In">输入的数据。</param>
/// <param name="Out">输出的数据。</param>
/// <param name="Invert">是否进行逆变换。</param>
/// <remarks> 1、输入和输出不能相同,即不支持in-place操作。</remarks>
/// <remarks> 2、算法只直接翻译IPOL上的,利用了SSE加速。</remarks>
/// <remarks> 3、在JPEG压缩等程序中的8*8DCT变换里优化的算法乘法比较少,但不好利用SSE优化,我用那里的代码测试还比下面的慢。</remarks>
/// <remarks> 4、有关8*8 DCT优化的代码见:http://insurgent.blog.hexun.com/1797358_d.html。 </remarks>
void DCT8X81D(float *In, float *Out, bool Invert)
{
__m128 Sum, A, B;
const float *Data = (const float *)&Sum;
A = _mm_load_ps(In);
B = _mm_load_ps(In + ); if (Invert == FALSE)
{
/*for (int Y = 0; Y < PATCHSIZE; Y ++)
{
Out[Y] = 0;
for (int X = 0; X < PATCHSIZE; X ++)
{
Out[Y] += In[X] * DCTbasis[Y * PATCHSIZE + X];
}
}*/ Sum = _mm_mul_ps(A, _mm_load_ps(DCTbasis));
Sum = _mm_add_ps(Sum, _mm_mul_ps(B, _mm_load_ps(DCTbasis + )));
Out[] = Data[] + Data[] + Data[] + Data[]; // 这里是否还有更好的方式实现呢? Sum = _mm_mul_ps(A, _mm_load_ps(DCTbasis + ));
Sum = _mm_add_ps(Sum, _mm_mul_ps(B, _mm_load_ps(DCTbasis + ))); // 不用一个Sum变量,用多个是不是还能提高指令级别的并行啊
Out[] = Data[] + Data[] + Data[] + Data[]; Sum = _mm_mul_ps(A, _mm_load_ps(DCTbasis + ));
Sum = _mm_add_ps(Sum, _mm_mul_ps(B, _mm_load_ps(DCTbasis + )));
Out[] = Data[] + Data[] + Data[] + Data[]; Sum = _mm_mul_ps(A, _mm_load_ps(DCTbasis + ));
Sum = _mm_add_ps(Sum, _mm_mul_ps(B, _mm_load_ps(DCTbasis + )));
Out[] = Data[] + Data[] + Data[] + Data[]; Sum = _mm_mul_ps(A, _mm_load_ps(DCTbasis + ));
Sum = _mm_add_ps(Sum, _mm_mul_ps(B, _mm_load_ps(DCTbasis + )));
Out[] = Data[] + Data[] + Data[] + Data[]; Sum = _mm_mul_ps(A, _mm_load_ps(DCTbasis + ));
Sum = _mm_add_ps(Sum, _mm_mul_ps(B, _mm_load_ps(DCTbasis + )));
Out[] = Data[] + Data[] + Data[] + Data[]; Sum = _mm_mul_ps(A, _mm_load_ps(DCTbasis + ));
Sum = _mm_add_ps(Sum, _mm_mul_ps(B, _mm_load_ps(DCTbasis + )));
Out[] = Data[] + Data[] + Data[] + Data[]; Sum = _mm_mul_ps(A, _mm_load_ps(DCTbasis + ));
Sum = _mm_add_ps(Sum, _mm_mul_ps(B, _mm_load_ps(DCTbasis + )));
Out[] = Data[] + Data[] + Data[] + Data[];
}
else
{
/*for (int Y = 0; Y < PATCHSIZE; Y ++)
{
Out[Y] = 0;
for (int X = 0; X < PATCHSIZE; X ++)
{
Out[Y] += In[X] * IDCTbasis[Y * PATCHSIZE + X];
}
}*/ Sum = _mm_mul_ps(A, _mm_load_ps(IDCTbasis));
Sum = _mm_add_ps(Sum, _mm_mul_ps(B, _mm_load_ps(IDCTbasis + )));
Out[] = Data[] + Data[] + Data[] + Data[]; Sum = _mm_mul_ps(A, _mm_load_ps(IDCTbasis + ));
Sum = _mm_add_ps(Sum, _mm_mul_ps(B, _mm_load_ps(IDCTbasis + )));
Out[] = Data[] + Data[] + Data[] + Data[]; Sum = _mm_mul_ps(A, _mm_load_ps(IDCTbasis + ));
Sum = _mm_add_ps(Sum, _mm_mul_ps(B, _mm_load_ps(IDCTbasis + )));
Out[] = Data[] + Data[] + Data[] + Data[]; Sum = _mm_mul_ps(A, _mm_load_ps(IDCTbasis + ));
Sum = _mm_add_ps(Sum, _mm_mul_ps(B, _mm_load_ps(IDCTbasis + )));
Out[] = Data[] + Data[] + Data[] + Data[]; Sum = _mm_mul_ps(A, _mm_load_ps(IDCTbasis + ));
Sum = _mm_add_ps(Sum, _mm_mul_ps(B, _mm_load_ps(IDCTbasis + )));
Out[] = Data[] + Data[] + Data[] + Data[]; Sum = _mm_mul_ps(A, _mm_load_ps(IDCTbasis + ));
Sum = _mm_add_ps(Sum, _mm_mul_ps(B, _mm_load_ps(IDCTbasis + )));
Out[] = Data[] + Data[] + Data[] + Data[]; Sum = _mm_mul_ps(A, _mm_load_ps(IDCTbasis + ));
Sum = _mm_add_ps(Sum, _mm_mul_ps(B, _mm_load_ps(IDCTbasis + )));
Out[] = Data[] + Data[] + Data[] + Data[]; Sum = _mm_mul_ps(A, _mm_load_ps(IDCTbasis + ));
Sum = _mm_add_ps(Sum, _mm_mul_ps(B, _mm_load_ps(IDCTbasis + )));
Out[] = Data[] + Data[] + Data[] + Data[];
}
}

   当然,简单的循环并不是效率最高的算法,在标准的JPG压缩中就有着8*8的DCT变换,哪里的计算量有着更少的乘法和加法,在 http://insurgent.blog.hexun.com/1797358_d.html 中提出代码里,只有32次乘法和更少的加法,但是由于这个代码的向量性很差,是很难用SSE进行优化的,我实测的结果时他的代码比我用SSE优化后的速度慢。

当进行2维的DCT的时候,其步骤为对每行先进行行方向的一维DCT,然后对结果转置,在对转置后的数据进行行方向一维DCT,得到的结果再次转置则为2维DCT。8*8的转置虽然直接实现基本不存在cache miss的问题,不过还是用有关的SSE来实现未尝不是个好主意,在intrinsic中提供了一个4*4浮点转置的宏_MM_TRANSPOSE4_PS,我们对这个宏稍作修改,修改后的代码如下:

//    http://stackoverflow.com/questions/5200338/a-cache-efficient-matrix-transpose-program
// http://stackoverflow.com/questions/16737298/what-is-the-fastest-way-to-transpose-a-matrix-In-c
// https://msdn.microsoft.com/en-us/library/4d3eabky(v=vs.90).aspx
// 高效的矩阵转置算法,速度约为直接编写的4倍, 整理时间 2015.10.12
inline void TransposeSSE4x4(float *Src, float *Dest)
{
__m128 Row0 = _mm_load_ps(Src);
__m128 Row1 = _mm_load_ps(Src + );
__m128 Row2 = _mm_load_ps(Src + );
__m128 Row3 = _mm_load_ps(Src + ); // _MM_TRANSPOSE4_PS(Row0, Row1, Row2, Row3); // 或者使用这个SSE的宏 __m128 Temp0 = _mm_shuffle_ps(Row0, Row1, _MM_SHUFFLE(, , , )); // Row0[0] Row0[1] Row1[0] Row1[1]
__m128 Temp2 = _mm_shuffle_ps(Row0, Row1, _MM_SHUFFLE(, , , )); // Row0[2] Row0[3] Row1[2] Row1[3]
__m128 Temp1 = _mm_shuffle_ps(Row2, Row3, _MM_SHUFFLE(, , , )); // Row2[0] Row2[1] Row3[0] Row3[1]
__m128 Temp3 = _mm_shuffle_ps(Row2, Row3, _MM_SHUFFLE(, , , )); // Row2[2] Row2[3] Row3[2] Row3[3] Row0 = _mm_shuffle_ps(Temp0, Temp1, _MM_SHUFFLE(, , , )); // Row0[0] Row1[0] Row2[0] Row3[0]
Row1 = _mm_shuffle_ps(Temp0, Temp1, _MM_SHUFFLE(, , , )); // Row0[1] Row1[1] Row2[1] Row3[1]
Row2 = _mm_shuffle_ps(Temp2, Temp3, _MM_SHUFFLE(, , , )); // Row0[2] Row1[2] Row2[2] Row3[2]
Row3 = _mm_shuffle_ps(Temp2, Temp3, _MM_SHUFFLE(, , , )); // Row0[3] Row1[3] Row2[3] Row3[3] _mm_store_ps(Dest, Row0);
_mm_store_ps(Dest + , Row1);
_mm_store_ps(Dest + , Row2);
_mm_store_ps(Dest + , Row3);
}

本质上说,这些优化都是小打小闹,提高不了多少速度,当然还有一些可以优化的地方,比如权重和累加和的更新,最后的累加和和权重的相除得到结果等等都有有关的SSE函数可以使用。

还有一个可以优化的地方就是,在高度方向上前后两个像素8*8领域 在进行2D的DCT变换时,其第一次行方向上的DCT变换有7行的结果是可以重复利用的,如果利用这一点,则可以获得约15%的速度提示。

   综合以上各种优化手段,在I5的机器上一幅512*512 的灰度图像大约处理用时为100ms左右 ,比起IPOL的demo运行速度快了很多倍了。

DCT滤波的效果上很多情况下也是相当不错的,想比NLM也毫不逊色,我们贴一些图片看下效果:

                  

    

                        噪音图像                                                                                            去噪后效果(Sigma = 10)

为显示方便,上面的图像都是设置了一定程度的缩放。

共享我改写的这个代码供大家共同学习提高,如果哪位能进一步提高算法的速度 ,也希望不吝赐教。

  代码下载链接:http://files.cnblogs.com/files/Imageshop/DCT_Denosing.rar

  后记:  继续优化了下8*8点的DCT里SSE代码的处理方式,改变了累加的方向,速度提高30%;然后把64次阈值处理的代码也改成SSE优化,速度提高10%;在如果考虑进行隔行隔列取样然后在DCT进行累加,经过测试基本上看不出有什么效果上的区别,但是速度大约能提高3.5倍左右;如果考虑多线程的方式,比如开个双线程,速度约能提高0.8倍,如果CPU支撑AVX,则大概又能提高0.9倍,算来算去,我感觉可以实时了。

****************************作者: laviewpbt   时间: 2015.11.14    联系QQ:  33184777 转载请保留本行信息**********************

优化IPOL网站中基于DCT(离散余弦变换)的图像去噪算法(附源代码)。的更多相关文章

  1. DCT(离散余弦变换)算法原理和源码(python)

    原理: 离散余弦变换(DCT for Discrete Cosine Transform)是与傅里叶变换相关的一种变换,它类似于离散傅里叶变换(DFT for Discrete Fourier Tra ...

  2. 运动目标检测中基于HSV空间的阴影去除算法

    在运动目标检测中,常常会出现由于光线被遮挡,或场景其他物体的遮挡,在目标附近或场景里出现阴影,阴影的出现对后期目标的正确分割与处理带了很大的不便.如今,国内外已有不少文献来研究这个问题,并且提出了各种 ...

  3. 隐马尔可夫模型中基于比例因子的前向算法(java实现)

    直接上干货哈,其他子算法,后续补上.                                                                  System.out.print ...

  4. SSE图像算法优化系列二十一:基于DCT变换图像去噪算法的进一步优化(100W像素30ms)。

    在优化IPOL网站中基于DCT(离散余弦变换)的图像去噪算法(附源代码) 一文中,我们曾经优化过基于DCT变换的图像去噪算法,在那文所提供的Demo中,处理一副1000*1000左右的灰度噪音图像耗时 ...

  5. 基于DCT系数的实时监控中运动目标检测

    本文的主要内容来自2009 Advanced Video and Signal Based Surveillance会议的一篇论文“Real-Time Moving Object Detection ...

  6. MySQL 并行复制演进及 MySQL 8.0 中基于 WriteSet 的优化

    MySQL 8.0 可以说是MySQL发展历史上里程碑式的一个版本,包括了多个重大更新,目前 Generally Available 版本已经已经发布,正式版本即将发布,在此将介绍8.0版本中引入的一 ...

  7. 基于DCT的图片数字水印实验

    1. 实验类别 设计型实验:MATLAB设计并实现基于DCT的图像数字水印算法. 2. 实验目的 了解基于DCT的图像数字水印技术,掌握基于DCT系数关系的图像水印算法原理,设计并实现一种基于DCT的 ...

  8. 让那些为Webkit优化的网站也能适配IE10

    特别声明:此篇文章由David根据Charles Morris的英文文章原名<Adapting your WebKit-optimized site for Internet Explorer ...

  9. 让那些为Webkit优化的网站也能适配IE10(转载)

    转载地址:http://www.w3cplus.com/css3/adapting-your-webkit-optimized-site-for-internet-explorer-10.html 特 ...

随机推荐

  1. ElasticSearch 5学习(6)——分布式集群学习分享1

    在使用中我们把文档存入ElasticSearch,但是如果能够了解ElasticSearch内部是如何存储的,将会对我们学习ElasticSearch有很清晰的认识.本文中的所使用的ElasticSe ...

  2. java自定义注解类

    一.前言 今天阅读帆哥代码的时候,看到了之前没有见过的新东西, 比如java自定义注解类,如何获取注解,如何反射内部类,this$0是什么意思? 于是乎,学习并整理了一下. 二.代码示例 import ...

  3. linux进程间通信之一:无名管道

    无名管道是linux中管道通信的一种原始方法,有以下特征: 1.单工通信模式,具有固定的读端和写端: 2.管道可以看成是一种特殊的文件,对于它的读写可以使用普通的read(),write()等文件IO ...

  4. WebSocket与消息推送

    B/S结构的软件项目中有时客户端需要实时的获得服务器消息,但默认HTTP协议只支持请求响应模式,这样做可以简化Web服务器,减少服务器的负担,加快响应速度,因为服务器不需要与客户端长时间建立一个通信链 ...

  5. RMAN异机恢复快速参考

    应用场景:服务器A为正常运行的生产环境,需要在服务器B上部署一套相同环境做测试. 数据库环境:RHEL6.4 + Oracle 11.2.0.4.7 一. 服务器A备份数据库 1.1 在线备份(数据库 ...

  6. 1Z0-053 争议题目解析

    1Z0-053 争议题目解析 Summary 题目NO. 题目解析链接地址 题库答案 参考答案 考查知识点  24 http://www.cnblogs.com/jyzhao/p/5319220.ht ...

  7. MySQL用户管理

    主要总结MySQL进行用户管理的基本实现,包含MySQL登录,添加用户,删除用户,为用户分配权限,移除某用户的权限,修改密码,查看权限等基本操作,所有命令均亲测实现.本博文是本人的劳动成果所得,在博客 ...

  8. Conversations is being developed

    Development Conversations is being developed on GitHub by a team of volunteers under the lead of pro ...

  9. C++进程间通信

    # C++进程间通信 # 进程间通讯的四种方式:剪贴板.匿名管道.命名管道和邮槽 ## 剪切板 ## //设置剪切板内容 CString str; this->GetDlgItemText(ID ...

  10. asp.net实现图片在线上传并在线裁剪

    1.说明 接上一篇文章uploadify实现多附件上传完成后,又突然用到头像上传并在线裁剪.在网上找个众多例子都没有符合要求的,有一篇文章写的不错,就是文旺老兄写的这篇Asp.Net平台下的图片在线裁 ...