我正在使用 GD 库动态创建图像。
但是当我使用 imagerotate() 函数旋转图像时
,它可以正常工作,但它会产生非常恼人的
旋转图像粗糙边缘。
如图所示。
那么如何使旋转图像的这些边/边缘平滑?
3 回答
避免旋转图像时出现锯齿效果的一种方法是使用另一种方法对像素进行采样,而不是仅采用调整后的像素,例如使用最近邻插值使边缘更平滑。您可以查看 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);
取自(这里)。基本上就是在对原始图片中的像素进行采样时,我们对附近的像素进行采样,并对其进行插值得到目标像素值。这样,您无需安装任何其他软件包即可实现您想要的效果。
编辑
我发现了一些我曾经用 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;
}
这听起来可能有点骇人听闻,但这是最简单的方法,甚至大型企业解决方案也使用它。
诀窍是首先将图像创建为所需大小的 2 倍,然后执行所有绘图调用,然后将其调整为所需的原始大小。
它不仅真的很容易做到,而且它的速度也很快,并且产生了非常好的结果。当我需要对边缘应用模糊时,我会在所有情况下使用这个技巧。
另一个优点是它不包括图像其余部分的模糊,并且保持清晰 - 只有旋转图像的边界得到平滑。
您可以尝试的一件事是使用imageantialias()
平滑边缘。
如果这不能满足您的需求,GD 本身可能还不够。
GD 对它的所有能力都使用了非常快速的方法,而没有任何实际的平滑或类似的东西。如果您想要一些适当的图像编辑,您可以查看ImageMagick(这需要服务器上的额外软件)或基于 GD 编写自己的函数。
但请记住,对于大量数据,php 真的很慢,所以编写自己的函数可能会令人失望。(根据我的经验,PHP 比编译代码慢大约 40 倍。)
我建议将ImageMagick用于任何结果质量很重要的图像工作。