更快地将位图与自定义算法结合起来?

时间:2013-12-08 23:41:36

标签: c# .net algorithm image-processing

我正在尝试将2张图片与某种算法结合起来。但在目前的状态下,它太慢了。组合两个512x512图像大约需要70毫秒。这是可以的,但是一旦图像变大,组合它们所花费的时间就会增加。

这是c#(Fast work with Bitmaps in C#

中的代码
var t = new Vec3f(0);
var u = new Vec3f(0);
var r = new Vec3f(0);

for (int i = 0; i < bData1.Height; ++i)
{
    for (int j = 0; j < bData1.Width; ++j)
    {
        byte* dataBase = bData1Scan0Ptr + i * bData1.Stride + j * m_BitsPerPixel / 8;
        byte* dataDetail = bData2Scan0Ptr + i * bData2.Stride + j * m_BitsPerPixel / 8;

        byte* dataCombined = bDataCombinedScan0Ptr + i * bDataCombined.Stride + j * m_BitsPerPixel / 8;

        t.x = (dataBase[2] / 255.0f) * 2.0f - 1.0f;
        t.y = (dataBase[1] / 255.0f) * 2.0f - 1.0f;
        t.z = (dataBase[0] / 255.0f) * 2.0f;

        u.x = (dataDetail[2] / 255.0f) * -2.0f + 1.0f;
        u.y = (dataDetail[1] / 255.0f) * -2.0f + 1.0f;
        u.z = (dataDetail[0] / 255.0f) * 2.0f - 1.0f;

        r = t * t.Dot(u) - u * t.z;

        r.Normalize();

        //Write data to our new bitmap
        dataCombined[2] = (byte)Math.Round((r.x * 0.5f + 0.5f) * 255.0f);
        dataCombined[1] = (byte)Math.Round((r.y * 0.5f + 0.5f) * 255.0f);
        dataCombined[0] = (byte)Math.Round((r.z * 0.5f + 0.5f) * 255.0f);

        m_VectorImageArray[index, i, j] = t;    //base
        m_VectorImageArray[index + 1, i, j] = u;  //detail
        m_VectorImageArray[index + 2, i, j] = r;  //Combined
    }
}

m_CombinedBitmap.UnlockBits(bDataCombined);

因为我想加快速度,我还尝试制作一个c ++ dll并使用DLLImport将其加载到我的C#项目中。我已经实现了这个矢量类(http://fastcpp.blogspot.co.uk/2011/12/simple-vector3-class-with-sse-support.html),认为它会带来显着的速度增益,但遗憾的是它只能快了~10ms。

我想让它更快,因为我想实时更新图像(循环存储在m_VectorImageArray中的向量)。

问题与读取/写入位图无关,而是与算法本身有关。我不认为我可以使用parallel.for因为像素需要完全相同的顺序,或者这毕竟是可能的?

4 个答案:

答案 0 :(得分:3)

我减少了每次迭代中执行的乘法和除法的次数,所以我想它应该更快一些。 未经过测试

var t = new Vec3f(0);
var u = new Vec3f(0);
var r = new Vec3f(0);

int xIncr = m_BitsPerPixel / 8;
byte* dataBase = bData1Scan0Ptr;
byte* dataDetail = bData2Scan0Ptr;
byte* nextBase = dataBase + bData1.Stride;
byte* nextDetail = dataDetail + bData2.Stride;

byte* dataCombined = bDataCombinedScan0Ptr;
byte* nextCombined = dataCombined + bDataCombined.Stride;

for (int y = 0; y < bData1.Height; ++y)
{
    for (int x = 0; x < bData1.Width; ++x)
    {
        t.x = (dataBase[2] / 255.0f) * 2.0f - 1.0f;
        t.y = (dataBase[1] / 255.0f) * 2.0f - 1.0f;
        t.z = (dataBase[0] / 255.0f) * 2.0f;

        u.x = (dataDetail[2] / 255.0f) * -2.0f + 1.0f;
        u.y = (dataDetail[1] / 255.0f) * -2.0f + 1.0f;
        u.z = (dataDetail[0] / 255.0f) * 2.0f - 1.0f;

        r = t * t.Dot(u) - u * t.z;

        r.Normalize();

        //Write data to our new bitmap
        dataCombined[2] = (byte)Math.Round((r.x * 0.5f + 0.5f) * 255.0f);
        dataCombined[1] = (byte)Math.Round((r.y * 0.5f + 0.5f) * 255.0f);
        dataCombined[0] = (byte)Math.Round((r.z * 0.5f + 0.5f) * 255.0f);

        m_VectorImageArray[index, y, x] = t;    //base
        m_VectorImageArray[index + 1, y, x] = u;  //detail
        m_VectorImageArray[index + 2, y, x] = r;  //Combined

        dataBase += xIncr;
        dataDetail += xIncr;
        dataCombined += xIncr;
    }
    dataBase = nextBase;
    nextBase += bData1.Stride;
    dataDetail = nextDetail;
    nextDetail += bData2.Stride;
    dataCombined = nextCombined;
    nextCombined += bDataCombined.Stride;
}

m_CombinedBitmap.UnlockBits(bDataCombined);

答案 1 :(得分:1)

我建议删除多个除以255的语句并缩放数学,这样你也可以将乘法值除去255。您可以将整个事物转换为整数数学。

要看的另一件事是你的内存访问模式或m_VectorImageArray的方法调用 - 它们会减慢它的速度吗?评论说要找出来。该对象的声明在哪里?

答案 2 :(得分:1)

我不确定这是否有意义,但我所做的只是为先前计算的值(以及一些清理......)创建了一个字典,主要原因是在进行了一些分析后,60%到70%的cpu时间是这两行:

    r = t * t.Dot(u) - u * t.z;

    r.Normalize();

所以在这里;

    private static unsafe void CombineImage(Bitmap image1, Bitmap image2, int index)
    {
        Dictionary<long, int> testDict = new Dictionary<long, int>(); //the magic is wit this dictionary

        var combinedBitmap = new Bitmap(image1.Width, image1.Height, image1.PixelFormat);

        BitmapData bData1 = image1.LockBits(new Rectangle(0, 0, image1.Width, image1.Height), ImageLockMode.ReadOnly, image1.PixelFormat);
        BitmapData bData2 = image2.LockBits(new Rectangle(0, 0, image2.Width, image2.Height), ImageLockMode.ReadOnly, image2.PixelFormat);
        BitmapData bDataCombined = combinedBitmap.LockBits(new Rectangle(0, 0, combinedBitmap.Width, combinedBitmap.Height), ImageLockMode.WriteOnly, combinedBitmap.PixelFormat);

        byte* dataBase = (byte*)bData1.Scan0.ToPointer();
        byte* dataDetail = (byte*)bData2.Scan0.ToPointer();
        byte* dataCombined = (byte*)bDataCombined.Scan0.ToPointer();

        const int bitsPerPixel = 24;
        const int xIncr = bitsPerPixel / 8;

        var t = new Vec3f(0);
        var u = new Vec3f(0);
        var r = new Vec3f(0);

        int h = bData1.Height, w = bData1.Width;
        long key;
        int value;

        Stopwatch combineStopwatch = Stopwatch.StartNew();
        for (int y = 0; y < h; ++y)
        {
            for (int x = 0; x < w; ++x)
            {
                //real magic!
                key = dataBase[0] | (dataBase[1] << 8) | (dataBase[2] << 16) | (dataDetail[0] << 24) | (dataDetail[1] << 32) | (dataDetail[2] << 40);
                if (testDict.ContainsKey(key))
                {
                    value = testDict[key];
                    dataCombined[0] = (byte)(value & 255);
                    dataCombined[1] = (byte)((value >> 8) & 255);
                    dataCombined[2] = (byte)((value >> 16) & 255);
                }
                else
                {
                    t.z = (dataBase[0] / 255.0f) * 2.0f;
                    t.y = (dataBase[1] / 255.0f) * 2.0f - 1.0f;
                    t.x = (dataBase[2] / 255.0f) * 2.0f - 1.0f;

                    u.z = (dataDetail[0] / 255.0f) * 2.0f - 1.0f;
                    u.y = (dataDetail[1] / 255.0f) * -2.0f + 1.0f;
                    u.x = (dataDetail[2] / 255.0f) * -2.0f + 1.0f;

                    r = t * t.Dot(u) - u * t.z;

                    r.Normalize();

                    //Write data to our new bitmap
                    dataCombined[0] = (byte)Math.Round((r.z * 0.5f + 0.5f) * 255.0f);
                    dataCombined[1] = (byte)Math.Round((r.y * 0.5f + 0.5f) * 255.0f);
                    dataCombined[2] = (byte)Math.Round((r.x * 0.5f + 0.5f) * 255.0f);

                    value = dataCombined[0] | (dataCombined[1] << 8) | (dataCombined[2] << 16);
                    testDict.Add(key, value);
                }

                dataBase += xIncr;
                dataDetail += xIncr;
                dataCombined += xIncr;
            }
        }
        combineStopwatch.Stop();

        combinedBitmap.UnlockBits(bDataCombined);
        image2.UnlockBits(bData1);
        image1.UnlockBits(bData1);
        //combinedBitmap.Save("helloyou.png", ImageFormat.Png);
        testDict.Clear();

        Console.Write(combineStopwatch.ElapsedMilliseconds + "\n");
    }

答案 3 :(得分:0)

我刚刚在你问题中提到的StackOverflow帖子中添加了另一个答案。 Fast work with Bitmaps

它告诉您如何直接使用整数数组或字节数组中的位图数据而不复制任何内容。它应该可以节省你一些时间。 您可以通过使用Integer数组而不是Bytes来节省时间,因为它需要较少的读写操作。你所需要的只是一些比特变换魔法,你也可以在我链接的帖子中找到它。

确保在循环内尽可能少地进行类型转换,因为它们非常昂贵。

我也同意Fredou的观点,你应该仔细看看这两行:

r = t * t.Dot(u) - u * t.z;

r.Normalize();

您可以尝试展开这些功能以节省一些时间。在循环外创建变量:

float rx, ry, rz;
float tx, ty, tz;
float ux, uy, uz;
float dot, len;

然后在循环中:

'Dot
dot = tx*ux + ty*uy + tz*uz;
rx = tx * dot - ux*tz;
ry = ty * dot - uy*tz;
rz = tz * dot - uz*tz;

'Normalize
len = Math.sqrt(rx*rx + ry*ry + rz*rz);
rx /= len;
ry /= len;
rz /= len;

如果你真的需要性能并且可以放松一些准确性,那么用this page中的一个替换你的Math.sqrt()。它基本上说你可以通过使用LayoutKind.Explicit这样的结构来转换int和float:

[StructLayout(LayoutKind.Explicit)]
private struct FloatIntUnion
{
    [FieldOffset(0)]
    public float f;

    [FieldOffset(0)]
    public int tmp;
}

观察者认为这不会在int和float中给出相同的值,因为它需要计算转换。它只允许您使用相同的存储位并将它们视为int / float。 然后你可以通过这样计算SQRT来节省一半的时间:

public static float QuickSqrt(float z){
    if (z == 0) return 0;
    FloatIntUnion u;
    u.tmp = 0;
    float xhalf = 0.5f * z;
    u.f = z;
    u.tmp = 0x5f375a86 - (u.tmp >> 1);
    u.f = u.f * (1.5f - xhalf * u.f * u.f);
    return u.f * z;
}

文章提到Quake 3使用了这种方法:)