比较图像并标记差异c#

本文关键字:图像 比较 | 更新日期: 2023-09-27 18:21:58

我目前正在进行一个项目,该项目要求我编写软件,比较由同一区域组成的两个图像,并围绕差异绘制一个框。我用c#.net在几个小时内编写了这个程序,但很快就意识到它的运行成本高得惊人。以下是我在中实现它的步骤

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

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

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

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

我的问题是:我这样做正确吗?四叉树对这个项目有价值吗?如果你能告诉我通常是如何实现这一目标的基本步骤,我将不胜感激。提前谢谢。

  • 戴夫

比较图像并标记差异c#

看起来您想要实现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或谷歌连接组件。

一旦你有了CC的列表,就在每个CC周围画一个方框。完成。

您的做法基本正确。如果实现正确,步骤3不应该导致StackOverflow异常,所以我会仔细研究一下该方法。

最有可能发生的情况是,您对PixelDifference的每个成员的递归检查都在无限运行。请确保跟踪已检查的像素。检查像素后,在检查相邻像素时不再需要考虑该像素。在检查任何相邻像素之前,请确保其本身尚未进行检查。

除了跟踪已检查的像素之外,您还可以在检查项目后从PixelDifference中删除该项目。当然,这可能需要更改实现算法的方式,因为在检查列表时从列表中删除元素可能会带来一系列新的问题。

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

所以如果你有两个图像

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坐标而变成矩形。

应该是快速简单的