翼度科技»论坛 编程开发 .net 查看内容

[C#] Bgr24彩色位图转为Gray8灰度位图的跨平台SIMD硬件加速向量算法

4

主题

4

帖子

12

积分

新手上路

Rank: 1

积分
12
将彩色位图转为灰度位图,是图像处理的常用算法。本文将介绍 Bgr24彩色位图转为Gray8灰度位图的算法,除了会给出标量算法外,还会给出向量算法。且这些算法是跨平台的,同一份源代码,能在 X86及Arm架构上运行,且均享有SIMD硬件加速。
一、标量算法

1.1 算法实现

对于彩色转灰度,由于人眼对红绿蓝三种颜色的敏感程度不同,在灰度转换时,每个颜色分配的权重也是不同的。有一个很著名的心理学公式:
Gray = R*0.299 + G*0.587 + B*0.114
该公式含有浮点数,而浮点数运算一般比较慢。
于是在具体实现时,需要做一定优化。可以将小数转为定点整数,这样便能将除法转为移位。整数计算比浮点型快,移位运算和加减法比乘除法快,于是取得了比较好的效果。
但是这种方法也会带来一定的精度损失,我们可以根据实际情况选择定点整数的精度位数。
这里我们使用16位精度,源代码如下。
  1. public static unsafe void ScalarDo(BitmapData src, BitmapData dst) {
  2.     const int cbPixel = 3; // Bgr24
  3.     const int shiftPoint = 16;
  4.     const int mulPoint = 1 << shiftPoint; // 0x10000
  5.     const int mulRed = (int)(0.299 * mulPoint + 0.5); // 19595
  6.     const int mulGreen = (int)(0.587 * mulPoint + 0.5); // 38470
  7.     const int mulBlue = mulPoint - mulRed - mulGreen; // 7471
  8.     int width = src.Width;
  9.     int height = src.Height;
  10.     int strideSrc = src.Stride;
  11.     int strideDst = dst.Stride;
  12.     byte* pRow = (byte*)src.Scan0.ToPointer();
  13.     byte* qRow = (byte*)dst.Scan0.ToPointer();
  14.     for (int i = 0; i < height; i++) {
  15.         byte* p = pRow;
  16.         byte* q = qRow;
  17.         for (int j = 0; j < width; j++) {
  18.             *q = (byte)((p[2] * mulRed + p[1] * mulGreen + p[0] * mulBlue) >> shiftPoint);
  19.             p += cbPixel; // Bgr24
  20.             q += 1; // Gray8
  21.         }
  22.         pRow += strideSrc;
  23.         qRow += strideDst;
  24.     }
  25. }
复制代码
1.2 基准测试代码

使用 BenchmarkDotNet 进行基准测试。
可以实现分配好数据。源代码如下。
  1. private static readonly Random _random = new Random(1);
  2. private BitmapData _sourceBitmapData = null;
  3. private BitmapData _destinationBitmapData = null;
  4. private BitmapData _expectedBitmapData = null;
  5. [Params(1024, 2048, 4096)]
  6. public int Width { get; set; }
  7. public int Height { get; set; }
  8. ~Bgr24ToGrayBgr24Benchmark() {
  9.     Dispose(false);
  10. }
  11. public void Dispose() {
  12.     Dispose(true);
  13.     GC.SuppressFinalize(this);
  14. }
  15. private void Dispose(bool disposing) {
  16.     if (_disposed) return;
  17.     _disposed = true;
  18.     if (disposing) {
  19.         Cleanup();
  20.     }
  21. }
  22. private BitmapData AllocBitmapData(int width, int height, PixelFormat format) {
  23.     const int strideAlign = 4;
  24.     if (width <= 0) throw new ArgumentOutOfRangeException($"The width({width}) need > 0!");
  25.     if (height <= 0) throw new ArgumentOutOfRangeException($"The width({height}) need > 0!");
  26.     int stride = 0;
  27.     switch (format) {
  28.         case PixelFormat.Format8bppIndexed:
  29.             stride = width * 1;
  30.             break;
  31.         case PixelFormat.Format24bppRgb:
  32.             stride = width * 3;
  33.             break;
  34.     }
  35.     if (stride <= 0) throw new ArgumentOutOfRangeException($"Invalid pixel format({format})!");
  36.     if (0 != (stride % strideAlign)) {
  37.         stride = stride - (stride % strideAlign) + strideAlign;
  38.     }
  39.     BitmapData bitmapData = new BitmapData();
  40.     bitmapData.Width = width;
  41.     bitmapData.Height = height;
  42.     bitmapData.PixelFormat = format;
  43.     bitmapData.Stride = stride;
  44.     bitmapData.Scan0 = Marshal.AllocHGlobal(stride * height);
  45.     return bitmapData;
  46. }
  47. private void FreeBitmapData(BitmapData bitmapData) {
  48.     if (null == bitmapData) return;
  49.     if (IntPtr.Zero == bitmapData.Scan0) return;
  50.     Marshal.FreeHGlobal(bitmapData.Scan0);
  51.     bitmapData.Scan0 = IntPtr.Zero;
  52. }
  53. [GlobalCleanup]
  54. public void Cleanup() {
  55.     FreeBitmapData(_sourceBitmapData); _sourceBitmapData = null;
  56.     FreeBitmapData(_destinationBitmapData); _destinationBitmapData = null;
  57.     FreeBitmapData(_expectedBitmapData); _expectedBitmapData = null;
  58. }
  59. [GlobalSetup]
  60. public void Setup() {
  61.     Height = Width;
  62.     // Create.
  63.     Cleanup();
  64.     _sourceBitmapData = AllocBitmapData(Width, Height, PixelFormat.Format24bppRgb);
  65.     _destinationBitmapData = AllocBitmapData(Width, Height, PixelFormat.Format8bppIndexed);
  66.     _expectedBitmapData = AllocBitmapData(Width, Height, PixelFormat.Format8bppIndexed);
  67.     RandomFillBitmapData(_sourceBitmapData, _random);
  68. }
复制代码
上面源代码中的“Vectors.ShiftRightLogical_Const”,是VectorTraits 库提供的方法。它能替代  NET 7.0 新增的 Vector.ShiftRightLogical 方法,使早期版本的 NET 也能使用逻辑右移位。
“Vectors.Multiply”也是VectorTraits 库提供的方法。它能避免无符号类型有时没有硬件加速的问题。
2.3 基准测试代码
  1. [Benchmark(Baseline = true)]
  2. public void Scalar() {
  3.     ScalarDo(_sourceBitmapData, _destinationBitmapData);
  4. }
复制代码
完整源码在 Bgr24ToGray8Benchmark.cs
随后为该算法编写基准测试代码。
三、基准测试结果

3.1 X86 架构

X86架构下的基准测试结果如下。
  1. static readonly Vector128<byte> YGroup3Unzip_Shuffle_Byte_X_Part0 = Vector128.Create((sbyte)0, 3, 6, 9, 12, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1).AsByte();
  2. static readonly Vector128<byte> YGroup3Unzip_Shuffle_Byte_X_Part1 = Vector128.Create((sbyte)-1, -1, -1, -1, -1, -1, 2, 5, 8, 11, 14, -1, -1, -1, -1, -1).AsByte();
  3. static readonly Vector128<byte> YGroup3Unzip_Shuffle_Byte_X_Part2 = Vector128.Create((sbyte)-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 4, 7, 10, 13).AsByte();
  4. static readonly Vector128<byte> YGroup3Unzip_Shuffle_Byte_Y_Part0 = Vector128.Create((sbyte)1, 4, 7, 10, 13, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1).AsByte();
  5. static readonly Vector128<byte> YGroup3Unzip_Shuffle_Byte_Y_Part1 = Vector128.Create((sbyte)-1, -1, -1, -1, -1, 0, 3, 6, 9, 12, 15, -1, -1, -1, -1, -1).AsByte();
  6. static readonly Vector128<byte> YGroup3Unzip_Shuffle_Byte_Y_Part2 = Vector128.Create((sbyte)-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 5, 8, 11, 14).AsByte();
  7. static readonly Vector128<byte> YGroup3Unzip_Shuffle_Byte_Z_Part0 = Vector128.Create((sbyte)2, 5, 8, 11, 14, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1).AsByte();
  8. static readonly Vector128<byte> YGroup3Unzip_Shuffle_Byte_Z_Part1 = Vector128.Create((sbyte)-1, -1, -1, -1, -1, 1, 4, 7, 10, 13, -1, -1, -1, -1, -1, -1).AsByte();
  9. static readonly Vector128<byte> YGroup3Unzip_Shuffle_Byte_Z_Part2 = Vector128.Create((sbyte)-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 3, 6, 9, 12, 15).AsByte();
  10. public static Vector128<byte> YGroup3Unzip(Vector128<byte> data0, Vector128<byte> data1, Vector128<byte> data2, out Vector128<byte> y, out Vector128<byte> z) {
  11.     var f0A = YGroup3Unzip_Shuffle_Byte_X_Part0;
  12.     var f0B = YGroup3Unzip_Shuffle_Byte_X_Part1;
  13.     var f0C = YGroup3Unzip_Shuffle_Byte_X_Part2;
  14.     var f1A = YGroup3Unzip_Shuffle_Byte_Y_Part0;
  15.     var f1B = YGroup3Unzip_Shuffle_Byte_Y_Part1;
  16.     var f1C = YGroup3Unzip_Shuffle_Byte_Y_Part2;
  17.     var f2A = YGroup3Unzip_Shuffle_Byte_Z_Part0;
  18.     var f2B = YGroup3Unzip_Shuffle_Byte_Z_Part1;
  19.     var f2C = YGroup3Unzip_Shuffle_Byte_Z_Part2;
  20.     var rt0 = Sse2.Or(Sse2.Or(Ssse3.Shuffle(data0, f0A), Ssse3.Shuffle(data1, f0B)), Ssse3.Shuffle(data2, f0C));
  21.     var rt1 = Sse2.Or(Sse2.Or(Ssse3.Shuffle(data0, f1A), Ssse3.Shuffle(data1, f1B)), Ssse3.Shuffle(data2, f1C));
  22.     var rt2 = Sse2.Or(Sse2.Or(Ssse3.Shuffle(data0, f2A), Ssse3.Shuffle(data1, f2B)), Ssse3.Shuffle(data2, f2C));
  23.     y = rt1;
  24.     z = rt2;
  25.     return rt0;
  26. }
复制代码

  • Scalar: 标量算法。
  • UseVectors: 矢量算法。
  • UseVectorsParallel: 并发的矢量算法。
3.2 Arm 架构

同样的源代码可以在 Arm 架构上运行。基准测试结果如下。
  1. public static unsafe void UseVectorsDoBatch(byte* pSrc, int strideSrc, int width, int height, byte* pDst, int strideDst) {
  2.     const int cbPixel = 3; // Bgr24
  3.     const int shiftPoint = 8;
  4.     const int mulPoint = 1 << shiftPoint; // 0x100
  5.     const ushort mulRed = (ushort)(0.299 * mulPoint + 0.5); // 77
  6.     const ushort mulGreen = (ushort)(0.587 * mulPoint + 0.5); // 150
  7.     const ushort mulBlue = mulPoint - mulRed - mulGreen; // 29
  8.     Vector<ushort> vmulRed = new Vector<ushort>(mulRed);
  9.     Vector<ushort> vmulGreen = new Vector<ushort>(mulGreen);
  10.     Vector<ushort> vmulBlue = new Vector<ushort>(mulBlue);
  11.     int vectorWidth = Vector<byte>.Count;
  12.     int maxX = width - vectorWidth;
  13.     byte* pRow = pSrc;
  14.     byte* qRow = pDst;
  15.     for (int i = 0; i < height; i++) {
  16.         Vector<byte>* pLast = (Vector<byte>*)(pRow + maxX * cbPixel);
  17.         Vector<byte>* qLast = (Vector<byte>*)(qRow + maxX * 1);
  18.         Vector<byte>* p = (Vector<byte>*)pRow;
  19.         Vector<byte>* q = (Vector<byte>*)qRow;
  20.         for (; ; ) {
  21.             Vector<byte> r, g, b, gray;
  22.             Vector<ushort> wr0, wr1, wg0, wg1, wb0, wb1;
  23.             // Load.
  24.             b = Vectors.YGroup3Unzip(p[0], p[1], p[2], out g, out r);
  25.             // widen(r) * mulRed + widen(g) * mulGreen + widen(b) * mulBlue
  26.             Vector.Widen(r, out wr0, out wr1);
  27.             Vector.Widen(g, out wg0, out wg1);
  28.             Vector.Widen(b, out wb0, out wb1);
  29.             wr0 = Vectors.Multiply(wr0, vmulRed);
  30.             wr1 = Vectors.Multiply(wr1, vmulRed);
  31.             wg0 = Vectors.Multiply(wg0, vmulGreen);
  32.             wg1 = Vectors.Multiply(wg1, vmulGreen);
  33.             wb0 = Vectors.Multiply(wb0, vmulBlue);
  34.             wb1 = Vectors.Multiply(wb1, vmulBlue);
  35.             wr0 = Vector.Add(wr0, wg0);
  36.             wr1 = Vector.Add(wr1, wg1);
  37.             wr0 = Vector.Add(wr0, wb0);
  38.             wr1 = Vector.Add(wr1, wb1);
  39.             // Shift right and narrow.
  40.             wr0 = Vectors.ShiftRightLogical_Const(wr0, shiftPoint);
  41.             wr1 = Vectors.ShiftRightLogical_Const(wr1, shiftPoint);
  42.             gray = Vector.Narrow(wr0, wr1);
  43.             // Store.
  44.             *q = gray;
  45.             // Next.
  46.             if (p >= pLast) break;
  47.             p += cbPixel;
  48.             ++q;
  49.             if (p > pLast) p = pLast; // The last block is also use vector.
  50.             if (q > qLast) q = qLast;
  51.         }
  52.         pRow += strideSrc;
  53.         qRow += strideDst;
  54.     }
  55. }
复制代码
附录

    出处:http://www.cnblogs.com/zyl910/    版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0.
来源:https://www.cnblogs.com/zyl910/p/18555805/VectorTraits_Sample_Image_Bgr24ToGray8Benchmark
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

举报 回复 使用道具