如何使用缩放有效地将 16 位 unsigned short 转换为 8 位 unsigned char?
How to convert 16-bit unsigned short to 8-bit unsigned char using scaling efficiently?
我正在尝试使用一些缩放函数将 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.0
、255.0/1024.0
或 255.0/2048.0
,因此不应为 255.0/65535.0
.
硬编码任何解决方案
好的,参考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;
}
如果你的代码中的比率是固定的,你可以用下面的算法进行缩放
- 将每个单词的高字节移到低字节。
例如。 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;
}
这是使用 _mm_mulhi_epu16
执行定点缩放操作的实现和测试工具。
scale_ref
是您的原始标量代码,scale_1
是您(当前已删除)答案中的浮点 SSE 实现,scale_2
是我的定点实现。
我已经将各种实现分解为单独的函数,还添加了一个大小参数和一个循环,以便它们可以用于任何大小的数组(尽管目前 n
必须是 8 的倍数对于 SSE 实施)。
有一个 compile-time 标志,ROUND
,它控制定点实现是截断(像您的标量代码)还是舍入(到最近)。截断速度稍快。
另请注意,scale
是一个run-time参数,目前在下面的测试工具中是hard-coded到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;
}
我正在尝试使用一些缩放函数将 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.0
、255.0/1024.0
或 255.0/2048.0
,因此不应为 255.0/65535.0
.
好的,参考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;
}
如果你的代码中的比率是固定的,你可以用下面的算法进行缩放
- 将每个单词的高字节移到低字节。
例如。 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;
}
这是使用 _mm_mulhi_epu16
执行定点缩放操作的实现和测试工具。
scale_ref
是您的原始标量代码,scale_1
是您(当前已删除)答案中的浮点 SSE 实现,scale_2
是我的定点实现。
我已经将各种实现分解为单独的函数,还添加了一个大小参数和一个循环,以便它们可以用于任何大小的数组(尽管目前 n
必须是 8 的倍数对于 SSE 实施)。
有一个 compile-time 标志,ROUND
,它控制定点实现是截断(像您的标量代码)还是舍入(到最近)。截断速度稍快。
另请注意,scale
是一个run-time参数,目前在下面的测试工具中是hard-coded到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;
}