【问题标题】:Capture desktop , Make it 256 color and send it over internet捕获桌面,将其设置为 256 色并通过 Internet 发送
【发布时间】:2015-11-08 11:08:58
【问题描述】:

我正在做一个项目,在这个项目中,我需要捕获桌面屏幕并将其通过 Internet 发送到其他客户端。要压缩图像,我想将其转换为 256 色图像。我有一个通用的 256 调色板,我正在使用欧几里德距离来找到最接近的颜色。问题是我需要以每秒 10-15 帧的速度发送这些图像,制作 256 色图像需要 7 秒。我想知道其他程序(如 teamviewer 或 real VNC )是如何做到这一点的。

for (int y=0;y<900;y++) //loop through the height
     for (int x=0;x<1600;x++) //loop through the width
         for (int p=0;p<256;p++) //loop through palette colors
             {
                calculate Euclidean distance of each pixel for each color in pallette and 
                 find the nearest color
                 ** these nested loops take 7 seconds to complete
             }

谢谢

【问题讨论】:

  • 无需将图像转换为 256 色,只需将屏幕截图另存为 24 位 PNG 或 JPEG 图像,您将获得更加清晰易读的内容,但仍然只有几百 KiB大小。
  • VNC 对帧使用 JPEG 压缩,Windows 中的远程桌面使用不同的技术,以协议复杂性为代价涉及更多的巫术。 TeamViewer 使用他们自己的协议,该协议采用了一些技巧,例如颜色下采样以及行倍增。如今,RDP 采用 JPEG 压缩来处理无法使用平铺方法的快速移动场景。
  • 谢谢,但最终所有这些应用程序都有 256 色选项,并且在连接速度较慢的情况下,它们会自动选择此选项。 256 色图像约为 70-80 KiB 并且具有合理的质量。我正在寻找在 1/15 秒内制作 256 色图像的最快方法。
  • 您是否考虑过使用调色板量化?如果您使用的是固定调色板,那么您的图像会看起来可怕。通过使用自适应调色板,可显着改善可感知的图像质量。见这里:msdn.microsoft.com/en-us/library/Aa479306.aspx
  • 您的简单方法需要一台可以处理 900 x 1600 x 256 x 4 x 15 = 22 GB/秒的机器。不是你买不到,而是它不便宜,很少有用户会拥有它。您需要使用视频编码器使用的那种代码、手动调整的 SIMD 代码或将作业卸载到 GPU。好吧,不要自己写了,这已经完成了。

标签: c# screen-capture color-palette 256color


【解决方案1】:

好的。经过几天与许多捕获方法和颜色量化器的斗争,我终于找到了解决方案。现在我能够以 10~14 FPS 的速度发送整个桌面图像,并以 20~30 FPS 的速度发送桌面区域。

在代码中,我使用rcravens 的类来捕获屏幕和屏幕变化。然后我将图像裁剪为 10 小块。在那之后,我用八叉树颜色量化器将它们制作成 256 色,感谢@Dai 向我指出了那个方向,here。减色后,我将每个片段转换为字节数组并使用 LZ4.Net 库对其进行压缩。

代码如下:

    int all_count = 0;
    Bitmap _desktop = null;

    Bitmap _merged_bitmap = new Bitmap(1600, 900);

    int _height_part_ = 0;
    int _total_rows = 10;
    Bitmap[] crops = null;
    Bitmap[] _new_crops = null;
    Stopwatch sw = new Stopwatch();

    int _desktop_height = 0;
    int _desktop_width = 0;

    ImageManipulation.OctreeQuantizer _q ;
    RLC.RemoteDesktop.ScreenCapture cap = new RLC.RemoteDesktop.ScreenCapture();



   private void CaptureAndSend()
    {
        sw.Restart();

        //cap = new RLC.RemoteDesktop.ScreenCapture();

        int _left = -1, _top = -1; //Changed regions
        _desktop = cap.Screen(out _left, out _top); //Capture desktop or changed region of it

        if (_desktop == null) return; //if nothing has changed since last capture skip everything

        _desktop_height = _desktop.Height;
        _desktop_width = _desktop.Width;

        // If very small part has changed since last capture skip everything
        if (_desktop_height < 10 || _desktop_width < 10) return; 

        TotalRows(_total_rows); // Calculate the total number of rows 

        crops = new Bitmap[_total_rows]; // Cropped pieces of image
        _new_crops = new Bitmap[_total_rows];

        for (int i = 0; i < _total_rows - 1; i++) //Take whole image and split it into smaller images
            crops[i] = CropRow(i);
        crops[_total_rows - 1] = CropLastRow(_total_rows - 1);


        Parallel.For(0, _total_rows, i =>
        {
            ImageManipulation.OctreeQuantizer _q = new ImageManipulation.OctreeQuantizer(255, 4); // Initialize Octree
            _new_crops[i] = _q.Quantize(crops[i]);

            using (MemoryStream ms=new MemoryStream())
            { 
                _new_crops[i].Save(ms, ImageFormat.Png);
                //Install-Package LZ4.net
                //Compress each part and send them over network
                byte[] data = Lz4Net.Lz4.CompressBytes(ms.ToArray(), Lz4Net.Lz4Mode.HighCompression);

                all_count += data.Length; //Just to check the final size of image
            }                  
        });



        Console.WriteLine(String.Format("{0:0.0} FPS , {1} seconds , size {2} kb", 1.0 / sw.Elapsed.TotalSeconds, sw.Elapsed.TotalSeconds.ToString(), all_count / 1024));
        all_count = 0;

    }
    private void TotalRows(int parts)
    {
        _height_part_ = _desktop_height / parts;
    }
    private Bitmap CropRow(int row)
    {
        return Crop(_desktop, new Rectangle(0, row * _height_part_, _desktop_width, _height_part_));
    }
    private Bitmap CropLastRow(int row)
    {
        return Crop(_desktop, new Rectangle(0, row * _height_part_, _desktop_width, _desktop_height - (row * _height_part_)));
    }
 [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
    private unsafe static extern int memcpy(byte* dest, byte* src, long count);

    private unsafe Bitmap Crop(Bitmap srcImg, Rectangle rectangle)
    {
        if ((srcImg.Width == rectangle.Width) && (srcImg.Height == rectangle.Height))
            return srcImg;

        var srcImgBitmapData = srcImg.LockBits(new Rectangle(0, 0, srcImg.Width, srcImg.Height), ImageLockMode.ReadOnly, srcImg.PixelFormat);
        var bpp = srcImgBitmapData.Stride / srcImgBitmapData.Width; // 3 or 4
        var srcPtr = (byte*)srcImgBitmapData.Scan0.ToPointer() + rectangle.Y * srcImgBitmapData.Stride + rectangle.X * bpp;
        var srcStride = srcImgBitmapData.Stride;

        var dstImg = new Bitmap(rectangle.Width, rectangle.Height, srcImg.PixelFormat);
        var dstImgBitmapData = dstImg.LockBits(new Rectangle(0, 0, dstImg.Width, dstImg.Height), ImageLockMode.WriteOnly, dstImg.PixelFormat);
        var dstPtr = (byte*)dstImgBitmapData.Scan0.ToPointer();
        var dstStride = dstImgBitmapData.Stride;

        for (int y = 0; y < rectangle.Height; y++)
        {
            memcpy(dstPtr, srcPtr, dstStride);
            srcPtr += srcStride;
            dstPtr += dstStride;
        }

        srcImg.UnlockBits(srcImgBitmapData);
        dstImg.UnlockBits(dstImgBitmapData);
        return dstImg;
    }

我知道我的代码内存效率不高。如果有人可以帮助我优化此代码,我将不胜感激。 再次感谢我的朋友 A. Abramov、Dai、HansPassant、TaW 和其他人。

【讨论】:

    【解决方案2】:

    编辑 2:我完全删除了我的旧帖子,因为它不相关! 256 色,我以为你的意思是 256 位——而你说的是 256 字节!我通过将原始坐标 (900 x 900) 放入我的计算器中,然后乘以 256 来计算颜色。结果是20,7360,000 位,大致为2.5 MB。压缩后,这可以达到大约1 MB - 而位颜色等效(除以8)将是300 KB 基数,并且压缩后会小得多。解决方案很简单——拍摄这样一张照片确实需要很长时间。根据计算机性能,您所说的大多数应用程序(例如 teamviewer)都具有较低的 FPS 和低得多的图像质量。因此,我很抱歉 - 但解决方案是在您要求的时间内使用像您这样的计算机可能无法做到这一点。

    编辑 3:Hans 根据您的问题在 cmets 中进行了数学运算 - 我们正在谈论 22 GB。这不是普通计算机的正常工作。这并非不可能,但从 2015 年开始,家用电脑在一秒钟内处理这么多数据的情况远非普遍。

    【讨论】:

    • 谢谢,但我认为你没有抓住重点。我知道如何将桌面捕获为位图,我想在 1/15 秒内将其设为 256 色,以便尽快通过互联网发送
    • 如果您告诉我这些方法中的哪些具有以 256 色捕获的参数,我将不胜感激。我到处找了,还没找到。
    • @user2840253 第二个是PNG,所以我默认使用它-第三个也很容易创建256色,你只需要稍微修改一下。跨度>
    • 保存后的 gif 文件大小约为 300 400 KiB。我查看了其他方法,但找不到任何方法来拍摄真正的 256 色捕获。 PNG 文件的大小可能很小,但将其设置为 256 色会对其进行更多压缩。如果您确定这些方法中的任何一种都可以拍摄 256 色图像(如调色板和颜色量化),如果您指出正确的参数集,我将不胜感激。
    • @user2840253 it 不应与 PNG 一起使用 - 当您使用 screen.save() 时,PNG 默认为 8 个字节
    猜你喜欢
    • 1970-01-01
    • 2014-04-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多