如何使用Dot Net v2.0技术获取屏幕图像差异?

2026-05-25 10:516阅读0评论SEO教程
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计1909个文字,预计阅读时间需要8分钟。

如何使用Dot Net v2.0技术获取屏幕图像差异?

前几篇利用类间隔扫描的方法实现了对屏幕图像差异的获取,并基于该算法实现了一个屏幕传输程序。通过使用该屏幕传输程序,可以明显感受到算法在性能上存在一定的不足。

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

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

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

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

图1

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

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

如何使用Dot Net v2.0技术获取屏幕图像差异?

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

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

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

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

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

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

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

初始化所有分块的数据 ///<summary>
///初始化所有分块的数据
///</summary>
publicvoidInitializeBlocks()
{
inttop=0;
intleft=0;

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

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

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

_blocks.Add(_oldBmp.Clone(newRectangle(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>
///<paramname="bmp">目标图片</param>
///<paramname="cursorPoint">鼠标所在的坐标</param>
publicvoidFindDifferences(Bitmapbmp,PointcursorPoint)
{
if(cursorPoint.X>=_oldBmp.Width||cursorPoint.X<0||cursorPoint.Y>=_oldBmp.Height||cursorPoint.Y<0)
{
return;
}

intcursorBlockIndex;
intcurrentIndex;
intblockTop;
intblockLeft;
BitmapDatabdOldBmp;
BitmapDatabdNewBmp;
BitmapnewBmpBlock;
RectanglerectBlock;

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=newRectangle(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);

intk=RtlCompareMemory(bdOldBmp.Scan0,bdNewBmp.Scan0,_blockWidth*3*_blockHeight);
if(k<bdOldBmp.Stride*_blockHeight)
{
pointerToOldBmp=(byte*)bdOldBmp.Scan0.ToPointer();
pointerToNewBmp=(byte*)bdNewBmp.Scan0.ToPointer();

for(intheight=0;height<_blockHeight;height++)
{
for(intwidth=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;
}
elseif(currentIndex-_blocksInRow>=0)
{
_isSupposedChanged[currentIndex-_blocksInRow]=true;
}

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

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

if(currentIndex%_blocksInRow<_blocksInRow-2)
{
_isSupposedChanged[currentIndex+2]=true;
_isSupposedChanged[currentIndex+1]=true;
}
elseif(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++;
}
}//endofwhile
}//endofunsafe
}//endofFindDifferences

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

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

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

本文共计1909个文字,预计阅读时间需要8分钟。

如何使用Dot Net v2.0技术获取屏幕图像差异?

前几篇利用类间隔扫描的方法实现了对屏幕图像差异的获取,并基于该算法实现了一个屏幕传输程序。通过使用该屏幕传输程序,可以明显感受到算法在性能上存在一定的不足。

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

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

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

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

图1

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

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

如何使用Dot Net v2.0技术获取屏幕图像差异?

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

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

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

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

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

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

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

初始化所有分块的数据 ///<summary>
///初始化所有分块的数据
///</summary>
publicvoidInitializeBlocks()
{
inttop=0;
intleft=0;

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

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

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

_blocks.Add(_oldBmp.Clone(newRectangle(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>
///<paramname="bmp">目标图片</param>
///<paramname="cursorPoint">鼠标所在的坐标</param>
publicvoidFindDifferences(Bitmapbmp,PointcursorPoint)
{
if(cursorPoint.X>=_oldBmp.Width||cursorPoint.X<0||cursorPoint.Y>=_oldBmp.Height||cursorPoint.Y<0)
{
return;
}

intcursorBlockIndex;
intcurrentIndex;
intblockTop;
intblockLeft;
BitmapDatabdOldBmp;
BitmapDatabdNewBmp;
BitmapnewBmpBlock;
RectanglerectBlock;

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=newRectangle(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);

intk=RtlCompareMemory(bdOldBmp.Scan0,bdNewBmp.Scan0,_blockWidth*3*_blockHeight);
if(k<bdOldBmp.Stride*_blockHeight)
{
pointerToOldBmp=(byte*)bdOldBmp.Scan0.ToPointer();
pointerToNewBmp=(byte*)bdNewBmp.Scan0.ToPointer();

for(intheight=0;height<_blockHeight;height++)
{
for(intwidth=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;
}
elseif(currentIndex-_blocksInRow>=0)
{
_isSupposedChanged[currentIndex-_blocksInRow]=true;
}

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

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

if(currentIndex%_blocksInRow<_blocksInRow-2)
{
_isSupposedChanged[currentIndex+2]=true;
_isSupposedChanged[currentIndex+1]=true;
}
elseif(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++;
}
}//endofwhile
}//endofunsafe
}//endofFindDifferences

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

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

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