定义:
所谓饱和运算(saturation),就是当运算结果大雨某个上限或是小于某个下限时,结果就等于上限或下限。 |
饱和运算是图像处理中比较基本的运算,在图像的滤波、亮度增强、编解码等操作中都会遇到这个操作,所以研究如何用最优的方式运行该运算就很有意义了。
由于我们最常见到的图像每个通道的位深为8比特,事实上我基本上没见过其他深度的。所以我们的讨论集中在如何优化8位图像的饱和运算。
首先,我们知道作为一个基本运算,由于我们需要频繁地使用,所以对它会有更高的要求,因为基本运算性能的降低会成为整个系统性能的瓶颈。
那么,一个好的运算算法的评价标准是什么呢?
(1) 时间复杂度低
(2) 空间要求少
在这里,由于是作为内核的基本运算,所以我们对程序的可读性的要求会有所降低,毕竟鱼和熊掌不可兼得,我们需要有所割舍。
So,我们看看有什么算法吧。首先,也是最容易想到的就是:
方法一、刀耕火种法
最直观的方法是根据定义来做。下面给出源码:
我们分析一下这个程序,它很直观,但是未必高效,其原因是什么呢:
(1) 对一个基本运算采用函数方式,增加了调用函数的时间开销。从c库函数的实现方式来看,一般基本操作需要优先考虑用宏定义的方式来解决,如getchar()等都是例证。
(2) 采用了if-else if-else的分支结构,既增加了系统判断的时间开销,又由于需要比较而增加了系统的空间开销。
如 i_pix > 255 语句
汇编后的代码为:cmp ecx,0FFh
而cmp指令是一个三字指令。
说到这里,顺便提一下 i_pix < 0
其汇编后代码为:test eax,eax
而test指令是一字指令,所以我们在做比较操作的时候如有可能尽量要与0比较。当然,这是题外话。
以上两点提出了改进方向,一是尽量考虑用宏,二是考虑用比较运算符替代分支结构。
下面的问题是:比较运算符只有两个比较条件,有没有可能把三个分支合并成两个分支呢?
经过分析,我们发现一个现象:大于255和小于0的像素值偶一个共同特征,那就是,他们的高八位都是非零的。而且由于大于255的像素最后输出为255(11111111),小于0的输出为0(00000000),与它们的符号相反,我们可以通过算术移位操作,把像素值的每位都填为符号位,然后再按位取反就行了。即,当pix越界时,输出为 ~(pix>>16) 或是(-pix>>16)就行了。
关键问题解决了,下面的事就很简单了:
然后就进行测试:
测试环境:
操作系统: windows xp professional sp2
CPU:intel T2080 1.73Ghz
内存:1.73Ghz, 504MB
编译环境: VC++6.0
我们随机生成了一个[-500,500]范围内的150*1000的数组,在Release版本下的性能为:
算法1: 15 ms
算法2: 0 ms
因为我们的作用对象是图像,一般规模都比较大,所以每幅图节约15ms还是很可观的。在视频条件下,按照每秒25帧的帧率来算的话,留给一帧的时间仅为40ms,这样看来节省15ms,算是很好的事了。
后来又突然想到,给算法1的函数前加了inline,使之变为内联函数,跑出来的结果为:
算法1: 0 ms
算法2: 0 ms
所以说在饱和运算的情况下,其实是函数调用对系统开销的影响最大,分支语句由于编译器的优化,所以已经不太占用系统开销了。
得到一个结论,那就是:很多时候优化首先要从技术角度来考虑。但是,一个巧妙的新算法给我们带来的成就感更大。一个用于商业,一个用于兴趣,都有用。
参考资源:
1.百度百科
http://baike.baidu.com/view/1547769.html?fromTaglist
2. H.264 中很有用的一些概念
http://hi.baidu.com/beily815/blog/item/