SSE图像算法优化体系八:自然饱和度(Vibrance)算法的上行下效实现及其SSE优化(附源码,可看成SSE图像入门,Vibrance算法也可用以简单的肤色调整)。

by admin on 2019年3月9日

  Vibrance那个单词搜索翻译一般震荡,抖动或然是嘹亮、活力,不过官方的词汇里还向来未出现过自然饱和度这几个词,也不通晓当时的Adobe普通话翻译职员怎么会那样处理。然则大家看看PS对这么些功能的分解:

Schindler名单拯救了1000多名犹太人,而中夏族民共和国法国巴黎解救了一万。

——“北京犹太人”、前美利坚合众国财政院长布罗门撒尔

       Vibrance: Adjusts the
saturation so that clipping is minimized as colors approach full
saturation. This setting changes the saturation of all lower-saturated
colors with less effect on the higher-saturated colors. Vibrance also
prevents skin tones from becoming oversaturated.

第一回世界大战时期,希特勒残忍迫害犹太人,在纳粹德意志联邦共和国的种族主义阴影下大致全部的欧美发达国家都推辞或限制犹太难民入境。

     
 确实是和饱和度有关的,这样敞亮中文的翻译反而倒是合理,那么只可以怪Adobe的开发者为啥给那几个效果起个名字叫Vibrance了。

1931年起,数以万计的犹太人为逃离纳粹的惶恐不安统治来到向她们敞开大门的中华夏族民共和国北京。

     
 闲话不多说了,其实自然饱和度也是近年来多少个本子的PS才面世的功用,在调节和测试有些图片的时候会有不易的职能,也能够看做简单的肤色调整的一个算法,比如上面那位闺女,用自然饱和度即能够让他失血过多,也能够让她肤色红晕。

一九三七年,法国首都被侵华日军占领,还是有2.5万名犹太人把那边作为他们的避难所。他们被东瀛政党迁入隔开区里居住,与华Sharp通百姓们同甘苦、共灾殃。

     
 图片 1   
 图片 2   
 图片 3

翻看一组组黑白照片,看到是多个笼罩着长逝的动荡时期,一座传说的都会,八个受难的中华民族,一段尘封的历史。

                                         
   原图                                                                
                                 面色苍白                              
                                                    肤色红晕一点

巴黎犹太难民回顾馆的七盏圣烛

     
 那么那么些算法的内在是什么落实的吗,笔者未曾仔细的去研商他,不过在开源软件Photo德姆on-master(开源地址:https://github.com/tannerhelland/PhotoDemon,visual
basic
6.0的创作,小编的最爱)提供了三个稍稍相像的职能,大家贴出他对改效果的一部分注释:

壹 | 远东的避难所

上个世纪③ 、四十年份,东京是北美洲的自由港、远东国际金融中央、冒险家的闭门却扫,比较纳粹德意志联邦共和国,遥远的中原并未反犹主义,当时民国的掌握人和东京犹太协会都积极的为犹太难民奔走求助。

早在1932年,以宋庆龄(Song Qingling)为首的四个代表团就曾向德意志驻香港(Hong Kong)总领事声讨了纳粹的反犹暴行,那些代表团里有民国文化界的元老们——周子余、周豫才、林语堂。

宋庆龄(Song Qingling)的代表团

一九三四年,纳粹德国发布《苏州法治》,犹太人被剥夺公民义务,希特勒走出了种族迫害的首先步。

时任中华民国驻华盛顿大使何凤山同情犹太人的面临,在迈阿密一百肆十一个多国家的外交官中,唯有她向犹太难民发放出多量的签证。

何凤山淡薄名利,直到壹玖玖陆年在United States多伦多长眠,在他的葬礼上女儿才揭露阿爸那段典故。何凤山的名字明日还刻在孟菲斯的杀戮记忆馆中。3000年,以色列国政党赋予她“国际义士”的荣耀。

民异国他乡交官何凤山大学生

哪个人又亮堂,希特勒即将在澳洲舞动他的屠刀……一张船票、一纸签证的骨子里都以一条条鲜活的生命。由此,难民们纷繁逃离欧洲,远渡大洋踏上了中国的土地。

犹太难民抵达东京

当下的十六铺码头挤满了人流和货物,和平女神像张开翅膀平静的鸟瞰着外滩万国建筑群,香港(Hong Kong)的犹太协会也开端为逃难的同胞们提供救济。

上个世纪③ 、四十年间的时尚之都外滩

'***************************************************************************
'Vibrance Adjustment Tool
'Copyright 2013-2017 by Audioglider
'Created: 26/June/13
'Last updated: 24/August/13
'Last update: added command bar
'
'Many thanks to talented contributer Audioglider for creating this tool.
'
'Vibrance is similar to saturation, but slightly smarter, more subtle. The algorithm attempts to provide a greater boost
' to colors that are less saturated, while performing a smaller adjustment to already saturated colors.
'
'Positive values indicate "more vibrance", while negative values indicate "less vibrance"
'
'All source code in this file is licensed under a modified BSD license. This means you may use the code in your own
' projects IF you provide attribution. For more information, please visit http://photodemon.org/about/license/
'
'***************************************************************************

贰 | 犹太人在北京

犹太民族是2个迷信宗教的部族,他们在流亡的时日中并未摒弃自身的迷信。Moses会堂(现长阳路62号)正是难民们的宗教场合。

一对犹太新人在Moses会堂举行婚礼

他们住在北京小弄堂的亭子间、阁楼里,用中式厨房做起了西式餐点。

犹太难民的住所

犹太人善于经营商业,崇尚文艺。他们在隔断区办起了高校、报社、还开起了商铺。

铜仁路的海口咖啡馆

她们在晌午阳光洒落的屋顶上啜着咖啡拉着小提琴,小孩子们则在破旧的大街上娱乐。

犹太小孩子在街头打弹子

唯恐他们领会平静的活着是战胜恐惧最实用的解药。

屋顶小憩

在那样狼狈的条件中,犹太青年们依然还在北京办起了本人的音信杂志社。

侧记“我们的活着”编辑部

在虹口隔开区居住的犹太难民们与华夏人民和平相处,不少犹太人有温馨的炎黄房东,还和华夏共事们一块干活,建立了牢固的友谊。

小女孩们在一块打闹

友人们在虹口游泳池

 个中的叙述和PS官方文书档案的叙说有类似之处。

叁 | 离别与重聚

在第二回世界大战时期,纳粹在澳大林茨杀害了近600万犹太人。前后有约3.5万犹太难民避难在北京或路过前往第③国,他们中间除自行消灭外大多都幸存了下来。

东瀛投降后,犹太难民们交叉离开香岛,对那些生活了八年的城市,他们怀有一种非凡的情丝。

亚脱门利女士在17岁的花样年华来到上海,后来她又赶回那里,她说那是她的第一家门。

亚脱门利17岁时的居住证

亚脱门利重反新加坡

现居美利哥的贝蒂老人一家和对象们重返北京,他们已经住在张家口路51号。

Betty一家

布罗门撒尔老人曾在北京位居八年,他说自身家族在澳洲的亲属都没能幸存下来,东京的紧Baba岁月影响了她从此的做官轨迹。离开东京后他去到U.S.,出任了美利坚合众国政坛的财政省长。

花旗国前财政部秘书长布罗门撒尔

布兰德女士跟随亲朋好友逃到北京时只是一个小女孩,70年后她回去香港,中中原人民共和国情侣将他当场留存下的护照交还给她。

布兰德女士赶回法国首都

犹太画家阿瑟先生尚未采用距离,他留下来在上音乐教育小提琴演奏。驾鹤归西后,他也葬在了此地。

Arthur先生和她的宅集散地

明日的摩西会堂上依然有一颗明亮的大卫星,中华夏族民共和国全体成员和以色列国(The State of Israel)全体成员的友谊源源不绝,亲欧洲和美洲的以色列国却是中东最早认可新中华夏族民共和国的国度。

以色列国(The State of Israel)总理内塔尼亚胡曾说到:

大家将会永远铭记在心你们,永远不会遗忘这一段历史。

现今,Moses会堂已进行东京犹太难民回想馆,侧边的墙面上层层的雕琢了137叁11个犹太难民的名字。

Moses会堂难民墙

墙面上也记录着几段犹太难民的名句:

——昨天大家将去一个不熟悉的城市生活。素不相识的言语,不熟稔的气象和人群,可是在那里,我们是高枕无忧和任意的。

——当时,没有多少个使领事馆给大家发签证。不过,有一天,小编去了中华夏族民共和国领事馆,意况产生了转变……大家买到了Bianco
Mano的船票。那是一艘意大利邮轮,在一九四〇年三月中从澳门距离,前往中国东方之珠,航程约30天。

——这是一回心境之旅,当本身和那个中华人民共和国定居者道别时,我们都含着泪水。大家早已在马来西亚人的统治下共同相处,那段经历使大家发出了一种亲近感,就接近相互是亲戚一样。


参考文献及图片出处:

《虹口回忆,犹太难民的生存》学林出版社

《犹太人在香江》法国巴黎画报出版社

《永恒的回想》北京世纪出版社

《生命的记念——犹太人在北京》纪录片,新加坡广播广播台音讯宗旨

北京犹太难民记念馆

   大家在贴出他的大旨代码:

     For x = initX To finalX
        quickVal = x * qvDepth
        For y = initY To finalY
            'Get the source pixel color values
            r = ImageData(quickVal + 2, y)
            g = ImageData(quickVal + 1, y)
            b = ImageData(quickVal, y)

            'Calculate the gray value using the look-up table
            avgVal = grayLookUp(r + g + b)
            maxVal = Max3Int(r, g, b)

            'Get adjusted average
            amtVal = ((Abs(maxVal - avgVal) / 127) * vibranceAdjustment)

            If r <> maxVal Then
                r = r + (maxVal - r) * amtVal
            End If
            If g <> maxVal Then
                g = g + (maxVal - g) * amtVal
            End If
            If b <> maxVal Then
                b = b + (maxVal - b) * amtVal
            End If

            'Clamp values to [0,255] range
            If r < 0 Then r = 0
            If r > 255 Then r = 255
            If g < 0 Then g = 0
            If g > 255 Then g = 255
            If b < 0 Then b = 0
            If b > 255 Then b = 255

            ImageData(quickVal + 2, y) = r
            ImageData(quickVal + 1, y) = g
            ImageData(quickVal, y) = b
        Next
    Next

  很简单的算法,先求出每种像素LX570GB分量的最大值和平均值,然后求两者之差,之后传说输入调节量求出调整量。

     
 VB的语法有个别人恐怕不熟练,笔者有个别做点更改翻译成C的代码如下:

    float VibranceAdjustment = -0.01 * Adjustment;        //       Reverse the vibrance input; this way, positive values make the image more vibrant.  Negative values make it less vibrant.
    for (int Y = 0; Y < Height; Y++)
    {
        unsigned char * LinePS = Src + Y * Stride;
        unsigned char * LinePD = Dest + Y * Stride;
        for (int X = 0; X < Width; X++)
        {
            int Blue = LinePS[0],    Green = LinePS[1],    Red = LinePS[2];
            int Avg = (Blue + Green + Green + Red) >> 2;
            int Max = IM_Max(Blue, IM_Max(Green, Red));
            float AmtVal = (abs(Max - Avg) / 127.0f) * VibranceAdjustment;                        //    Get adjusted average
            if (Blue != Max)    Blue += (Max - Blue) * AmtVal;
            if (Green != Max)    Green += (Max - Green) * AmtVal;
            if (Red != Max)    Red += (Max - Red) * AmtVal;
            LinePD[0] = IM_ClampToByte(Blue);
            LinePD[1] = IM_ClampToByte(Green);
            LinePD[2] = IM_ClampToByte(Red);
            LinePS += 3;
            LinePD += 3;
        }
    }

  这一个的结果和PS的是相比接近的,最起码趋势是可怜类似的,不过细节照旧不等同,可是能够判断的是来势是对的,假如您肯定要复制PS的结果,小编提议你花点时间转移当中的一些常数只怕计算办法看看。应该能有获取,国内曾经有人摸索出来了。

     
我们最重要讲下这么些算法的优化及其SSE完结,特别是SSE版本代码是本文的重点。

      第叁步优化,去除掉不供给总括和除法,很显明,这一句是本段代码中耗时较为分明的一些

        float AmtVal = (abs(Max –
Avg) / 127.0f) * VibranceAdjustment;     

  /127.0f能够优化为乘法,同时注意VibranceAdjustment在内部不变,能够把她们结成到循环的最外层,即改为:

      float VibranceAdjustment = -0.01 * Adjustment / 127.0f;

  再小心abs里的参数, 马克斯 –
Avg,那有须求取相对值吗,最大值难道会比平均值小,浪费时间,最终改为:

      float AmtVal = (Max - Avg) * VibranceAdjustment;

    这是浮点版本的归纳优化,假如不勾选编写翻译器的SSE优化,直接选拔FPU,对于一副三千*2000的2三人图像耗费时间在I5的一台机器上运营用时大致70微秒,但那不是主要。

  大家来考虑某个近似和固化优化。

     
 第二我们把/127改为/128,那基本不影响效应,同时Adjustment暗中认可的界定为[-100,100],把它也线性扩张学一年级点,比如扩充1.28倍,扩展到[-128,128],那样在结尾大家2遍性移位,减弱中间的损失,差不离的代码如下:

int IM_VibranceI(unsigned char *Src, unsigned char *Dest, int Width, int Height, int Stride, int Adjustment)
{
    int Channel = Stride / Width;
    if ((Src == NULL) || (Dest == NULL))                return IM_STATUS_NULLREFRENCE;
    if ((Width <= 0) || (Height <= 0))                    return IM_STATUS_INVALIDPARAMETER;
    if (Channel != 3)                                    return IM_STATUS_INVALIDPARAMETER;

    Adjustment = -IM_ClampI(Adjustment, -100, 100) * 1.28;        //       Reverse the vibrance input; this way, positive values make the image more vibrant.  Negative values make it less vibrant.
    for (int Y = 0; Y < Height; Y++)
    {
        unsigned char *LinePS = Src + Y * Stride;
        unsigned char *LinePD = Dest + Y * Stride;
        for (int X = 0; X < Width; X++)
        {
            int Blue, Green, Red, Max;
            Blue = LinePS[0];    Green = LinePS[1];    Red = LinePS[2];
            int Avg = (Blue + Green + Green + Red) >> 2;
            if (Blue > Green)
                Max = Blue;
            else
                Max = Green;
            if (Red > Max)
                Max = Red;
            int AmtVal = (Max - Avg) * Adjustment;                                //    Get adjusted average
            if (Blue != Max)    Blue += (((Max - Blue) * AmtVal) >> 14);
            if (Green != Max)    Green += (((Max - Green) * AmtVal) >> 14);
            if (Red != Max)        Red += (((Max - Red) * AmtVal) >> 14);
            LinePD[0] = IM_ClampToByte(Blue);
            LinePD[1] = IM_ClampToByte(Green);
            LinePD[2] = IM_ClampToByte(Red);
            LinePS += 3;
            LinePD += 3;
        }
    }
    return IM_STATUS_OK;
}

  那样优化后,同样大小的图像算法用时35纳秒,效果和浮点版本的着力没啥区别。

     
 最终我们重点来讲讲SSE版本的优化。

  
对于那种单像素点、和世界无关的图像算法,为了能使用SSE进步程序速度,一个着力的步子正是把各颜色分量分离为独立的连接的变量,对于2三个人图像,我们知晓图像在内部存储器中的布局为:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
B1 G1 R1 B2 G2 R2 B3 G3 R3 B4 G4 R4 B5 G5 R5 B6 G6 R6 B7 G7 R7 B8 G8 R8 B9 G9 R9 B10 G10 R10 B11 G11 R11 B12 G12 R12 B13 G13 R13 B14 G14 R14 B15 G15 R15 B16 G16 R16

 

       

      我们需求把它们变成:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
B1 B2 B3 B4 B4 B5 B7 B8 B9 B10 B11 B12 B13 B14 B15 B16 G1 G2 G3 G4 G5 G6 G7 G8 G9 G10 G11 G12 G13 G14 G15 G16 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16

 

     

   
 处理完后大家又要把她们过来到原来的BG奥德赛布局。

   
 为了兑现那一个成效,小编参考了采石工铁汉的有关代码,分享如下:

     大家先贴下代码:

    Src1 = _mm_loadu_si128((__m128i *)(LinePS + 0));
    Src2 = _mm_loadu_si128((__m128i *)(LinePS + 16));
    Src3 = _mm_loadu_si128((__m128i *)(LinePS + 32));

    Blue8 = _mm_shuffle_epi8(Src1, _mm_setr_epi8(0, 3, 6, 9, 12, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1));
    Blue8 = _mm_or_si128(Blue8, _mm_shuffle_epi8(Src2, _mm_setr_epi8(-1, -1, -1, -1, -1, -1, 2, 5, 8, 11, 14, -1, -1, -1, -1, -1)));
    Blue8 = _mm_or_si128(Blue8, _mm_shuffle_epi8(Src3, _mm_setr_epi8(-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 4, 7, 10, 13)));

    Green8 = _mm_shuffle_epi8(Src1, _mm_setr_epi8(1, 4, 7, 10, 13, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1));
    Green8 = _mm_or_si128(Green8, _mm_shuffle_epi8(Src2, _mm_setr_epi8(-1, -1, -1, -1, -1, 0, 3, 6, 9, 12, 15, -1, -1, -1, -1, -1)));
    Green8 = _mm_or_si128(Green8, _mm_shuffle_epi8(Src3, _mm_setr_epi8(-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 5, 8, 11, 14)));

    Red8 = _mm_shuffle_epi8(Src1, _mm_setr_epi8(2, 5, 8, 11, 14, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1));
    Red8 = _mm_or_si128(Red8, _mm_shuffle_epi8(Src2, _mm_setr_epi8(-1, -1, -1, -1, -1, 1, 4, 7, 10, 13, -1, -1, -1, -1, -1, -1)));
    Red8 = _mm_or_si128(Red8, _mm_shuffle_epi8(Src3, _mm_setr_epi8(-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 3, 6, 9, 12, 15)));

     
首先,1次性加载四十三个图像数据到内部存款和储蓄器,正好放置在多少个__m128i变量中,同时别的贰个很好的事务正是48刚好能被3整除,也正是说大家全体的加载了17个2二人像素,那样就不相会世断层,只代表上面四十7个像素能够和当今的四十三个像素使用相同的主意举行拍卖。

     
如上代码,则Src第11中学保留着:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
B1 G1 R1 B2 G2 R2 B3 G3 R3 B4 G4 R4 B5 G5 R5 B6

 

 

      Src第22中学保留着:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
G6 R6 B7 G7 R7 B8 G8 R8 B9 G9 R9 B10 G10 R10 B11 G11

 

 

  Src3中的数据则为:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
R11 B12 G12 R12 B13 G13 R13 B14 G14 R14 B15 G15 R15 B16 G16 R16

 

 

    为了达到我们的指标,我们即将选择SSE中强有力的shuffle指令了,如若能够把shuffle指令运用的神工鬼斧,可以博得很多很有意思的职能,有如鸠摩智的化骨绵掌一样,能够催动长拳发、袈裟服魔攻等等,成就世间能和作者鸠摩智打成平成的尚未多少人一如既往的丰功伟绩。哈哈,说远了。

   
 简单的接头shuffle指令,就是将__m128i变量内的次第数据遵照内定的次第实行重复铺排,当然这一个布阵不必然要统统接纳本来的数据,也得以再度某个数据,或然有些地方无数据,比如在实行上边那条指令

    Blue8 = _mm_shuffle_epi8(Src1,
_mm_setr_epi8(0, 3, 6, 9, 12, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1));

   Blue第88中学的多寡为:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
B1 B2 B3 B4 B5 B6 0 0 0 0 0  0  0  0  0
_mm_setr_epi8指令的参数顺序可能更适合于我们常用的从左到右的理解习惯,其中的某个参数如果不在0和15之间时,则对应位置的数据就是被设置为0。

 
 能够见到进展上述操作后Blue8的签五个字节已经符合大家的急需了。

   在看代码的下一句:

        Blue8 = _mm_or_si128(Blue8, _mm_shuffle_epi8(Src2, _mm_setr_epi8(-1, -1, -1, -1, -1, -1, 2, 5, 8, 11, 14, -1, -1, -1, -1, -1)));

  那句的后半部分和前面包车型客车近乎,只是个中的常数分裂,由_mm_shuffle_epi8(Src2,
_mm_setr_epi8(-1, -1, -1, -1, -1, -1, 2, 5, 8, 11, 14, -1, -1, -1,
-1, -1))获得的权且数据为:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
0 0 0 0 0 0 B7 B8 B9 B10 B11 0 0 0 0 0

 

 

   
 假诺把这么些暂且结果和事先的Blue8实行或操作依然平素开始展览加操作,新的Blue8变量则为:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
B1 B2 B3 B4 B5 B6 B7 B8 B9 B10 B11 0 0 0 0 0

 

 

     
最后这一句和Blue8相关的代码为:

Blue8 = _mm_or_si128(Blue8, _mm_shuffle_epi8(Src3, _mm_setr_epi8(-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 4, 7, 10, 13)));

  后边的shuffle近来的获得的变量为:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
0 0 0 0 0 0 0 0 0 0 0 B12 B13 B14 B15 B16

 

 

   
 再度和前面包车型地铁Blue8结果开始展览或操作获得最后的结果:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
B1 B2 B3 B4 B5 B6 B7 B8 B9 B10 B11 B12 B13 B14 B15 B16

 

 

   
 对于格林和Red分量,处理的不二法门和步子是一律的,只是出于地方差异,每趟进行shuffle操作的常数有所分歧,但原理完全一致。

  如果明白了由BGRBGRBGENVISION—》变为了BBBGGGPAJEROGL450揽胜极光那样的方式的法则后,那么由BBBGGG奥迪Q3宝马X3奥迪Q5–>变为BGRBGRBG路虎极光的道理就老大浅显了,这里不赘述,直接贴出代码:

    Dest1 = _mm_shuffle_epi8(Blue8, _mm_setr_epi8(0, -1, -1, 1, -1, -1, 2, -1, -1, 3, -1, -1, 4, -1, -1, 5));
    Dest1 = _mm_or_si128(Dest1, _mm_shuffle_epi8(Green8, _mm_setr_epi8(-1, 0, -1, -1, 1, -1, -1, 2, -1, -1, 3, -1, -1, 4, -1, -1)));
    Dest1 = _mm_or_si128(Dest1, _mm_shuffle_epi8(Red8, _mm_setr_epi8(-1, -1, 0, -1, -1, 1, -1, -1, 2, -1, -1, 3, -1, -1, 4, -1)));

    Dest2 = _mm_shuffle_epi8(Blue8, _mm_setr_epi8(-1, -1, 6, -1, -1, 7, -1, -1, 8, -1, -1, 9, -1, -1, 10, -1));
    Dest2 = _mm_or_si128(Dest2, _mm_shuffle_epi8(Green8, _mm_setr_epi8(5, -1, -1, 6, -1, -1, 7, -1, -1, 8, -1, -1, 9, -1, -1, 10)));
    Dest2 = _mm_or_si128(Dest2, _mm_shuffle_epi8(Red8, _mm_setr_epi8(-1, 5, -1, -1, 6, -1, -1, 7, -1, -1, 8, -1, -1, 9, -1, -1)));

    Dest3 = _mm_shuffle_epi8(Blue8, _mm_setr_epi8(-1, 11, -1, -1, 12, -1, -1, 13, -1, -1, 14, -1, -1, 15, -1, -1));
    Dest3 = _mm_or_si128(Dest3, _mm_shuffle_epi8(Green8, _mm_setr_epi8(-1, -1, 11, -1, -1, 12, -1, -1, 13, -1, -1, 14, -1, -1, 15, -1)));
    Dest3 = _mm_or_si128(Dest3, _mm_shuffle_epi8(Red8, _mm_setr_epi8(10, -1, -1, 11, -1, -1, 12, -1, -1, 13, -1, -1, 14, -1, -1, 15)));

  宗旨依然这么些常数的选用。

     
以上是处理的第①步,看上去那一个代码很多,实际上他们的施行时相当慢的,2000*3000的图那个拆分和归并进程也就大概2ms。

     
当然是因为字节数据类型的发布范围十二分有限,除了少有的多少个少于的操作能针对字节类型直接处理外,比如本例的丘SportageGB的马克斯值,就足以直接用下边的SIMD指令达成:

Max8 = _mm_max_epu8(_mm_max_epu8(Blue8, Green8), Red8);

     
很其余多划算都是心有余而力不足直接在这么的范围内开始展览了,因而就有必不可上将数据类型增添,比如扩张到short类型或许int/float类型。

     
在SSE里开始展览那样的操作也是分外简单的,SSE提供了汪洋的数据类型转换的函数和下令,比如有byte扩充到short,则足以用_mm_unpacklo_epi8和_mm_unpackhi_epi8配合zero来实现:

BL16 = _mm_unpacklo_epi8(Blue8, Zero);
BH16 = _mm_unpackhi_epi8(Blue8, Zero);

  其中

Zero = _mm_setzero_si128();

   很有趣的操作,比如_mm_unpacklo_epi8是将四个__m128i的低六位交错安插形成3个新的127人数据,要是中间一个参数为0,则正是把其余3个参数的低九个字节无损的扩展为13位了,以上述BL16为例,在那之中间布局为:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
B1 0 B2 0 B3 0 B3 0 B4 0 B5 0 B6 0 B7 0

 

 

  假诺大家须求展开在int范围内展开计算,则还需进一步壮大,此时得以应用_mm_unpackhi_epi16/_mm_unpacklo_epi16合营zero继续开始展览扩充,这样二个Blue8变量要求多少个__m128i
int范围的多少来表明。

     
好,说道那里,大家后续看我们C语言里的那句:

  int Avg = (Blue + Green + Green + Red) >> 2;

  能够看到,那里的计量是心有余而力不足再byte范围内做到的,中间的Blue

  • Green + 格林 +
    Red在多数动静下都会超出255而相对小于255*4,,由此大家须要扩展数据到十伍位,按上述措施,对Blue8\Green8\Red8\马克斯8拓展扩大,如下所示:

      BL16 = _mm_unpacklo_epi8(Blue8, Zero);
      BH16 = _mm_unpackhi_epi8(Blue8, Zero);
      GL16 = _mm_unpacklo_epi8(Green8, Zero);
      GH16 = _mm_unpackhi_epi8(Green8, Zero);
      RL16 = _mm_unpacklo_epi8(Red8, Zero);
      RH16 = _mm_unpackhi_epi8(Red8, Zero);
      MaxL16 = _mm_unpacklo_epi8(Max8, Zero);
      MaxH16 = _mm_unpackhi_epi8(Max8, Zero);
    

  此时总结Avg就马到成功了:

     AvgL16 = _mm_srli_epi16(_mm_add_epi16(_mm_add_epi16(BL16, RL16), _mm_slli_epi16(GL16, 1)), 2);
     AvgH16 = _mm_srli_epi16(_mm_add_epi16(_mm_add_epi16(BH16, RH16), _mm_slli_epi16(GH16, 1)), 2);

  中间三个格林相加是用移动照旧直接相加对进程没啥影响的。

     
 接下来的优化则是本例的2个风味部分了。我们来详细分析。

     
 我们知道,SSE对于跳转是很不自个儿的,他那些擅长类别化处理一个工作,尽管他提供了许多相比指令,不过洋洋意况下复杂的跳转SSE依然无论为力,对于本例,意况相比卓殊,若是要利用SSE的相比较指令也是足以一向促成的,实现的办法时,使用相比较指令获得1个Mask,Mask中符合相比结实的值会为FFFFFFFF,不适合的为0,然后把那一个Mask和后边必要总括的某部值举办And操作,由于和FFFFFFFF进行And操作不会转移操作数本身,和0进行And操作则变为0,在广大情况下,正是随便你符合条件与否,都进行末端的持筹握算,只是不符合条件的测算不会影响结果,那种计算只怕会失效SSE优化的有的提速效果,这一个将要具体情况具体分析了。

     
注意观望本例的代码,他的本意是若是最大值和某些分量的值不同,则展开末端的调整操作,不然不开始展览调剂。可前面的调动操作中有最大值减去该分量的操作,也就象征一旦最大值和该分量相同,两者相减则为0,调整量此时也为0,并不影响结果,也就一定于尚未调节,因而,把这几个原则判断去掉,并不会潜移默化结果。同时考虑到真实境况,最大值在多如牛毛场所也只会和某三个分量相同,也等于说只有三分之一的概率不进行跳转后的言语,在本例中,跳转后的代码执行复杂度并不高,去掉那么些原则判断从而扩张一道代码所消耗的性质和压缩一个判断的年华已经在四个程度上了,因而,完全能够去除这个判断语句,那样就非常适合于SSE达成了。

  接着分析,由于代码中有((马克斯 –
Blue) * AmtVal) >> 14,其中AmtVal
= (Max – Avg) * Adjustment,展开即为:  ((马克斯 – Blue) * (Max – Avg) *
Adjustment)>>14;那七个数据相乘极大程度上会超出short所能表明的限定,因而,大家还索要对地方的拾二位数据进行扩展,扩充到31人,那样就多了好多限令,那么有没有不须要扩展的艺术吗。经过一番想想,我建议了下述化解方案:

   
超高速指数模糊算法的贯彻和优化(10000*10000在100ms左右兑现 一文中,小编在篇章最终提到了极端的3个指令:_mm_mulhi_epi16(a,b),他能三次性处理九个十多人数据,其总括结果一定于对于(a*b)>>16,但此间很明a和b必须是short类型所能表明的范围。

       注意大家的这一个表明式:

              ((Max – Blue) * (Max –
Avg) * Adjustment)>>14

   
   首先,大家将她恢弘为移动14位的结果,变为如下:

         ((Max – Blue) * 4 * (Max –
Avg) * Adjustment)>>16

     
Adjustment我们已经将她限定在了[-128,128]里面,而(马克斯 –
Avg)理论上的最大值为255 –
85=170,(即智跑GB分量有三个是255,别的的都为0),最小值为0,因此,两者在各自范围内的实际业绩不会胜出short所能表达的范围,而(Max-Blue)的最大值为255,最小值为0,在乘以4也在short类型所能表明的限制内。所以,下一步你们懂了啊?

     
 经过上述分析,上面那四行C代码可由下述SSE函数实现:

    int AmtVal = (Max - Avg) * Adjustment;                                //    Get adjusted average
    if (Blue != Max)    Blue += (((Max - Blue) * AmtVal) >> 14);
    if (Green != Max)    Green += (((Max - Green) * AmtVal) >> 14);
    if (Red != Max)        Red += (((Max - Red) * AmtVal) >> 14);

  对应的SSE代码为:

    AmtVal = _mm_mullo_epi16(_mm_sub_epi16(MaxL16, AvgL16), Adjustment128);
    BL16 = _mm_adds_epi16(BL16, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(MaxL16, BL16), 2), AmtVal));
    GL16 = _mm_adds_epi16(GL16, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(MaxL16, GL16), 2), AmtVal));
    RL16 = _mm_adds_epi16(RL16, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(MaxL16, RL16), 2), AmtVal));

    AmtVal = _mm_mullo_epi16(_mm_sub_epi16(MaxH16, AvgH16), Adjustment128);
    BH16 = _mm_adds_epi16(BH16, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(MaxH16, BH16), 2), AmtVal));
    GH16 = _mm_adds_epi16(GH16, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(MaxH16, GH16), 2), AmtVal));
    RH16 = _mm_adds_epi16(RH16, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(MaxH16, RH16), 2), AmtVal));

  最终一步就是将这几个拾伍人的数据重复员和转业移为七位的,注意原始代码中有Clamp操作,那些操作实际是个耗费时间的历程,而SSE天然的享有抗饱和的函数。

  Blue8 = _mm_packus_epi16(BL16, BH16);
  Green8 = _mm_packus_epi16(GL16, GH16);
  Red8 = _mm_packus_epi16(RL16, RH16);

 _mm_packus_epi16这个的用法和含义自己去MSDN搜索一下吧,实在是懒得解释了。

   最后优化速度:5ms。

   来个速度相比较:

版本 VB6.0 C++,float优化版本 C++定点版 C++/SSE版
速度 400ms 70ms 35ms 5ms

 

     

  上边的VB6.0的耗时是原著者的代码编写翻译后的实施进程,借使自身要好去用VB6.0去优化他的话,有信念能成功70ms以内的。

  但无论如何,SSE优化的进度提高是伟大的。

结论:

       不难的分析了自然饱和度算法的兑现,分享了其SSE达成的进度,对于这个刚刚接触SSE,想做图像处理的心上人有肯定的相助。

     
  源代码下载地址:http://files.cnblogs.com/files/Imageshop/Vibrance.rar

       
写的真正好累,休息去了,觉得对您有效的请给本身买杯红酒也许咖啡呢。

图片 4

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图