无意中浏览一篇文章,中间提到了基于多尺度的图像的细节提升算法,尝试了一下,还是有一定的效果的,结合最近一直研究的SSE优化,把算法的步骤和优化过程分享给大家。

  论文的全名是DARK IMAGE ENHANCEMENT BASED ON PAIRWISE TARGET CONTRAST AND MULTI-SCALE DETAIL BOOSTING,好像在百度上搜索不到,由于博客的空间不多了,这里就不上传了, 我贴出论文核心的字段。

  论文的核心思想类似于Retinex,使用了三个尺度的高斯模糊,再和原图做减法,获得不同程度的细节信息,然后通过一定的组合方式把这些细节信息融合到原图中,从而得到加强原图信息的能力。

  值得一提的就是对D1的系数做了特殊的处理,这个是值得学习的。

  这个算法的编码实在是简单,一个简单的C语言代码如下:

int IM_MultiScaleSharpen(unsigned char *Src, unsigned char *Dest, int Width, int Height, int Stride, int Radius)
{
int Channel = Stride / Width;
if ((Src == NULL) || (Dest == NULL)) return IM_STATUS_NULLREFRENCE;
if ((Width <= ) || (Height <= )) return IM_STATUS_INVALIDPARAMETER;
if ((Channel != ) && (Channel != ) && (Channel != )) return IM_STATUS_INVALIDPARAMETER; int Status = IM_STATUS_OK; unsigned char *B1 = (unsigned char *)malloc(Height * Stride * sizeof(unsigned char)); // 如果最后的大图在这里内存有问题,一种解决办法就是下面的模糊用BoxBlur
unsigned char *B2 = (unsigned char *)malloc(Height * Stride * sizeof(unsigned char)); // 然后不要调用现有的BoxBlur函数,而是直接在本函数实现三种不同半径的模糊
unsigned char *B3 = (unsigned char *)malloc(Height * Stride * sizeof(unsigned char)); // 运算速度(因为有些循环是公共的)会有点点提高,额外的内存占用则可以忽略 if ((B1 == NULL) || (B2 == NULL) || (B3 == NULL))
{
if (B1 != NULL) free(B1);
if (B2 != NULL) free(B2);
if (B3 != NULL) free(B3);
return IM_STATUS_OUTOFMEMORY;
} Status = IM_ExpBlur(Src, B1, Width, Height, Stride, Radius);
if (Status != IM_STATUS_OK) goto FreeMemory;
Status = IM_ExpBlur(Src, B2, Width, Height, Stride, Radius * );
if (Status != IM_STATUS_OK) goto FreeMemory;
Status = IM_ExpBlur(Src, B3, Width, Height, Stride, Radius * );
if (Status != IM_STATUS_OK) goto FreeMemory;
for (int Y = ; Y < Height * Stride; Y++)
{
int DiffB1 = Src[Y] - B1[Y];
int DiffB2 = B1[Y] - B2[Y];
int DiffB3 = B2[Y] - B3[Y];
Dest[Y] = IM_ClampToByte((( - * IM_Sign(DiffB1)) * DiffB1 + * DiffB2 + DiffB3) / + Src[Y]);
} FreeMemory:
free(B1);
free(B2);
free(B3);
return Status;
}

  为避免浮点计算,我们将W1、W2、W3放大四倍,累加玩后在除以4就可以了,整个的书写过程就是按照公式(13)进行的。

  其中IM_Sign函数定义如下:

inline int IM_Sign(int X)
{
return (X >> ) | (unsigned(-X)) >> ;
}

 

              处理前                                      处理后(半径5)

  处理效果由上面两幅图的比较来说,还是相当明显的。

  上面的代码中我用的ExpBlur代替了高斯模糊,关于指数模糊可以参考:SSE图像算法优化系列五:超高速指数模糊算法的实现和优化(10000*10000在100ms左右实现) 一文,他的效果和高斯模糊差不多,速度要快不少。

  当指数模糊使用SSE优化后,剩下的代码用纯C实现,对于1080P的24位彩色图,测试时间为73毫秒,如果除去3次指数模糊,纯C部分代码耗时约40 ms,所以有很大的优化空间,我们就用SSE来处理。

  第一,我们需要来处理IM_Sign这个函数,这个函数当参数大于0时,返回1,参数小于0时,返回-1,参数等于0时,返回0,SSE没有直接这样的函数,幸好之前收集过一个这个的自定义函数,写的也很巧妙:

inline __m128i _mm_sgn_epi16(__m128i v)
{
#ifdef __SSSE3__
v = _mm_sign_epi16(_mm_set1_epi16(), v); // use PSIGNW on SSSE3 and later
#else
v = _mm_min_epi16(v, _mm_set1_epi16()); // use PMINSW/PMAXSW on SSE2/SSE3.
v = _mm_max_epi16(v, _mm_set1_epi16(-));
//_mm_set1_epi16(1) = _mm_srli_epi16(_mm_cmpeq_epi16(v, v), 15);
//_mm_set1_epi16(-1) = _mm_cmpeq_epi16(v, v); #endif
return v;
}

  如上所示,当系统只支持SSE2以及其下的版本时,直接用_mm_min_epi16和_mm_max_epi16这样的函数硬实现,这个逻辑也很好理解。

  如果系统支持SSE3及其以上的版本,系统提供了_mm_sign_epi16这个函数,关于这个函数其作用解释如下:

//  extern __m128i _mm_sign_epi16 (__m128i a, __m128i b);
//  Negate packed words in a if corresponding sign in b is less than zero.
//  Interpreting a, b, and r as arrays of signed 16-bit integers:
for (i = ; i < ; i++)
{
if (b[i] < )
{
r[i] = -a[i];
}
else if (b[i] == )
{
r[i] = ;
}
else
{
r[i] = a[i];
}
}

  如果参数a传值为1,不就能实现IM_Sign这个效果了吗,真好玩。

  解决了IM_Sign这个函数,其他的部分都很简单了,考虑到数据范围,把字节数据扩展为signed short类型处理就可以了,这样一次性可以处理8个字节的数据,修改后的代码如下所示:

int IM_MultiScaleSharpen(unsigned char *Src, unsigned char *Dest, int Width, int Height, int Stride, int Radius)
{
int Channel = Stride / Width;
if ((Src == NULL) || (Dest == NULL)) return IM_STATUS_NULLREFRENCE;
if ((Width <= ) || (Height <= )) return IM_STATUS_INVALIDPARAMETER;
if ((Channel != ) && (Channel != ) && (Channel != )) return IM_STATUS_INVALIDPARAMETER;
int Status = IM_STATUS_OK;
unsigned char *B1 = (unsigned char *)malloc(Height * Stride * sizeof(unsigned char)); // 如果最后的大图在这里内存有问题,一种解决办法就是下面的模糊用BoxBlur
unsigned char *B2 = (unsigned char *)malloc(Height * Stride * sizeof(unsigned char)); // 然后不要调用现有的BoxBlur函数,而是直接在本函数实现三种不同半径的模糊
unsigned char *B3 = (unsigned char *)malloc(Height * Stride * sizeof(unsigned char)); // 运算速度(因为有些循环是公共的)会有点点提高,额外的内存占用则可以忽略
if ((B1 == NULL) || (B2 == NULL) || (B3 == NULL))
{
if (B1 != NULL) free(B1);
if (B2 != NULL) free(B2);
if (B3 != NULL) free(B3);
return IM_STATUS_OUTOFMEMORY;
}
Status = IM_ExpBlur(Src, B1, Width, Height, Stride, Radius);
if (Status != IM_STATUS_OK) goto FreeMemory;
Status = IM_ExpBlur(Src, B2, Width, Height, Stride, Radius * );
if (Status != IM_STATUS_OK) goto FreeMemory;
Status = IM_ExpBlur(Src, B3, Width, Height, Stride, Radius * );
if (Status != IM_STATUS_OK) goto FreeMemory;
int BlockSize = , Block = (Height * Stride) / BlockSize;
__m128i Zero = _mm_setzero_si128();
__m128i Four = _mm_set1_epi16();
for (int Y = ; Y < Block * BlockSize; Y += BlockSize)
{
__m128i SrcV = _mm_unpacklo_epi8(_mm_loadl_epi64((__m128i *)(Src + Y)), Zero);
__m128i SrcB1 = _mm_unpacklo_epi8(_mm_loadl_epi64((__m128i *)(B1 + Y)), Zero);
__m128i SrcB2 = _mm_unpacklo_epi8(_mm_loadl_epi64((__m128i *)(B2 + Y)), Zero);
__m128i SrcB3 = _mm_unpacklo_epi8(_mm_loadl_epi64((__m128i *)(B3 + Y)), Zero);
__m128i DiffB1 = _mm_sub_epi16(SrcV, SrcB1);
__m128i DiffB2 = _mm_sub_epi16(SrcB1, SrcB2);
__m128i DiffB3 = _mm_sub_epi16(SrcB2, SrcB3);
__m128i Offset = _mm_srai_epi16(_mm_add_epi16(_mm_add_epi16(_mm_mullo_epi16(_mm_sub_epi16(Four, _mm_slli_epi16(_mm_sgn_epi16(DiffB1), )), DiffB1), _mm_slli_epi16(DiffB2, )), DiffB3), );
_mm_storel_epi64((__m128i *)(Dest + Y), _mm_packus_epi16(_mm_add_epi16(SrcV, Offset), Zero));
}
for (int Y = Block * BlockSize; Y < Height * Stride; Y++)
{
int DiffB1 = Src[Y] - B1[Y];
int DiffB2 = B1[Y] - B2[Y];
int DiffB3 = B2[Y] - B3[Y];
Dest[Y] = IM_ClampToByte((( - * IM_Sign(DiffB1)) * DiffB1 + * DiffB2 + DiffB3) / + Src[Y]);
}
FreeMemory:
free(B1);
free(B2);
free(B3);
return Status;
}

  基本上就是按照C语言或者公式(13)所示的流程一步一步的编写,只不过把有些乘法变成了移位。

   对于1080P的彩色图像,上述改动后处理时间变为了35ms,纯C语言部分的耗时约在11ms左右,同之前的相比速度提高了4倍多,提速还是相当的明显的。

  在仔细观察,觉得在IM_Sign这个的处理上还是有问题,虽然上述代码能完美解决问题,但是总觉得有改进空间,当我们把IM_Sign(DiffB1) * DiffB1放在一起观察时,就会发现这个整体不是可以直接使用_mm_sign_epi16予以实现吗,比如 _mm_sign_epi16(DiffB1, DiffB1) 难道不是吗? 这样就少了一次乘法。

  最后,我们把DiffB2, DiffB3展开,合并掉一些同类项,然后把乘以相同系数的变量在合并,又可以优化掉一些计算,最终的SSE部分代码如下:

    int BlockSize = , Block = (Height * Stride) / BlockSize;
__m128i Zero = _mm_setzero_si128();
for (int Y = ; Y < Block * BlockSize; Y += BlockSize)
{
__m128i SrcV = _mm_unpacklo_epi8(_mm_loadl_epi64((__m128i *)(Src + Y)), Zero);
__m128i SrcB1 = _mm_unpacklo_epi8(_mm_loadl_epi64((__m128i *)(B1 + Y)), Zero);
__m128i SrcB2 = _mm_unpacklo_epi8(_mm_loadl_epi64((__m128i *)(B2 + Y)), Zero);
__m128i SrcB3 = _mm_unpacklo_epi8(_mm_loadl_epi64((__m128i *)(B3 + Y)), Zero);
__m128i DiffB1 = _mm_sub_epi16(SrcV, SrcB1);
__m128i Offset = _mm_add_epi16(_mm_srai_epi16(_mm_sub_epi16(_mm_slli_epi16(_mm_sub_epi16(SrcB1, _mm_sign_epi16(DiffB1, DiffB1)), ), _mm_add_epi16(SrcB2, SrcB3)), ), DiffB1);
_mm_storel_epi64((__m128i *)(Dest + Y), _mm_packus_epi16(_mm_add_epi16(SrcV, Offset), Zero));
}

  虽然测试表明,速度没有提高多少(主要是计算量太少),但这样写明显合理很多。

  测试工程的地址:http://files.cnblogs.com/files/Imageshop/SSE_Optimization_Demo.rar

SSE图像算法优化系列十二:多尺度的图像细节提升。的更多相关文章

  1. SSE图像算法优化系列十:简单的一个肤色检测算法的SSE优化。

    在很多场合需要高效率的肤色检测代码,本人常用的一个C++版本的代码如下所示: void IM_GetRoughSkinRegion(unsigned char *Src, unsigned char ...

  2. SSE图像算法优化系列十五:YUV/XYZ和RGB空间相互转化的极速实现(此后老板不用再担心算法转到其他空间通道的耗时了)。

    在颜色空间系列1: RGB和CIEXYZ颜色空间的转换及相关优化和颜色空间系列3: RGB和YUV颜色空间的转换及优化算法两篇文章中我们给出了两种不同的颜色空间的相互转换之间的快速算法的实现代码,但是 ...

  3. SSE图像算法优化系列十四:局部均方差及局部平方差算法的优化。

    关于局部均方差有着较为广泛的应用,在我博客的基于局部均方差相关信息的图像去噪及其在实时磨皮美容算法中的应用及使用局部标准差实现图像的局部对比度增强算法中都有谈及,即可以用于去噪也可以用来增强图像,但是 ...

  4. SSE图像算法优化系列十八:三次卷积插值的进一步SSE优化。

    本文是在学习https://blog.csdn.net/housisong/article/details/1452249一文的基础上对算法的理解和重新整理,再次非常感谢原文作者的深入分析以及分享. ...

  5. SSE图像算法优化系列十九:一种局部Gamma校正对比度增强算法及其SSE优化。

    这是一篇2010年比较古老的文章了,是在QQ群里一位群友提到的,无聊下载看了下,其实也没有啥高深的理论,抽空实现了下,虽然不高大上,还是花了点时间和心思优化了代码,既然这样,就顺便分享下优化的思路和经 ...

  6. SSE图像算法优化系列十六:经典USM锐化中的分支判断语句SSE实现的几种方法尝试。

    分支判断的语句一般来说是不太适合进行SSE优化的,因为他会破坏代码的并行性,但是也不是所有的都是这样的,在合适的场景中运用SSE还是能对分支预测进行一定的优化的,我们这里以某一个算法的部分代码为例进行 ...

  7. SSE图像算法优化系列二十:一种快速简单而又有效的低照度图像恢复算法。

    又有很久没有动笔了,主要是最近没研究什么东西,而且现在主流的趋势都是研究深度学习去了,但自己没这方面的需求,同时也就很少有动力再去看传统算法,今天一个人在家,还是抽空分享一个简单的算法吧. 前段日子在 ...

  8. SSE图像算法优化系列二十三: 基于value-and-criterion structure 系列滤波器(如Kuwahara,MLV,MCV滤波器)的优化。

    基于value-and-criterion structure方式的实现的滤波器在原理上其实比较简单,感觉下面论文中得一段话已经描述的比较清晰了,直接贴英文吧,感觉翻译过来反而失去了原始的韵味了. T ...

  9. SSE图像算法优化系列二十五:二值图像的Euclidean distance map(EDM)特征图计算及其优化。

    Euclidean distance map(EDM)这个概念可能听过的人也很少,其主要是用在二值图像中,作为一个很有效的中间处理手段存在.一般的处理都是将灰度图处理成二值图或者一个二值图处理成另外一 ...

随机推荐

  1. oracle temporary table

    oralce 有两种临时表  a.会话级临时表 b.事物级临时表 A.事物级临时表 语法 create  global temporary table table_name( col1  type1, ...

  2. 创建静态库Static Library(Framework库原理相似)

    在项目开发的过程中,经常使用静态库文件.例如两个公司之间业务交流,不可能把源代码都发送给另一个公司,这时候将私密内容打包成静态库,别人只能调用接口,而不能知道其中实现的细节. 简介: 库是一些没有ma ...

  3. C# 7.0 特性

    在昨天WR发布了vs17,vs17可以使用C#7.0,在之前,我写有一篇博客,关于C#7.0,参见:http://lindexi.oschina.io/lindexi/post/C-7.0/ 但是WR ...

  4. layim+signalr2.0在线轻聊版解决方案

    本内容有版权限制,仅提供学习交流参考等等,请勿随便转载或者代码商用.     /** layui-v2.1.5 MIT License By http://www.layui.com */; layu ...

  5. jfinal拦截器配置

    实现aop @Before(Tx.class) public void index(){ // renderText("hello world!"); render("/ ...

  6. Volley图片加载并加入缓存处理(转自http://blog.csdn.net/viewhandkownhealth/article/details/50957024)

    直接上代码  两种方式 ImageView 和NetworkImageView 如有问题或者好的建议.意见 欢迎大家加入技术群(群号: 387648673 ) 先自定义全局Application 获取 ...

  7. [Web开发(1)] MyEclipse/Eclipse 使用Tomcat部署Web/Maven项目经典错误

    1.Tomcat启动报错:server tomcat start within 45 seconds 问题原因:由于eclipse默认Tomcat设置是启动时间为45s,所以在45s内没有启动成功就会 ...

  8. VirtualBox虚拟磁盘文件.vdi从C盘转移到D盘

    起因很简单:C盘快爆满了,用PS修个图的都无法保存.用Everything搜索了下C盘,发现有个WIN7的.vdi文件,竟然占据了15G的宝贵C盘空间!记得有一次,我直接把.vdi文件转移,然后在Vi ...

  9. 启动报错 Unsupported major.minor version 51.0

    Unsupported major.minor version 51.0错误, 是使用jdk6启动jdk7编译的项目,更换jdk7就好了,或者用jdk6重新打包项目. 解决起来也很方便:打开excli ...

  10. R学习笔记 第三篇:数据框

    数据框(data.frame)用于存储二维表(即关系表)的数据,每一列存储的数据类型必须相同,不同的数据列的数据类型可以相同,也可以不同,但是,每列的长度必须相同.数据框的每列可以有唯一的命名,在已创 ...