【问题标题】:Two image compare using win32 api c#使用win32 api c#比较两个图像
【发布时间】:2012-12-11 14:37:21
【问题描述】:

我有两张图片,我想比较两张图片并希望有所不同。我搜索谷歌并找到一个链接,我从中复制粘贴代码以使用 win32 api 进行图像比较。 所以这是网址 http://blog.bobcravens.com/2009/04/create-a-remote-desktop-viewer-using-c-and-wcf/

我在这里粘贴代码。

        private void button1_Click(object sender, EventArgs e)
        {
        Bitmap _prevBitmap = new Bitmap(@"d:\prev.jpg");
        Bitmap _newBitmap = new Bitmap(@"d:\current.jpg");

        Rectangle bounds = GetBoundingBoxForChanges(_prevBitmap, _newBitmap);
        if (bounds == Rectangle.Empty)
        {
        }

        Bitmap diff = new Bitmap(bounds.Width, bounds.Height);
        Graphics g = Graphics.FromImage(diff);
        g.DrawImage(_newBitmap, 0, 0, bounds, GraphicsUnit.Pixel);
        g.Dispose();

        // Set the current bitmap as the previous to prepare
        //    for the next screen capture.
        //
        diff.Save(@"d:\diff.bmp");

        //return diff;
    }

    private Rectangle GetBoundingBoxForChanges(Bitmap _prevBitmap, Bitmap _newBitmap)
    {
        // The search algorithm starts by looking
        //    for the top and left bounds. The search
        //    starts in the upper-left corner and scans
        //    left to right and then top to bottom. It uses
        //    an adaptive approach on the pixels it
        //    searches. Another pass is looks for the
        //    lower and right bounds. The search starts
        //    in the lower-right corner and scans right
        //    to left and then bottom to top. Again, an
        //    adaptive approach on the search area is used.
        //

        // Note: The GetPixel member of the Bitmap class
        //    is too slow for this purpose. This is a good
        //    case of using unsafe code to access pointers
        //    to increase the speed.
        //

        // Validate the images are the same shape and type.
        //
        if (_prevBitmap.Width != _newBitmap.Width ||
            _prevBitmap.Height != _newBitmap.Height ||
            _prevBitmap.PixelFormat != _newBitmap.PixelFormat)
        {
            // Not the same shape...can't do the search.
            //
            return Rectangle.Empty;
        }

        // Init the search parameters.
        //
        int width = _newBitmap.Width;
        int height = _newBitmap.Height;
        int left = width;
        int right = 0;
        int top = height;
        int bottom = 0;

        BitmapData bmNewData = null;
        BitmapData bmPrevData = null;
        try
        {
            // Lock the bits into memory.
            //
            bmNewData = _newBitmap.LockBits(
                new Rectangle(0, 0, _newBitmap.Width, _newBitmap.Height),
                ImageLockMode.ReadOnly, _newBitmap.PixelFormat);
            bmPrevData = _prevBitmap.LockBits(
                new Rectangle(0, 0, _prevBitmap.Width, _prevBitmap.Height),
                ImageLockMode.ReadOnly, _prevBitmap.PixelFormat);

            // The images are ARGB (4 bytes)
            //
            int numBytesPerPixel = 4;

            // Get the number of integers (4 bytes) in each row
            //    of the image.
            //
            int strideNew = bmNewData.Stride / numBytesPerPixel;
            int stridePrev = bmPrevData.Stride / numBytesPerPixel;

            // Get a pointer to the first pixel.
            //
            // Note: Another speed up implemented is that I don't
            //    need the ARGB elements. I am only trying to detect
            //    change. So this algorithm reads the 4 bytes as an
            //    integer and compares the two numbers.
            //
            System.IntPtr scanNew0 = bmNewData.Scan0;
            System.IntPtr scanPrev0 = bmPrevData.Scan0;

            // Enter the unsafe code.
            //
            unsafe
            {
                // Cast the safe pointers into unsafe pointers.
                //
                int* pNew = (int*)(void*)scanNew0;
                int* pPrev = (int*)(void*)scanPrev0;

                // First Pass - Find the left and top bounds
                //    of the minimum bounding rectangle. Adapt the
                //    number of pixels scanned from left to right so
                //    we only scan up to the current bound. We also
                //    initialize the bottom & right. This helps optimize
                //    the second pass.
                //
                // For all rows of pixels (top to bottom)
                //
                for (int y = 0; y < _newBitmap.Height; ++y)
                {
                    // For pixels up to the current bound (left to right)
                    //
                    for (int x = 0; x < left; ++x)
                    {
                        // Use pointer arithmetic to index the
                        //    next pixel in this row.
                        //
                        if ((pNew + x)[0] != (pPrev + x)[0])
                        {
                            // Found a change.
                            //
                            if (x < left)
                            {
                                left = x;
                            }
                            if (x > right)
                            {
                                right = x;
                            }
                            if (y < top)
                            {
                                top = y;
                            }
                            if (y > bottom)
                            {
                                bottom = y;
                            }
                        }
                    }

                    // Move the pointers to the next row.
                    //
                    pNew += strideNew;
                    pPrev += stridePrev;
                }

                // If we did not find any changed pixels
                //    then no need to do a second pass.
                //
                if (left != width)
                {
                    // Second Pass - The first pass found at
                    //    least one different pixel and has set
                    //    the left & top bounds. In addition, the
                    //    right & bottom bounds have been initialized.
                    //    Adapt the number of pixels scanned from right
                    //    to left so we only scan up to the current bound.
                    //    In addition, there is no need to scan past
                    //    the top bound.
                    //

                    // Set the pointers to the first element of the
                    //    bottom row.
                    //
                    pNew = (int*)(void*)scanNew0;
                    pPrev = (int*)(void*)scanPrev0;
                    pNew += (_newBitmap.Height - 1) * strideNew;
                    pPrev += (_prevBitmap.Height - 1) * stridePrev;

                    // For each row (bottom to top)
                    //
                    for (int y = _newBitmap.Height - 1; y > top; y--)
                    {
                        // For each column (right to left)
                        //
                        for (int x = _newBitmap.Width - 1; x > right; x--)
                        {
                            // Use pointer arithmetic to index the
                            //    next pixel in this row.
                            //
                            if ((pNew + x)[0] != (pPrev + x)[0])
                            {
                                // Found a change.
                                //
                                if (x > right)
                                {
                                    right = x;
                                }
                                if (y > bottom)
                                {
                                    bottom = y;
                                }
                            }
                        }

                        // Move up one row.
                        //
                        pNew -= strideNew;
                        pPrev -= stridePrev;
                    }
                }
            }
        }
        catch (Exception ex)
        {
            int xxx = 0;
        }
        finally
        {
            // Unlock the bits of the image.
            //
            if (bmNewData != null)
            {
                _newBitmap.UnlockBits(bmNewData);
            }
            if (bmPrevData != null)
            {
                _prevBitmap.UnlockBits(bmPrevData);
            }
        }

        // Validate we found a bounding box. If not
        //    return an empty rectangle.
        //
        int diffImgWidth = right - left + 1;
        int diffImgHeight = bottom - top + 1;
        if (diffImgHeight < 0 || diffImgWidth < 0)
        {
            // Nothing changed
            return Rectangle.Empty;
        }

        // Return the bounding box.
        //
        return new Rectangle(left, top, diffImgWidth, diffImgHeight);
    }

当 GetBoundingBoxForChanges() 调用时,我收到错误消息,错误消息是 尝试读取或写入受保护的内存。这通常表明其他内存已损坏。

如果 ((pNew + x)[0] != (pPrev + x)[0]),此代码会发生错误

所以我无法找出原因。如何解决此错误。请指导。谢谢

【问题讨论】:

  • 愚蠢的问题,但使用 'unsafe' 有时需要进行不同的编译,或者至少打开一些标志,你尝试过吗?
  • @Davio 这将导致编译时错误,而不是运行时错误。
  • 我改变了一些东西以避免编译错误。我收到运行时错误。
  • 为什么是负分。我在这里发帖是因为我遇到错误并且无法弄清楚如何解决它。
  • @Rotem 好吧,这仍然是一个很好的起点 :)

标签: c# image-comparison


【解决方案1】:
  bmNewData = _newBitmap.LockBits(...., _newBitmap.PixelFormat);

这个算法隐含地假设一个像素有 4 个字节并且可以用int* 来寻址。但是,它未能提供该保证。在 LockBits() 中请求 _newBitmap.PixelFormat足够的,它只要求与原始图像使用的格式相同。例如,如果图像是 24bpp,你会遇到严重的崩溃,这很常见。

改为明确要求 32bppArgb。

【讨论】:

  • 您的建议确实有效。当我从这个 newBitmap.PixelFormat 更改为 PixelFormat.Format32bppArgb 时,我现有的例程开始工作而不是给出错误。您能否详细讨论一下 PixelFormat.Format32bppArgb 中有什么结果,当我使用 PixelFormat.Format32bppArgb 时,我的例程就可以工作,但是在 _newBitmap.PixelFormat 的情况下,同样的例程会出错。感谢您的大力帮助。
【解决方案2】:

您可以使用托管图像处理库(例如AForge.NET)来代替 Win32 API。在 documentation 中查找 AForge.Imaging.Filters.Difference 类。它适用于Bitmap 对象,因此您只需对程序进行最少的更改。

Bitmap overlayImage;
Bitmap sourceImage;

//ToDo: Load the two images.        

// Create filter.
Difference filter = new Difference(overlayImage);
// Apply the filter and return a new bitmap that is the difference between the source and overlay images.
Bitmap resultImage = filter.Apply(sourceImage);

// If you don't want a new image the you can apply the filter directly to the source image.
filter.ApplyInPlace(sourceImage);

【讨论】:

  • 我去 AForge.NET 网站。这很棒,因为它是开源的。但我不明白一件事是不是像win32 api一样快用于图像比较??给我一些关于性能的见解。
  • 假设我有两个相同高度和宽度的相同图像。你已经展示了 AForge 例程来获得图像的差异。假设您有两个桌面图像,它们的区别很小,分别称为 img1 和 img2。在 AForge 的帮助下,我们可以获得您所展示的差异,但是在获得差异之后,我如何将差异图像 img3 应用到我的第一张图像 img1 上,并具有确切的位置,结果第三张图像看起来像 img2 图像。是否可以使用 AForge 库。如果可以,您可以给我看示例代码吗?谢谢
  • @Thomas 我不确定性能。您可以通过定时操作来比较这两种实现。如果这是您所关心的,它肯定不会使用 GetPixel。在内部,它使用一个 UnmanagedImage 对象,该对象以与您问题中的实现相同的方式将图像包装在非托管内存中。
  • @Thomas 我不确定我是否跟随。您想获取差异并将其添加到第一张图像中吗?如果是这样,那么有一个 AForge.Imaging.Filters.Add 类可以像 AForge.Imaging.Filters.Difference 一样使用。
  • 谢谢你能给我一些代码 sn-p 来获取差异并将其添加到第一张图像中。你说通过 AForge.Imaging.Filters.Add 是可能的。你能给我看看示例代码吗?谢谢
【解决方案3】:

这是我在 C# 中用于计算图像差异的机制。请注意,它需要使用unsafe 指令进行编译。希望对您有所帮助:

using System;
using System.Collections.Generic;
using System.Text;

using System.Drawing;
using System.Drawing.Imaging;
using log4net;

namespace ImageDiff
{
    public class ImageDifferences
    {
        private static ILog mLog = LogManager.GetLogger("ImageDifferences");

        public static unsafe Bitmap PixelDiff(Image a, Image b)
        {
            if (!a.Size.Equals(b.Size)) return null;
            if (!(a is Bitmap) || !(b is Bitmap)) return null;

            return PixelDiff(a as Bitmap, b as Bitmap);
        }

        public static unsafe Bitmap PixelDiff(Bitmap a, Bitmap b)
        {
            Bitmap output = new Bitmap(
                Math.Max(a.Width, b.Width),
                Math.Max(a.Height, b.Height),
                PixelFormat.Format32bppArgb);

            Rectangle recta = new Rectangle(Point.Empty, a.Size);
            Rectangle rectb = new Rectangle(Point.Empty, b.Size);
            Rectangle rectOutput = new Rectangle(Point.Empty, output.Size);

            BitmapData aData = a.LockBits(recta, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
            BitmapData bData = b.LockBits(rectb, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
            BitmapData outputData = output.LockBits(rectOutput, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);

            try
            {
                byte* aPtr = (byte*)aData.Scan0;
                byte* bPtr = (byte*)bData.Scan0;
                byte* outputPtr = (byte*)outputData.Scan0;
                int len = aData.Stride * aData.Height;

                for (int i = 0; i < len; i++)
                {
                    // For alpha use the average of both images (otherwise pixels with the same alpha won't be visible)
                    if ((i + 1) % 4 == 0)
                        *outputPtr = (byte)((*aPtr + *bPtr) / 2);
                    else
                        *outputPtr = (byte)~(*aPtr ^ *bPtr);

                    outputPtr++;
                    aPtr++;
                    bPtr++;
                }

                return output;
            }
            catch (Exception ex)
            {
                mLog.Error("Error calculating image differences: " + ex.Message);
                return null;
            }
            finally
            {
                a.UnlockBits(aData);
                b.UnlockBits(bData);
                output.UnlockBits(outputData);
            }
        }
    }
}

【讨论】:

  • 我肯定会测试你的代码。什么返回输出;只会有区别。如果是,那么您能告诉我稍后如何合并第一张图片上的差异。请告诉我这个逻辑如何在有差异的确切位置缝合第一张图像的差异。谢谢
  • 合并图像是一个不同且复杂得多的问题。我在这里发布的问题是如何区分相同大小的图像。合并算法是我不太了解的东西。
  • 抱歉你的代码没有生成正确的差异图像。获取两个具有相同高度和宽度且差异很小的图像,然后针对这些图像运行您的代码并将差异图像保存在您的文件系统中。然后你可以理解你的代码没有很好地工作。谢谢
  • 只需使用两个具有相同高度和宽度的图像测试您的代码。最好拍两张你的桌面屏幕截图,差异很小,然后将两个图像与你的代码进行比较。谢谢
猜你喜欢
  • 2017-03-14
  • 2013-12-30
  • 2011-06-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-10
  • 2016-02-18
  • 1970-01-01
相关资源
最近更新 更多