随笔 - 62  文章 - 73  评论 - 592 

      前几篇利用类似隔行扫描的方式实现了对屏幕图像差异的获取,并基于该算法实现了一个屏幕传输程序,通过使用该屏幕传输程序,明显感受到该算法的性能存在一定的不足,因此需要改进。最近参考了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左右的时间。

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

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

 

 

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

 

posted on 2009-12-15 22:29 stg609 阅读(3006) 评论(12) 编辑 收藏

 回复 引用 查看   
#1楼 2009-12-15 23:20 | 斯克迪亚      
很强大,受教了
 回复 引用 查看   
#2楼 2009-12-15 23:47 | shenzhen      
好东西暂时用不上但是先收藏了。。谢了
 回复 引用 查看   
#3楼 2009-12-16 01:57 |       
靠。。lz把我想做的都做了。。。

觉得单纯图像处理到查分应该就极限了,最多就是一些变动扫描的算法,可能用到一些数学,矩阵之类的。

再走应该就是视频压缩了,比如divx之类的。

 回复 引用 查看   
#4楼 2009-12-16 02:06 |       
看到个做捕捉的,竟然卖到1500元。。。无语了。

http://www.software168.com/sdk.htm

⊙ 屏幕捕获SDK (Mirror driver方式)
性能好,速度快,CPU占用率低,可直接捕获正在播放的视频,需安装驱动程序,不支持Win98。获得屏幕变化区域位图,性能可参考海天屏幕广播。接口和GDI方式类似,支持VC++,C#,DELPHI,VB。

价格:1500元 SDK+屏幕广播DEMO代码:3200元 汇款开户银行见联系我们页面

 回复 引用 查看   
#5楼 2009-12-16 07:16 | 徐少侠      
呵呵
和我那个思路基本一致
不过我没有那么“智能”得去判断差异位置
而是全屏扫描存在差异的块,当然,扫描算法是优化过的

另外,获得一个图像的局部小片,貌似用框架内的方法效率都不高

建议在获得原始图的BitmapData对象后,按目标小片尺寸新建bmp图。
同样获得该碎片图的BitmapData对象
然后在两个图片的Scan0指针帮助下,进行非安全代码内的直接像素字节复制。

速度可以快不少
目前我就是用这个方式获得我自定义的48*50大小的局部差异图的

另外,我昨天测试了一下整图1280*800像素的逐字节异或
一屏要45毫秒左右。
加上截屏约30毫秒
另外一些处理估计20毫秒(主要是差异位图压缩)
那么不算上网络发送,100毫秒左右大致能完成一屏的数据处理
并且这种位操作所需要的差异查找时间是彻底和屏幕变化百分比无关的。
只在最后差异图压缩和传输时有所影响。



更新得很快啊。
 回复 引用 查看   
#7楼[楼主] 2009-12-16 11:33 | stg609      
@吉日嘎拉&gt;不仅权限设计
呵呵,这也要多亏徐少侠

@徐少侠
哇,你速度好快,异或都搞定了,看来我要抓紧了。

@辰
呵呵,那个东西,我之前也看到过,不过感觉他的效果貌似还不错。

 回复 引用 查看   
#8楼 2009-12-16 16:55 | 徐少侠      
异或主体也搞定了
一个问题就是,我本来用UDP的,但是第一张图一定大于65K了
所以,发送不了。
大范围的图形变化也不行
要么改TCP再试

还有一个发现就是对CPU要求不低,我开个PPlive,没播放,就在那里下载呢。服务器就只能发4帧每秒了。正常应该在10帧左右的

 回复 引用 查看   
#9楼[楼主] 2009-12-19 13:10 | stg609      
@徐少侠
你不算上网络发送,100毫秒左右大致能完成一屏的数据处理?
我现在对80*100的图片进行异或(逐字节)然后再进行压缩(Zlib),需要近30毫秒的时间,而我一屏有128个80*100的图片,所以如果完成一屏(逐个块比较)的话估计要300毫秒左右的时间。但是如果不做压缩一屏估计只需要60毫秒左右时间。我不是很明白你所说的差异位图压缩是什么意思?

 回复 引用 查看   
#10楼[楼主] 2009-12-19 14:12 | stg609      
如果简单的使用jpeg压缩,而不是先异或再压缩的方式,则整个屏幕图像处理时间可以控制在100ms以内,估计96ms左右。
 回复 引用 查看   
#11楼[楼主] 2009-12-19 14:25 | stg609      
而且,我实验后发现,如果采用先异或后(Zlib)压缩,CPU(双核)占用率达到50%。而如果我用jpeg压缩,则CPU占用率只有20%。
 回复 引用 查看   
#12楼 2011-02-21 21:39 | 断星      
学习了,尤其是楼主和那位徐少侠的交流内容更是让我受益匪浅