去除/平滑gd库中的粗糙边缘

时间:2012-08-12 21:09:46

标签: php gd

我正在使用GD库动态创建图像 但是当我使用imagerotate()函数旋转图像时 它工作正常,但它给图像带来了非常刺激性的粗糙边缘 旋转。
如图所示。
那么如何使旋转图像的这些边/边平滑?
enter image description here

3 个答案:

答案 0 :(得分:6)

避免在旋转图像时获取Jaggies effect的一种方法是使用另一种方式对像素进行采样,而不仅仅是调整像素,例如使用Nearest-neighbor interpolation使边缘更平滑。你可以看到matlab代码示例:

im1 = imread('lena.jpg');imshow(im1);  
[m,n,p]=size(im1);
thet = rand(1);
mm = m*sqrt(2);
nn = n*sqrt(2);
for t=1:mm
   for s=1:nn
      i = uint16((t-mm/2)*cos(thet)+(s-nn/2)*sin(thet)+m/2);
      j = uint16(-(t-mm/2)*sin(thet)+(s-nn/2)*cos(thet)+n/2);
      if i>0 && j>0 && i<=m && j<=n           
         im2(t,s,:)=im1(i,j,:);
      end
   end
end
figure;
imshow(im2);

取自(here)。基本上,这意味着在对原始图片中的像素进行采样时,我们对近像素进行采样并对其进行插值以获得目标像素值。这条路  你可以通过安装任何附加包来实现你想要的。

修改

我发现了一些我曾用Java编写的旧代码,其中包含几个采样算法的实现。这是代码:

最近邻采样器:

/**
 * @pre (this!=null) && (this.pixels!=null)
 * @post returns the sampled pixel of (x,y) by nearest neighbor sampling
 */
private Pixel sampleNearestNeighbor(double x, double y) {
    int X = (int) Math.round(x);
    int Y = (int) Math.round(y);
    if (X >= 0 && Y >= 0 && X < this.pixels.length
            && Y < this.pixels[0].length)
        // (X,Y) is within this.pixels' borders
        return new Pixel(pixels[X][Y].getRGB());
    else
        return new Pixel(255, 255, 255);
    // sample color will be default white
}

双线性采样器:

/**
 * @pre (this!=null) && (this.pixels!=null)
 * @post returns the sampled pixel of (x,y) by bilinear interpolation
 */
private Pixel sampleBilinear(double x, double y) {
    int x1, y1, x2, y2;
    x1 = (int) Math.floor(x);
    y1 = (int) Math.floor(y);
    double weightX = x - x1;
    double weightY = y - y1;
    if (x1 >= 0 && y1 >= 0 && x1 + 1 < this.pixels.length
            && y1 + 1 < this.pixels[0].length) {
        x2 = x1 + 1;
        y2 = y1 + 1;

        double redAX = (weightX * this.pixels[x2][y1].getRed())
                + (1 - weightX) * this.pixels[x1][y1].getRed();
        double greenAX = (weightX * this.pixels[x2][y1].getGreen())
                + (1 - weightX) * this.pixels[x1][y1].getGreen();
        double blueAX = (weightX * this.pixels[x2][y1].getBlue())
                + (1 - weightX) * this.pixels[x1][y1].getBlue();
        // bilinear interpolation of A point

        double redBX = (weightX * this.pixels[x2][y2].getRed())
                + (1 - weightX) * this.pixels[x1][y2].getRed();
        double greenBX = (weightX * this.pixels[x2][y2].getGreen())
                + (1 - weightX) * this.pixels[x1][y2].getGreen();
        double blueBX = (weightX * this.pixels[x2][y2].getBlue())
                + (1 - weightX) * this.pixels[x1][y2].getBlue();
        // bilinear interpolation of B point

        int red = (int) (weightY * redBX + (1 - weightY) * redAX);
        int green = (int) (weightY * greenBX + (1 - weightY) * greenAX);
        int blue = (int) (weightY * blueBX + (1 - weightY) * blueAX);
        // bilinear interpolation of A and B
        return new Pixel(red, green, blue);

    } else if (x1 >= 0
            && y1 >= 0 // last row or column
            && (x1 == this.pixels.length - 1 || y1 == this.pixels[0].length - 1)) {
        return new Pixel(this.pixels[x1][y1].getRed(), this.pixels[x1][y1]
                .getGreen(), this.pixels[x1][y1].getBlue());
    } else
        return new Pixel(255, 255, 255);
    // sample color will be default white
}

高斯采样器:

/**
 * @pre (this!=null) && (this.pixels!=null)
 * @post returns the sampled pixel of (x,y) by gaussian function
 */
private Pixel sampleGaussian(double u, double v) {
    double w = 3; // sampling distance
    double sqrSigma = Math.pow(w / 3.0, 2); // sigma^2
    double normal = 0;
    double red = 0, green = 0, blue = 0;
    double minIX = Math.round(u - w);
    double maxIX = Math.round(u + w);
    double minIY = Math.round(v - w);
    double maxIY = Math.round(v + w);

    for (int ix = (int) minIX; ix <= maxIX; ix++) {
        for (int iy = (int) minIY; iy <= maxIY; iy++) {
            double sqrD = Math.pow(ix - u, 2) + Math.pow(iy - v, 2);
            // squared distance between (ix,iy) and (u,v)
            if (sqrD < Math.pow(w, 2) && ix >= 0 && iy >= 0
                    && ix < pixels.length && iy < pixels[0].length) {
                // gaussian function
                double gaussianWeight = Math.pow(2, -1 * (sqrD / sqrSigma));
                normal += gaussianWeight;
                red += gaussianWeight * pixels[ix][iy].getRed();
                green += gaussianWeight * pixels[ix][iy].getGreen();
                blue += gaussianWeight * pixels[ix][iy].getBlue();
            }
        }
    }
    red /= normal;
    green /= normal;
    blue /= normal;
    return new Pixel(red, green, blue);
}

实际轮播:

     /**
 * @pre (this!=null) && (this.pixels!=null) && (1 <= samplingMethod <= 3)
 * @post creates a new rotated-by-degrees Image and returns it
 */
public myImage rotate(double degrees, int samplingMethod) {
    myImage outputImg = null;

    int t = 0;
    for (; degrees < 0 || degrees >= 180; degrees += (degrees < 0) ? 180
            : -180)
        t++;

    int w = this.pixels.length;
    int h = this.pixels[0].length;
    double cosinus = Math.cos(Math.toRadians(degrees));
    double sinus = Math.sin(Math.toRadians(degrees));

    int width = Math.round((float) (w * Math.abs(cosinus) + h * sinus));
    int height = Math.round((float) (h * Math.abs(cosinus) + w * sinus));

    w--;
    h--; // move from (1,..,k) to (0,..,1-k)

    Pixel[][] pixelsArray = new Pixel[width][height];
    double x = 0; // x coordinate in the source image
    double y = 0; // y coordinate in the source image

    if (degrees >= 90) { // // 270 or 90 degrees turn
        double temp = cosinus;
        cosinus = sinus;
        sinus = -temp;
    }

    for (int i = 0; i < width; i++) {
        for (int j = 0; j < height; j++) {

            double x0 = i;
            double y0 = j;
            if (degrees >= 90) {
                if ((t % 2 == 1)) { // 270 degrees turn
                    x0 = j;
                    y0 = width - i - 1;
                } else { // 90 degrees turn
                    x0 = height - j - 1;
                    y0 = i;
                }
            } else if (t % 2 == 1) { // 180 degrees turn
                x0 = width - x0 - 1;
                y0 = height - y0 - 1;
            }

            // calculate new x/y coordinates and
            // adjust their locations to the middle of the picture
            x = x0 * cosinus - (y0 - sinus * w) * sinus;
            y = x0 * sinus + (y0 - sinus * w) * cosinus;

            if (x < -0.5 || x > w + 0.5 || y < -0.5 || y > h + 0.5)
                // the pixels that does not have a source will be painted in
                // default white
                pixelsArray[i][j] = new Pixel(255, 255, 255);
            else {
                if (samplingMethod == 1)
                    pixelsArray[i][j] = sampleNearestNeighbor(x, y);
                else if (samplingMethod == 2)
                    pixelsArray[i][j] = sampleBilinear(x, y);
                else if (samplingMethod == 3)
                    pixelsArray[i][j] = sampleGaussian(x, y);
            }
        }
        outputImg = new myImage(pixelsArray);
    }

    return outputImg;
}

答案 1 :(得分:2)

这可能听起来相当黑客,但这是最简单的方法,甚至大型企业解决方案都使用它。

诀窍是首先创建所需大小的2X图像,然后进行所有绘图调用,然后将其调整为所需的原始大小。

它不仅非常容易实现,而且它的速度也很快,并且可以产生非常好的效果。当我需要对边缘应用模糊时,我会在所有情况下使用此技巧。

另一个优点是它不会在图像的其余部分包含模糊,并且保持清晰 - 只有旋转图像的边框才能变得平滑。

答案 2 :(得分:0)

您可以尝试的一件事是使用imageantialias()来平滑边缘。

如果这不符合您的需求,GD本身可能就不够了。

GD使用非常快速的方法来实现所有它的实际平滑或任何相关的东西。如果你想要一些正确的图像编辑,你可以查看ImageMagick(这需要服务器上的额外软件)或者根据GD编写你自己的函数。

请记住,PHP的数据量非常大,因此编写自己的函数可能会令人失望。 (根据我的经验,PHP比编译代码慢大约40倍。)

我建议将ImageMagick用于结果质量很重要的任何图像处理。