对于学校项目,我们的信息学老师希望我们重新发明轮子。我们给出了一个数组,表示图像的像素,包含在另一个脚本中定义的颜色对象。它们代表一组4个整数,红色,绿色,蓝色和Alpha值的值为0到255。现在我们必须对此数组进行图像处理的标准操作。我们被明确告知,使用互联网和问题网站就像堆栈溢出一样供参考。
我没有办法:如何将给定的Color-Object-Array转换为另一个表示相同Image的Array,但旋转x度(带扩展)。新的颜色/像素落在哪里,以及如何计算?如何计算这个数组的新大小? 是否有任何易于理解的PDF格式,我可以通过,理解,如何,等等。 PIL image.rotate(expand = true)算法在理论上有效,或者任何人都可以提出解释如何做到这一点?我很欣赏伪代码或python 3,因为它是我理解的唯一编程语言。
此类数组的简短示例:
BLUE = Colour(0 ,0 ,255,255)
BLACK = Colour(0 ,0 ,0 ,255)
WHITE = Colour(255,255,255,255)
Array = [ [BLUE , BLACK, WHITE, BLUE ],
[BLACK, BLACK, BLUE , WHITE],
[WHITE, WHITE, BLUE , WHITE] ]
编辑:要访问颜色值,有方法getred(),getgreen(),getblue()和gettuple() - 我已经实现了“painters”算法,意思是颜色可以是通过调用merge(bottomColour,topColour)合并,这将返回结果颜色,如果一个放在另一个上面。理论可以在这里找到: Determine RGBA colour received by combining two colours
我们不允许使用numpy或任何其他模块或库。没有Color / Pixel的地方应该是'None'。
提前致谢!
答案 0 :(得分:3)
我们需要将旋转图像中的每个坐标映射到原始对应的坐标。
假设绕(a, b)
旋转并逆时针旋转θ
度:
(x, y)
位于原始图片中,(x', y')
位于原始图片中。
简单技术:最近邻居
当使用计算的坐标对像素数据进行采样时,我们可以简单地将它们舍入到最接近的整数(即最近的像素)。这给出了以下结果:
乍一看这看起来不错,但网页重新缩放+图像压缩模糊了边缘。放大视图显示生成的图像具有令人讨厌的锯齿状边缘(锯齿):
过滤:双线性近似
为了改善这一点,我们需要意识到旋转的像素"区域实际上覆盖了原始图像中的多个像素:
然后我们可以将平均像素颜色计算为每个被覆盖的原始像素的贡献之和,并通过它们的相对面积加权。让我们称之为“各向异性”"过滤以方便(不是这个术语的确切含义,但我能想到的最接近)。
但是这些区域很难准确计算。所以我们可以"欺骗"稍微通过应用近似值,其中旋转的样本区域(红色)与网格线对齐:
这使得区域更容易计算。我们将使用一阶线性平均法 - "双线性"过滤
C#代码示例:
Transform trn = new Transform(a, cx, cy); // inverse rotation transform to original image space
for (int y = 0; y < h; y++)
{
for (int x = 0; x < w; x++)
{
Vector v = trn.Get((float)x, (float)y);
int i = (int)Math.Floor(v.x),
j = (int)Math.Floor(v.y);
float s = v.x - (float)i,
t = v.y - (float)j;
RGB c = RGB.Black, u; float z, r = 0.0f;
if ((u = src.getPixel(i, j)).Valid)
{
z = (1 - s) * (1 - t); // area of overlap in top-left covered pixel
c += u * z; r += z; // add to total color and total area
}
if ((u = src.getPixel(i + 1, j)).Valid)
{
z = s * (1 - t);
c += u * z; r += z;
}
if ((u = src.getPixel(i, j + 1)).Valid)
{
z = (1 - s) * t;
c += u * z; r += z;
}
if ((u = src.getPixel(i + 1, j + 1)).Valid)
{
z = s * t;
c += u * z; r += z;
}
if (r > 0.0f)
dst.setPixel(x, y, c * (1.0f / r)); // normalize the sum by total area
}
}
放大结果:
很多比天真的最近邻法更好!
OCD警报!
出于好奇,我实施了完整的&#34;各向异性&#34;之前提到的方法。采取比它应该更长的方式,并且不是非常有效(使用Sutherland-Hodgman剪裁来计算旋转像素区域和每个网格像素之间的交叉区域)。计算时间通过屋顶 - 大约7秒,而双线性方法小于0.5。最终的结果?根本不值得努力!
(L:双线性,R:各向异性)
代码(我的实现是垃圾,不要太费力阅读它,真的):
private static Vector[][] clipboxes = new Vector[][] {
new Vector[] { new Vector(-1f,-1f), new Vector(0f,-1f), new Vector(0f,0f), new Vector(-1f,0f)},
new Vector[] { new Vector(0f,-1f), new Vector(1f,-1f), new Vector(1f,0f), new Vector(0f,0f)},
new Vector[] { new Vector(1f,-1f), new Vector(2f,-1f), new Vector(2f,0f), new Vector(1f,0f)},
new Vector[] { new Vector(-1f,0f), new Vector(0f,0f), new Vector(0f,1f), new Vector(-1f,1f)},
new Vector[] { new Vector(0f,0f), new Vector(1f,0f), new Vector(1f,1f), new Vector(0f,1f)},
new Vector[] { new Vector(1f,0f), new Vector(2f,0f), new Vector(2f,1f), new Vector(1f,1f)},
new Vector[] { new Vector(-1f,1f), new Vector(0f,1f), new Vector(0f,2f), new Vector(-1f,2f)},
new Vector[] { new Vector(0f,1f), new Vector(1f,1f), new Vector(1f,2f), new Vector(0f,2f)},
new Vector[] { new Vector(1f,1f), new Vector(2f,1f), new Vector(2f,2f), new Vector(1f,2f)}
};
private static bool inside(Vector a, Vector b, Vector c)
{
return ((c - b) ^ (a - b)) > 0f;
}
private static Vector intersect(Vector a, Vector b, Vector c, Vector d)
{
return (((c - d) * (a ^ b)) - ((a - b) * (c ^ d))) * (1.0f / ((a - b) ^ (c - d)));
}
private static float getArea(List<Vector> l)
{
if (l.Count == 0)
return 0f;
float sum = 0.0f;
Vector b = l.Last();
foreach (Vector c in l)
{
sum += b ^ c;
b = c;
}
return 0.5f * Math.Abs(sum);
}
private static float getOverlap(Vector[] clip, Vector[] box)
{
List<Vector> lO = box.ToList();
Vector lC = clip[clip.Length - 1];
foreach (Vector C in clip)
{
if (lO.Count == 0)
return 0.0f;
List<Vector> lI = lO;
Vector lB = lI.Last();
lO = new List<Vector>();
foreach (Vector B in lI)
{
if (inside(B, lC, C))
{
if (!inside(lB, lC, C))
lO.Add(intersect(lB, B, lC, C));
lO.Add(B);
}
else
if (inside(lB, lC, C))
lO.Add(intersect(lB, B, lC, C));
lB = B;
}
lC = C;
}
return getArea(lO);
}
// image processing code, as before
Transform trn = new Transform(a, cx, cy);
for (int y = 0; y < h; y++)
{
for (int x = 0; x < w; x++)
{
Vector p = trn.Get((float)x, (float)y);
int i = p.X, j = p.Y;
Vector d = new Vector(i, j);
List<Vector> r = new List<Vector>();
r.Add(p - d);
r.Add(trn.Get((float)(x+1), (float)y) - d);
r.Add(trn.Get((float)(x+1), (float)(y+1)) - d);
r.Add(trn.Get((float)x, (float)(y+1)) - d);
RGB c = RGB.Black;
float t = 0.0f;
for (int l = 0; l < 3; l++)
{
for (int m = 0; m < 3; m++)
{
float area = getOverlap(clipboxes[m * 3 + l], r.ToArray());
if (area > 0.0f)
{
RGB s = src.getPixel(i + l - 1, j + m - 1);
if (s.Valid)
{
c += s * area;
t += area;
}
}
}
}
if (t > 0.0f)
dst.setPixel(x, y, c * (1.0f / t));
}
}
有更先进的技术,例如使用傅里叶变换 - 参见达斯茅斯大学的这篇论文:http://www.cs.dartmouth.edu/reports/TR96-301.pdf。而且,我们可以使用更高的顺序,而不是双线性插值。 bicubic,可以提供更平滑的结果。
答案 1 :(得分:0)
至于展示位置,请尝试进行后退转换。让每个结果像素正方形由其中心点表示。对于每个这样的点,让我们找到属于源点的位置。找到哪个像素方块属于该源点,并且 - 瞧 - 您有源方块和源组颜色。
请注意,使用直接顺序,从源到结果,如果要获得没有孔或重叠的图片,则必须找到结果转换像素的区域。这很复杂。另一方面,逆转任务可以快速而简单地完成。
不要忘记计算一次正弦和余弦,并将它们用于每一点计算。