You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用SSE将__m128i寄存器乘浮点因子实现图像灰度化?

SSE实现RGBA转灰度的修改方案

嘿,第一次上手SSE就能搞定通道拆分已经很厉害了!咱们直接把现有代码改成灰度化处理,核心是实现fY(R, G, B)=R×0.29891 + G×0.58661 + B×0.11448这个加权公式,同时保留Alpha通道(如果需要的话)。下面一步步来调整你的代码:

核心思路梳理

你的现有代码已经完成了通道拆分,但现在需要把「通道直接相加」换成「加权乘法→累加→饱和转8位」,还要注意定点数运算的精度问题(这是你当前代码的一个关键小问题)。

修改后的完整代码

__m128i vecSrc = _mm_loadu_si128((__m128i*) &src[srcIndex]);

// 1. 定义定点数权重:把浮点系数转成16.16定点格式(乘以0x10000=65536)
const __m128i factorR = _mm_set1_epi16((short)(0.29891 * 65536.0f));
const __m128i factorG = _mm_set1_epi16((short)(0.58661 * 65536.0f));
const __m128i factorB = _mm_set1_epi16((short)(0.11448 * 65536.0f));
__m128i zero = _mm_setzero_si128();

// 2. 把8位通道扩展成16位整数(方便定点乘法)
// vectSrcLow: 前8个字节(4个RGBA像素的R/G/B/A)转成8个epi16
__m128i vectSrcLow = _mm_cvtepu8_epi16(vecSrc);
// vectSrcHigh: 后8个字节(另外4个RGBA像素的R/G/B/A)转成8个epi16
__m128i vectSrcHigh = _mm_unpackhi_epi8(vecSrc, zero);

// 3. 提取每个通道的16位值(优化原来的mask乘法方式,用shuffle更高效)
// 掩码说明:每个epi16位置对应要提取的通道,比如R通道是每个RGBA的第0个字节
__m128i shuffleR = _mm_setr_epi16(0, 4, 8, 12, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF); // 前4个像素的R
__m128i shuffleG = _mm_setr_epi16(1, 5, 9, 13, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF); // 前4个像素的G
__m128i shuffleB = _mm_setr_epi16(2, 6, 10, 14, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF); // 前4个像素的B
__m128i shuffleA = _mm_setr_epi16(3, 7, 11, 15, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF); // 前4个像素的A

// 提取前4个像素的通道
__m128i vecR_L = _mm_shuffle_epi8(vectSrcLow, shuffleR);
__m128i vecG_L = _mm_shuffle_epi8(vectSrcLow, shuffleG);
__m128i vecB_L = _mm_shuffle_epi8(vectSrcLow, shuffleB);
__m128i vecA_L = _mm_shuffle_epi8(vectSrcLow, shuffleA);

// 提取后4个像素的通道(掩码对应后4个RGBA的位置)
__m128i shuffleR_H = _mm_setr_epi16(0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0, 4, 8, 12);
__m128i shuffleG_H = _mm_setr_epi16(0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 1, 5, 9, 13);
__m128i shuffleB_H = _mm_setr_epi16(0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 2, 6, 10, 14);
__m128i shuffleA_H = _mm_setr_epi16(0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 3, 7, 11, 15);

__m128i vecR_H = _mm_shuffle_epi8(vectSrcHigh, shuffleR_H);
__m128i vecG_H = _mm_shuffle_epi8(vectSrcHigh, shuffleG_H);
__m128i vecB_H = _mm_shuffle_epi8(vectSrcHigh, shuffleB_H);
__m128i vecA_H = _mm_shuffle_epi8(vectSrcHigh, shuffleA_H);

// 4. 加权计算:用_mm_mulhi_epi16取定点乘法的高16位(这是关键!原来的_mullo_epi16会丢精度)
__m128i grayR_L = _mm_mulhi_epi16(vecR_L, factorR);
__m128i grayG_L = _mm_mulhi_epi16(vecG_L, factorG);
__m128i grayB_L = _mm_mulhi_epi16(vecB_L, factorB);

__m128i grayR_H = _mm_mulhi_epi16(vecR_H, factorR);
__m128i grayG_H = _mm_mulhi_epi16(vecG_H, factorG);
__m128i grayB_H = _mm_mulhi_epi16(vecB_H, factorB);

// 5. 累加三个通道的加权值,得到灰度值(16位)
__m128i gray_L = _mm_add_epi16(_mm_add_epi16(grayR_L, grayG_L), grayB_L);
__m128i gray_H = _mm_add_epi16(_mm_add_epi16(grayR_H, grayG_H), grayB_H);

// 6. 把16位灰度值饱和转成8位,同时保留Alpha通道
// 先把灰度值和Alpha值合并:每个像素的R=G=B=灰度,A=原Alpha
__m128i grayPacked_L = _mm_packus_epi16(gray_L, gray_L); // 把16位灰度转成8位,重复两次(给R和G)
grayPacked_L = _mm_packus_epi16(grayPacked_L, gray_L); // 再重复一次给B,然后和Alpha合并
// 合并Alpha:把原Alpha放到每个像素的第3个字节
__m128i alphaPacked_L = _mm_packus_epi16(vecA_L, vecA_L);
alphaPacked_L = _mm_packus_epi16(alphaPacked_L, vecA_L);
// 用shuffle把灰度值放到R/G/B位置,Alpha放到A位置
__m128i maskRGBA_L = _mm_setr_epi8(0,0,0,1, 4,4,4,5, 8,8,8,9, 12,12,12,13);
__m128i rgba_L = _mm_shuffle_epi8(_mm_or_si128(grayPacked_L, alphaPacked_L), maskRGBA_L);

// 对后4个像素做同样的处理
__m128i grayPacked_H = _mm_packus_epi16(gray_H, gray_H);
grayPacked_H = _mm_packus_epi16(grayPacked_H, gray_H);
__m128i alphaPacked_H = _mm_packus_epi16(vecA_H, vecA_H);
alphaPacked_H = _mm_packus_epi16(alphaPacked_H, vecA_H);
__m128i rgba_H = _mm_shuffle_epi8(_mm_or_si128(grayPacked_H, alphaPacked_H), maskRGBA_L);

// 7. 把前后8个字节的结果合并成128位寄存器
__m128i resultVect = _mm_or_si128(_mm_unpacklo_epi8(rgba_L, rgba_H), _mm_unpackhi_epi8(rgba_L, rgba_H));

// 8. 存回原数组
_mm_storeu_si128((__m128i*)&src[srcIndex], resultVect);

关键修改点说明

  • 定点乘法的正确姿势:原来的_mm_mullo_epi16取的是乘法结果的低16位,而我们用的是16.16定点数,相乘后需要取高16位(相当于除以65536),所以必须用_mm_mulhi_epi16,否则结果会完全错误。
  • 通道提取优化:用_mm_shuffle_epi8直接提取通道比原来的掩码乘法更高效,减少了不必要的运算。
  • 饱和转换:用_mm_packus_epi16把16位的累加结果转成8位时,会自动处理超过255的情况(饱和到255),避免溢出。
  • 保留Alpha通道:如果不需要Alpha,可以直接把灰度值填充到所有三个颜色通道,忽略Alpha即可。

额外小提示

如果你的CPU支持SSE4.1,可以用_mm_shuffle_epi32或者_mm_blend_epi16进一步优化通道提取步骤,不过上面的代码兼容SSE2,适配性更广。

内容的提问来源于stack exchange,提问作者CoffeRight

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:56:02