SSE图像算法优化系列三十一:RGB2HSL/RGB2HSV及HSL2RGB/HSV2RGB的指令集优化-上。

这篇具有很好参考价值的文章主要介绍了SSE图像算法优化系列三十一:RGB2HSL/RGB2HSV及HSL2RGB/HSV2RGB的指令集优化-上。。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

       RGB和HSL/HSV颜色空间的相互转换在我们的图像处理中是有着非常广泛的应用的,无论是是图像调节,还是做一些肤色算法,HSL/HSV颜色空间都非常有用,他提供了RGB颜色空间不具有的一些独特的特性,但是由于HSL/HSV颜色空间的复杂性,他们之间的转换的效率一直不是很高的,有一些基于定点算法的尝试,对速度有一定的提升,但一个是提升不是特别的明显,另外就是对结果的精度有一定的影响。

   对于这两个算法的指令集优化,网络上就根本没有任何资料,也没有任何人进行过尝试,我也曾经有想法去折腾他,但是初步判断觉得他里面有太多的分支了,应该用了指令集后也不会有多大的速度区别,所以一直没有动手。 

      但是最近的一个朋友的潜在需求,然后我又对这个算法有些期待,重新动手拾起这个转换过程,结果还是有所收获,速度获得了3到4倍的提升。、

      我们先来谈谈RGB到HSL或者HSV颜色空间的转换优化

       这个网络上一大堆,我也就不浪费时间去重新整理,我直接分享一段代码和网址吧:

     参考网址:  http://www.xbeat.net/vbspeed

  这个文章给出的是VB6的代码,可以参考下。

  我们约定:RGB数据源是unsigned char 类型, 有效范围就是[0,255],而HSL/HSV都是浮点型,其中H的有效范围时[0,6],S的有效范围是[0,1], L/V的有效范围也是[0,1]。

  经过我个人的整理和稍微优化,一个简单的RGB2HSV代码如下所示:

void IM_RGB2HSV_PureC(unsigned char Blue, unsigned char Green, unsigned char Red, float &Hue, float &Sat, float &Val)
{
    int Min = IM_Min(Red, IM_Min(Green, Blue));
    int Max = IM_Max(Red, IM_Max(Green, Blue));
    if (Max == Min)
    {
        Hue = 0;
        Sat = 0;
    }
    else
    {
        int Delta = Max - Min;
        if (Max == Red)
            Hue = (float)(Green - Blue) / Delta;
        else if (Max == Green)
            Hue = 2.0f + (float)(Blue - Red) / Delta;
        else
            Hue = 4.0f + (float)(Red - Green) / Delta;

        //    实际上只有Max==Red时,方有可能Hue < 0 (对应Green < Blue),
        //    所以有的代码在Max == Red内再做判断,对于C代码来说,这样效率应该会高一点
        if (Hue < 0)  Hue += 6;
        Sat = (float)Delta / Max;
    }
    Val = Max * IM_Inv255;
}

  RGB2HSL的代码如下:

void IM_RGB2HSL_PureC(unsigned char Blue, unsigned char Green, unsigned char Red, float &Hue, float &Sat, float &Val)
{
    int Min = IM_Min(Red, IM_Min(Green, Blue));
    int Max = IM_Max(Red, IM_Max(Green, Blue));
    int Sum = Max + Min;
    if (Max == Min)
    {
        Hue = 0;
        Sat = 0;
    }
    else
    {
        int Delta = Max - Min;
        if (Max == Red)
            Hue = (float)(Green - Blue) / Delta;
        else if (Max == Green)
            Hue = 2.0f + (float)(Blue - Red) / Delta;
        else
            Hue = 4.0f + (float)(Red - Green) / Delta;

        if (Hue < 0)  Hue += 6;

        if (Sum <= 255)
            Sat = (float)Delta / Sum;
        else
            Sat = (float)Delta / (510 - Sum);
    }
    Val = Sum * IM_Inv510;
}

   比较两个不同的模型的代码可以发现,他们对于H分量的定义是相同的,对于V/L分量一个使用了最大值,一个使用了最大值和最小值的平均值,对于S分量,大家都考虑了最大值和最下值的差异,只是一个和最大值做比较,一个是和最大值和最小值之和做比较,整体来说,RGB2HSV模型相对来说简单一些,计算量也少一些。

  可以看到,无论是RGB2HSL还是RGB2HSV,求H的过程都有非常多的判断和分支语句,而且整体考虑除零错误(Max == Min)还有一些其他的特殊判断, 正如我在博文中多次提到,指令集里没有分支跳转的东西,这些跳转是非常不利于指令集优化。指令集里要实现这样的东西,只有两个办法:

  1、想办法把所有分支跳转用一些奇技淫巧合并到一起,用一个语句来表达他。

  2、对所有分支语句的结果都计算出来,然后使用相关的Blend进行条件合并。

  仔细的分析上面的C代码,我是没有想到什么特别好的技巧把色相部分的三个分支合并为一个语句。凭个人的感觉,只能使用第二种方式。 

  为了描述方便,我先贴出RGB2HSV算法一个比较简单的SIMD指令集优化的结果:

 1 void IM_RGB2HSV_SSE_Old(__m128i Blue, __m128i Green, __m128i Red, __m128 &Hue, __m128 &Sat, __m128 &Val)
 2 {
 3     __m128i Max = _mm_max_epi32(Red, _mm_max_epi32(Green, Blue));    //    R/G/B的最大值Max
 4     __m128i Min = _mm_min_epi32(Red, _mm_min_epi32(Green, Blue));    //    R/G/B的最小值Min
 5     __m128i Delta = _mm_sub_epi32(Max, Min);                        //    最大值和最小值之间的差异Delta = Max - Min
 6 
 7     __m128 MaxS = _mm_cvtepi32_ps(Max);
 8     __m128 DeltaS = _mm_cvtepi32_ps(Delta);
 9 
10     Sat = _mm_divz_ps(DeltaS, MaxS);                        //    S = Delta / Max, 注意有了除零的异常处理,同时如果Max == Min, Delta就为0, S也返回0,是正确的
11     Val =  _mm_mul_ps(MaxS, _mm_set1_ps(IM_Inv255));        //    V = Max / 255;
12 
13     __m128 Inv = _mm_divz_ps(_mm_set1_ps(1), DeltaS);
14 
15     //if (Max == Red)
16     //    Hue = (float)(Green - Blue) / Delta;
17 
18     __m128 HueR = _mm_mul_ps(_mm_cvtepi32_ps(_mm_sub_epi32(Green, Blue)), Inv);
19 
20     //else if (Max == Green)
21     //    Hue = 2.0f + (float)(Blue - Red) / Delta;
22 
23     __m128i Mask = _mm_cmpeq_epi32(Max, Green);
24     __m128 HueG = _mm_add_ps(_mm_set1_ps(2.0f), _mm_mul_ps(_mm_cvtepi32_ps(_mm_sub_epi32(Blue, Red)), Inv));
25 
26     Hue = _mm_castsi128_ps(_mm_blendv_epi8(_mm_castps_si128(HueR), _mm_castps_si128(HueG), Mask));
27 
28     //else
29     //    Hue = 4.0f + (float)(Red - Green) / Delta;
30     Mask = _mm_cmpeq_epi32(Max, Blue);
31     __m128 HueB = _mm_add_ps(_mm_set1_ps(4.0f), _mm_mul_ps(_mm_cvtepi32_ps(_mm_sub_epi32(Red, Green)), Inv));
32 
33     Hue = _mm_castsi128_ps(_mm_blendv_epi8(_mm_castps_si128(Hue), _mm_castps_si128(HueB), Mask));
34 
35     //    if (H < 0)    H += 6;    其实这个主要是针对Max == R的情况会出现
36     Hue = _mm_blendv_ps(Hue, _mm_add_ps(Hue, _mm_set1_ps(6)), _mm_cmplt_ps(Hue, _mm_setzero_ps()));
37 
38 }

  说明: IM_RGB2HSV_SSE函数中的Blue、Green、Red三个__m128i 变量中保存的是4个32位的颜色分量,而不是16个颜色。 

  第三、第四、第五行求Max\Min\Delta这些过程没有什么难以理解的。第七、第八行只是把整形转换为浮点型(注意SSE指令也是强类型的哦,必须自己手动转换类型)。

  第十、第十一行直接就求出了Sat和Val分量, Val不难理解,Sat在对应的C代码中是分了Max == Min及Max != Min两种状况,当Max == Min时,为0,否则,要使用(Max - Min) / Max, 其实这里不用做判断直接统一使用 (Max - Min) / Max即可,因为Max == Min时, Max - Min也是0, 但是唯一需要注意的就是如果Max = Min = 0时, Max也为0, 0 / 0 在数学时不容许的,在计算上也会有溢出错误,所以这里使用了一个自定义的_mm_divz_ps函数,实现当除数为0时,返回0的结果。这样就可以剥离掉这个分支语句了。

  复杂的是Hue分量的计算,从第十三行开始一直到最后都是关于他的优化。 

  第13行,我们先计算出1.0f / Delta,注意这里也是使用的_mm_divz_ps函数。

  第16行我们先按照公式计算出当Max == Red时Hue的结果。

  第23行我们比较Max和Green是否相等,注意这里也是使用的32位int类型的比较。

  第24行按照公式计算出当Max == Green时,Hue分量的结果。

  第25行则对这两个结果进行混合,这里的混合有很多编码上的技巧,因为我们两次计算的HueR和HueG都是__m128类型,而我们的比较是用的整形的比较,返回的是__m128i型的数据,而_mm_blendv_ps的混合需要的__m128的比较结果,但是如果直接将Mask强制转换为浮点类型,作为_mm_blendv_ps的参数,将会产生不正确的结果。那么解决方案有2个:

  一、使用浮点类型的比较,即将Blue\Green分量先转换为__m128型,然后使用_mm_cmpeq_ps进行比较,这样增加几条类型转换函数。

  二、就是使用本例的代码,使用_mm_blendv_epi8 + _mm_castps_si128进行混合,表面上看多了3次cast的过程,似乎更为耗时,但是实际上cast系列的语句只是个语法糖,编译后他不产生任何汇编指令。他只是让编译器认为他是另外一个类型的数据类型了,这样就可以编译了,实际上__m128、__m128i这些东西在硬件上都是保存在XMM寄存器上的,寄存器本身不分数据类型。

  第30和31行也是类似的到里,对那些Max == Blue分量的结果进行混合。

  第36行则是对Hue < 0的特殊情况进行处理。也没有什么特别复杂的。 

  我们对一副5000*5000大小的24位图像(填充的随机数据)进行测试,普通C语言的耗时约为114ms,上述SIMD优化的耗时约为 49ms,提速比接近2.2倍。

  实际上上述SIMD指令优化的代码还有一定的优化空间,我们注意到为了计算HueR\HueG\HueB,我们进行了3次浮点版本的乘法和加法。但是如果我们把这个乘法和加法的部分单独提出来,每次都进行相应的混合,那么只需要最后进行一次乘法和加法即可以了,这样增加了混合的次数,但是减少了计算的次数,而混合指令其实都是通过位运算实现的,相对来说非常快,具体的代码如下所示:

 1 void IM_RGB2HSV_SSE(__m128i Blue, __m128i Green, __m128i Red, __m128 &Hue, __m128 &Sat, __m128 &Val)
 2 {
 3     __m128i Max = _mm_max_epi32(Red, _mm_max_epi32(Green, Blue));    //    R/G/B的最大值Max
 4     __m128i Min = _mm_min_epi32(Red, _mm_min_epi32(Green, Blue));    //    R/G/B的最小值Min
 5     __m128i Delta = _mm_sub_epi32(Max, Min);                        //    最大值和最小值之间的差异Delta = Max - Min
 6 
 7     __m128 MaxS = _mm_cvtepi32_ps(Max);
 8     __m128 DeltaS = _mm_cvtepi32_ps(Delta);
 9 
10     Sat = _mm_divz_ps(DeltaS, MaxS);                        //    S = Delta / Max, 注意有了除零的异常处理,同时如果Max == Min, Delta就为0, S也返回0,是正确的
11     Val = _mm_mul_ps(MaxS, _mm_set1_ps(IM_Inv255));        //    V = Max / 255;
12 
13     //    SIMD没有跳转方面的指令,只能用Blend加条件判断来实现多条件语句。注意观察三种判断的情况可以看成是一个Base(0/120/240)加上不同的Diff乘以Inv。
14     //    以Max == B为基础,这样做的好处是:当Max == Min时,H是要返回0的,但是如果按照C语言的那个混合顺序,则最后判断Max == B时成立,则H返回的是4,那么为了返回正确的结果
15     //    就还要多一个_mm_blendv_epi8语句,注意这里隐藏的一个事实是Max == Min时,G - B, B - R, R - G其实都是为0的,那么类似这样的 (float)(G - B) / Delta * 60结果也必然是0。
16 
17     //  if (Max == bB)
18     //        H = 4.0f + (float)(R - G) / Delta;
19 
20     __m128i Base = _mm_set1_epi32(4);
21     __m128i Diff = _mm_sub_epi32(Red, Green);
22 
23     //if (Max == G)
24     //        H = 2.0f + (float)(B - R) / Delta;
25 
26     __m128i Mask = _mm_cmpeq_epi32(Max, Green);
27     Base = _mm_blendv_epi8(Base, _mm_set1_epi32(2), Mask);        //    当Mask为真时,_mm_blendv_epi8返回第二个参数的值,否则返回第一个参数的值
28     Diff = _mm_blendv_epi8(Diff, _mm_sub_epi32(Blue, Red), Mask);
29 
30     // if (Max == R)
31     //        H = (float)(G - B) / Delta;
32     Mask = _mm_cmpeq_epi32(Max, Red);
33     Base = _mm_blendv_epi8(Base, _mm_setzero_si128(), Mask);
34     Diff = _mm_blendv_epi8(Diff, _mm_sub_epi32(Green, Blue), Mask);
35 
36     __m128 Inv = _mm_divz_ps(_mm_set1_ps(1), DeltaS);                            //    1 / Delta,注意有了除零的异常处理
37 
38                                                                                 //    H = Base + Diff * Inv
39     Hue = _mm_add_ps(_mm_cvtepi32_ps(Base), _mm_mul_ps(_mm_cvtepi32_ps(Diff), Inv));
40 
41     //    if (H < 0)    H += 6;    其实这个主要是针对Max == R的情况会出现
42     Hue = _mm_blendv_ps(Hue, _mm_add_ps(Hue, _mm_set1_ps(6)), _mm_cmplt_ps(Hue, _mm_setzero_ps()));
43 
44 }

  通过这种方式优化大概还能获取15-25%的性能提升。

  当然,这里可能还有一部分空间可以考虑,即我们使用的是32位int类型的比较,一次只能比较4个数,另外诸如_mm_max_epi32这样的计算,对于原始的图像数据来说,都可以使用epi8来做的,这样一次性就是可以获取16个像素的信息,而不是8位,但是这样做面临的问题就是后面要做多次数据类型转换。这些转换的耗时和比较的耗时孰重孰轻暂时还没有结论,有兴趣的读者可以自行测试下。

  如果您看懂了RGB2HSV的SSE代码,那么RGB2HSL你觉得还会有难度吗,希望读者可以自行编码实现。

  下一篇将着重讲述HSL2RGB及HSV2RGB空间的优化,那个的优化难度以及优化的提速比相对来讲要比RGB2HSL和RGB2HSL更为复杂和有效。

  本文的测试代码可从下述链接获取: https://files.cnblogs.com/files/Imageshop/RGB2HSV.rar?t=1689216617&download=true

  如果想时刻关注本人的最新文章,也可关注公众号或者添加本人微信:  laviewpbt

                             SSE图像算法优化系列三十一:RGB2HSL/RGB2HSV及HSL2RGB/HSV2RGB的指令集优化-上。

 文章来源地址https://www.toymoban.com/news/detail-553845.html

  如果想时刻关注本人的最新文章,也可关注公众号或者添加本人微信:  laviewpbt

                             SSE图像算法优化系列三十一:RGB2HSL/RGB2HSV及HSL2RGB/HSV2RGB的指令集优化-上。

 

到了这里,关于SSE图像算法优化系列三十一:RGB2HSL/RGB2HSV及HSL2RGB/HSV2RGB的指令集优化-上。的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • JavaScript从入门到精通系列第三十一篇:详解JavaScript中的字符串和正则表达式相关的方法

      文章目录 知识回顾 1:概念回顾 2:正则表达式字面量 一:字符串中正则表达式方法 1:split 2:search 3:match 4:replace         正则表达式用于定义一些字符串的规则,计算机可以根据正则表达式检查一个字符串是否符合规则,或者将字符串中符合规则的内容提取出来。

    2024年01月17日
    浏览(58)
  • 【嵌入式经验系列】基于色坐标的RGB灯调光调色算法

    关注且发送\\\"001\\\"即可获取完整全文内容; 如果想要驱动一个三基色LED灯亮出不同的颜色,只需要控制三路PWM的输出即可实现。但是如何控制三路的输出使三基色LED灯亮出我们想要得到的理想目标颜色并且只有很小的色差就没有那么简单了。本文将介绍一种基于色坐标的三基色

    2024年02月13日
    浏览(91)
  • 图像处理算法大全(基于libyuv或IPP)----RGB32(ARGB)转成yuv420,RGB24,nv12,yuv422集合

    《周星星教你学ffmpeg》技巧 libyuv源码:  ok!打完收工!

    2024年02月07日
    浏览(44)
  • 数字图像处理(十一)白平衡算法

      当一副彩色图像数字化后,在显示时颜色有时会看起来有些不正常。这是因为颜色通道中不同的敏感度、增光因子、偏移量等,导致数字化中的三个图像分量(R,G,B)出现不同的变换,使结果图像的三原色\\\"不平衡\\\",从而使景物中所有物体的颜色都偏离了其原有的真实色彩。

    2024年02月05日
    浏览(44)
  • 图像处理算法大全(基于libyuv或IPP)----NV12转成I420,RGB24,ARGB集合

    《周星星教你学ffmpeg》技巧 libyuv源码: static void NV12ToI420(BYTE* pNV12_Y, BYTE* pNV12_UV, BYTE* pYV12, int width, int height) {     libyuv::NV12ToI420(pNV12_Y, width, pNV12_UV, width, pYV12, width, pYV12 + height*width, width / 2, pYV12 + height*width * 5 / 4, width / 2, width, height);//去掉注释就是libyuv了 } static void NV12ToBGR

    2024年02月12日
    浏览(44)
  • 算法学习系列(二十一):拓扑排序

    这个拓扑排序大家应该都听说过,用的地方也是很多,考研和面试也是经常考,其实这个排序算法的思想比较简单,应用的话就是可以用来判断一个图中是否存在一个环,本文主要是介绍拓扑排序的思想,以及一个简单的模板题,来帮助了解什么是拓扑排序,以及怎么写。

    2024年01月16日
    浏览(49)
  • 三十一、Fluent空化模型

    流体中的声波其实是一种压力波,在液体中机械振动使其内部压强发生变化,当压力降低时候流体内部或者在一些流固界面上会出现结构断裂从而形成空腔,这种形成空腔的过程便称为空化。 常见的便是船舶螺旋桨在水中的剥蚀 手枪在水中发射 Fluent多相流模型中,无论哪个

    2024年02月04日
    浏览(35)
  • OpenCV(三十一):形态学操作

    ​​​​​​1.形态学操作        OpenCV 提供了丰富的函数来进行形态学操作,包括腐蚀、膨胀、开运算、闭运算等。下面介绍一些常用的 OpenCV 形态学操作函数: 腐蚀操作(Erosion): erode(src, dst, kernel, anchor, iterations, borderType, borderValue) 该函数对输入图像中的前景区域进行

    2024年02月09日
    浏览(54)
  • 第三十一章 Unity骨骼动画

    关于骨骼动画的原理,我们这里不再详细介绍,有不清楚的可以回去看DirectX课程和3dsMAX课程。接下来,我们来讲解一下Unity的骨骼动画系统。Unity 的动画系统基于动画剪辑(Animation Clip)的概念,它的本质就是一小段动画,代表了一个游戏角色的动作,例如:走路,跑步,攻

    2024年02月08日
    浏览(64)
  • C#(三十一)之自定义事件

    自定义事件:一定要注意下面六部曲 1:声明关于事件的委托 2:声明事件 3:编写触发事件的函数 4:创建事件处理程序 5:注册事件处理程序 6:触发事件 定义事件: 声明事件时event可以省略(最好不要省略) 事件继承 事件event都是继承自EventArgs,通过继承EventArgs可以给自

    2024年02月13日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包