【AI PC端算法优化】二,一步步优化自然饱和度算法

2023-10-29

上一节的RGB转灰度图算法我又做了两个相关优化,加入了多线程以及去掉了上次SSE计算中的一些重复计算,现在相对于传统实现已经可以获得4倍加速。同时我也在做一个AVX2的优化,所以不久后我将发布一个RGB转灰度图算法优化的升级版,尝试触摸这一个算法的优化极限,我会尽快做完实验发出来的。今天我先介绍一个有趣的自然饱和度算法,并讲解如何一步步进行优化。

1. 原始实现

今天要介绍的自然饱和度算法是一个开源图像处理软件PhotoDemon(地址:https://github.com/tannerhelland/PhotoDemon)上的,原版是C#的,代码如下:

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

然后将其翻译为C++就获得了原始实现,代码如下:

//Adjustment如果为正值,会增加饱和度
void VibranceAlgorithm_FLOAT(unsigned char *Src, unsigned char *Dest, int Width, int Height, int Stride, int Adjustment) {
	float VibranceAdjustment = -0.01 * Adjustment;
	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 = max(max(Blue, Green), Red);
			float AmtVal = (abs(Max - Avg) / 127.0f) * VibranceAdjustment;
			if (Blue != Max) Blue += (Max - Blue) * AmtVal;
			if (Green != Max) Green += (Max - Green) * AmtVal;
			if (Red != Max) Red += (Max - Red) * AmtVal;
			if (Red < 0) Red = 0;
			else if (Red > 255) Red = 255;
			if (Green < 0) Green = 0;
			else if (Green > 255) Green = 255;
			if (Blue < 0) Blue = 0;
			else if (Blue > 255) Blue = 255;
			LinePD[0] = Blue;
			LinePD[1] = Green;
			LinePD[2] = Red;
			LinePS += 3;
			LinePD += 3;
		}
	}
}

代码看起来非常简单,我们可以使用这个代码去对人像进行处理,效果如下:

原图

Adjustment=50,面色红润有精神

Adjustment=-50,面色苍白

接下来看一下这个算法原始实现的速度测试:

分辨率 优化 循环次数 速度
4032x3024 原始实现 100 115.36ms

2. 自然饱和度算法优化第一版

首先,我们可以考虑去掉算法中的浮点运算,即是将float AmtVal = (abs(Max - Avg) / 127.0f) * VibranceAdjustment;这里的127.0f优化为乘法,怎么优化为乘法呢?这里 127 127 127是接近 128 128 128的,如果我们把它换成 128 128 128,那么我们可以用位运算来代替这个除法,实测将 127 127 127换成 128 128 128基本不影响算法的效果,所以这里直接采用了这种优化技巧。另外,Adjustment默认的范围为[-100,100],如果把它的范围线性扩大一些,比如扩大 1.28 1.28 1.28倍变成 [ − 128 , 128 ] [-128,128] [128,128],这样在最后我们一次性移位,减少中间的损失。再然后,我们将这VibranceAdjustment里面的*0.01变成*0.01=1.28/128,然后把128放到下面的计算中并将VibranceAdjustment 重新设置为:int VibranceAdjustment = -1.28 * Adjustment;。最后还有一个点就是这个算法中的绝对值运算完全可以去掉,因为平均值肯定是小于最大值的。可能有点小晕哈,但是看代码很容易就理解了,下面给出优化后的代码:

void VibranceAlgorithm_INT(unsigned char *Src, unsigned char *Dest, int Width, int Height, int Stride, int Adjustment) {
	int VibranceAdjustment = -1.28 * Adjustment;
	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) * VibranceAdjustment;
			if (Blue != Max) Blue += (((Max - Blue) * AmtVal) >> 14);
			if (Green != Max) Green += (((Max - Green) * AmtVal) >> 14);
			if (Red != Max) Red += (((Max - Red) * AmtVal) >> 14);
			if (Red < 0) Red = 0;
			else if (Red > 255) Red = 255;
			if (Green < 0) Green = 0;
			else if (Green > 255) Green = 255;
			if (Blue < 0) Blue = 0;
			else if (Blue > 255) Blue = 255;
			LinePD[0] = Blue;
			LinePD[1] = Green;
			LinePD[2] = Red;
			LinePS += 3;
			LinePD += 3;
		}
	}
}

下面看一下速度测试:

分辨率 优化 循环次数 速度
4032x3024 原始实现 100 115.36ms
4032x3024 第一版优化 100 62.43ms

可以看到稍加优化,速度快了近2倍,还是比较可观的。

3. 自然饱和度算法优化第二版

在上面算法的基础上如果使用多线程(OpenMP)来优化的话那么会获得多少加速呢?我们来试试,源码如下:

void VibranceAlgorithm_INT_OpenMP(unsigned char *Src, unsigned char *Dest, int Width, int Height, int Stride, int Adjustment) {
	int VibranceAdjustment = -1.28 * Adjustment;
	for (int Y = 0; Y < Height; Y++) {
		unsigned char *LinePS = Src + Y * Stride;
		unsigned char *LinePD = Dest + Y * Stride;
#pragma omp parallel for num_threads(4)
		for (int X = 0; X < Width; X++) {
			int Blue, Green, Red, Max;
			Blue = LinePS[X*3 + 0], Green = LinePS[X*3 + 1], Red = LinePS[X*3 + 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) * VibranceAdjustment;
			if (Blue != Max) Blue += (((Max - Blue) * AmtVal) >> 14);
			if (Green != Max) Green += (((Max - Green) * AmtVal) >> 14);
			if (Red != Max) Red += (((Max - Red) * AmtVal) >> 14);
			if (Red < 0) Red = 0;
			else if (Red > 255) Red = 255;
			if (Green < 0) Green = 0;
			else if (Green > 255) Green = 255;
			if (Blue < 0) Blue = 0;
			else if (Blue > 255) Blue = 255;
			LinePD[X*3 + 0] = Blue;
			LinePD[X*3 + 1] = Green;
			LinePD[X*3 + 2] = Red;
		}
	}
}

我们来看一下加速效果:

分辨率 优化 循环次数 速度
4032x3024 原始实现 100 115.36ms
4032x3024 第一版优化 100 62.43ms
4032x3024 第二版优化(4线程) 100 28.89ms

可以看到使用OpenMP开启4线程,可以将我们的算法又优化接近2倍,仍然是可观的。接下来我们开始今天的主角,使用SSE指令集对这段代码进行优化。

4. 自然饱和度算法优化第三版

注意,在这个例子中,我们一次性加载48个图像数据到内存中,刚好可以放在3个__m128i变量中,同时看了我第一篇优化的人应该知道48正好被3整除,也就是说我们完整的加载了16个24位像素,这不会出现上一篇文章中的断层现象,使得下面48个像素可以和现在的48个像素使用同样的方法进行处理。上篇文章传送门:【AI PC端算法优化】一,一步步优化RGB转灰度图算法

首先,对于这样单像素点且邻域无关的算法,为了利用SSE提高运行速度,一个核心步骤就是把各个颜色分量分离为单独的连续的变量。然后在计算完之后,我们又需要把单独连续的变量重新分解成BGR(注意OpenCV默认读图方式是BGR)的形式,这两部分的代码实现如下:

  • BGRBGR->BBGGRR
Src1 = _mm_loadu_si128((__m128i *)(LinePS + 0)); //B1,G1,R1,B2,G2,R2,B3,G3,R3,B4,G4,R4,B5,G5,R5,B6
Src2 = _mm_loadu_si128((__m128i *)(LinePS + 16));//G6,R6,B7,G7,R7,B8,G8,R8,B9,G9,R9,B10,G10,R10,B11,G11
Src3 = _mm_loadu_si128((__m128i *)(LinePS + 32));//R11,B12,G12,R12,B13,G13,R13,B14,G14,R14,B15,G15,R15,B16,G16,R16

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)));
  • BBGGRR->BGRBGR
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)));

这两个过程就是巧妙的利用__mm_shuffle_epi8指令完成的,而那个_mm_or_si128指令就是实现了将这次操作时所有的B或者G或者R放在了一个SSE向量中,对照后面的注释就很好理解了,也可以自己手推一下这个过程。如果想看详细步骤可以参考ImageShop大佬的博客,链接如下:https://www.cnblogs.com/Imageshop/p/7234463.html

接下来我们看看其它代码的实现,由于uchar数据类型的表示范围非常有限,除了少数几个操作能针对字节类型直接处理外(比如这段代码中的求RGB的Max值,就可以直接用下面的SIMD指令实现)

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

其他的一些操作无法在这样的范围(uchar)内进行了,所以我们需要将数据扩展到short或者int/float范围内,在SSE中完成这种操作是有直接命令的,例如byte扩展到short,则可以用_mm_unpacklo_epi8_mm_unpackhi_epi8配合Zero来实现:

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

其中_mm_unpacklo_epi8是将两个__m128i的低8位交错布置形成一个新的128位数据,如果其中一个参数为0,则就是把另外一个参数的低8个字节无损的扩展为16位了,以上述BL16为例,其内部布局为:

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

接下来我们需要来实现int Avg = (Blue + Green + Green + Red) >> 2;这行代码了,可以看到里的计算是无法在uchar范围内完成的,中间的Blue + Green + Green + Red在大部分情况下都会超出255并且绝对小于 255 × 4 255\times 4 255×4,因此我们需要扩展数据到16位(short),按照上面介绍的指令集对Blue8\Green8\Red8\Max8进行扩展,代码如下所示:

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);

上面的分析都非常常规,接下来要到本文的硬核部分了,首先SSE对于跳转处理是很不好做的,它擅长的是序列化的处理一件事情,虽然SSE提供了比较指令,但很多复杂的跳转情况,SSE仍然无能为力。而我们这段代码中跳转情况不算复杂,我们可以使用SSE中的比较指令得到个MaskMask中符合比较结果的值会为F,不符合的为0,然后把这个Mask和后面需要计算的某个值进行And操作,由于和F进行And操作不会改变操作数本身,和0进行And操作则变为0。因此,这种操作方式是无论你符合条件与否,都要进行后面的计算,只是不符合条件的计算并不会影响结果,这种多余的计算可能会低效SSE优化的部分提速效果,这个就要具体情况具体分析了。

然后参考ImageShop博主的思路,仔细观察我们的代码可以发现,这里跳转语句的本意是如果最大值和某个分量的值不相同,则进行后面的调整操作,否则就不进行调整。而后面的调整操作中有最大值减去这个分量的逻辑,也就是说如果满足条件两者相减则为 0 0 0,调整量此时也为 0 0 0,并不会对结果产生影响。基于此,我们可以直接把这个条件判断去掉,这并不会影响结果。 同时我们能节省一些SSE指令,并且也更加适合SSE的处理。

继续分析,由于代码中有((Max - Blue) * AmtVal) >> 14这行逻辑,其中AmtVal = (Max - Avg) * Adjustment,展开即为:

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

这三个数据相乘很大程度上会超出short所能表达的范围,因此,我们还需要对上面的 16 16 16位数据进行扩展,扩展到 32 32 32位,这样就多了很多指令,那么有没有不需要扩展的方式呢?ImageShop博主提出了一种方式,这里搬运一下:

在SSE指令集中有一个指令叫做_mm_mulhi_epi16,我们看一下这个指令能干什么?

_mm_mulhi_epi16 指令

这个指令剋一次性处理8个16位的数据,其计算结果相当于对于 ( a ∗ b ) > > 16 (a*b)>>16 (ab>>16,但 a a a b b b必须是short类型所能表达的范围。而我们要求解的等式是:
((Max - Blue) * (Max - Avg) * Adjustment)>>14
因此,我们这里首先将其扩展为移位16位的结果,变成:
((Max - Blue) * 4 * (Max - Avg) * Adjustment)>>16
然后,已知的是Adjustment我们已经将他限定在了[-128,128]之间,而(Max - Avg)理论上的最大值为255 - 85=170,(即RGB分量有一个是255,其他的都为0),最小值为0,因此,两者在各自范围内的成绩不会超出short所能表达的范围,而(Max-Blue)的最大值为255,最小值为0,再乘以4也在short类型所能表达的范围内。所以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));

最后一步就是将获得的B8,G8,R8别转换为不连续存储的形式即是BGR的格式,然后再存储即可。

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)));

_mm_storeu_si128((__m128i *)(LinePD + 0), Dest1);
_mm_storeu_si128((__m128i *)(LinePD + 16), Dest2);
_mm_storeu_si128((__m128i *)(LinePD + 32), Dest3);

完整代码实现请看:https://github.com/BBuf/Image-processing-algorithm-Speed/blob/master/speed_rgb2gray_sse.cpp

我们来看一下速度测试:

分辨率 优化 循环次数 速度
4032x3024 原始实现 100 115.36ms
4032x3024 第一版优化 100 62.43ms
4032x3024 第二版优化(4线程) 100 28.89ms
4032x3024 第三版优化(SSE) 100 12.69ms

5. 结论

这篇文章介绍了如何一步步优化一个自然饱和度算法,从原始算法的115.36ms优化到了13.04ms,加速比达到了9.09倍,还是比较可观的。

6. 参考

  • https://www.cnblogs.com/Imageshop/p/7234463.html
  • https://software.intel.com/sites/landingpage/IntrinsicsGuide/#expand=4155,3983,3956&text=_mm_mulhi_epi16

欢迎关注GiantPandaCV, 在这里你将看到独家的深度学习分享,坚持原创,每天分享我们学习到的新鲜知识。( • ̀ω•́ )✧

有对文章相关的问题,或者想要加入交流群,欢迎添加BBuf微信:

二维码

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【AI PC端算法优化】二,一步步优化自然饱和度算法 的相关文章

  • QLineEdit的信号与槽用法

    QLineEdit一共有6个信号函数 并不多 很好理解 1 void cursorPositionChanged intold intnew 当鼠标移动时发出此信号 old为先前的位置 new为新位置 在文本框中输入一个字符 光标移动一格
  • 封神台靶场-第四章

    第四章 为了更多的权限 Tips 1 存储型Xss 2 flag在cookie里 格式为zkz xss bot 每10秒访问一次页面 3 自建Xss平台源码 http www zkaq org t 99 html 登陆后根据提示要进行存储型
  • 程序性能

    文章目录 1 程序性能 1 1 空间效率 1 2 时间效率 1 3 f n 程序性能表示 程序性能包括两个部分 空间效率和时间效率 1 程序性能 1 1 空间效率 一个运行的程序所占用的内存 1 栈 函数调用时操作系统自动分配的内存 函数调
  • commitlint配置

    聊一下如何配置commit lint 其实这个问题一直困扰我很久 看了好多文章 都无济于事今天狠下心来终于解决 废话不多说 先上代码 最后阐述原因 因为有的小伙伴就是想找到解决问题办法 至于原因不太关心 注意 npm 版本大于 7 安装 h
  • 网站服务器被攻击的形式,服务器被攻击的不同表现类型以及应对策略

    服务器被攻击的不同表现类型以及应对策略 许多网站站长都遇到过网络服务器被攻击的状况 被攻击之后也大多数都只有束手待毙 由于大伙儿广泛对攻击不足掌握 很多人把多很多的攻击和防御力挂在嘴上 却压根不清楚网络服务器是怎么被攻击的 更不要说怎样应对
  • MyBatis-Plus-自定义模板-生成代码

    MyBatis Plus代码生成器所需jar包
  • windows无法访问FTP服务器的各种报错解决

    本文转载自windows无法访问FTP服务器的各种报错解决 对于windows无法访问FTP服务 原因有很多 但首先要保证 你的服务配置无误 用户有访问资源的权限 防火墙拦截 服务器防火墙通行了 忽略了客户端的防火墙可不行 对于不信任的连接
  • special effects - 樱花飘落背景特效

    樱花飘落背景特效 一 效果图 二 实现代码 一 效果图 二 实现代码
  • VPS是虚拟专用服务器,部署VPS包括以下步骤

    VPS是虚拟专用服务器 部署VPS包括以下步骤 购买VPS主机 选择可靠的云服务提供商 如亚马逊AWS 谷歌云 腾讯云或阿里云 并注册账户和创建VPS实例 为了确保您的应用程序在运行过程中具有快速的性能和可靠的服务级别协议 请评估不同主机计
  • Intel TBB简介及在Windows7 VS2013上源码的编译过程

    Intel TBB Intel Threading Building Blocks 是Intel线程构建块开源库 它的License是Apache 2 0 Intel TBB是一种用于并行编程的基于C 语言的框架 它是一套C 模板库 它提供
  • 【http协议】超详细介绍

    文章目录 一 协议 1 1 Method 1 1 1 HEAD 1 2 Status 1 3 Http Head Http 头 1 3 1 Http Request Body 1 3 1 1 Content Type 1 3 1 2 miu
  • 浅谈Web用户体验(二)

    在所有网页或浏览器的GUI中我最喜欢的是google的用户体验效果 平时我们只把它作为搜索工具 从没仔细欣赏过它的设计细节 有篇博客对google的用户体验总结的非常好 google的设计一向以简单著称 让页面清爽优雅地呈现 一方面减少页面
  • Notepad++找回缓存的内容

    场景 在使用Notepad 的时候 有一个非常方便的自动保存功能 也就是你在新建一个文档后 输入你的内容 在你没有手动保存前 Notepad 会帮你保存当前内容 当你没有保存就退出Notepad 下次进来Notepad 会自动把你上次编辑的
  • 图像处理之三角法图像二值化

    图像处理之三角法图像二值化 三角法求阈值最早见于Zack的论文 Automatic measurement of sister chromatid exchange frequency 主要是用于染色体的研究 该方法是使用直方图数据 基于纯
  • mybatis常见的报错点总结

    1 invalid comparison java util Date and java lang String 错误代码 正确代码 错误原因 将日期格式和字符串进行了比较 2 NumberFormatException 错误代码 正确代码
  • IDEA项目编写到一半 突然Install报错Cannot resolve org.apache.maven.plugins:maven-compiler-plugin:X.X.X

    描述 是一次很蛇皮的踩坑经历 原因很简单 但是太容易中招了 简单记录一下 项目没有做热部署 添加完一个CRUD的接口后 重启项目测试 突然就报错了 mvn clear install也没用 Cannot resolve org apache
  • 笔试真题解析

    恭喜发现宝藏 搜索公众号 TechGuide 回复公司名 解锁更多新鲜好文和互联网大厂的笔经面经 作者 TechGuide 全网同名 订阅专栏 进阶版 2023最新大厂笔试真题 题解 不容错过的宝藏资源 第一题 服务器能耗统计 100分 题
  • Fine BI、Smart BI、永洪BI、瓴羊Quick BI这些国产BI工具,都擅长哪些功能?

    近几年 随着国内大数据的火热 商业智能BI工具市场也迎来了大爆发 市面上涌现出了各种各样的BI工具 让很多企业在选择BI工具的时候遇上了 选择困难症 在最初 大多数企业都会选择Tableau 微软Power BI等外国公司的BI工具 然而
  • ARouter(七) 依赖注入

    当你从FirstActivity跳到SecondActivity 有时需要把参数值放在intent里面传过去 ARouter的依赖注入会帮你在SecondActivity里面的属性直接赋好参数值 当然需要你事先在SecondActivity

随机推荐

  • 西瓜书-3.9,3.10理解过程-线性模型

    许多人对3 9公式不理解 其实 min右侧只是用矩阵的形式表示了均方差的概念 大家可以结合矩阵的乘法运算举个例子试试 argmin表示右侧式子最小时w取得的值 我们的目标就是求出这个向量w 需要注意的是 实际问题中 我们的样例数可能很少 而
  • GPU基础与CUDA编程入门

    文章目录 一 GPU和CPU的区别 GPU 高吞吐量导向设计 CPU 低延迟导向设计 GPU适合什么场景 什么是Prefetch 二 CUDA与OpenCL 三 CUDA编程并行计算整体流程 内存模型 线程块 网格Grid 并行线程块组合
  • 【Python解题1】PAT 乙级1055 集体照(详解)测试点345错误原因分析

    题目传送门 语言 python3 9 编译工具 pycharm 社区版 输入样例 10 3 Tom 188 Mike 170 Eva 168 Tim 160 Joe 190 Ann 168 Bob 175 Nick 186 Amy 160
  • 《C++ System Programming Cookbook》第二章读书笔记

    第二章 C 重要知识点回顾 C primitive types 原生数据类型 char int float double wchar t bool void nullptr t 可用numeraic limits检查范围 sizeof检查大
  • 【翻译】如何在你的IT转型中浪费数以亿计的资金

    你担任范德雷工业公司的首席执行官已经有几年时间了 这是一家在运输领域存在了几十年的巨无霸 真正的战略 你真的希望股价能尽快上涨 这样你就可以在命运之轮转动之前 在最佳的时间点卖掉你的股票 而董事会也不可避免地要把你赶走 你已经厌倦了蒲公英
  • 甘特图排程组件开发(TsyGantt)

    甘特图在Project2000中 很是好用 可是我想在项目开发中还要买了MS的Project2000的版权 所以索性自己开发一套相关的组件
  • 【JavaWeb_Part05】JDBC?弱爆了,看看轻量级的 Mybatis FreeStyle

    开篇 上篇文章我们已经讲了 Mybatis 的入门以及简单的对单表进行增删改查 那么今天我们就来讲一下使用 mybatis 开发dao的两种方式以及 mysql 比较厉害的动态 sql 利用 mybatis 开发 DAO 1 原始的方式开发
  • 多线程之间共享数据的方式探讨

    多线程之间共享数据的方式探讨 方式一 代码一致 如果每个线程执行的代码相同 可以用一个 Runnable 对象 这个 Runnable 对象中存放那个共享数据 卖票系统可以这样做 public class MultiThreadShareD
  • java系统运维:使用java自带的工具监控系统(java)虚机的运行

    公司采用的B S架构开发的系统林林总总 有时候系统不稳定 分析起来很头疼 除了看日志 还很希望能知道系统虚拟机的运行情况 java 虚拟机从1 5开始 有类似的工具 jconsole 1 6开始不但有jconsole 还有升级版 jvisu
  • 图神经网络(一)DGL框架搭建GCN图卷积神经网络模型

    一 DGL DGL是基于pytorch开发的一个专门用于图神经网络模型搭建的框架 到现在为止 DGL已经高度封装了如GCN GraphSage GAT等常见的图神经网络模型 可以直接调用 比较方便 当然针对非常想挑战自己的编程能力又或者非常
  • 电梯系统OO设计

    理论上应该先黑盒用例 分析需要求 系统边界的输入输出 再白盒类图 但是对于现实世界模拟的OO 个人感觉先emulate现实世界 初步识别类和类之间的关系 再用用例和顺序图丰富 修正类图 识别类 最主要的原则是封装 数据和数据的操作封装成一个
  • 【Linux】ibus输入法安装教程

    My name is Linus and I am your God Linus Torvalds 安装其他输入法总是会遇到各种问题 而当新的发行版出来时 原来的输入法总是就失效了或是其他问题不能使用 经过我的多次实测 发现ibus输入法非
  • uniapp小程序实现上传图片功能,并显示上传进度

    效果图 实现方法 一 通过uni chooseMedia OBJECT 方法 拍摄或从手机相册中选择图片或视频 官方文档链接 https uniapp dcloud net cn api media video html choosemed
  • 正则表达式获取两个字符之间的字符串信息

    今天工作有一个需求就是捕获两个字符之间的字符串信息 类似就是AcakeB这个字符串中提取cake 当时只想到如下的表达式 A B 复制代码 上述是将以A开头和以B结尾的字符串提取出来 但是本意是不想要有A和B了 通过查资料发现了如下写法 l
  • JSP四大作用域

    一 ServletContext 1 生命周期 当Web应用被加载进容器时创建代表整个web应用的ServletContext对象 当服务器关闭或Web应用被移除时 ServletContext对象跟着销毁 2 作用范围 整个Web应用 3
  • sql两张表统计求比分比。

    数据 表一 compay 表二person 第一步 第一张表统计结果 select sum money from person 第二张表统计结果 select sum money from compay 相除结果 select select
  • 读取word中表格的数据

    在pro文件中加入 QT axcontainer 按步骤 1 创建Word应用程序对象 2 获取文档集 3 打开文档 4 获取活动表格 5 读取表格中的信息 QAxObject myword new QAxObject Word Appli
  • 区块链-技术简介(*)

    1 什么是区块链 区块链技术是利用块链式数据结构来验证与存储数据 利用分布式节点共识算法来生成和更新数据 利用密码学的方式保证数据传输和访问的安全 利用由自动化脚本代码组成的智能合约来编程和操作数据的一种全新的分布式基础架构与计算范式 简单
  • PTA(Basic Level) 1029_旧键盘 Python实现

    1029 旧键盘 Python实现 下面是题目信息 旧键盘上坏了几个键 于是在敲一段文字的时候 对应的字符就不会出现 现在给出应该输入的一段文字 以及实际被输入的文字 请你列出肯定坏掉的那些键 输入格式 输入在 2 行中分别给出应该输入的文
  • 【AI PC端算法优化】二,一步步优化自然饱和度算法

    上一节的RGB转灰度图算法我又做了两个相关优化 加入了多线程以及去掉了上次SSE计算中的一些重复计算 现在相对于传统实现已经可以获得4倍加速 同时我也在做一个AVX2的优化 所以不久后我将发布一个RGB转灰度图算法优化的升级版 尝试触摸这一