You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#实现录屏功能时如何对比前后截图检测屏幕画面变动

实现思路

你的核心逻辑方向是对的,但要注意两个优化点:一是帧比对全程在内存中完成,不要先存盘再读盘比对,平白增加IO开销;二是不要直接比对全尺寸原始帧,高分辨率下逐像素比对的CPU占用会很高,完全没必要。
实际生产环境用的屏幕变动检测通用方案是缩略图灰度比对法,性能开销极低,检测灵敏度完全满足录屏需求:

  • 每次截到全屏帧后,先将其缩放到极小尺寸(常用64*36,总像素仅2304个),转成单通道灰度图
  • 维护一个上一帧的灰度缩略图缓存,每次计算当前帧和上一帧的像素差异占比
  • 预设一个差异阈值(推荐0.2%~1%,可根据需要的灵敏度调整),只有差异占比超过阈值时,才执行存盘、写入帧序列的操作,同时更新上一帧缓存;否则直接丢弃当前帧,不做任何IO操作
  • 第一帧没有历史缓存时直接保存即可

注意避坑
  • 取位图像素不要用GetPixel/SetPixel方法,这俩是GDI+的包装方法,性能极差,要通过LockBits锁定位图内存直接读取,速度能提升几十上百倍;如果项目不允许开启unsafe编译,用Marshal.Copy拷贝内存数据读取即可,性能差异很小
  • 阈值不要设为0,否则任务栏时间跳动、光标闪烁、托盘图标微小动画都会触发帧保存,会产生大量无意义的冗余帧
  • 原代码中bitmap已经包裹在using块中,块结束时会自动释放资源,不需要手动调用Dispose()
  • 路径拼接建议用Path.Combine,自动适配不同系统的路径分隔符,避免硬编码//导致的路径异常

改造后的代码

首先添加类级别常量和缓存字段:

// 缩略图尺寸,越小比对速度越快,不要小于32*18
private const int ThumbWidth = 64;
private const int ThumbHeight = 36;
// 差异阈值,0.005代表0.5%的像素存在差异时判定为画面变动
private const double DifferenceThreshold = 0.005;
// 灰度差判定阈值,过滤缩放插值产生的微小噪点
private const int GrayDiffThreshold = 20;
// 上一帧的灰度缩略图缓存
private byte[] _lastFrameGrayThumb = null;

添加两个辅助方法:

// 将位图转为灰度缩略图字节数组(unsafe版本,性能最优)
private unsafe byte[] GetGrayThumbnail(Bitmap sourceBmp)
{
    using (Bitmap thumbBmp = new Bitmap(ThumbWidth, ThumbHeight, PixelFormat.Format8bppIndexed))
    {
        // 先把原图缩放到缩略图尺寸
        using (Graphics g = Graphics.FromImage(thumbBmp))
        {
            g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.Bilinear;
            g.DrawImage(sourceBmp, 0, 0, ThumbWidth, ThumbHeight);
        }
        // 锁内存直接读像素
        Rectangle rect = new Rectangle(0, 0, ThumbWidth, ThumbHeight);
        BitmapData bmpData = thumbBmp.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format8bppIndexed);
        byte[] grayValues = new byte[ThumbWidth * ThumbHeight];
        byte* ptr = (byte*)bmpData.Scan0;
        for (int i = 0; i < grayValues.Length; i++)
        {
            grayValues[i] = ptr[i];
        }
        thumbBmp.UnlockBits(bmpData);
        return grayValues;
    }
}

// 计算两个灰度帧的差异占比
private double GetFrameDifferenceRatio(byte[] currentGray, byte[] lastGray)
{
    if (currentGray.Length != lastGray.Length)
        return 1.0;
    int diffPixelCount = 0;
    for (int i = 0; i < currentGray.Length; i++)
    {
        if (Math.Abs(currentGray[i] - lastGray[i]) > GrayDiffThreshold)
        {
            diffPixelCount++;
        }
    }
    return (double)diffPixelCount / currentGray.Length;
}

改造原有RecordVideo方法:

public void RecordVideo()
{
    watch.Start();
    using (Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height))
    {
        using (Graphics g = Graphics.FromImage(bitmap))
        {
            g.CopyFromScreen(new Point(bounds.Left, bounds.Top), Point.Empty, bounds.Size);
        }
        // 先计算当前帧的灰度缩略图
        byte[] currentGrayThumb = GetGrayThumbnail(bitmap);
        bool needSave = false;
        // 第一帧直接保存
        if (_lastFrameGrayThumb == null)
        {
            needSave = true;
        }
        else
        {
            double diffRatio = GetFrameDifferenceRatio(currentGrayThumb, _lastFrameGrayThumb);
            // 差异超过阈值才保存
            if (diffRatio >= DifferenceThreshold)
            {
                needSave = true;
            }
        }
        if (needSave)
        {
            string name = Path.Combine(tempPath, $"screenshot-{fileCount}.png");
            bitmap.Save(name, ImageFormat.Png);
            inputImageSequence.Add(name);
            fileCount++;
            // 更新上一帧缓存
            _lastFrameGrayThumb = currentGrayThumb;
        }
    }
}

可调参数说明
  • 如果需要更高的检测灵敏度(比如要捕捉小范围的光标移动、小字变化),可以把ThumbWidth/ThumbHeight调到128*72,DifferenceThreshold调到0.002(0.2%)
  • 如果更在意存储空间,只需要捕捉大范围画面变动,可以把DifferenceThreshold调到0.01(1%),过滤掉更多微小变动
  • GrayDiffThreshold可以根据实际显示效果调整,值越大越能过滤噪点,但可能漏掉低对比度的画面变动

内容的提问来源于stack exchange,提问作者Kostas G

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 11:57:12