【问题标题】:Faster way to combine bitmaps with custom algorithm?将位图与自定义算法相结合的更快方法?
【发布时间】:2013-12-25 23:28:10
【问题描述】:

我正在尝试将 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),认为它会显着提高速度,但不幸的是它只快了大约 10 毫秒。

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

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

【问题讨论】:

  • 你可以并行化这个..你没有处理不同的数据集。将图像的每一部分分割成块并在它们自己的线程上处理它们。假设您将它们适当地拆分......就算法而言,您不应该遇到任何线程问题。
  • 你分析过它吗?你知道瓶颈在哪里吗?
  • 你能提供一组图像来测试和适当的代码来做完整的测试吗?
  • @Simon Whitehead 谢谢!我将不得不与所有其他更改一起尝试这个!
  • @acfrancis 我已经对其进行了分析,但因为这是我第一次真正需要优化某些东西,我可能误读了分析器给我的信息。 profiler 我认为这意味着大部分时间都花在了我的 Vec3f 课程上,所以我尝试优化那个。谢谢!

标签: c# .net algorithm image-processing


【解决方案1】:

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

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);

【讨论】:

  • 谢谢!我已经实现了它,它在 4096x4096 图像上节省了大约 300 毫秒。过去大约需要 4800 毫秒。
  • 您也可以尝试将这种代码 t.x = (dataBase[2] / 255.0f) * 2.0f - 1.0f; 更改为 t.x = dataBase[2] * (2 / 255.0f) - 1.0f; 之类的代码。这可能会有所帮助,但我不确定。
  • 哇!再次感谢!我已经改变了它,现在只需要大约 3600 毫秒。然而,我确实有一个小问题,这是否更快,因为除法是在编译时完成的,所以你在运行时得到乘法?就像 Jason S 解释的那样here
  • 是的,编译器将此2 / 255.0f 视为文字,因为该操作可以在编译时执行(这就是它的作用)。因此,您可以避免缓慢的除法。
【解决方案2】:

我不确定这是否有意义,但我所做的只是为先前计算的值创建了一个字典(以及一些清理......),主要原因是在进行了一些分析之后,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");
    }

【讨论】:

  • 取决于两张图片,这些可能会吃掉很多内存。
  • 我只是从相机中随机拍摄了 2 张 12 兆像素的照片,还不错,最大内存使用量没有超过 400 兆内存。
  • 非常感谢!我只是对“真正的魔法”部分有几个问题:)。看看我是否完全理解:“键”由所有颜色(位?)值组成,所以如果在基本位图和细节位图中再次看到相同的颜色,它会从字典中取出值吗?我也不确定 (value & 255) 是做什么的?
  • @VincentC 确切地说,“值和 255”确保返回最大字节,因此转换时不会“溢出”。读取:按位运算
【解决方案3】:

我建议删除许多除以 255 的语句并缩放数学,以便您也删除乘以 255 的乘积。您也可以将整个事物转换为整数数学。

要查看的另一件事是您的内存访问模式或对 m_VectorImageArray 的方法调用——它们是否会减慢速度?发表评论以找出答案。该对象的声明在哪里?

【讨论】:

  • 我已经注释掉了 m_VectorImageArray 并且它节省了大约 500 毫秒!您知道减少填充数组所需时间的方法吗? (约 4800 毫秒之前)。它在两个 for 循环的上方声明,如下所示: m_VectorImageArray = new Vec3f[(m_ImagesCount - 1) / 10 + 1 + m_ImagesCount, bData1.Width, bData1.Height];我将尝试删除 /255 和 *255 并将其全部转换为 int 数学。我已经做了后者,虽然 /255 和 *255 仍然存在,但效果并不好,但确实节省了大约 900 毫秒。谢谢!
【解决方案4】:

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

它告诉您如何在不复制任何内容的情况下直接使用整数数组或字节数组中的位图数据。它应该可以为您节省很多时间。 您可以通过使用整数数组而不是字节来节省时间,因为读取和写入所需的操作更少。您所需要的只是一些移位魔法,您也可以在我链接到的帖子中找到它。

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

我也同意 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;

如果您确实需要性能并且可以承受一些准确性的损失,请将您的 Math.sqrt() 替换为来自 this page 的一个。它基本上说您可以通过使用 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 使用了这种方法:)

【讨论】:

    猜你喜欢
    • 2016-04-24
    • 2014-11-05
    • 1970-01-01
    • 1970-01-01
    • 2017-04-28
    • 1970-01-01
    • 1970-01-01
    • 2015-02-15
    • 2023-03-17
    相关资源
    最近更新 更多