【问题标题】:Quickly calculating the 'dirtied' areas between two similar images快速计算两个相似图像之间的“脏”区域
【发布时间】:2012-08-13 09:56:09
【问题描述】:

我有两个非常相似的图像(特别是两个屏幕截图),我正在尝试找到最好(最快)的方法来查找图像的哪些区域发生了变化(作为代表不同区域的矩形数组)

几个标准:

  • 它不需要是像素精确的,但必须包括所有变化,无论多么小(即,单像素变化周围有很大的误差是可以接受的)
  • 需要快速(理想情况下,在今天购买的典型消费类机器上,2x 1920x1080 图像应该花费
  • 它不需要可配置的阈值(但如果有解决方案允许这样做,那将是一个不错的奖励)
  • 可以假设输入图像始终是完美的无损图像。

我有两种可行的解决方案,但一种是蛮力逐像素计算,这当然非常慢。对于另一个,我尝试将两个图像分成不同大小的块并计算每个块的校验和,但这也很慢。

仅供那些想知道我在构建什么的人使用 - 它是一种更笨(也更慢)的远程桌面,无需任何插件即可在浏览器中使用。

【问题讨论】:

  • OpenCV 是一个很好的图像处理库。也就是说,如果你不坚持自己编程,我发现 addictivetips.com/windows-tips/… 似乎有点类似。
  • 我很高兴使用外部库,但它需要非 GPL 的良好 C# 绑定(LGPL、BSD、Apache 许可证很好 - 只是不是“病毒”许可证)和一些文档关于有助于我实现这一点的功能。
  • 您必须比较图像之间的每个像素 - 没有办法绕过它。您目前如何进行比较?您是否已经在使用非托管代码?
  • 我没有使用任何非托管或不安全的代码。只是与 GetPixel() 的比较。如果可能的话,我宁愿纯粹保留在托管代码中(但使用带有托管包装器的现有非托管库就可以了,只要包装器也在 Mono 中编译)
  • 是的 GetPixel() 非常慢。使用LockBitsexample here。您将不得不用非托管内存和指针弄脏自己的手,但这并不难。

标签: c# .net image-processing


【解决方案1】:

您需要进行逐像素比较。我认为它不应该那么慢。例如代码:

        int size = 1920 * 1080 * 3;
        byte[] image1 = new byte[size];
        byte[] image2 = new byte[size];
        byte[] diff = new byte[size];

        var sw = new System.Diagnostics.Stopwatch();
        sw.Start();
        for (int i = 0; i < size; i++)
        {
            diff[i] = (byte) (image1[i] - image1[i]);
        }
        sw.Stop();       
        Console.WriteLine(sw.ElapsedMilliseconds);

在我的笔记本电脑上运行大约 40 毫秒。如果它只是灰度,它运行在 20 毫秒以下。如果您使用真实的图像数据,diff[i] != 0 将表明两个图像发生了变化。

如果您使用 Bitmap.GetPixel 或其他慢速方法读取像素值,您的解决方案可能会很慢。如果是这种情况,我建议查看 Bitmap.LockBits 或使用不安全的方法。

【讨论】:

  • 如何将稀疏的差异数组转换为详细说明修改区域的矩形数组?还是我应该坚持在固定的“区域”做这件事?
  • 例如,您可以在 diff 数组上使用某种洪水填充来获得具有连续变化的区域的边界区域。 (填充值不同于 0 的区域)。这会增加处理时间。
  • 另一种方法可能是生成一个新图像,在其中将 alpha 通道设置为透明,其中 diff 数组为零,否则设置稍微透明的颜色,其中 diff 数组大于 0。然后你可以在其中一个输入图像上绘制此图像。
  • 唯一的问题是,一个 1920x1080 完全透明的图像中间有一个正方形,其文件大小明显大于仅一个正方形本身大小。缩小尺寸是一个问题。
  • 您可以将差异掩码存储为二进制图像(每像素 1 位),其中 [0 = 透明,1 = 掩码],并在加载后仅在内存中生成全透明图像。
【解决方案2】:

我之前的回答因为格式问题被删除了,我会再写一遍,以更好的方式。

我在问您是否考虑使用 GPU 来计算两个图像之间的图像差异。由于 GPU 与 CPU 计算相比具有高度并行性,因此该解决方案可以显着延长您的计算时间。

使用 C#,您可以尝试使用 XNA 来实现此目的。实际上,我使用单通道 HLSL(它用于使用 direct3D 对 GPU 进行编程)像素着色器做了一个小测试:

texture texture1;
texture texture2;

sampler textureSampler1 = sampler_state{
    Texture = <texture1>;
};

sampler textureSampler2 = sampler_state{
    Texture = <texture2>;
};

float4 pixelShaderFunction(float2 TextureCoordinate : TEXCOORD0) : COLOR0{
    float4 color1 = tex2D(textureSampler1,TextureCoordinate);
    float4 color2 = tex2D(textureSampler2,TextureCoordinate);
    if((color1.r == color2.r) && (color1.g == color2.g) && (color1.b == color2.b)){
        color1.r = 0;
        color1.g = 0;
        color1.b = 0;
    }
    else{
        color1.r = 255;
        color1.g = 255;
        color1.b = 255;
    }

    return color1;
}


technique Compare
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 pixelShaderFunction();
    }
}

XNA 部分的计算非常简单。将 XNA 的基本 sn-p 与 Visual Studio 一起使用,我只是将绘图函数编写为:

protected override void Draw(GameTime gameTime) {
   Stopwatch sw = new Stopwatch();
   sw.Start();
   GraphicsDevice.Clear(Color.CornflowerBlue);
   e.Parameters["texture1"].SetValue(im1);
   e.Parameters["texture2"].SetValue(im2);
   spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearClamp, DepthStencilState.Default, RasterizerState.CullNone, e);
   spriteBatch.Draw(im1,new Vector2(0,0),Color.White);
   spriteBatch.End();
   base.Draw(gameTime);
   sw.Stop();
   Console.WriteLine(sw.ElapsedMilliseconds);
}

im1 和 im2 是加载为 Texture2D 的两个 1920*1080 彩色 bmp 图像,e 是加载为效果的 file.fx。

使用这种技术,我在相当普通的计算机(I5-2410m @ 2.3Ghz、4Gb 内存、Nvidia Geforce GT525m 的笔记本电脑)上获得了 17/18ms 的计算时间。

这是程序的输出,显示了不同的图像(抱歉,这是高度放大的,因为我没有 1920*1080 的屏幕 :>),此外还有两个图像 im1 和 im2,它们之间有一些细微差别他们 : http://img526.imageshack.us/img526/2345/computationtime.jpg

我对 GPU 编程还很陌生,所以如果我在如何计算时间或其他任何事情上犯了一个大错误,请随时告诉!

编辑:要注意的第一件事,我刚刚读到“这将是一个重要的操作,因为 GPU 不能很好地处理分支。”。

最好的问候

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-06-22
    • 2017-08-22
    • 2012-03-11
    • 2021-08-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多