将二维数组中的像素数据旋转x度(伪代码或Python 3)

将二维数组中的像素数据旋转x度(伪代码或Python 3),python,arrays,algorithm,image-processing,language-agnostic,Python,Arrays,Algorithm,Image Processing,Language Agnostic,对于一个学校项目,我们的信息学老师希望我们重新发明轮子。我们已经给出了一个数组,表示图像的像素,包含在另一个脚本中定义的彩色对象。它们表示一组4个整数,红色、绿色、蓝色和Alpha值为0到255。现在我们必须在这个阵列上执行图像处理的标准操作。我们被明确告知,要使用互联网和质疑网站的堆栈溢出作为参考 对此我没有办法:如何将一个给定的彩色对象数组转换为另一个表示相同图像的数组,但旋转x度(扩展)。新的颜色/像素在哪里,如何计算?如何计算此数组的新大小? 是否有任何易于理解的pdf,我可以通过,来理

对于一个学校项目,我们的信息学老师希望我们重新发明轮子。我们已经给出了一个数组,表示图像的像素,包含在另一个脚本中定义的彩色对象。它们表示一组4个整数,红色、绿色、蓝色和Alpha值为0到255。现在我们必须在这个阵列上执行图像处理的标准操作。我们被明确告知,要使用互联网和质疑网站的堆栈溢出作为参考

对此我没有办法:如何将一个给定的彩色对象数组转换为另一个表示相同图像的数组,但旋转x度(扩展)。新的颜色/像素在哪里,如何计算?如何计算此数组的新大小? 是否有任何易于理解的pdf,我可以通过,来理解,例如,PIL image.rotate(expand=true)算法在理论上是如何工作的,或者有人能想出一个解释如何做到这一点吗?我会喜欢伪代码或Python3,因为它是我唯一理解的编程语言

此类阵列的简短示例:

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(bottomcolor、topColour)来合并颜色。如果一种颜色放在另一种颜色的顶部,则返回结果颜色。这方面的理论可以在这里找到:

我们不允许使用numpy或任何其他模块或库。没有颜色/像素的位置应为“无”


非常感谢

至于位置,尝试进行反向变换。让每个结果像素正方形由其中心点表示。对于每个这样的点,让我们找到源点的位置。找到这个源点所属的像素方块,瞧,你有了这个源方块和一组颜色

请注意,使用从源到结果的直线顺序,如果要获得没有孔或重叠的图片,必须找到结果变换像素的区域。这是非常复杂的。另一方面,反向任务可以快速而简单地完成


不要忘记计算正弦和余弦一次,并将其用于每个点的计算。

我们需要将旋转图像中的每个坐标映射到原始图像中相应的坐标

假设绕
(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
    }
}
Transform trn=新变换(a、cx、cy);//原始图像空间的逆旋转变换
对于(int y=0;y0.0f)
dst.setPixel(x,y,c*(1.0f/r));//按总面积标准化总和
}
}
放大结果:

比天真的最近邻法要好得多


强迫症警报

出于好奇,我实现了前面提到的完整的“各向异性”方法。所花费的时间比它应该花费的时间长,而且效率也不高(使用Sutherland-Hodgman剪裁来计算旋转像素区域和每个网格像素之间的相交区域)。计算时间非常长,大约7秒,而双线性方法的计算时间不到0.5秒。最终结果如何?一点也不值得努力

(L:双线性,R:各向异性)

代码(我的实现是垃圾,真的不用费心去读):

私有静态向量[][]剪贴簿=新向量[][]{
新向量[]{新向量(-1f,-1f),新向量(0f,-1f),新向量(0f,0f),新向量(-1f,0f)},
新向量[]{新向量(0f,-1f),新向量(1f,-1f),新向量到
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));
        }
    }