线性插值算法实现图像缩放

摘自:http://shenghuafen.cnblogs.com/articles/22486.aspx 和http://www.winu.cn/space-14160-do-blog-id-4053.html
作者:

Windows的GDI有一个API函数:StretchBlt,对应在VCL中是TCanvas类的StretchDraw方法。它可以很简单地实现图像的缩放操作。但问题是它是用了速度最快,最简单但效果也是最差的“最近邻域法”,虽然在大多数情况下,它也够用了,但对于要求较高的情况就不行了。

从数字图像处理的基本理论,我们可以知道:图像的变形变换就是源图像到目标图像的坐标变换。简单的想法就是把源图像的每个点坐标通过变形运算转为目标图像的相应点的新坐标,但是这样会导致一个问题就是目标点的坐标通常不会是整数,而且像放大操作会导致目标图像中没有被源图像的点映射到,这是所谓“向前映射”方法的缺点。所以一般都是采用“逆向映射”法。

但是逆向映射法同样会出现映射到源图像坐标时不是整数的问题。这里就需要“重采样滤波器”。这个术语看起来很专业,其实不过是因为它借用了电子信号处理中的惯用说法(在大多数情况下,它的功能类似于电子信号处理中的带通滤波器),理解起来也不复杂,就是如何确定这个非整数坐标处的点应该是什么颜色的问题。前面说到的三种方法:最近邻域法,线性插值法和三次样条法都是所谓的“重采样滤波器”。

所谓“最近邻域法”就是把这个非整数坐标作一个四舍五入,取最近的整数点坐标处的点的颜色。而“线性插值法”就是根据周围最接近的几个点(对于平面图像来说,共有四点)的颜色作线性插值计算(对于平面图像来说就是二维线性插值)来估计这点的颜色,在大多数情况下,它的准确度要高于最近邻域法,当然效果也要好得多,最明显的就是在放大时,图像边缘的锯齿比最近邻域法小非常多。当然它同时还带业个问题:就是图像会显得比较柔和。这个滤波器用专业术语来说叫做:带阻性能好,但有带通损失,通带曲线的矩形系数不高。

再来讨论一下坐标变换的算法。简单的空间变换可以用一个变换矩阵来表示:

[x’,y’,w’]=[u,v,w]*T

其中:x’,y’为目标图像坐标,u,v为源图像坐标,w,w’称为齐次坐标,通常设为1,T为一个3X3的变换矩阵。

这种表示方法虽然很数学化,但是用这种形式可以很方便地表示多种不同的变换,如平移,旋转,缩放等。对于缩放来说,相当于:

[Su  0  0 ]

[x, y, 1] = [u, v, 1] * | 0  Sv  0 |

[0   0  1 ]

其中Su,Sv分别是X轴方向和Y轴方向上的缩放率,大于1时放大,大于0小于1时缩小,小于0时反转。

矩阵是不是看上去比较晕?其实把上式按矩阵乘法展开就是:

{ x = u * Su

{ y = v * Sv


有了上面三个方面的准备,就可以开始编写代码实现了。思路很简单:首先用两重循环遍历目标图像的每个点坐标,通过上面的变换式(注意:因为是用逆向映射,相应的变换式应该是:u = x / Su 和v = y / Sv)取得源坐标。因为源坐标不是整数坐标,需要进行二维线性插值运算:

P = n*b*PA + n * ( 1 – b )*PB + ( 1 – n ) * b * PC + ( 1 – n ) * ( 1 – b ) * PD

其中:n为v(映射后相应点在源图像中的Y轴坐标,一般不是整数)下面最接近的行的Y轴坐标与v的差;同样b也类似,不过它是X轴坐标。PA-PD分别是(u,v)点周围最接近的四个(左上,右上,左下,右下)源图像点的颜色(用TCanvas的Pixels属性)。P为(u,v)点的插值颜色,即(x,y)点的近似颜色。

这段代码的效率实在太低:要对目标图像的每一个点的RGB进行上面那一串复杂的浮点运算。所以一定要进行优化。对于VCL应用来说,有个比较简单的优化方法就是用TBitmap的ScanLine属性,按行进行处理,可以避免Pixels的像素级操作,对性能可以有很大的改善。这已经是算是用VCL进行图像处理的基本优化常识了。不过这个方法并不总是管用的,比如作图像旋转的时候。

无论如何,浮点运算的开销都是比整数大很多的,这个也是一定要优化掉的。从上面可以看出,浮点数是在变换时引入的,而变换参数Su,Sv通常就是浮点数,所以就从它下手优化。一般来说,Su,Sv可以表示成分数的形式:

Su = ( double )Dw / Sw; Sv = ( double )Dh / Sh

其中Dw, Dh为目标图像的宽度和高度,Sw, Sh为源图像的宽度和高度(因为都是整数,为求得浮点结果,需要进行类型转换)。

将新的Su, Sv代入前面的变换公式和插值公式,可以导出新的插值公式:

因为:

b = 1 – x * Sw % Dw / ( double )Dw;  n = 1 – y * Sh % Dh / ( double )Dh

设:

B = Dw – x * Sw % Dw; N = Dh – y * Sh % Dh

则:

b = B / ( double )Dw; n = N / ( double )Dh

用整数的B,N代替浮点的b, n,转换插值公式:

P = ( B * N * ( PA – PB – PC + PD ) + Dw * N * PB + DH * B * PC + ( Dw * Dh – Dh * B – Dw * N ) * PD ) / ( double )( Dw * Dh )

这里最终结果P是浮点数,对其四舍五入即可得到结果。为完全消除浮点数,可以用这样的方法进行四舍五入:

P = ( B * N … * PD + Dw * Dh / 2 ) / ( Dw * Dh )

这样,P就直接是四舍五入后的整数值,全部的计算都是整数运算了。

Delphi实现:

procedure StretchLinear(Dest, Src: TBitmap); // 仅适用于24位色的位图处理
var
  sw, sh, dw, dh, B, N, x, y, i, j, k, nPixelSize: DWord;
  pLinePrev, pLineNext, pDest, pA, pB, pC, pD: PByte;
begin
  sw := Src.Width -1;
  sh := Src.Height -1;
  dw := Dest.Width -1;
  dh := Dest.Height -1;
  nPixelSize := 3;       //GetPixelSize(Dest.PixelFormat)
  for i := 0 to dh do begin
    pDest := Dest.ScanLine[i];
    y := i * sh div dh;
    N := dh - i * sh mod dh;
    pLinePrev := Src.ScanLine[y];
    Inc(y);
    if N = dh then begin
      pLineNext := pLinePrev;
    end else begin
      pLineNext := Src.ScanLine[y];
    end;
    for j := 0 to dw do begin
      x := j * sw div dw * nPixelSize;
      B := dw - j * sw mod dw;
      pA := pLinePrev;
      Inc(pA, x);
      pB := pA;
      Inc(pB, nPixelSize);
      pC := pLineNext;
      Inc(pC, x);
      pD := pC;
      Inc(pD, nPixelSize);
      if B = dw then begin
        pB := pA;
        pD := pC;
      end;
      for k := 0 to nPixelSize -1 do begin
        pDest^ := Byte(DWord( (B * N * DWord(pA^ - pB^ - pC^ + pD^) + dw * N * pB^
                              + dh * B * pC^ + (dw * dh - dh * B - dw * N)* pD^
                              + dw * dh div 2) div (dw * dh) ));
        Inc(pDest);
        Inc(pA);
        Inc(pB);
        Inc(pC);
        Inc(pD);
      end;
    end;
  end;
end;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言中的双线性插值算法可以用于图像缩放。它是一种高质量的图像缩放算法,可以在不失真的情况下对图像进行缩放,保持图像的细节和清晰度。 双线性插值算法的基本思想是,在进行图像缩放时,通过对原始图像中的像素进行插值来获得新的像素值。具体来说,双线性插值算法使用了周围4个像素的灰度值来计算新的像素值。这四个像素的灰度值分别位于目标像素的左上角、右上角、左下角和右下角。 具体实现时,双线性插值算法先将原始图像缩放到目标大小,然后通过计算每个目标像素在原始图像中的位置来确定需要用哪四个像素进行插值。最后,通过线性插值计算出新的像素值。 以下是C语言中实现线性插值算法的伪代码: ``` for (int y = 0; y < target_height; y++) { for (int x = 0; x < target_width; x++) { // 计算目标像素在原始图像中的位置 float src_x = (x + 0.5) * scale_x - 0.5; float src_y = (y + 0.5) * scale_y - 0.5; int src_x1 = floor(src_x); int src_y1 = floor(src_y); int src_x2 = src_x1 + 1; int src_y2 = src_y1 + 1; // 计算权重 float weight_x1 = src_x2 - src_x; float weight_x2 = src_x - src_x1; float weight_y1 = src_y2 - src_y; float weight_y2 = src_y - src_y1; // 获取四个像素的灰度值 int pixel1 = get_pixel(src_x1, src_y1); int pixel2 = get_pixel(src_x2, src_y1); int pixel3 = get_pixel(src_x1, src_y2); int pixel4 = get_pixel(src_x2, src_y2); // 计算新的像素值 int new_pixel = (int)(pixel1 * weight_x1 * weight_y1 + pixel2 * weight_x2 * weight_y1 + pixel3 * weight_x1 * weight_y2 + pixel4 * weight_x2 * weight_y2); set_pixel(x, y, new_pixel); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值