如何使用缩放有效地将 16 位无符号短转换为 8 位无符号字符?

Posted

技术标签:

【中文标题】如何使用缩放有效地将 16 位无符号短转换为 8 位无符号字符?【英文标题】:How to convert 16-bit unsigned short to 8-bit unsigned char using scaling efficiently? 【发布时间】:2017-01-20 06:55:23 【问题描述】:

我正在尝试使用一些缩放功能将 16 位 unsigned short 数据转换为 8 位 unsigned char。目前我正在通过转换为浮点数和按比例缩小然后饱和到 8 位来做到这一点。有没有更有效的方法来做到这一点?

int _tmain(int argc, _TCHAR* argv[])

    float Scale=255.0/65535.0;

    USHORT sArr[8]=512,1024,2048,4096,8192,16384,32768,65535;
    BYTE bArr[8],bArrSSE[8];        

    //Desired Conventional Method
    for (int i = 0; i < 8; i++)
    
        bArr[i]=(BYTE)(sArr[i]*Scale);                  
    

    __m128  vf_scale = _mm_set1_ps(Scale),
            vf_Round = _mm_set1_ps(0.5),                      
            vf_zero = _mm_setzero_ps();         
    __m128i vi_zero = _mm_setzero_si128();

    __m128i vi_src = _mm_loadu_si128(reinterpret_cast<const __m128i*>(&sArr[0]));

    __m128 vf_Src_Lo=_mm_cvtepi32_ps(_mm_unpacklo_epi16(vi_src, _mm_set1_epi16(0)));    
    __m128 vf_Src_Hi=_mm_cvtepi32_ps(_mm_unpackhi_epi16(vi_src, _mm_set1_epi16(0)));    

    __m128 vf_Mul_Lo=_mm_sub_ps(_mm_mul_ps(vf_Src_Lo,vf_scale),vf_Round);   
    __m128 vf_Mul_Hi=_mm_sub_ps(_mm_mul_ps(vf_Src_Hi,vf_scale),vf_Round);   

    __m128i v_dst_i = _mm_packus_epi16(_mm_packs_epi32(_mm_cvtps_epi32(vf_Mul_Lo), _mm_cvtps_epi32(vf_Mul_Hi)), vi_zero);
    _mm_storel_epi64((__m128i *)(&bArrSSE[0]), v_dst_i);

    for (int i = 0; i < 8; i++)
           
        printf("ushort[%d]= %d     * %f = %.3f ,\tuChar[%d]= %d,\t SSE uChar[%d]= %d \n",i,sArr[i],Scale,(float)(sArr[i]*Scale),i,bArr[i],i,bArrSSE[i]);
    

    return 0;

请注意,缩放因子可能需要设置为其他值,例如255.0/512.0255.0/1024.0255.0/2048.0,因此任何解决方案都不应针对 255.0/65535.0 进行硬编码。

【问题讨论】:

忽略调试视图中的签名,它不会影响任何东西。这些值是正确的,只需重新解释您脑海中的位(或打印它们)。 我看到你删除了你的答案——你真的不需要这样做——尽管它的工作效率有点低,它可以作为改进其他答案的一个很好的起点。请考虑取消删除它。 (我会自己写一个答案,可能使用_mm_mulhi_epu16,但我今天很忙——也许我会在周末整理一些东西。) 【参考方案1】:

如果您的代码中的比例是固定的,您可以使用以下算法执行缩放

    将每个字的高字节移入低字节。 例如。 0x200 -> 0x2, 0xff80 -> 0xff 如果低字节小于 0x80,则添加偏移量 -1。 例如。 0x200 -> 偏移量 -1, 0xff80 -> 偏移量 0

第一部分很容易用_mm_srli_epi16实现

第二个比较棘手,但它基本上包括获取每个字的第 7 位(低字节的高位),在整个字中复制它,然后取反。

我使用了另一种方法:通过比较向量与自身的相等性,创建了一个值为 -1 的单词向量。 然后我把每个源词的bit7分离出来,加到-1个词上。

#include <stdio.h>
#include <emmintrin.h>

int main(int argc, char* argv[])

    float Scale=255.0/65535.0;

    unsigned short sArr[8]=512,1024,2048,4096,8192,16384,32768,65535;
    unsigned char bArr[8], bArrSSE[16];        

    //Desired Conventional Method
    for (int i = 0; i < 8; i++)
    
        bArr[i]=(unsigned char)(sArr[i]*Scale);                  
    



    //Values to be converted
    __m128i vi_src = _mm_loadu_si128((__m128i const*)sArr);

    //This computes 8 words (16-bit) that are
    // -1 if the low byte of relative word in vi_src is less than 0x80
    // 0  if the low byte of relative word in vi_src is >= than 0x80

    __m128i vi_off = _mm_cmpeq_epi8(vi_src, vi_src);   //Set all words to -1
    //Add the bit15 of each word in vi_src to each -1 word
    vi_off 
    = _mm_add_epi16(vi_off, _mm_srli_epi16(_mm_slli_epi16(vi_src, 8), 15));

    //Shift vi_src word right by 8 (move hight byte into low byte)
    vi_src = _mm_srli_epi16 (vi_src, 8);  
    //Add the offsets
    vi_src = _mm_add_epi16(vi_src, vi_off); 
    //Pack the words into bytes
    vi_src = _mm_packus_epi16(vi_src, vi_src);

    _mm_storeu_si128((__m128i *)bArrSSE, vi_src);

    for (int i = 0; i < 8; i++)
           
        printf("%02x %02x\n",   bArr[i],bArrSSE[i]);
    

    return 0;

【讨论】:

【参考方案2】:

这是一个使用_mm_mulhi_epu16 执行定点缩放操作的实现和测试工具。

scale_ref 是您的原始标量代码,scale_1 是您(当前已删除)答案中的浮点 SSE 实现,scale_2 是我的定点实现。

我已将各种实现分解为单独的函数,还添加了一个大小参数和一个循环,以便它们可以用于任何大小的数组(尽管目前 n 对于 SSE 实现必须是 8 的倍数)。

有一个编译时标志ROUND,它控制定点实现是截断(如您的标量代码)还是舍入(到最近)。截断速度稍快。

另请注意,scale 是一个运行时参数,目前在下面的测试工具中硬编码为 255(相当于 255.0/65535.0),但它可以是任何合理的值。

#include <stdio.h>
#include <stdint.h>
#include <limits.h>
#include <xmmintrin.h>

#define ROUND 1     // use rounding rather than truncation

typedef uint16_t USHORT;
typedef uint8_t BYTE;

static void scale_ref(const USHORT *src, BYTE *dest, const USHORT scale, const size_t n)

    const float kScale = (float)scale / (float)USHRT_MAX;

    for (size_t i = 0; i < n; i++)
    
        dest[i] = src[i] * kScale;
    


static void scale_1(const USHORT *src, BYTE *dest, const USHORT scale, const size_t n)

    const float kScale = (float)scale / (float)USHRT_MAX;

    __m128 vf_Scale = _mm_set1_ps(kScale);
    __m128 vf_Round = _mm_set1_ps(0.5f);

    __m128i vi_zero = _mm_setzero_si128();

    for (size_t i = 0; i < n; i += 8)
    
        __m128i vi_src = _mm_loadu_si128((__m128i *)&src[i]);

        __m128 vf_Src_Lo = _mm_cvtepi32_ps(_mm_unpacklo_epi16(vi_src, _mm_set1_epi16(0)));
        __m128 vf_Src_Hi = _mm_cvtepi32_ps(_mm_unpackhi_epi16(vi_src, _mm_set1_epi16(0)));
        __m128 vf_Mul_Lo = _mm_mul_ps(vf_Src_Lo, vf_Scale);
        __m128 vf_Mul_Hi = _mm_mul_ps(vf_Src_Hi, vf_Scale);

        //Convert -ive to +ive Value
        vf_Mul_Lo = _mm_max_ps(_mm_sub_ps(vf_Round, vf_Mul_Lo), vf_Mul_Lo);
        vf_Mul_Hi = _mm_max_ps(_mm_sub_ps(vf_Round, vf_Mul_Hi), vf_Mul_Hi);

        __m128i v_dst_i = _mm_packus_epi16(_mm_packs_epi32(_mm_cvtps_epi32(vf_Mul_Lo), _mm_cvtps_epi32(vf_Mul_Hi)), vi_zero);
        _mm_storel_epi64((__m128i *)&dest[i], v_dst_i);
    


static void scale_2(const USHORT *src, BYTE *dest, const USHORT scale, const size_t n)

    const __m128i vk_scale = _mm_set1_epi16(scale);
#if ROUND
    const __m128i vk_round = _mm_set1_epi16(scale / 2);
#endif

    for (size_t i = 0; i < n; i += 8)
    
        __m128i v = _mm_loadu_si128((__m128i *)&src[i]);
#if ROUND
        v = _mm_adds_epu16(v, vk_round);
#endif
        v = _mm_mulhi_epu16(v, vk_scale);
        v = _mm_packus_epi16(v, v);
        _mm_storel_epi64((__m128i *)&dest[i], v);
    


int main(int argc, char* argv[])

    const size_t n = 8;
    const USHORT scale = 255;

    USHORT src[n] =  512, 1024, 2048, 4096, 8192, 16384, 32768, 65535 ;
    BYTE dest_ref[n], dest_1[n], dest_2[n];

    scale_ref(src, dest_ref, scale, n);
    scale_1(src, dest_1, scale, n);
    scale_2(src, dest_2, scale, n);

    for (size_t i = 0; i < n; i++)
    
        printf("src = %u, ref = %u, test_1 = %u, test_2 = %u\n", src[i], dest_ref[i], dest_1[i], dest_2[i]);
    

    return 0;

【讨论】:

感谢您的回答!你能告诉我如何将浮点刻度值转换为int?即 255.0/65535.0 = 255 如何? @BalajiR:因为我们使用的是“乘高”,所以有一个隐式除以 65536,所以比例因子只是变成了 255。(255.0/65535.0255.0/65536.0 之间的差异并不显着,鉴于您的结果数据只有 8 位精度。) 是的,我明白了!但是如果分母不是常数呢?即 255.0/ 512.0、255.0/ 1024.0、255.0/ 2048.0?那么如何将其转换为比例因子?对不起,如果我没有解释清楚,我的分子是不变的,但分母只会改变! 好的 - 您并没有在问题中明确说明此要求,因此例如@MargaretBloom 的答案不适用于您新添加的用例。无论如何,对于上面的代码,生成所需的比例因子只是简单的算术,例如对于您的255.0/512.0 示例,它将是255.0/512.0*65536 = 32640 好的 - 我已经编辑了这个问题,让它现在更清楚一点。【参考方案3】:

好的,参考this找到了解决办法。

这是我的解决方案:

int _tmain(int argc, _TCHAR* argv[])

    float Scale=255.0/65535.0;

    USHORT sArr[8]=512,1024,2048,4096,8192,16384,32768,65535;
    BYTE bArr[8],bArrSSE[8];        

    //Desired Conventional Method
    for (int i = 0; i < 8; i++)
    
        bArr[i]=(BYTE)(sArr[i]*Scale);                  
    

    __m128  vf_scale = _mm_set1_ps(Scale),                      
            vf_zero = _mm_setzero_ps();         
    __m128i vi_zero = _mm_setzero_si128();

    __m128i vi_src = _mm_loadu_si128(reinterpret_cast<const __m128i*>(&sArr[0]));

    __m128 vf_Src_Lo=_mm_cvtepi32_ps(_mm_unpacklo_epi16(vi_src, _mm_set1_epi16(0)));    
    __m128 vf_Src_Hi=_mm_cvtepi32_ps(_mm_unpackhi_epi16(vi_src, _mm_set1_epi16(0)));    
    __m128 vf_Mul_Lo=_mm_mul_ps(vf_Src_Lo,vf_scale);    
    __m128 vf_Mul_Hi=_mm_mul_ps(vf_Src_Hi,vf_scale);

    //Convert -ive to +ive Value
    vf_Mul_Lo=_mm_max_ps(_mm_sub_ps(vf_zero, vf_Mul_Lo), vf_Mul_Lo);
    vf_Mul_Hi=_mm_max_ps(_mm_sub_ps(vf_zero, vf_Mul_Hi), vf_Mul_Hi);

    __m128i v_dst_i = _mm_packus_epi16(_mm_packs_epi32(_mm_cvtps_epi32(vf_Mul_Lo), _mm_cvtps_epi32(vf_Mul_Hi)), vi_zero);
    _mm_storel_epi64((__m128i *)(&bArrSSE[0]), v_dst_i);

    for (int i = 0; i < 8; i++)
           
        printf("ushort[%d]= %d     * %f = %.3f ,\tuChar[%d]= %d,\t SSE uChar[%d]= %d \n",i,sArr[i],Scale,(float)(sArr[i]*Scale),i,bArr[i],i,bArrSSE[i]);
    

    return 0;

【讨论】:

这比它需要的要复杂和低效 - 您可以只使用定点算法进行缩放,而不需要转换为浮点数。 @Paul R 感谢您的评论!能否请您指出该方法的一些示例! 对不起,我现在才开始使用 SSE!

以上是关于如何使用缩放有效地将 16 位无符号短转换为 8 位无符号字符?的主要内容,如果未能解决你的问题,请参考以下文章

如何将 8 位无符号 wav 文件转换为 8 位有符号 wav 文件?

将 8 位无符号 PCM 转换为 8 位有符号 PCM

有效地将 16 位 short 转换为 8 位 char

uint8是8位无符号整型,uint16是16位无符号整型。

uint是几位无符号整数?

从 16 位无符号值到分钟和秒