0

我只是对我的桌面进行了两次截图,几乎没有什么变化,并使用 win32 api 以编程方式比较两个图像。我的图像扩展名是 jpg,图像高度和宽度都是:- 高度:-768 宽度:- 1366。我使用秒表类只是为了查看我的例行程序花费了多少时间来获取差异图像并将其保存到桌面。我发现它需要 47 毫秒。有什么办法可以减少例行程序所花费的时间。这是我的常规代码。请查看我的例程并检查如何减少此例程所花费的时间。谢谢

    private void button1_Click(object sender, EventArgs e)
    {
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();

        Bitmap firstImg = new Bitmap(@"C:\Users\TRIDIP\Desktop\pic1.jpg");
        Bitmap secondImg = new Bitmap(@"C:\Users\TRIDIP\Desktop\pic2.jpg");

        Rectangle bounds = GetBoundingBoxForChanges(firstImg, secondImg);

        if (bounds == Rectangle.Empty)
        {
    return;
        }

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

        stopwatch.Stop();

        string strmm = string.Format("Time elapsed {0} {1} {2} {3}",  stopwatch.Elapsed.Hours.ToString(), stopwatch.Elapsed.Minutes.ToString(), stopwatch.Elapsed.Seconds.ToString(), stopwatch.Elapsed.Milliseconds.ToString());
        MessageBox.Show(strmm);
    }

    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, PixelFormat.Format32bppArgb);
            bmPrevData = _prevBitmap.LockBits(new Rectangle(0, 0, _prevBitmap.Width, _prevBitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

            // 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);
    }
4

3 回答 3

1

首先,我建议通过代码计时数百/数千次以确定平均速度 - 单次通过绝不是计时代码的好方法。此外,您正在从磁盘加载两个大图像,这很容易比整个 diff 操作花费更长的时间,因此您可能希望从时序测试中消除加载过程(因为无论如何您都无法优化该代码)。

通过查找顶部/左侧然后底部/右侧,您已经开始走上正确的轨道。但也许你可以做得更好一点。您需要寻找早退和不必要的工作。例如,为什么第一个循环跟踪上/左/下/右?在您遇到第一个差异之前,检查是否需要更新底部是没有意义的。一旦找到顶部,就没有必要检查是否需要更新它。因此,请考虑将循环拆分,以便在循环中更新更少的状态(但不增加您考虑的像素数量)

然后你必须开始减少每像素的开销并利用你对处理器/总线/内存架构的了解——例如,如果你有一个 64 位处理器,一次读取 32 位很可能比一次读取 64 位 - 所以使用 longs (Int64) 来比较每次迭代的像素数,而不是只有一个。这将更好地利用处理器的寄存器宽度并将循环的迭代次数减半(将与 x 值相加的次数减半,以及再次绕过循环的分支数)。(注意:您必须小心宽度为奇数的图像,如果您发现一对不同的像素,则需要检查该对中的每个像素以确定差异的确切位置)。根据您的处理器/总线架构,您可能会使用更广泛的数据类型获得更多收益。

接下来,您可以查看缓存一致性是否有帮助。在您的第二个(底部/右侧)循环中,您正在反向迭代 X 值。根据您的处理器架构,这可能比向前遍历它们要慢得多 - 因此,通过向前搜索每条扫描线,您平均可以获得更好的时间。

(注意:以上可能不是做到这一点的“最佳方式”,并且可能有硬件加速技巧可以使处理器更快,但希望它能给你一些关于懒惰如何帮助你的想法走得更快)

于 2012-12-12T21:32:00.597 回答
0

这并不是这个问题的真正答案,但也许这会有所帮助。

首先,我认为您将大部分时间都花在了在屏幕上绘制图像而不是比较图像上。尝试在没有平局的情况下测量时间,这通常是一项繁重的操作。

其次,在时间测量之前运行该方法一次(或两次)是个好主意,因为第一次运行代码可能需要更长的时间才能运行,更好的是运行 1000 次并将总时间乘以 1000 得到平均估计。

最后但并非最不重要的一点是,也许您可​​以跳过几个像素并检查每个第二个像素,但它仍然会给您带来良好的结果吗?

于 2012-12-12T19:18:48.347 回答
0

在这样的代码中 x > right inside loop 可以被删除(我们在循环“x”中有相同的检查)

for (int x = _newBitmap.Width - 1; x > right; x--)
// Found a change.
//
if (x > right)
{
    right = x;
}

但是尝试在优化之前重构代码。

于 2012-12-12T19:29:58.120 回答