• Dot Net下实现屏幕图像差异获取v2.0


          前几篇利用类似隔行扫描的方式实现了对屏幕图像差异的获取,并基于该算法实现了一个屏幕传输程序,通过使用该屏幕传输程序,明显感受到该算法的性能存在一定的不足,因此需要改进。最近参考了DG大哥DGScreenSpy_0.4e版的算法实现了分块的方式。由于还没有实现基于此算法的屏幕传输程序,所以目前还无法断然认为该算法一定优于隔行的方式。不过,从当前对该算法的测试数据上来看,估计可以达到20帧每秒(不考虑网络传输等的影响)。

          按照惯例应该先帖几张效果图,但是发现和之前发布的图像差异获取v1.0中的图片相差无几,所以就不帖了。直接来看下文。

          我们对两幅图像的比较,可以逐个像素比较,也可以先把图像分块,然后比较对应块中的数据。那么,到底分块是什么个概念,又该如何分块,分块后的数据如何比较呢?下面我将逐一进行介绍。

         分块也就是说把图片分成“几乘几”的小块,如图1所示一幅200*80的图像。按横向划分4块,纵向划分2块的方式,可以画出一个二行四列的网格,其中每一格就是分块数据。

    screenshare-v2-1 图1

          看起来似乎很简单,其实实现上也很简单,^_^。在Dot Net中,Bitmap类有一个很好用的方法就是Clone,该方法的其中一个重载方法为:public Bitmap Clone(Rectangle rect, PixelFormat format); 怎么样?知道怎么做了吧。你只需要建立一个图像数组用于存储所有块数据,然后其中的每一个块数据均通过Clone方法从母图像中获取。

          分块后的数据其实仍然是一个图像,怎么比较这个图像呢?我第一次接触到分块算法的时候,就愣住了,难道对于分块数据仍然采用像素扫描的方式?研究了DG的算法后才发现自己太天真了。对于分块数据,可以直接在内存中进行比较,这种比较方式速度最快。说到这里,不知道是否有朋友会有疑问:“既然内存中比较速度最快,那为什么还要搞隔行、分块呢?”,答案很简单,因为我们不止是要比较出不同,更重要的是要把不同的部分加以利用。不管是隔行还是分块,目的都是为了能最小化变化的区域,只有这样才能有效降低网络负载。

          如果对上述内容理解清楚了,那接下来就来看下分块的思路。这个思路主要还要感谢DG的算法。

          首先对原图进行分块的初始化,经过这步之后会得到一个最原始的分块数组。然后拿该分块数组与第二张图片进行内存比较。比较的时候,并非逐个分块进行比较,而是有选择的进行比较,这种选择建立在三种假设的基础上:

         1. 鼠标所在的块会发生变化;

         2. 当一个块变化的时候,该块周围的块也会发生变化;

         3. 图片第一行和最后一行会发生变化;

         当比较出不同的时候,即可以采取我们想要的一些行为。另外,要注意的就是选择一个合适分块粒度(即你要把图像分成“几乘几”)。如果分块多,则每一个分块的数据量就小,但是比较的次数就会变多。如果分块少,则每一个分块的数据量较多,但是比较次数就会变少。因此选择一个合适的粒度会影响程序的性能,据文献资料的记载和他人的尝试,一般认为把屏幕分成16*8块最为合适。

          说了这么多,下面来看下关键代码吧:

    初始化所有分块的数据
    /// <summary>
    /// 初始化所有分块的数据
    /// </summary>
    public void InitializeBlocks()
    {
        
    int top = 0;
        
    int left = 0;

        _blocks 
    = new List<Bitmap>(_blocksInColumn * _blocksInRow);
        _isSupposedChanged 
    = new List<bool>(_blocks.Capacity);
        _isScanned 
    = new List<bool>(_blocks.Capacity);

        _blockWidth 
    = (_oldBmp.Width + _blocksInRow - 1/ _blocksInRow;
        _blockHeight 
    = (_oldBmp.Height + _blocksInColumn - 1/ _blocksInColumn;

        
    for (int i = 1; i <= _blocks.Capacity; i++)
        {
            top 
    = ((i + _blocksInRow - 1/ _blocksInRow) - 1;
            left 
    = i - _blocksInRow * top - 1;

            _blocks.Add(_oldBmp.Clone(
    new Rectangle(left * _blockWidth, top * _blockHeight, _blockWidth, _blockHeight), _oldBmp.PixelFormat));
            
    if (i <= _blocksInRow || _blocks.Capacity - i - 1 < _blocksInRow)
            {
                _isSupposedChanged.Add(
    true);
            }
            
    else
            {
                _isSupposedChanged.Add(
    false);
            }
            _isScanned.Add(
    false);
        }
    }

    查找差异的块
    /// <summary>
    /// 查找出差异的块,调用该方法前请确认已经调用了InitalizeBlocks方法对所有块进行初始化
    /// </summary>
    /// <param name="bmp">目标图片</param>
    /// <param name="cursorPoint">鼠标所在的坐标</param>
    public void FindDifferences(Bitmap bmp, Point cursorPoint)
    {
        
    if (cursorPoint.X >= _oldBmp.Width || cursorPoint.X < 0 || cursorPoint.Y >= _oldBmp.Height || cursorPoint.Y < 0)
        {
            
    return;
        }

        
    int cursorBlockIndex;
        
    int currentIndex;
        
    int blockTop;
        
    int blockLeft;
        BitmapData bdOldBmp;
        BitmapData bdNewBmp;
        Bitmap newBmpBlock;
        Rectangle rectBlock;

        cursorBlockIndex 
    = (cursorPoint.X / _blockWidth) + (cursorPoint.Y / _blockHeight) * _blocksInRow;
        _isSupposedChanged[cursorBlockIndex] 
    = true;
        _isScanned[cursorBlockIndex] 
    = false;
        currentIndex 
    = 0;
        
        
    unsafe
        {
            
    byte* pointerToOldBmp;
            
    byte* pointerToNewBmp;

            
    while (currentIndex < _blocks.Capacity)
            {
                
    if (!_isScanned[currentIndex] && _isSupposedChanged[currentIndex])
                {
                    _isScanned[currentIndex] 
    = true;
                    blockTop 
    = (currentIndex / _blocksInRow) * _blockHeight;
                    blockLeft 
    = (currentIndex % _blocksInRow) * _blockWidth;
                    rectBlock
    =new Rectangle(blockLeft, blockTop, _blockWidth, _blockHeight);
                    newBmpBlock 
    = (Bitmap)bmp.Clone(rectBlock,_format);//克隆的时间需要0.015~0.016s左右
                    rectBlock.X = 0;
                    rectBlock.Y 
    = 0;

                    bdOldBmp 
    = _blocks[currentIndex].LockBits(rectBlock, ImageLockMode.ReadWrite, _format);
                    bdNewBmp 
    = newBmpBlock.LockBits(rectBlock, ImageLockMode.ReadWrite, _format);
                    
                    
    int k = RtlCompareMemory(bdOldBmp.Scan0, bdNewBmp.Scan0, _blockWidth * 3 * _blockHeight);
                    
    if (k < bdOldBmp.Stride * _blockHeight)
                    {
                        pointerToOldBmp 
    = (byte*)bdOldBmp.Scan0.ToPointer();
                        pointerToNewBmp 
    = (byte*)bdNewBmp.Scan0.ToPointer();

                        
    for (int height = 0; height < _blockHeight; height++)
                        {
                            
    for (int width = 0; width < _blockWidth; width++)
                            {
                                pointerToOldBmp[
    0= pointerToNewBmp[0];
                                pointerToOldBmp[
    1= pointerToNewBmp[1];
                                pointerToOldBmp[
    2= pointerToNewBmp[2];
                                pointerToNewBmp 
    += 3;
                                pointerToOldBmp 
    += 3;
                            }
                            pointerToNewBmp 
    += bdNewBmp.Stride - newBmpBlock.Width * 3;
                            pointerToOldBmp 
    += bdNewBmp.Stride - newBmpBlock.Width * 3;
                        }

                        
    if (currentIndex - _blocksInRow - 1 >= 0)
                        {
                            _isSupposedChanged[currentIndex 
    - _blocksInRow - 1= true;
                            _isSupposedChanged[currentIndex 
    - _blocksInRow] = true;
                        }
                        
    else if (currentIndex - _blocksInRow >= 0)
                        {
                            _isSupposedChanged[currentIndex 
    - _blocksInRow] = true;
                        }

                        
    if (currentIndex + _blocksInRow + 1 < _blocks.Capacity)
                        {
                            _isSupposedChanged[currentIndex 
    + _blocksInRow + 1= true;
                            _isSupposedChanged[currentIndex 
    + _blocksInRow] = true;
                        }
                        
    else if (currentIndex + _blocksInRow < _blocks.Capacity)
                        {
                            _isSupposedChanged[currentIndex 
    + _blocksInRow] = true;
                        }

                        
    if (currentIndex % _blocksInRow > 1)
                        {
                            _isSupposedChanged[currentIndex 
    - 2= true;
                            _isSupposedChanged[currentIndex 
    - 1= true;
                        }
                        
    else if (currentIndex % _blocksInRow > 0)
                        {
                            _isSupposedChanged[currentIndex 
    - 1= true;
                        }

                        
    if (currentIndex % _blocksInRow < _blocksInRow - 2)
                        {
                            _isSupposedChanged[currentIndex 
    + 2= true;
                            _isSupposedChanged[currentIndex 
    + 1= true;
                        }
                        
    else if (currentIndex % _blocksInRow < _blocksInRow - 1)
                        {
                            _isSupposedChanged[currentIndex 
    + 1= true;
                        }

                        
    if (handler != null)
                        {
                            handler(newBmpBlock, rectBlock);
                        }
                    }

                    _blocks[currentIndex].UnlockBits(bdOldBmp);
                    newBmpBlock.UnlockBits(bdNewBmp);
                    currentIndex 
    = Math.Max(Math.Min(currentIndex - _blocksInRow - 1, currentIndex - 2), 0);
                }
                
    else
                {
                    currentIndex
    ++;
                }
            }
    //end of while
        }//end of unsafe
    }//end of FindDifferences

          注:该实现由于并不是特地为了实现屏幕传输,所以在比较的时候,我利用了Clone方式获取第二张图片的对应块。该方式需要耗时0.015s左右,因此比较100块图像就会额外使用1.5s左右的时间。

          项目打包下载:https://files.cnblogs.com/stg609/Pic-v2-0.rar

          参考:http://iamgyg.blog.163.com/blog/static/382232572009518113852872/

    作者:stg609
    出处:http://stg609.cnblogs.com/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
    作者:stg609

    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

    本人博客已经转移到Charley Blog

  • 相关阅读:
    重排列
    最多分成多少块
    后面第一个大于
    走格子
    硬币游戏
    还是01串
    戴德兰
    个人所得税
    最长高地
    执行Commit时Oracle做哪些工作
  • 原文地址:https://www.cnblogs.com/stg609/p/1625126.html
Copyright © 2020-2023  润新知