SSE图像算法优化系列十六:经典USM锐化中的分支判断语句SSE实现的几种方法尝试。...
分支判断的语句一般来说是不太适合进行SSE优化的,因为他会破坏代码的并行性,但是也不是所有的都是这样的,在合适的场景中运用SSE还是能对分支预测进行一定的优化的,我们这里以某一个算法的部分代码为例进行讲解。
在某一个版本的USM锐化算法中有这样的一段代码:
int IM_UnsharpMask(unsigned char *Src, unsigned char *Dest, int Width, int Height, int Stride, int Radius, int Amount, int Threshold) {int Channel = Stride / Width;if ((Src == NULL) || (Dest == NULL)) return IM_STATUS_NULLREFRENCE;if ((Width <= 0) || (Height <= 0)) return IM_STATUS_INVALIDPARAMETER;if ((Channel != 1) && (Channel != 3) && (Channel != 4)) return IM_STATUS_INVALIDPARAMETER;int Status = IM_STATUS_OK;Status = IM_ExpBlur(Src, Dest, Width, Height, Stride, Radius); // 这里标准过程是用IM_GaussBlur代替if (Status != IM_STATUS_OK) return Status;const float Inv255 = 1.0f / 255.0f;int *Table = (int *)malloc(511 * 256 * sizeof(int));if (Table == NULL) return IM_STATUS_OUTOFMEMORY;for (int Y = 0; Y < 256; Y++){float TempUp = Amount * sqrtf(1.0f - Y * Inv255) / 100.0f;float TempDown = Amount * sqrtf(Y * Inv255) / 100.0f;for (int X = -255; X <= 255; X++){int Diff = X;if (Diff >= Threshold){Diff -= Threshold;Table[((X + 255) << 8) + Y] = IM_ClampToByte(int(Diff * TempUp + 0.5f) + Y);}else if (Diff < -Threshold){Diff += Threshold;Table[((X + 255) << 8) + Y] = IM_ClampToByte(int(Diff * TempDown + 0.5f) + Y);}else{Table[((X + 255) << 8) + Y] = Y; // 不做变化 }}}for (int Y = 0; Y < Height * Stride; Y++) // 分四路并行速度只有一点点提高 {Dest[Y] = Table[((Src[Y] - Dest[Y] + 255) << 8) + Src[Y]];}free(Table);return IM_STATUS_OK; }
这个USM锐化的算法参考自:https://github.com/pluginguy/plugins/tree/master/USM2,源代码中的算法还提供了对高光、暗调和中间调进行不同调节的参数,我这里对他那个代码进行了适度的修改和简化,并且用查找表进行了优化。这个github的作者还提供了关于高斯模糊方面的资料,是个不错的参考点。
上述代码起始已经很高效了,复杂的浮点和开方计算都已经用查表的形式进行了简化,实测一副1080P的24位图像大处理时间大约在14.5ms左右,而其中的IM_ExpBlur耗时约有6.75ms,建立查找表花了0.75ms,后面的遍历图像进行查找表替换使用了7ms,注意前面的IM_ExpBlur的时间是已经进行了SSE编码后的优化时间。
查找表其实本身也是个耗时的工作,因为这个可能有着严重的cache miss,特别是查找表比较大的时候。但是查找表本身呢在目前SIMD框架下是无法使用SSE优化的(除非是16个字节的查找表,可以使用_mm_shuffle_epi8来优化),因此,如果查找表本身的建立算法并不特别复杂,是可以考虑使用SSE来对表中每个元素进行直接的实现的,鉴于此,我们来考虑上述代码的查找表的直接SSE实现。
为了表示清楚,我们把上述算法的非查找表方式实现的代码整理出来如下:
int IM_UnsharpMask(unsigned char *Src, unsigned char *Dest, int Width, int Height, int Stride, int Radius, int Amount, int Threshold) {int Channel = Stride / Width;if ((Src == NULL) || (Dest == NULL)) return IM_STATUS_NULLREFRENCE;if ((Width <= 0) || (Height <= 0)) return IM_STATUS_INVALIDPARAMETER;if ((Channel != 1) && (Channel != 3) && (Channel != 4)) return IM_STATUS_INVALIDPARAMETER;int Status = IM_STATUS_OK;Status = IM_ExpBlur(Src, Dest, Width, Height, Stride, Radius); // 这里标准过程是用IM_GaussBlur代替if (Status != IM_STATUS_OK) return Status;float Adjust = Amount / 100.0f / sqrtf(255.0f);for (int Y = 0; Y < Height * Stride; Y++) {int Diff = Src[Y] - Dest[Y];if (Diff >= Threshold){Dest[Y] = IM_ClampToByte(int((Diff - Threshold) * Adjust * sqrtf(255.0f - Src[Y]) + 0.5f) + Src[Y]);}else if (Diff < -Threshold){Dest[Y] = IM_ClampToByte(int((Diff + Threshold) * Adjust * sqrtf((float)Src[Y]) + 0.5f) + Src[Y]);}else{Dest[Y] = Src[Y]; // 不做变化 }}return IM_STATUS_OK; }
注意为减少计算我已经把一些重复的计算提取到Adjust变量中,其中的/sqrtf(255.0f)可以让循环内部的sqrtf的参数少一次乘法计算,并且在后面我们还可以看到他起到了另外一个特殊的作用。运行上述代码的同参数同照片耗时变为了55ms左右,可见查找表的优化也是很给力的。
我注意到这段代码已经有很久了,也一直想使用SSE优化他们,但苦于能力,一直未得良方,不过最近过年重新审视这段代码,发现只要手指按住键盘,总会有新大陆发现的。
第一方案:既然SSE不太好做分支判断,我就把所有分支的结果都计算出来,最后再根据分支条件做数据融合不就可以了吗,可以肯定SSE计算每个分支的速度肯定比C快,但是如果要每个分支都计算,这个增加的耗时和加速的时间比例如何呢,只有实践才知道,于是我硬着头皮把他们用SSE做个硬编码,代码如下所示:
// 实在没有好的办法,极端情况下把所有的分支的结果都算出来,然后在最后根据判断条件合成,比如下面的代码,写出来后比原始的查找表方式也还是要快一点的。 int IM_UnsharpMask(unsigned char *Src, unsigned char *Dest, int Width, int Height, int Stride, int Radius, int Amount, int Threshold) {int Channel = Stride / Width;if ((Src == NULL) || (Dest == NULL)) return IM_STATUS_NULLREFRENCE;if ((Width <= 0) || (Height <= 0)) return IM_STATUS_INVALIDPARAMETER;if ((Channel != 1) && (Channel != 3) && (Channel != 4)) return IM_STATUS_INVALIDPARAMETER;int Status = IM_STATUS_OK;Status = IM_ExpBlur(Src, Dest, Width, Height, Stride, Radius);if (Status != IM_STATUS_OK) return Status;const float Adjust = Amount / 100.0f / sqrt(255.0f);const int BlockSize = 8;int Block = (Height * Stride) / BlockSize;const __m128i Zero = _mm_setzero_si128();const __m128i ThresholdV = _mm_set1_epi16(Threshold);const __m128i MinusThresholdV = _mm_set1_epi16(-Threshold);const __m128i One = _mm_set1_epi16(1);const __m128i MinusOne = _mm_set1_epi16(-1);const __m128 Const255 = _mm_set1_ps(255.0f);const __m128 AdjustV = _mm_set1_ps(Adjust);for (int Y = 0; Y < Block * BlockSize; Y += BlockSize){__m128i SrcV = _mm_unpacklo_epi8(_mm_loadl_epi64((__m128i *)(Src + Y)), Zero);__m128i DstV = _mm_unpacklo_epi8(_mm_loadl_epi64((__m128i *)(Dest + Y)), Zero);__m128 SrcL = _mm_cvtepi32_ps(_mm_unpacklo_epi8(SrcV, Zero));__m128 SrcH = _mm_cvtepi32_ps(_mm_unpackhi_epi8(SrcV, Zero));__m128i Diff = _mm_sub_epi16(SrcV, DstV);__m128i DiffA = _mm_add_epi16(Diff, ThresholdV);__m128i DiffS = _mm_sub_epi16(Diff, ThresholdV);__m128 DiffL = _mm_cvtepi32_ps(_mm_cvtepi16_epi32(Diff));__m128 DiffH = _mm_cvtepi32_ps(_mm_cvtepi16_epi32(_mm_srli_si128(Diff, 8)));__m128 UpL = _mm_mul_ps(AdjustV, _mm_sqrt_ps(_mm_sub_ps(Const255, SrcL)));__m128 UpH = _mm_mul_ps(AdjustV, _mm_sqrt_ps(_mm_sub_ps(Const255, SrcH)));__m128 DownL = _mm_mul_ps(AdjustV, _mm_sqrt_ps(SrcL));__m128 DownH = _mm_mul_ps(AdjustV, _mm_sqrt_ps(SrcH));__m128 DiffUpL = _mm_mul_ps(_mm_cvtepi32_ps(_mm_cvtepi16_epi32(DiffS)), UpL);__m128 DiffUpH = _mm_mul_ps(_mm_cvtepi32_ps(_mm_cvtepi16_epi32(_mm_srli_si128(DiffS, 8))), UpH);__m128 DiffDownL = _mm_mul_ps(_mm_cvtepi32_ps(_mm_cvtepi16_epi32(DiffA)), DownL);__m128 DiffDownH = _mm_mul_ps(_mm_cvtepi32_ps(_mm_cvtepi16_epi32(_mm_srli_si128(DiffA, 8))), DownH);__m128i DiffUp = _mm_adds_epi16(_mm_packs_epi32(_mm_cvtps_epi32(DiffUpL), _mm_cvtps_epi32(DiffUpH)), SrcV);__m128i DiffDown = _mm_adds_epi16(_mm_packs_epi32(_mm_cvtps_epi32(DiffDownL), _mm_cvtps_epi32(DiffDownH)), SrcV);__m128i DestV = _mm_blendv_si128(SrcV, DiffUp, _mm_cmpgt_epi16(Diff, ThresholdV));DestV = _mm_blendv_si128(DestV, DiffDown, _mm_cmplt_epi16(Diff, MinusThresholdV));_mm_storel_epi64((__m128i *)(Dest + Y), _mm_packus_epi16(DestV, Zero));}for (int Y = Block * BlockSize; Y < Height * Stride; Y++){int Diff = Src[Y] - Dest[Y];if (Diff >= Threshold){Dest[Y] = IM_ClampToByte(int((Diff - Threshold) * Adjust * sqrtf(255.0f - Src[Y]) + 0.5f) + Src[Y]);}else if (Diff < -Threshold){Dest[Y] = IM_ClampToByte(int((Diff + Threshold) * Adjust * sqrtf(0.0f + Src[Y]) + 0.5f) + Src[Y]);}else{Dest[Y] = Src[Y]; }}return IM_STATUS_OK; }
上述代码基本就是普通C语言的翻译,这里讲几个需要注意的地方。
第一、_mm_cvtepi16_epi32这是个讲signed short转换为signed int的函数,只处理XMM寄存的低8位,如果需要将高8位也进行转换,就必须得配合_mm_srli_si128一起使用,如果需要转换的signed short能确认是大于等于0的,也可以使用_mm_unpacklo_epi16及_mm_unpackhi_epi16配合_mm_setzero_si128来实现,比如上面的SrcL和SrcH就是使用的这个技巧,但是如果有小于0的情况出现,一定只能用_mm_cvtepi16_epi32来实现,比如上面的DiffL和DiffH,我以前在这个上面吃过很多亏。
第二、在计算DiffUp和DiffDown这两个结果时,注意需要使用_mm_packs_epi32,而不是_mm_packus_epi32,因为计算结果是有负数存在的。
第三、结果的融合这里的技巧很好,我们知道SSE4提供了两个__m128i变量融合的函数,比如_mm_blendv_epi8,但是他要求最后的融合选项是个常数,而我们这里的融合选项是变化的,所以无法使用,我们使用了一个叫做_mm_blendv_si128的内联函数,这个函数用一个__m128i变量作为融合参数,对128个位进行融合,其代码如下:
static inline __m128i _mm_blendv_si128(__m128i x, __m128i y, __m128i mask) {return _mm_or_si128(_mm_andnot_si128(mask, x), _mm_and_si128(mask, y)); }
当mask的某一位为0时,选择x中的对应位的值,否则选择y中对应位的值。
这个函数正是我需要的,而且恰好前几天在浏览文章:A few missing SSE intrinsics发现了他,有的时候真的觉得处处留心皆学问啊。
这时我们来看下上面的融合的代码:__m128i DestV = _mm_blendv_si128(SrcV, DiffUp, _mm_cmpgt_epi16(Diff, ThresholdV));
后面的_mm_cmpgt_epi16的比较函数会返回一个__m128i变量,当Diff > Threshold时,对应的16位数据为0xFFFF,否则为0,这样我们使用_mm_blendv_si128融合时,满足条件的部分结果就为DiffUp了,其他部分还保持SrcV不变。
接着 DestV = _mm_blendv_si128(DestV, DiffDown, _mm_cmplt_epi16(Diff, MinusThresholdV)); 使用Diff < -Threshold作为判断条件,因为该条件和Diff > Threshold不可能同时成立,所以_mm_cmplt_epi16的返回结果中的为true的部分和_mm_cmpgt_epi16返回的true部分的值不可能重叠,因此,再次执行_mm_blendv_si128混合的值就是我们融合的正确结果。
那么我们最关心的速度来了,经过测试,上述算法对1080P彩色图能达到约14ms的执行速度,和查找表的C语言版本速度差不多,唯一的优势就是运算时少占用了一部分内存。但是同时也说明SSE的计算能力真的不是盖的,算一算,正正的SSE执行时间实际上只有14-6.75 =7.25ms,而不用查找表的C代码的用时为55-6.75=48.25ms,达到了进7倍的提速比,但这就是我们的终点了吗?
第二方案:我们在仔细观察下Diff > Threshold和Diff < -Threshold时计算的不同,第一个不同是Diff > Threshold时使用了Diff - Threshold,而Diff < -Threshold时使用了Diff + Threshold;第二个不同为Diff > Threshold时使用了255.0f - Src[Y]作为开平方的算式,而Diff < -Threshold时使用了 Src[Y]。关于第一个不同,我们可以看到仅仅是个符号位不同,如果在Threshold前面根据不同的条件加个符号位在进行乘法不就可以了,也就是说如果我们根据Diff和Threshold的关系构建一个-1和1的中间变量,则可以把他们写在一个式子里,那这样的符号为要如何构建呢?
自然而然我们又想到了上述方法的_mm_blendv_si128,简单的方式如下所示:
__m128i Sign = _mm_blendv_si128(Zero, MinusOne, _mm_cmpgt_epi16(Diff, ThresholdV));
Sign = _mm_blendv_si128(Sign, One, _mm_cmplt_epi16(Diff, MinusThresholdV));
Zero,MinusOne,One这个还需不需要解释,上面的代码还需不需要解释?
第二个不同,我们这样看,我们把它们放在一起 255.0f - Src[Y] | Src[Y],稍微改写一下255 - Src[Y] | 0 - Src[Y],后面的+和-可以用类似前面的同样的方法处理,我们还需处理255和0,如果我们能够根据判断条件构造出255 和 0这样的序列,那是不是就解决问题了,如何构造?
前面说过,_mm_cmpgt_epi16会返回0xFFFF和0,看成unsigned short类型则为65535和0, 如果我们把这个返回结果右移8位,是不是就变为了255和0呢,明白了吗?
最后我们注意一点,当-Threshold < Diff <Threshold时,我们的返回的是原图像的值,那在这种情况下是不是有问题呢,其实也不会,我们注意到此条件下Sign对应的符号位为0,而_mm_cmpgt_epi16返回的那部分数据也为0,也就是说此时对应的sqrt参数为0,那么作为乘法的一部分,整个前面的算式就为0,结果返回的恰好是原值。
我们还来在说下前面的符号问题,正或者负某个数,直接用符号位加乘法固然是可以实现的,但是有么有其他的方式更好的实现呢,翻一番SSE的手册,我们会发现有_mm_sign_epi8 、_mm_sign_epi16 、_mm_sign_epi32 这样的函数,他们是干什么的呢,我们以_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 = 0; i < 8; i++) { if (b[i] < 0) { r[i] = -a[i]; } else if (b[i] == 0){ r[i] = 0; } else { r[i] = a[i]; } }
什么意思,就是以参数b的符号位来决定a的值,当b为负数是,对a求反,当b为0时,a也为0,否则a值保持不变。这不就可以直接实现上述的符号位的问题了吗?
说了那么多,我贴出代码大家看一看:
int IM_UnsharpMask(unsigned char *Src, unsigned char *Dest, int Width, int Height, int Stride, int Radius, int Amount, int Threshold) {int Channel = Stride / Width;if ((Src == NULL) || (Dest == NULL)) return IM_STATUS_NULLREFRENCE;if ((Width <= 0) || (Height <= 0)) return IM_STATUS_INVALIDPARAMETER;if ((Channel != 1) && (Channel != 3) && (Channel != 4)) return IM_STATUS_INVALIDPARAMETER;int Status = IM_STATUS_OK;Status = IM_ExpBlur(Src, Dest, Width, Height, Stride, Radius);if (Status != IM_STATUS_OK) return Status;const float Adjust = Amount / 100.0f / sqrt(255.0f);const int BlockSize = 8;int Block = (Height * Stride) / BlockSize;const __m128i Zero = _mm_setzero_si128();const __m128i ThresholdV = _mm_set1_epi16(Threshold);const __m128i MinusThresholdV = _mm_set1_epi16(-Threshold);const __m128i MinusOne = _mm_set1_epi16(-1);const __m128 AdjustV = _mm_set1_ps(Adjust);const __m128i One = _mm_set1_epi16(1);for (int Y = 0; Y < Block * BlockSize; Y += BlockSize){__m128i SrcV = _mm_unpacklo_epi8(_mm_loadl_epi64((__m128i *)(Src + Y)), Zero);__m128i DstV = _mm_unpacklo_epi8(_mm_loadl_epi64((__m128i *)(Dest + Y)), Zero);__m128i Diff = _mm_sub_epi16(SrcV, DstV); // int Diff = Src[Y] - Dest[Y];// 当Diff > ThresholdV时,Sign设置为负数,当Diff < -ThresholdV时,Sign设置为正数,// 介于-ThresholdV和ThresholdV之间时为0,这里One和MinusOne只是取得一个代表性的值 __m128i SignA = _mm_cmpgt_epi16(Diff, ThresholdV); __m128i SignB = _mm_cmplt_epi16(Diff, MinusThresholdV); __m128i Sign = _mm_blendv_si128(Zero, MinusOne, SignA);Sign = _mm_blendv_si128(Sign, One, SignB);// Diff 为不同值时,NewDiff需要带上不同符号,利用上面的Sign配合_mm_sign_epi16能很好的解决问题__m128i NewDiff = _mm_add_epi16(Diff, _mm_sign_epi16(ThresholdV, Sign));// _mm_cmpgt_epi16返回0xfffff和0两种值,我们这里需要的是0xff和0,因此需要进行下移位,注意此时在Diff < Threshold(Sign为0或者1时)// _mm_add_epi16的第一个参数都是0,而第二个参数对于Sign为0的情况则也返回0,这样0+0正好为0,Sqrt后也为0,对结果正好没有影响(巧合还是天意?)__m128i NewPower = _mm_add_epi16(_mm_srli_epi16(SignA, 8), _mm_sign_epi16(SrcV, Sign));// 注意这里有负数存在,则必须用这种强制转换函数__m128 NewDiffL = _mm_cvtepi32_ps(_mm_cvtepi16_epi32(NewDiff)); __m128 NewDiffH = _mm_cvtepi32_ps(_mm_cvtepi16_epi32(_mm_srli_si128(NewDiff, 8)));// 都是正数就可以这样转化__m128 NewPowerL = _mm_cvtepi32_ps(_mm_unpacklo_epi16(NewPower, Zero)); __m128 NewPowerH = _mm_cvtepi32_ps(_mm_unpackhi_epi16(NewPower, Zero));// 按公式计算结果__m128 DstL = _mm_mul_ps(_mm_mul_ps(AdjustV, NewDiffL), _mm_sqrt_ps(NewPowerL));__m128 DstH = _mm_mul_ps(_mm_mul_ps(AdjustV, NewDiffH), _mm_sqrt_ps(NewPowerH));// 合成到16位的结果,注意这里不要用_mm_packus_epi32,因为后面还有一个加法要进行__m128i Result = _mm_packs_epi32(_mm_cvtps_epi32(DstL), _mm_cvtps_epi32(DstH)); // 合成到8位的结果,注意这要用抗饱和的加法_mm_adds_epi16_mm_storel_epi64((__m128i *)(Dest + Y), _mm_packus_epi16(_mm_adds_epi16(Result, SrcV), Zero));}for (int Y = Block * BlockSize; Y < Height * Stride; Y++){int Diff = Src[Y] - Dest[Y];if (Diff >= Threshold){Dest[Y] = IM_ClampToByte(int((Diff - Threshold) * Adjust * sqrtf(255.0f - Src[Y]) + 0.5f) + Src[Y]);}else if (Diff < -Threshold){Dest[Y] = IM_ClampToByte(int((Diff + Threshold) * Adjust * sqrtf(0.0f + Src[Y]) + 0.5f) + Src[Y]);}else{Dest[Y] = IM_ClampToByte(int(Diff * Adjust * sqrtf(0.0f + 0.0f) + 0.5f) + Src[Y]); // 不做变化 }}return IM_STATUS_OK; }
最后回到我们关心的速度问题上去,经过上述优化后能达到的速度平均值在11.5ms左右,比查找表版本的还要快了3ms左右。
实际上上述求Sign的过程还有更为简单的优化过程的,想通了也很有道理,这个留个读者自行去研究,大概能加快0.4ms左右的速度。
关于分支预测的SSE优化,目前我掌握的技巧也就这么多,管件还是要看算法本身,有的时候要脱离原始算法,为了能用SSE而稍微改变下算法的外表。这就各位神仙各显神通了,当然有很多分支预测由于太复杂还是不能够用SIMD指令优化的。
最后说一句,关于Photoshop的标准USM锐化并不是使用的上述算法,其原理应该说比上面的还要简单,但也不是网络上流行的那个计算公式,我已经通过测试推到得到了和其一模一样的计算式,这里不提,不过呢,为什么非要一样呢,这里的这个算法也是不错的。
算法Demo下载地址:https://files.cnblogs.com/files/Imageshop/SSE_Optimization_Demo.rar
转载于:https://www.cnblogs.com/Imageshop/p/8461025.html
SSE图像算法优化系列十六:经典USM锐化中的分支判断语句SSE实现的几种方法尝试。...相关推荐
- SSE图像算法优化系列十四:局部均方差及局部平方差算法的优化。
关于局部均方差有着较为广泛的应用,在我博客的基于局部均方差相关信息的图像去噪及其在实时磨皮美容算法中的应用及使用局部标准差实现图像的局部对比度增强算法中都有谈及,即可以用于去噪也可以用来增强图像,但是 ...
- SSE图像算法优化系列十九:一种局部Gamma校正对比度增强算法及其SSE优化。
这是一篇2010年比较古老的文章了,是在QQ群里一位群友提到的,无聊下载看了下,其实也没有啥高深的理论,抽空实现了下,虽然不高大上,还是花了点时间和心思优化了代码,既然这样,就顺便分享下优化的思路和经 ...
- SSE图像算法优化系列十八:三次卷积插值的进一步SSE优化。
本文是在学习https://blog.csdn.net/housisong/article/details/1452249一文的基础上对算法的理解和重新整理,再次非常感谢原文作者的深入分析以及分享. ...
- SSE图像算法优化系列二十五:二值图像的Euclidean distance map(EDM)特征图计算及其优化。...
Euclidean distance map(EDM)这个概念可能听过的人也很少,其主要是用在二值图像中,作为一个很有效的中间处理手段存在.一般的处理都是将灰度图处理成二值图或者一个二值图处理成另外一 ...
- SSE图像算法优化系列二十二:优化龚元浩博士的曲率滤波算法,达到约1000 MPixels/Sec的单次迭代速度...
2015年龚博士的曲率滤波算法刚出来的时候,在图像处理界也曾引起不小的轰动,特别是其所说的算法的简洁性,以及算法的效果.执行效率等方面较其他算法均有一定的优势,我在该算法刚出来时也曾经有关注,不过 ...
- SSE图像算法优化系列二十九:基础的拉普拉斯金字塔融合用于改善图像增强中易出现的过增强问题(一)...
拉普拉斯金字塔融合是多图融合相关算法里最简单和最容易实现的一种,我们在看网络上大部分的文章都是在拿那个苹果和橙子融合在一起,变成一个果橙的效果作为例子说明.在这方面确实融合的比较好.但是本文我们主要讲 ...
- SSE图像算法优化系列八:自然饱和度(Vibrance)算法的模拟实现及其SSE优化(附源码,可作为SSE图像入门,Vibrance算法也可用于简单的肤色调整)。...
Vibrance这个单词搜索翻译一般振动,抖动或者是响亮.活力,但是官方的词汇里还从来未出现过自然饱和度这个词,也不知道当时的Adobe中文翻译人员怎么会这样处理.但是我们看看PS对这个功能的解释: ...
- SSE图像算法优化系列一:一段BGR2Y的SIMD代码解析。
一个同事在github上淘到一个基于SIMD的RGB转Y(彩色转灰度或者转明度)的代码,我抽了点时间看了下,顺便学习了一些SIMD指令,这里把学习过程中的一些理解和认识共享给大家. github上相关 ...
- SSE图像算法优化系列二十一:基于DCT变换图像去噪算法的进一步优化(100W像素30ms)。...
在优化IPOL网站中基于DCT(离散余弦变换)的图像去噪算法(附源代码) 一文中,我们曾经优化过基于DCT变换的图像去噪算法,在那文所提供的Demo中,处理一副1000*1000左右的灰度噪音图像耗时 ...
最新文章
- stl algorithm清单
- 随手记:IDAPro蛮强大
- base target=_self是什么意思
- Windows Server 2012 R2 WSUS-6:配置计算机组和客户端目标
- 计算机数据库技术的应用现状,数据库技术发展现状及趋势.doc
- 计算机学业水平考试答题卡,高一年级期末信息技术考试(含答题卡)
- list大数据转换对象_EXCEL比分列强大百倍的数据转换功能
- 表哥首发送书100本,感谢老铁们支持!
- 盗窟手机小我私人撤军华强北:市场一年不如一年
- 修炼一名程序员的职业水准
- 堪称神器的办公工具,国产精品福昕PDF编辑器上榜
- GRE词汇会难记到什么程度
- MAC Photoshop标题栏不见了
- c语言mud游戏制作,MUD游戏制作工具下载
- golang not enough arguments in call to uuid.Must
- Java常见问题之Data too long for column 'orResponse' at row 1
- ES8218E低功耗24位ADC芯片 可直接接麦克风
- Python(5):上机练习——创建一个类People
- 如何添加其他网段的打印机
- 莫烦Python[基础教程]