Delphi图像处理 -- 平面几何变换(上)

阅读提示:

    《Delphi图像处理》系列以效率为侧重点,一般代码为PASCAL,核心代码采用BASM。

    《C++图像处理》系列以代码清晰,可读性为主,全部使用C++代码。

    尽可能保持二者内容一致,可相互对照。

    本文代码必须包括文章《Delphi图像处理 -- 数据类型及公用过程》中的ImageData.pas单元和《Delphi图像处理 -- 平面几何变换类TransformMatrix.pas单元

 

    在《Delphi图像处理 -- 平面几何变换类》一文中,介绍了图像平面几何变换类TTransformMatrix,并写了一个简单的临近插值法图像几何变换函数Transform,用于测试。很显然,Transform函数产生的变换图像不仅质量较差,而且也不具备通用性,只能作为一个实现图像几何变换的框架。

    本文拟采用临近插值法、双线性插值法和双立方插值法等三种插值方式,来实现较完整、通用的图形图像平面几何变换。三种插值过程代码包含在《Delphi图像处理 -- 平面几何变换类》一文中,本文代码不包括TTransformMatrix类和三种插值过程。

procedure SetMixerMM;
asm
    pxor      mm7, mm7
    mov       eax, 1011h
    movd      mm6, eax
    pshufw    mm6, mm6, 0
    mov       eax, 8
    movd      mm5, eax
    pshufw    mm5, mm5, 0
end;

procedure MixerColor;
asm
    cmp       eax, 255
    jb        @@ArgbMix
    movd      [edi], xmm0
    ret
@@ArgbMix:
    movdq2q   mm0, xmm0
    movd      mm1, [edi]
    punpcklbw mm0, mm7
    punpcklbw mm1, mm7
    pshufw    mm2, mm0, 255
    movzx     eax, [edi].TARGBQuad.Alpha
    cmp       eax, 255
    jne       @@PArgbMix

    // dest.argb = dest.argb + source.argb - dest.argb * source.alpha / 255
    paddw     mm0, mm1
    pmullw    mm1, mm2
    pmulhuw   mm1, mm6
    paddusw   mm1, mm5
    psrlw     mm1, 4
    psubsw    mm0, mm1
    packuswb  mm0, mm0
    movd      [edi], mm0
    ret
@@PArgbMix:
    // dest.rgb = dest.rgb * dest.alpha / 255
    pshufw    mm3, mm1, 255
    pmullw    mm1, mm3
    pmulhuw   mm1, mm6
    paddusw   mm1, mm5
    psrlw     mm1, 4
    pinsrw    mm1, eax, 3

    // dest.argb = dest.argb + source.argb - dest.argb * sourec.alpha / 255
    paddw     mm0, mm1
    pmullw    mm1, mm2
    pmulhuw   mm1, mm6
    paddusw   mm1, mm5
    psrlw     mm1, 4
    psubsw    mm0, mm1
    pextrw    eax, mm0, 3

    // dest.rgb = dest.rgb * 255 / dest.alpha
    movq      mm1, mm0
    psllw     mm0, 8
    psubw     mm0, mm1
    pmulhuw   mm0, qword ptr MMDivTab[eax*8]
    packuswb  mm0, mm7
    movd      [edi], mm0
    mov       [edi].TARGBQuad.Alpha, al
end;

procedure CopyInterpolateData(var Deat: TImageData; const Source: TImageData; Alpha: Integer);
asm
    push      esi
    push      edi
    push      ebx
    cmp       ecx, 256
    jne       @@CvtPArgb
    cmp       [edx].TImageData.AlphaFlag, True
    je        @@CvtPArgb
    call      _SetCopyRegs
@@CpyLoop:
    push      ecx
    rep       movsd
    pop       ecx
    add       esi, eax
    add       edi, ebx
    dec       edx
    jne       @@CpyLoop
    jmp       @@Exit

@@CvtPArgb:
    push      ebp
    push      ecx
    call      _SetCopyRegs
    mov       ebp, eax
    call      SetMixerMM
    pop       eax
    movd      mm2, eax
    pshufw    mm2, mm2, 0
@@yLoop:
    push      ecx
@@xLoop:
    movd      mm0, [esi]
    punpcklbw mm0, mm7
    pshufw    mm1, mm0, 255
    pmullw    mm1, mm2
    psrlw     mm1, 8      // alpha0 = Source.Alpha * Alpha / 256
    pmullw    mm0, mm1    // Source.RGB = (Source.RGB * alpha0 + 127) / 255
    pmulhuw   mm0, mm6
    paddusw   mm0, mm5
    psrlw     mm0, 4
    packuswb  mm0, mm7
    movd      eax, mm1
    movd      [edi], mm0
    mov       [edi].TARGBQuad.Alpha, al
    add       esi, 4
    add       edi, 4
    loop      @@xLoop
    add       esi, ebp
    add       edi, ebx
    pop       ecx
    dec       edx
    jnz       @@yLoop
    pop       ebp
    emms
@@Exit:
    pop       ebx
    pop       edi
    pop       esi
end;

procedure FillBorder(var Data: TImageData; Radius: Integer; FillFlag: Integer);
asm
    push    ebp
    push    esi
    push    edi
    push    ebx
    test    ecx, 0fffh
    jne     @@yFill
    push    eax
    push    ecx
    mov     edi, [eax].TImageData.Stride
    imul    edi, edx
    add		  edi, [eax].TImageData.Scan0
    mov     ebp, [eax].TImageData.Width
    mov     ebx, [eax].TImageData.Height
    mov     esi, edx
    shl     esi, 1
    sub     ebx, esi
    sub     ebp, esi
    shl     ebp, 2
    shl     esi, 1
@@cLoop:
    mov     eax, [edi+esi]
    mov		  ecx, edx
    rep     stosd
    add     edi, ebp
    mov     eax, [edi-4]
    mov     ecx, edx
    rep		  stosd
    dec     ebx
    jnz     @@cLoop
    pop     ecx
    pop     eax
@@yFill:
    test    ecx, 0fff0000h
    jne     @@Exit
    mov     ebp, [eax].TImageData.Width
    mov		  edi, [eax].TImageData.Scan0
    mov     esi, [eax].TImageData.Stride
    imul    esi, edx
    add     esi, edi
    push    edx
@@tLoop:
    push    esi
    mov     ecx, ebp
    rep     movsd
    pop     esi
    dec     edx
    jnz     @@tLoop
    pop     edx
    mov     ebx, [eax].TImageData.Height
    sub     ebx, edx
    sub     ebx, edx
    imul    ebx, [eax].TImageData.Stride
    add     edi, ebx
    mov     esi, edi
    sub     esi, [eax].TImageData.Stride
@@bLoop:
    mov     ecx, ebp
    rep     movsd
    dec     edx
    jnz     @@bLoop
@@Exit:
    pop     ebx
    pop     edi
    pop     esi
    pop     ebp
end;

type
  TInterpolateProc = procedure;
  TElementsI = array[0..5] of Integer;

procedure _DoTransform(var Dest: TImageData; const Soutce: TImageData;
  const Elements: TElementsI; Radius: Integer; ipProc: TInterpolateProc);
var
  im22, im21, im12, im11: Integer;
  up, xDown, yDown: Integer;
  width, height, dstOffset: Integer;
  scan0: Pointer;
asm
    push    esi
    push    edi
    push    ebx
    mov     ebx, [ecx]
    mov     im11, ebx
    mov     ebx, [ecx+4]
    mov     im12, ebx
    mov     ebx, [ecx+8]
    mov     im21, ebx
    mov     ebx, [ecx+12]
    mov     im22, ebx
    push    dword ptr[ecx+16]
    push    dword ptr[ecx+20]
    mov     esi, Radius
    mov     ebx, [edx].TImageData.Width
    mov     edi, [edx].TImageData.Height
    sub     ebx, esi
    sub     edi, esi
    shl     ebx, 12
    shl     edi, 12
    mov     xDown, ebx          // xDown = (Source.Width - radius) * 4096
    mov     yDown, edi          // yDown = (Source.Height - radius) * 4096
    mov     ebx, [eax].TImageData.Height
    mov     height, ebx         // height = dest.Height
    mov     ebx, [eax].TImageData.Width
    mov     width, ebx          // width = dest.Width
    shl     ebx, 2
    neg     ebx
    add     ebx, [eax].TImageData.Stride
    mov     dstOffset, ebx      // dstOffset = dest.Stride - dest.Width * 4
    mov     edi, [eax].TImageData.Scan0
    mov     ebx, [edx].TImageData.Scan0
    mov     scan0, ebx
    mov     ebx, [edx].TImageData.Stride
    pop     ecx                 // ys
    pop     edx                 // xs
    shl     esi, 11
    mov     up, esi             // up = radius * 2048
    add     esi, 800h
    add     edx, esi            // xs += (up + 2048)
    add     ecx, esi            // ys += (up + 2048)
    pxor    xmm7, xmm7
    mov     eax, 40004h
    movd    xmm6, eax
    pshufd  xmm6, xmm6, 0
    call    SetMixerMM
@@yLoop:
    push    ecx                 // for (pd = (ARGB*)Dest.Scan0; height > 0; height --)
    push    edx                 // {
    push    width               //   x = xs
@@xLoop:                        //   y = ys
    cmp     ecx, up             //   for (w = width; w > 0; w --)
    jl      @@Next              //   {
    cmp     ecx, yDown
    jge     @@Next              
    cmp     edx, up
    jl      @@Next
    cmp     edx, xDown          //     if (y >= up && y < yDown && x >= up && x < xDown)
    jge     @@Next              //     {
    mov     esi, ecx            //       y0 = y / 4096
    mov     eax, edx            //       x0 = x / 4096
    sar     esi, 12
    sar     eax, 12
    imul    esi, ebx
    shl     eax, 2
    add     esi, eax
    add     esi, scan0          //       esi = Source.Scan0 + x0 * 4 + y0 * Source.Stride
    call    ipProc

    movd    eax, xmm0           //       xmm0 = eax = ipProc(src, x, y, esi)
    shr     eax, 24
    jz      @@Next
    call    MixerColor          //       if (eax >> 24) MixerColor(pd, mm0, eax)
@@Next:
    add     edi, 4              //       pd ++
    add     edx, im11           //       x += im11
    add     ecx, im12           //       y += im12
    dec     width               //     }
    jnz     @@xLoop
    pop     width
    pop     edx
    pop     ecx
    add     edi, dstOffset      //   (LPBYTE)pd += dstOffset
    add     edx, im21           //   xs += im21
    add     ecx, im22           //   ys += im22
    dec     height              // }
    jnz     @@yLoop
    emms
@@Exit:
    pop     ebx
    pop     edi
    pop     esi
end;

// 获取插值过程和扩展半径
function GetInterpolateProc(const Data: TImageData; var Proc: TInterpolateProc): Integer;
const
  procs: array[TInterpolateMode] of TInterpolateProc =
    (_GetBilinearColor, _GetNearColor, _GetBilinearColor, _GetBicubicColor);
  radius: array[TInterpolateMode] of Integer = (2, 1, 2, 4);
begin
  Proc := procs[Data.IpMode];
  Result := radius[Data.IpMode];
end;

// 按Matrix计算目标和源图像几何变换的裁剪矩形
function _GetTransformParams(dstWidth, dstHeight, srcWidth, srcHeight: Integer;
  var Matrix: TTransformMatrix; var dst, src: TRect): Boolean;

  function CalcRectI(Width, height: Integer; var r: TRect): Boolean;
  begin
    Inc(r.Right, r.Left);
    Inc(r.Bottom, r.Top);
    if r.Right > Width then r.Right := Width;
    if r.Bottom > Height then r.Bottom := Height;
    if r.Left > 0 then Dec(r.Right, r.Left) else r.Left := 0;
    if r.Top > 0 then Dec(r.Bottom, r.Top) else r.Top := 0;
    Result := (r.Right > 0) and (r.Bottom > 0);
  end;

var
  fx, fy, fwidth, fheight: Single;
begin
  Matrix.GetTransformSize(srcWidth, srcHeight, fx, fy, fwidth, fheight);
  Matrix.Invert;
  dst := Rect(Trunc(fx), Trunc(fy), _Infinity(fwidth), _Infinity(fheight));
  Result := CalcRectI(dstWidth, dstHeight, dst);
  if not Result then Exit;
  if (fx > 0) or (fy > 0) then
  begin
    if fx < 0 then fx := 0
    else if (fy < 0) then fy := 0;
    Matrix.Translate(fx, fy);
  end;
  Matrix.GetTransformSize(dst.Right, dst.Bottom, fx, fy, fwidth, fheight);
  src := Rect(Trunc(fx), Trunc(fy), _Infinity(fwidth), _Infinity(fheight));
  Result := CalcRectI(srcWidth, srcHeight, src);
  if not Result then Exit;
  if fx > 0 then Matrix.OffsetX := Matrix.OffsetX - fx;
  if fy > 0 then Matrix.OffsetY := Matrix.OffsetY - fy;
end;

procedure ImageTransform(var Dest: TImageData; x, y: Integer;
  const Source: TImageData; const Matrix: TTransformMatrix; Alpha: Single = 1);
var
  m: TTransformMatrix;
  e: TMatrixElements;
  eI: TElementsI;
  dstR, srcR: TRect;
  i, alphaI, radius: Integer;
  proc: TInterpolateProc;
  dst, src, tmp, sub: TImageData;
begin
  alphaI := Round(Alpha * 256);
  if alphaI <= 0 then Exit;
  if alphaI > 256 then alphaI := 256;
  m := TTransformMatrix.Create(Matrix);
  try
    m.OffsetX := m.OffsetX + x;
    m.OffsetY := m.OffsetY + y;
    if not _GetTransformParams(Dest.Width, Dest.Height,
      Source.Width, Source.Height, m, dstR, srcR) then  Exit;
    e := m.Elements;
    for i := 0 to 5 do
      eI[i] := Round(e.Elements[i] * 4096);
    radius := GetInterpolateProc(Source, proc);
    dst := GetSubImageData(Dest, dstR.Left, dstR.Top, dstR.Right, dstR.Bottom);
    tmp := GetSubImageData(Source, srcR.Left, srcR.Top, srcR.Right, srcR.Bottom);
    src := NewImageData(tmp.Width + radius * 2, tmp.Height + radius * 2);
    try
      src.AlphaFlag := Source.AlphaFlag;
      sub := GetSubImageData(src, radius, radius, tmp.Width, tmp.Height);
      CopyInterpolateData(sub, tmp, alphaI);
      FillBorder(src, radius, (eI[1] shl 16) or (eI[2] and $ffff));
      _DoTransform(dst, src, eI, radius, proc);
      if (eI[1] = 0) and (eI[2] = 0) and (alphaI = 256) and not Source.AlphaFlag
        and (Dest.Width = dst.Width) and (Dest.Height = dst.Height) then
        Dest.AlphaFlag := False;
    finally
      FreeImageData(src);
    end;
  finally
    m.Free;
  end;
end;

    代码很长,但核心代码还是_DoTransform过程和《Delphi图像处理 -- 平面几何变换类》中的三个插值过程。几何变换过程原理亦可参见该文。

    有关插值边界的处理,一般有2种办法,一是在插值过程中进行判断坐标是否超界而作相应的处理,二是舍弃边界部分,对于后者我是不主张的,因为那样是不完整的处理。我采用了扩展边框的办法进行边框插值处理,这样一来,虽然多了一道拷贝过程,却少了具体插值过程的坐标判断,二者抵消,插值速度应该是差不多的(据我测试,扩展边框办法在图像放大和旋转处理中速度还是略快一些),但是简化了插值代码。

    下面是图像几何变换的例子: 

procedure TForm1.Button1Click(Sender: TObject);
var
  bmp, newBmp: TBitmap;
  jpg: TJPEGImage;
  matrix: TTransformMatrix;
  source, dest: TImageData;
  r: TRect;
begin
  bmp := TBitmap.Create;
  matrix := TTransformMatrix.Create;
  try
    jpg := TJPEGImage.Create;
    try
      jpg.LoadFromFile('..\..\media\IMG_9440_mf.jpg');
      bmp.Assign(jpg);
    finally
      jpg.Free;
    end;
    matrix.Scale(1.2, 1.2);
    matrix.Shear(0.3, 0.3);
    r := matrix.GetTransformRect(bmp.Width, bmp.Height);
    if (r.Right <= 0) or (r.Bottom <= 0) then Exit;
    source := GetBitmapData(bmp);
    newBmp := TBitmap.Create;
    try
      newBmp.PixelFormat := pf32bit;
      newBmp.Width := r.Right;
      newBmp.Height := r.Bottom;
      dest := GetBitmapData(newBmp);
      SetInterpolateMode(source, imBicubic);
      ImageTransform(dest, 0, 0, source, matrix);
      Canvas.Draw(0, 0, newBmp);
    finally
      newBmp.Free;
    end;
  finally
    matrix.Free;
    bmp.Free;
  end;
end;

    代码中的SetInterpolateMode(source, imBicubic);语句为设置双立方插值方式,该函数也在《Delphi图像处理 -- 数据类型及公用过程》的ImageData.pas单元。

    运行效果截图如下:

    运行效果还是很好的,特别是边界效果。

    对于含Alpha信息的图像,必须作自乘预处理,这在CopyAlphaData函数中是自动判断后转换的。比较下面同一张png图片2种方式的线性插值几何变换处理效果截图,左边是使用自乘预处理后的插值处理效果,右边是没做自乘预处理后的插值处理效果:

    《Delphi图像处理》系列使用GDI+单元下载地址和说明见文章《GDI+ for VCL基础 -- GDI+ 与 VCL》。

    因水平有限,错误在所难免,欢迎指正和指导。邮箱地址:maozefa@hotmail.com

    这里可访问《Delphi图像处理 -- 文章索引》。

 

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
Delphi11 是最新的 Delphi 版本,而 `delphi-opencv-master` 是一个开源的 Delphi 与 OpenCV 集成的项目,它提供了一组 Delphi 封装的 OpenCV 函数和类,方便 Delphi 开发者使用 OpenCV 进行图像处理和计算机视觉操作。 在 Delphi11 中,你可以通过以下步骤来使用 `delphi-opencv-master`: 1. 首先,你需要下载 `delphi-opencv-master` 项目的源代码,可以从 GitHub 上下载:https://github.com/Laex/Delphi-OpenCV 2. 解压缩下载的源代码,并将其中的 `OpenCV` 文件夹复制到 Delphi11 项目的根目录下。 3. 在 Delphi11 中打开你的项目,然后在菜单栏中选择 `Project` -> `Options` -> `Delphi Compiler` -> `Search Path`,将 OpenCV 文件夹所在的路径添加到搜索路径中。 4. 在 Delphi11 中使用 `uses` 语句引入 `OpenCV_Image` 单元,并调用其中的函数和类来进行图像处理和计算机视觉操作。 例如,以下代码演示了如何使用 `delphi-opencv-master` 进行图像读取和灰度化: ``` uses OpenCV_Image; var img: IplImage; begin // 读取图像 img := LoadImage('lena.jpg'); // 将图像转换为灰度图像 cvCvtColor(img, img, CV_RGB2GRAY); // 显示图像 ShowImage('My Image', img); // 等待用户按下任意键 WaitKey; // 释放图像内存 img.Release; end; ``` 这段代码使用了 `OpenCV_Image` 单元中提供的 `LoadImage` 函数和 `ShowImage` 函数来读取和显示图像,使用了 OpenCV 库中的 `cvCvtColor` 函数将图像转换为灰度图像。你可以根据自己的需要,使用 `delphi-opencv-master` 中提供的其他函数和类来进行更加复杂的图像处理和计算机视觉操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值