Delphi 汇编学习(三)--- 图像灰值化的极致优化

17 篇文章 4 订阅
15 篇文章 2 订阅

看过
Delphi 汇编学习(一)--- 图像灰值化            (https://blog.csdn.net/dbyoung/article/details/112001642)、
Delphi 汇编学习(二)--- 学习 SIMD 的痛苦 (https://blog.csdn.net/dbyoung/article/details/113768478
这两篇文章后,你就知道了:
其实 AVX/AVX2 的优化效果并不佳。而且很多机器也不支持这些指令。
而 SSE 的优化效果是比较不错的。并且大多数的计算机都支持 SSE 指令集。
Delphi 编译器也支持 SSE 指令集(易博龙是不是也知道 AVX 优化效果不好,所以才不支持的)。
在示例中 SSE 优化后,一幅 4096x4096x32 的图像灰值化依然需要将近 20 毫秒的时间。能不能优化到 10 毫秒以下呢?
在示例中我们也使用了并行优化的方法。将并行优化和 SSE 优化,这两个组合起来效果如何呢?试试吧。
(为什么要在这个问题上纠结呢?因为它是图像处理的基础。这个弄好了,其它的如调节亮度、对比度、饱和度,等等,都是同一个原理)

procedure Gray_SSEParallel_Proc(pColor: PRGBQuad; const bmpWidth: Integer);
asm
  MOV     ECX,  EDX
  MOVSS   XMM1, [c_GraySSEMask]             // XMM1 = 000000000000000000000000000000FF
  MOVSS   XMM2, [c_GraySSERioB]             // XMM2 = 0000000000000000000000000000001C
  MOVSS   XMM3, [c_GraySSERioG]             // XMM3 = 00000000000000000000000000000097
  MOVSS   XMM4, [c_GraySSERioR]             // XMM4 = 0000000000000000000000000000004D
  SHUFPS  XMM1, XMM1, 0                     // XMM1 = 000000FF000000FF000000FF000000FF
  SHUFPS  XMM2, XMM2, 0                     // XMM2 = 0000001C0000001C0000001C0000001C
  SHUFPS  XMM3, XMM3, 0                     // XMM3 = 00000097000000970000009700000097
  SHUFPS  XMM4, XMM4, 0                     // XMM4 = 0000004D0000004D0000004D0000004D

@LOOP:
  MOVUPS  XMM0, [EAX]                       // XMM0 = |A3R3G3B3|A2R2G2B2|A1R1G1B1|A0R0G0B0|
  MOVAPS  XMM5, XMM0                        // XMM5 = |A3R3G3B3|A2R2G2B2|A1R1G1B1|A0R0G0B0|
  MOVAPS  XMM6, XMM0                        // XMM6 = |A3R3G3B3|A2R2G2B2|A1R1G1B1|A0R0G0B0|
  MOVAPS  XMM7, XMM0                        // XMM7 = |A3R3G3B3|A2R2G2B2|A1R1G1B1|A0R0G0B0|

  // 获取 4 个像素的 B3, B2, B1, B0
  ANDPS   XMM5, XMM1                        // XMM5 = |000000B3|000000B2|000000B1|000000B0|

  // 获取 4 个像素的 G3, G2, G1, G0
  PSRLD   XMM6, 8                           // XMM6 = |00A3R3G3|00A2R2G2|00A1R1G1|00A0R0G0|
  ANDPS   XMM6, XMM1                        // XMM6 = |000000G3|000000G2|000000G1|000000G0|

  // 获取 4 个像素的 R3, R2, R1, R0
  PSRLD   XMM7, 16                          // XMM7 = |0000A3R3|0000A2R2|0000A1R1|0000A0R0|
  ANDPS   XMM7, XMM1                        // XMM7 = |000000R3|000000R2|000000R1|000000R0|

  // 计算灰度值
  PMULLW  XMM5, XMM2                        // XMM5 = |B3 *  28|B2 *  28|B1 *  28|B0 *  28|
  PMULLW  XMM6, XMM3                        // XMM6 = |G3 * 151|G2 * 151|G1 * 151|G0 * 151|
  PMULLW  XMM7, XMM4                        // XMM7 = |R3 *  77|R2 *  77|R1 *  77|R0 *  77|
  PADDD   XMM5, XMM6                        // XMM5 = G+B
  PADDD   XMM5, XMM7                        // XMM5 = G+B+R
  PSRLD   XMM5, 8                           // XMM5 = |000000Y3|000000Y2|000000Y1|000000Y0|

  // 返回结果
  MOVAPS  XMM6, XMM5                        // XMM6  = |000000Y3|000000Y2|000000Y1|000000Y0|
  MOVAPS  XMM7, XMM5                        // XMM7  = |000000Y3|000000Y2|000000Y1|000000Y0|
  PSLLD   XMM6, 8                           // XMM6  = |0000Y300|0000Y200|0000Y100|0000Y000|
  PSLLD   XMM7, 16                          // XMM7  = |00Y30000|00Y20000|00Y10000|00Y00000|
  ORPS    XMM5, XMM6                        // XMM5  = |0000Y3Y3|0000Y2Y2|0000Y1Y1|0000Y0Y0|
  ORPS    XMM5, XMM7                        // XMM5  = |00Y3Y3Y3|00Y2Y2Y2|00Y1Y1Y1|00Y0Y0Y0|
  MOVUPS  [EAX], XMM5                       // [EAX] = XMM5

  ADD     EAX, 16                           // pColor 地址加 16,EAX 指向下4个像素的地址
  SUB     ECX, 4                            // Width 减 4, 每 4 个像素一循环
  JNZ     @LOOP                             // 循环
end;

{ 4 ms  需要脱离 IDE 执行 / ScanLine 不能用于 TParallel.For 中 }
procedure Gray_SSEParallel(bmp: TBitmap);
var
  StartScanLine: Integer;
  bmpWidthBytes: Integer;
begin
  StartScanLine := Integer(bmp.ScanLine[0]);
  bmpWidthBytes := Integer(bmp.ScanLine[1]) - Integer(bmp.ScanLine[0]);

  TParallel.For(0, bmp.Height - 1,
    procedure(Y: Integer)
    var
      pColor: PRGBQuad;
    begin
      pColor := PRGBQuad(StartScanLine + Y * bmpWidthBytes);
      Gray_SSEParallel_Proc(pColor, bmp.Width);
    end);
end;

不试不知道,一试吓一跳。一大跳!!!
在我的机器上,这段代码用时只需要 5毫秒左右,速度整整提高了4倍。
这可是一个4096x4096x32 的图片呀。那些小图片耗时可以忽略不计了。
这段代码也很容易扩展至 x64 平台。添加一行代码就可以了。
修改为 C++ 代码,也基本上没有什么困难。稍微懂得一点 C++ 的程序员就可以做到。这就留作课后作业吧。
总结:多核并行 + SSE 是最好的优化方案了。简单,易实现,效果极佳。
总结:相信指令,相信编译器,不如相信自己。自己动手,丰衣足食!
详细代码:https://github.com/dbyoung720/ImageGray.git
qq交流群:101611228

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值