6

我目前正在做一个项目,我需要编写软件来比较由相同区域组成的两个图像,并在差异周围画一个框。我在几个小时内用 c# .net 编写了程序,但很快意识到运行起来非常昂贵。以下是我实施它的步骤。

  1. 创建了一个存储每个像素的 x、y 坐标的 Pixel 类和一个存储像素列表以及宽度、高度、x 和 y 属性的 PixelRectangle 类。

  2. 循环遍历每个图像的每个像素,比较每个对应像素的颜色。如果颜色不同,我会使用该像素的 x,y 坐标创建一个新的像素对象,并将其添加到 pixelDifference 列表中。

  3. 接下来,我编写了一个递归检查 pixelDifference 列表中的每个像素的方法,以创建仅包含彼此直接相邻的像素的 PixelRectangle 对象。(很确定这个坏男孩造成了大部分破坏,因为它给了我一个堆栈溢出错误。)

  4. 然后,我根据存储在 PixelRectangle 对象列表中的像素计算出矩形的 x、y 坐标和尺寸,并在原始图像上绘制一个矩形以显示差异所在的位置。

我的问题是:我是否以正确的方式进行此操作?四叉树对这个项目有什么价值吗?如果您能给我有关如何通常实现此类目标的基本步骤,我将不胜感激。提前致谢。

  • 戴夫。
4

3 回答 3

2

看起来你想实现 blob 检测。我的建议是不要重新发明轮子,而只是使用 openCVSharp 或 emgu 来做到这一点。谷歌“斑点检测”和 opencv

如果你想在这里自己做,我的 2 美分价值:

首先,让我们澄清你想要做什么。真的是两件不同的事情:

  1. 计算两个图像之间的差异(我假设它们是相同的尺寸)

  2. 在以 1 衡量的“不同”的“区域”周围画一个框。这里的问题是什么是“区域”以及什么被认为是“不同的”。

我对每一步的建议:

(我的假设是两个图像都是灰度。如果不是,计算每个像素的颜色总和以获得灰度值)

1)循环遍历两个图像中的所有像素并减去它们。设置绝对差异的阈值以确定它们的差异是否足以代表场景中的实际变化(如果图像来自相机,则与传感器噪声等相反)。然后将结果存储在第三张图像中。0 表示没有区别。255 的区别。如果做得对,这应该非常快。但是,在 C# 中,您必须使用指针才能获得不错的性能。这里有一个如何做到这一点的例子(注意:代码未经测试!!):

  /// <summary>
    /// computes difference between two images and stores result in a third image
    /// input images must be of same dimension and colour depth
    /// </summary>
    /// <param name="imageA">first image</param>
    /// <param name="imageB">second image</param>
    /// <param name="imageDiff">output 0 if same, 255 if different</param>
    /// <param name="width">width of images</param>
    /// <param name="height">height of images</param>
    /// <param name="channels">number of colour channels for the input images</param>
    unsafe void ComputeDiffernece(byte[] imageA, byte[] imageB, byte[] imageDiff, int width, int height, int channels, int threshold)
    {
        int ch = channels;

        fixed (byte* piA = imageB, piB = imageB, piD = imageDiff)
        {

            if (ch > 1) // this a colour image (assuming for RGB ch == 3 and RGBA  == 4)
            {
                for (int r = 0; r < height; r++)
                {
                    byte* pA = piA + r * width * ch;
                    byte* pB = piB + r * width * ch;
                    byte* pD = piD + r * width; //this has only one channels!

                    for (int c = 0; c < width; c++)
                    {
                        //assuming three colour channels. if channels is larger ignore extra (as it's likely alpha)
                        int LA = pA[c * ch] + pA[c * ch + 1] + pA[c * ch + 2];
                        int LB = pB[c * ch] + pB[c * ch + 1] + pB[c * ch + 2];

                        if (Math.Abs(LA - LB) > threshold)
                        {
                            pD[c] = 255;
                        }
                        else
                        {
                            pD[c] = 0;
                        }

                    }
                }
            }
            else //single grey scale channels
            {
                for (int r = 0; r < height; r++)
                {
                    byte* pA = piA + r * width;
                    byte* pB = piB + r * width;
                    byte* pD = piD + r * width; //this has only one channels!

                    for (int c = 0; c < width; c++)
                    {
                        if (Math.Abs(pA[c] - pB[c]) > threshold)
                        {
                            pD[c] = 255;
                        }
                        else
                        {
                            pD[c] = 0;
                        }
                    }
                }
            }
        }
    }

2)

不知道你说的区域是什么意思。几种解决方案,具体取决于您的意思。从最简单到最难。

a)将输出中的每个差异像素涂成红色

b)假设您只有一个差异区域(不太可能)计算输出图像中所有 255 个像素的边界框。这可以使用所有 255 个像素上的 x 和 y 位置的简单 max/min 来完成。单次通过图像,应该非常快。

c)如果您有很多不同的区域发生变化 - 计算“连接组件”。那是相互连接的像素的集合。当然,这只适用于二进制图像(即开或关,或在我们的例子中为 0 和 255)。你可以在 c# 中实现这个,我以前做过。但我不会在这里为你做这个。这有点涉及。算法就在那里。再次 opencv 或 google连接的组件

一旦你有一个 CC 的列表,在每个周围画一个框。完毕。

于 2013-07-15T13:25:32.597 回答
0

你几乎以正确的方式去做。如果正确实施,第 3 步不应导致 StackOverflow 异常,因此我将仔细研究该方法。

最有可能发生的是您对 PixelDifference 的每个成员的递归检查正在无限运行。确保跟踪已检查的像素。一旦您检查了一个像素,在检查相邻像素时就不再需要考虑它。在检查任何相邻像素之前,请确保它本身还没有被检查过。

作为跟踪已检查的像素的替代方法,您可以在检查后从 PixelDifference 中删除项目。当然,这可能需要改变你实现算法的方式,因为从 List 中删除一个元素同时检查它会带来一系列全新的问题。

于 2013-07-12T15:11:53.353 回答
0

有一种更简单的方法可以找到两个图像的差异。

所以如果你有两张图片

Image<Gray, Byte> A;
Image<Gray, Byte> B;

您可以通过以下方式快速了解他们的差异

A - B

当然,图像不存储负值,以便在图像 B 中的像素大于图像 A 的情况下获得差异

B - A

将这些结合在一起

(A - B) + (B - A)

这没关系,但我们可以做得更好。

这可以使用傅里叶变换来评估。

CvInvoke.cvDFT(A.Convert<Gray, Single>().Ptr, DFTA.Ptr, Emgu.CV.CvEnum.CV_DXT.CV_DXT_FORWARD, -1);
CvInvoke.cvDFT(B.Convert<Gray, Single>().Ptr, DFTB.Ptr, Emgu.CV.CvEnum.CV_DXT.CV_DXT_FORWARD, -1);

CvInvoke.cvDFT((DFTB - DFTA).Convert<Gray, Single>().Ptr, AB.Ptr, Emgu.CV.CvEnum.CV_DXT.CV_DXT_INVERSE, -1);
CvInvoke.cvDFT((DFTA - DFTB).Ptr, BA.Ptr, Emgu.CV.CvEnum.CV_DXT.CV_DXT_INVERSE, -1);

我发现这种方法的结果要好得多。您可以从中制作二进制图像,即:对图像设置阈值,使没有变化的像素为 0,而有变化的像素存储 255。

现在就问题的第二部分而言,我想有一个简单的粗略解决方案:

将图像划分为矩形区域。也许没有必要使用四叉树。比如说,一个 8x8 的网格......(对于不同的结果,您可以尝试不同的网格大小)。

然后在这些区域内使用凸包函数。这些凸包可以通过找到它们顶点的最小和最大 x 和 y 坐标来变成矩形。

应该快速简单

于 2013-07-27T17:44:40.670 回答