对位图执行数组操作

Performing array operations on a Bitmap

我正在尝试将一些代码从旧的 python 项目迁移到当前的 C# 项目中。

我尝试改编的 python 代码将 sobel 滤镜应用于每个 RGB 颜色通道图像,然后使用一些基本的线性代数将通道结果拼接成孤立边缘的 灰度 图像。

我 运行 遇到的问题是 python 可以很容易地将图像视为简单的数值数组,因此可以对它们进行切片并执行线性代数运算在他们身上随意(C# 在打字方面更加挑剔)。

我需要对从 运行 sobel 滤镜得到的结果进行矩阵乘法,但不清楚如何处理位图变成一种很容易允许这样做的形式。

我看到了关于转换为 byte array 的内容,但我不确定这是否适合我的目的。我真的很喜欢包含像素值的常规 int 或 float array

我可以遍历像素并对它们执行按位操作,但这势必会减慢速度,所以我想知道是否有一种好的方法可以执行矩阵 对位图进行操作或将位图转换为适用于此的形式。

我不确定它究竟有多大用处,但如果它有助于说明我在说什么,这是我的 python 代码:

image_array = numpy.float64(scaled_image)

R_x = scipy.ndimage.filters.correlate(image_array[:, :, 0], [[1, 2, 1], [0, 0, 0], [-1, -2, -1]])
G_x = scipy.ndimage.filters.correlate(image_array[:, :, 1], [[1, 2, 1], [0, 0, 0], [-1, -2, -1]])
B_x = scipy.ndimage.filters.correlate(image_array[:, :, 2], [[1, 2, 1], [0, 0, 0], [-1, -2, -1]])

R_y = scipy.ndimage.filters.correlate(image_array[:, :, 0], [[1, 0 , -1], [2, 0, -2], [1, 0, -1]])
G_y = scipy.ndimage.filters.correlate(image_array[:, :, 1], [[1, 0 , -1], [2, 0, -2], [1, 0, -1]])
B_y = scipy.ndimage.filters.correlate(image_array[:, :, 2], [[1, 0 , -1], [2, 0, -2], [1, 0, -1]])

Jacobian_x = R_x**2 + G_x**2 + B_x**2
Jacobian_y = R_y**2 + G_y**2 + B_y**2
Jacobian_xy = R_x * R_y + G_x * G_y + B_x * B_y
D = numpy.sqrt(numpy.fabs((Jacobian_x**2) - (2 * Jacobian_x * Jacobian_y) + (Jacobian_y**2) + 4 * (Jacobian_xy**2)))
E = (Jacobian_x + Jacobian_y + D) / 2
Edges = numpy.sqrt(E)

到目前为止我使用等效的 C# 代码的情况:

Bitmap newImage = resize.Apply(bmp);
Bitmap RedImage = extractRed.Apply(newImage);
Bitmap GreenImage = extractGreen.Apply(newImage);
Bitmap BlueImage = extractBlue.Apply(newImage);

Bitmap Rx = SobelX.Apply(RedImage);
Bitmap Gx = SobelX.Apply(GreenImage);
Bitmap Bx = SobelX.Apply(BlueImage);

Bitmap Ry = SobelY.Apply(RedImage);
Bitmap Gy = SobelY.Apply(GreenImage);
Bitmap By = SobelY.Apply(BlueImage);

***Where all my math would go.
   Jacobian_x = yadda yadda yadda***

提前感谢任何可以提供帮助的人!

假设您在该示例中使用 System.Drawing.Bitmap,当您引用 Bitmap 时,您将需要使用 Bitmap.LockBits() 方法来获取指向位图中原始位的指针.然后您可以使用不安全代码(更快,但您最好知道自己在做什么)或将位图数据作为字节数组复制到托管内存或从托管内存中复制位图数据以对其进行操作。

这是关于 LockBits() 的官方文档,他们有一个合理的例子(使用安全但较慢的复制到和从托管内存技术) https://docs.microsoft.com/en-us/dotnet/api/system.drawing.bitmap.lockbits?view=netframework-4.8

您需要使用 C# 进行较低的级别并自己编写相关性。

首先,您必须检索 colors for each pixel in the Bitmap,并构建包含所有信息的矩阵。

Bitmap.GetPixel(Int32, Int32)

Gets the color of the specified pixel in this Bitmap.

public System.Drawing.Color GetPixel (int x, int y);

然后将矩阵相关并相乘。为了实现这一点,您可以参考 third-part library(就像 Python 世界中的 NumPy)或手动完成。

using System;
using System.Drawing;
using System.Windows.Forms;

static class Util {
    public static Color[][] ExtractColorArrayFrom(Bitmap bm)
    {
        int height = bm.Height;
        int width = bm.Width;
        var toret = new Color[height][];
        
        for(int i = 0; i < height; ++i) {
            toret[ i ] = new Color[ width ];
            
            for(int j = 0; j < width; ++j) {
                toret[ i ][ j ] = bm.GetPixel( i, j );
            }
        }
        
        return toret;
    }
    
    public static int[] ARGBFrom(Color c)
    {
        return new int[]{ c.A, c.R, c.G, c.B };
    }
}

因此,Tim 上面关于锁定位的回答作为此类问题的通用解决方案非常有用。然而,由于我不会在这里讨论的原因,因为问题是与我的一些外部依赖项的非常具体的兼容性问题,所以它最终没有为我工作。我最终做了我说过我不想做的事,并循环浏览图像来做我的数学。它最终仍然有效,并且没有使程序减慢用户注意到的程度。然而,这通常不是真的,因为优化速度不是这个特定应用程序的首要任务,而是执行类似操作的许多其他应用程序的首要任务。我最终创建了与我的图像数组大小相同的双精度数组,然后 运行 我对这些数组进行了数学运算。完成所有数学运算后,我使用 SetPixel 遍历空白图像并将这些像素值应用于新位图。

Bitmap RedImage = extractRed.Apply(newImage);
Bitmap GreenImage = extractGreen.Apply(newImage);
Bitmap BlueImage = extractBlue.Apply(newImage);

Bitmap Rx = SobelX.Apply(RedImage);
Bitmap Gx = SobelX.Apply(GreenImage);
Bitmap Bx = SobelX.Apply(BlueImage);

Bitmap Ry = SobelY.Apply(RedImage);
Bitmap Gy = SobelY.Apply(GreenImage);
Bitmap By = SobelY.Apply(BlueImage);
double[,] JacobianX = new double[Rx.Width, Rx.Height];
double[,] JacobianY = new double[Rx.Width, Rx.Height];
double[,] JacobianXY = new double[Rx.Width, Rx.Height];
double[,] Determinant = new double[Rx.Width, Rx.Height];
double[,] E = new double[Rx.Width, Rx.Height];
Bitmap Edges = new Bitmap(Rx.Width, Rx.Height);

for (int i = 1; i < Rx.Width-1; i++)
    {
    for (int j = 1; j < Rx.Height-1; j++)
        {
        Color redX = Rx.GetPixel(i, j);
        Color greenX = Gx.GetPixel(i, j);
        Color blueX = Bx.GetPixel(i, j);

        Color redY = Ry.GetPixel(i, j);
        Color greenY = Gy.GetPixel(i, j);
        Color blueY = By.GetPixel(i, j);

        JacobianX[i, j] = Math.Pow(redX.R, 2) + Math.Pow(greenX.G, 2) + Math.Pow(blueX.B, 2);
        JacobianY[i, j] = Math.Pow(redY.R, 2) + Math.Pow(greenY.G, 2) + Math.Pow(blueY.B, 2);
        JacobianXY[i, j] = redX.R * redY.R + greenX.G * greenY.G + blueX.B * blueY.B;
        D[i, j] = Math.Sqrt(Math.Abs(Math.Pow(JacobianX[i, j], 2) - (2 * JacobianX[i, j] * JacobianY[i, j]) + (Math.Pow(JacobianY[i, j], 2)) + 4 * Math.Pow(JacobianXY[i, j], 2)));
        E[i, j] = (JacobianX[i, j] + JacobianY[i, j] + D[i, j]) / 2;

        if (Math.Sqrt(E[i, j]) > 255) { E[i, j] = Math.Pow(255,2); }

        Color newcolor = Color.FromArgb(255, (int)Math.Sqrt(E[i, j]), (int)Math.Sqrt(E[i, j]), (int)Math.Sqrt(E[i, j]));
        Edges.SetPixel(i, j, newcolor);
    }
}

这是一个相当显着的改进:

Sobel on Grayscale(默认使用 Aforge 库)

VS.

Sobel on Color(使用上面的代码)