2D照片的线性变换

时间:2017-06-24 18:31:06

标签: matrix algorithm

前言:我不确定是否将其归类为更多的数学问题或编程问题,而且我是线性代数中的一个菜鸟并且这样做是为了一个爱好。非常感谢任何帮助。

假设我有一些任意编程语言没有任何已建立的机制来对位图图像(或任何x,y值的任意集合)执行线性变换。假设我想执行一些任意的旋转,缩放和翻译。

现在我将迭代每个x,y,获取像素颜色,然后对其执行变换,舍入到最近的新x,y以将我的像素颜色值复制到,然后生成最终图像。它适用于我已经预先计算的简单旋转,但是在i5下面的TransformImage方法中需要几秒钟来计算,所以我想知道什么是更快的方式?

这是我目前使用C#进行测试的方法:

        Color[,] BackupOriginal = OriginalColorData.Clone() as Color[,];
        float angle = 25.0f;
        float angleRadians = (float)(angle * (Math.PI / 180f));
        float cosAngle = (float)Math.Cos(angleRadians);
        float sinAngle = (float)Math.Sin(angleRadians);
        BackupOriginal = LinearTransformation.TransformImage(BackupOriginal, new float[2, 2] {
            {cosAngle,-1f * sinAngle},
            {sinAngle,cosAngle}
        });

...

    public static Color[,] TransformImage(Color[,] originalImage, float[,] transformationMatrix)
    {
        if (transformationMatrix.GetUpperBound(1) < 1 || transformationMatrix.GetUpperBound(0) < 1) return null;

        int width = originalImage.GetUpperBound(1) + 1;
        int height = originalImage.GetUpperBound(0) + 1;
        Color[,] newImage = new Color[height, width];
        for (int y=0;y<height;y++)
        {
            for (int x=0;x<width;x++)
            {
                Color currentPixel = originalImage[y, x];
                int newX = (int)Math.Round((x * transformationMatrix[0, 0]) + (y * transformationMatrix[0, 1]));
                int newY = (int)Math.Round((x * transformationMatrix[1, 0]) + (y * transformationMatrix[1, 1]));
                if (IsValidPixel(newX, newY, width, height))
                    newImage[newY, newX] = currentPixel;
            }
        }
        return newImage;
    }

1 个答案:

答案 0 :(得分:0)

基本上,这是一种您不希望以任意语言自己实现它的操作。您需要考虑几个问题:

  1. 所有边界检查都会导致一些费用。 C#将检查所有数组索引的限制,所以你也是。对于每个像素。如果幸运的话,JIT编译器可以解决其中的一些问题,但它仍然很昂贵。 2D阵列比更常见的1D情况更昂贵,特别是在C#,Why are multi-dimensional arrays in .NET slower than normal arrays?
  2. 浮点和整数之间的来回转换成本很高。您将int转换为float四次并浮动到int两次。每像素。
  3. 我们可以在这里停下来,只考虑你现在为每个像素做的操作次数,超出了琐碎的任务。

    然而,更重要的是:

    1. 记忆力不统一。您希望在有些连续的块中读取和写入它。你现在正在以完美的顺序阅读,但根据变化,你正在写所有的地方。这很难针对“任意变换”进行优化,但总体思路是尝试执行适合缓存层次结构的小块。由于变换是线性的,因此一个空间中的任何小块都将非常粗略地位于另一个空间中的类似位置。
    2. 现代CPU非常擅长矢量操作。使用适当的逻辑,可以复制32个字节(8个32位像素)的块,并且计算新的像素位置也是如此。
    3. 然后,您有关于如何处理别名的问题。没有简单的方法可以保证newImage中的每个像素都映射到originalImage中的一个且只有一个像素。您最终可能会多次写入同一像素(您可能真的想要混合结果),而某些像素可能最终为空。

      那么,在一个相当短的代码片段中可以做些什么呢?不是太多。如果你正在寻找性能,我至少会试图摆脱Math.Round(在转换为粗略舍入之前添加.5),理想情况下完全避免浮点数。一个选项是存储整数乘数,然后将结果(>>运算符)移回相同范围,即将cosAngle存储为cosAngle * 65536并将newX移位16根据之前的评论,甚至可能将变换矩阵存储为四个单独的浮点值可能比那里的2D阵列更可取。我希望JIT编译器处理这种情况。

      但是,最终,没有任何魔力。高性能实现往往更长,也倾向于用其他语言编写。相同逻辑的AC或C ++实现可以加速一些事情,但你仍然必须处理特别是内存带宽问题,其中在完整源图像上的顺序循环将给你一个不利的内存访问模式在目标中任意角。那部分你可以测试自己,0的角度明显快于比如.4?

      C实现的大部分加速都来自优化编译器正在积极调整以便为计算密集型循环发出简洁代码,而.NET环境实际上更侧重于业务线应用程序。

      我不认为这里使用Color类型是一个问题,因为.NET结构往往很快,但如果你想评估性能,我可能会尝试使用一个简单的int或float数组灰度值,以了解OO抽象是否会妨碍正确的优化。