c#图像泛洪填充算法改进
本文关键字:算法 填充 图像 泛洪 | 更新日期: 2023-09-27 18:09:52
我正在制作一个屏幕共享应用程序,它运行循环并使用GDI方法快速抓取屏幕截图。例子
当然,我也使用洪水填充算法来找到两张图像(前一张截图和当前)之间的变化区域。
我使用另一个小技巧-我将快照分辨率降低到10,因为非常持续地处理1920*1080=2073600像素不是很有效。
然而,当我找到矩形边界时-我将其应用于原始的全尺寸位图,并将尺寸(包括顶部,左侧,宽度,高度)乘以10。
这是扫描码:
unsafe bool ArePixelsEqual(byte* p1, byte* p2, int bytesPerPixel)
{
for (int i = 0; i < bytesPerPixel; ++i)
if (p1[i] != p2[i])
return false;
return true;
}
private unsafe List<Rectangle> CodeImage(Bitmap bmp, Bitmap bmp2)
{
List<Rectangle> rec = new List<Rectangle>();
var bmData1 = bmp.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
var bmData2 = bmp2.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp2.PixelFormat);
int bytesPerPixel = 4;
IntPtr scan01 = bmData1.Scan0;
IntPtr scan02 = bmData2.Scan0;
int stride1 = bmData1.Stride;
int stride2 = bmData2.Stride;
int nWidth = bmp.Width;
int nHeight = bmp.Height;
bool[] visited = new bool[nWidth * nHeight];
byte* base1 = (byte*)scan01.ToPointer();
byte* base2 = (byte*)scan02.ToPointer();
for (int y = 0; y < nHeight; y ++)
{
byte* p1 = base1;
byte* p2 = base2;
for (int x = 0; x < nWidth; ++x)
{
if (!ArePixelsEqual(p1, p2, bytesPerPixel) && !(visited[x + nWidth * y]))
{
// fill the different area
int minX = x;
int maxX = x;
int minY = y;
int maxY = y;
var pt = new Point(x, y);
Stack<Point> toBeProcessed = new Stack<Point>();
visited[x + nWidth * y] = true;
toBeProcessed.Push(pt);
while (toBeProcessed.Count > 0)
{
var process = toBeProcessed.Pop();
var ptr1 = (byte*)scan01.ToPointer() + process.Y * stride1 + process.X * bytesPerPixel;
var ptr2 = (byte*)scan02.ToPointer() + process.Y * stride2 + process.X * bytesPerPixel;
//Check pixel equality
if (ArePixelsEqual(ptr1, ptr2, bytesPerPixel))
continue;
//This pixel is different
//Update the rectangle
if (process.X < minX) minX = process.X;
if (process.X > maxX) maxX = process.X;
if (process.Y < minY) minY = process.Y;
if (process.Y > maxY) maxY = process.Y;
Point n; int idx;
//Put neighbors in stack
if (process.X - 1 >= 0)
{
n = new Point(process.X - 1, process.Y); idx = n.X + nWidth * n.Y;
if (!visited[idx]) { visited[idx] = true; toBeProcessed.Push(n); }
}
if (process.X + 1 < nWidth)
{
n = new Point(process.X + 1, process.Y); idx = n.X + nWidth * n.Y;
if (!visited[idx]) { visited[idx] = true; toBeProcessed.Push(n); }
}
if (process.Y - 1 >= 0)
{
n = new Point(process.X, process.Y - 1); idx = n.X + nWidth * n.Y;
if (!visited[idx]) { visited[idx] = true; toBeProcessed.Push(n); }
}
if (process.Y + 1 < nHeight)
{
n = new Point(process.X, process.Y + 1); idx = n.X + nWidth * n.Y;
if (!visited[idx]) { visited[idx] = true; toBeProcessed.Push(n); }
}
}
//finaly set a rectangle.
Rectangle r = new Rectangle(minX * 10, minY * 10, (maxX - minX + 1) * 10, (maxY - minY + 1) * 10);
rec.Add(r);
//got the rectangle now i'll do whatever i want with that.
//notify i scaled everything by x10 becuse i want to apply the changes on the originl 1920x1080 image.
}
p1 += bytesPerPixel;
p2 += bytesPerPixel;
}
base1 += stride1;
base2 += stride2;
}
bmp.UnlockBits(bmData1);
bmp2.UnlockBits(bmData2);
return rec;
}
这是我的电话:
private void Start()
{
full1 = GetDesktopImage();//the first,intial screen.
while (true)
{
full2 = GetDesktopImage();
a = new Bitmap(full1, 192, 108);//resizing for faster processing the images.
b = new Bitmap(full2, 192, 108); // resizing for faster processing the images.
CodeImage(a, b);
count++; // counter for the performance.
full1 = full2; // assign old to current bitmap.
}
}
然而,在我使用了所有的技巧和技术之后,算法运行得相当慢…在我的机器上- Intel i5 4670k 3.4ghz -它只运行20次-意味着CodeImage
方法执行50ms!(最大!)它可能会更低)!这听起来可能很快(不要忘记我必须通过网络发送每个更改后的区域),但我希望实现每秒处理更多图像。我认为主要的瓶颈是在调整两个图像的大小-但我只是认为它会更快-因为它必须通过更少的像素循环… 192 * 108 = 200000 只有. .
我将非常感谢任何帮助,任何改进。
如果有不清楚的地方,我会重新编辑和重写。
编辑:我已经做了一些分析,它不是你的代码或调整大小,这是花费最长的时间,但屏幕截图本身。我已经用我用来测试的代码更新了我的示例代码:
public static void ResizeImage(Bitmap image, Bitmap destImage)
{
var destRect = new Rectangle(0, 0, destImage.Width, destImage.Height);
using (var graphics = Graphics.FromImage(destImage))
{
graphics.CompositingMode = CompositingMode.SourceCopy;
graphics.CompositingQuality = CompositingQuality.HighSpeed;
graphics.InterpolationMode = InterpolationMode.Low;
graphics.SmoothingMode = SmoothingMode.None;
graphics.PixelOffsetMode = PixelOffsetMode.Default;
using (var wrapMode = new ImageAttributes())
{
wrapMode.SetWrapMode(WrapMode.TileFlipXY);
graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);
}
}
}
private void CaptureScreen(Bitmap destImage)
{
using (var graphics = Graphics.FromImage(destImage))
{
graphics.CopyFromScreen(0, 0, 0, 0, new Size(destImage.Width, destImage.Height), CopyPixelOperation.SourceCopy);
}
}
public void Start()
{
var size = new Size(1920, 1080);
var fullSizeImgs = new Bitmap[2]
{
new Bitmap(size.Width, size.Height),
new Bitmap(size.Width, size.Height)
};
var resizedImgs = new Bitmap[2]
{
new Bitmap((int)(size.Width / 10.0f), (int)(size.Height / 10.0f)),
new Bitmap((int)(size.Width / 10.0f), (int)(size.Height / 10.0f))
};
var globalWatch = new Stopwatch();
var codeWatch = new Stopwatch();
int current = 0;
int counter = 0;
CaptureScreen(fullSizeImgs[current]);
ResizeImage(fullSizeImgs[current], resizedImgs[current]);
globalWatch.Start();
while (true)
{
var next = (current + 1) % 2;
long totalFrameTime = 0;
{
codeWatch.Reset();
codeWatch.Start();
CaptureScreen(fullSizeImgs[next]);
codeWatch.Stop();
var elapsed = codeWatch.ElapsedMilliseconds;
Console.WriteLine("Capture : {0} ms", elapsed);
totalFrameTime += elapsed;
}
{
codeWatch.Reset();
codeWatch.Start();
ResizeImage(fullSizeImgs[next], resizedImgs[next]);
codeWatch.Stop();
var elapsed = codeWatch.ElapsedMilliseconds;
Console.WriteLine("Resize : {0} ms", elapsed);
totalFrameTime += elapsed;
}
{
codeWatch.Reset();
codeWatch.Start();
var rects = CodeImage(resizedImgs[current], resizedImgs[next]);
codeWatch.Stop();
var elapsed = codeWatch.ElapsedMilliseconds;
Console.WriteLine("Code : {0} ms", elapsed);
totalFrameTime += elapsed;
}
counter++;
Console.WriteLine("Total : {0} ms'nFPS : {1}'n", totalFrameTime, counter / ((double)globalWatch.ElapsedMilliseconds / 1000.0));
current = (current + 1) % 2;
}
globalWatch.Stop();
}
无论我是否使用较小的图像,我在电脑上的平均帧率都是20。考虑到屏幕截图本身已经花费了30-40毫秒,你的回旋余地很小。
你可以评估其他技术(DirectX,镜像驱动等)。你可以看看这篇文章和那篇文章,看看每种技术的比较。