Захватите рабочий стол, сделайте его 256-цветным и отправьте через Интернет

Я работаю над проектом, и в этом проекте мне нужно захватить экран рабочего стола и отправить его через Интернет другому клиенту. Чтобы сжать изображение, я хочу преобразовать его в 256-цветное изображение. У меня есть общая палитра из 256 цветов, и я использую евклидово расстояние, чтобы найти ближайший цвет. Проблема в том, что мне нужно отправить эти изображения в 10-15 кадрах в секунду, а создание 256-цветного изображения занимает 7 секунд. Мне интересно, как это делают другие программы (например, teamviewer или реальный 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
             }

Спасибо


person miladrasooli    schedule 15.08.2015    source источник
comment
Нет необходимости преобразовывать изображение в 256-цветное, просто сохраните снимок экрана как 24-битное изображение PNG или JPEG, и вы получите что-то значительно более разборчивое, но при этом размером всего в несколько сотен КиБ.   -  person Dai    schedule 15.08.2015
comment
VNC использует сжатие JPEG для кадров, удаленный рабочий стол в Windows использует другой метод, который включает больше вуду за счет сложности протокола. TeamViewer использует свой собственный протокол, в котором используются некоторые хаки, такие как понижение частоты дискретизации цвета, а также удвоение строк. В настоящее время RDP использует сжатие JPEG для быстро движущихся сцен, где их тайловый подход не работает.   -  person Dai    schedule 15.08.2015
comment
Спасибо, но, в конце концов, все эти приложения имеют 256-цветную опцию, и при медленном соединении они автоматически выбирают эту опцию. 256-цветное изображение весит около 70-80 КиБ и имеет приемлемое качество. Я ищу самый быстрый способ сделать 256-цветное изображение за 1/15 секунды.   -  person miladrasooli    schedule 15.08.2015
comment
Рассматривали ли вы использование квантизации палитры? Если вы используете фиксированную палитру, ваши изображения будут выглядеть ужасно. При использовании адаптивной палитры ощутимое качество изображения значительно улучшается. См. здесь: msdn.microsoft.com/en-us/library/Aa479306.aspx   -  person Dai    schedule 15.08.2015
comment
@Дай спасибо. Но все эти методы очень медленные, и все они имеют как минимум 3 вложенных цикла, что, очевидно, требует больших ресурсов процессора и времени.   -  person miladrasooli    schedule 15.08.2015
comment
Для вашего упрощенного подхода требуется машина, способная обрабатывать 900 x 1600 x 256 x 4 x 15 = 22 гигабайта в секунду. Дело не в том, что вы не можете купить его, но это недешево, и немногие пользователи будут владеть им. Вам нужно использовать тип кода, который используют видеокодеры, вручную настроенный SIMD-код или разгрузить задание на GPU. Ну не пиши свой, это уже сделано.   -  person Hans Passant    schedule 15.08.2015
comment
@HansPassant Спасибо. Не могли бы вы объяснить больше или дать мне несколько ссылок об этом?   -  person miladrasooli    schedule 15.08.2015
comment
Вместо того, чтобы перемещать полную информацию, вы можете попытаться отправить только различия. См. здесь или здесь для примера   -  person TaW    schedule 15.08.2015


Ответы (2)


ХОРОШО. После нескольких дней борьбы со многими методами захвата и квантизаторами цвета я наконец нашел решение. Теперь я могу отправлять полное изображение рабочего стола со скоростью 10–14 кадров в секунду и измененные области рабочего стола со скоростью 20–30 кадров в секунду.

В коде я использовал rcravens для захвата экрана и смены экрана. Затем я обрезал изображение до 10 штук. После этого я взял небольшие фрагменты и сделал их 256-цветными, используя квантизатор цвета Octree, описанный здесь. спасибо @Dai, который указал мне на это направление. После уменьшения цвета я преобразовал каждый кусок в массив байтов и сжал их с помощью библиотеки 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 и другим.

person miladrasooli    schedule 18.08.2015

РЕДАКТИРОВАТЬ 2: я полностью удалил свой старый пост, потому что он неактуален! я думал, что под 256 цветами вы имели в виду 256 бит, а вы говорите о 256 байтах! Я вычислил это, введя в свой калькулятор исходные координаты (900 x 900) и умножив на 256 для цвета. В результате получилось 20,7360,000 бита, что примерно равно 2.5 MB. При сжатии это может достигать 1 MB, в то время как битовый цветовой эквивалент (деленный на 8) будет 300 KB базовым, а сжатый будет намного меньше. Решение простое - действительно нужно много времени, чтобы сделать такое изображение. Большинство приложений, о которых вы говорите, таких как teamviewer, имеют более низкий FPS и гораздо более низкое качество изображения в зависимости от производительности компьютера. Таким образом, мне очень жаль, но решение, вероятно, невозможно сделать за то время, которое вы запросили, с таким компьютером, как ваш.

РЕДАКТИРОВАТЬ 3: Ханс подсчитал в комментариях к вашему вопросу - мы говорим о 22 GB. Это не нормальная работа для среднего компьютера. Это не невозможно, но, если говорить о 2015 году, домашний компьютер не может обрабатывать столько данных за секунду.

person A. Abramov    schedule 15.08.2015
comment
Спасибо, но я думаю, что вы упустили суть. Я знаю, как захватить рабочий стол в растровое изображение, я хочу сделать его 256-цветным за 1/15 секунды, чтобы отправить его через Интернет как можно быстрее. - person miladrasooli; 15.08.2015
comment
Я был бы признателен, если бы вы сказали мне, какие из этих методов имеют параметры для захвата в 256-цветном режиме. Я искал везде, и я еще не нашел его. - person miladrasooli; 15.08.2015
comment
@ user2840253 Второй - PNG, поэтому я бы выбрал его по умолчанию - третий также легко может создать 256 цветов, вам просто нужно немного повозиться с ним. - person A. Abramov; 15.08.2015
comment
Размер GIF-файла при сохранении составляет около 300 400 КиБ. Я просмотрел другие методы и не смог найти никакого реального 256-цветного захвата. PNG-файл может быть небольшим по размеру, но создание 256-цветного изображения сжимает его сильнее. и снова, если вы уверены, что любой из этих методов может получить 256-цветное изображение (например, в палитрах и квантовании цвета), я был бы признателен, если бы вы указали правильный набор параметров для этого. - person miladrasooli; 15.08.2015
comment
@user2840253 это не должно быть с PNG - PNG по умолчанию 8 байт, когда вы используете screen.save() - person A. Abramov; 15.08.2015
comment
спасибо, посмотрю. Я также буду работать над Octree-Quantizer для квантования цветов. - person miladrasooli; 15.08.2015
comment
Я протестировал предоставленный вами код, и он не захватывает 256-цветное изображение. Я сделал снимок со своего рабочего стола, и он был около 1 МБ !!! - person miladrasooli; 15.08.2015
comment
@user2840253 user2840253 Это действительно прояснило для меня ситуацию. Взгляните на редактирование. - person A. Abramov; 16.08.2015