Как сжать исходные данные YUYV в JPEG с помощью libjpeg?

Я ищу пример того, как сохранить кадр формата YUYV в файл JPEG, используя libjpeg библиотека.

2 ответа

В типичных компьютерных API "YUV" фактически означает YCbCr, а "YUYV" означает "YCbCr 4:2:2", сохраняемый как Y0, Cb01, Y1, Cr01, Y2 ...

Таким образом, если у вас есть изображение "YUV", вы можете сохранить его в libjpeg, используя цветовое пространство JCS_YCbCr.

Если у вас есть изображение 422 (YUYV), вы должны продублировать значения Cb/Cr на два пикселя, которые им нужны, прежде чем записать строку развертки в libjpeg. Таким образом, этот цикл записи сделает это за вас:

// "base" is an unsigned char const * with the YUYV data
// jrow is a libjpeg row of samples array of 1 row pointer
cinfo.image_width = width & -1; 
cinfo.image_height = height & -1; 
cinfo.input_components = 3; 
cinfo.in_color_space = JCS_YCbCr; 
jpeg_set_defaults(&cinfo); 
jpeg_set_quality(&cinfo, 92, TRUE); 
jpeg_start_compress(&cinfo, TRUE); 
unsigned char *buf = new unsigned char[width * 3]; 
while (cinfo.next_scanline < height) { 
    for (int i = 0; i < cinfo.image_width; i += 2) { 
        buf[i*3] = base[i*2]; 
        buf[i*3+1] = base[i*2+1]; 
        buf[i*3+2] = base[i*2+3]; 
        buf[i*3+3] = base[i*2+2]; 
        buf[i*3+4] = base[i*2+1]; 
        buf[i*3+5] = base[i*2+3]; 
    } 
    jrow[0] = buf; 
    base += width * 2; 
    jpeg_write_scanlines(&cinfo, jrow, 1); 
}
jpeg_finish_compress(&cinfo);
delete[] buf;

Используйте ваш любимый auto-ptr, чтобы избежать утечки "buf", если ваша ошибка или функция записи может генерировать / longjmp.

Предоставление YCbCr для libjpeg напрямую предпочтительнее, чем преобразование в RGB, поскольку оно будет хранить его непосредственно в этом формате, тем самым сохраняя большую часть работы по преобразованию. Когда изображение поступает с веб-камеры или другого видеоисточника, обычно также наиболее эффективно получить его в YCbCr (например, YUYV).

Наконец, "U" и "V" означают что-то немного другое в аналоговом компонентном видео, поэтому наименование YUV в компьютерных API, которое действительно означает, что YCbCr очень запутанно.

libjpeg также имеет режим необработанных данных, благодаря которому вы можете напрямую предоставлять необработанные данные с пониженной дискретизацией (что практически соответствует формату YUYV). Это более эффективно, чем дублирование значений UV только для того, чтобы libjpeg снова уменьшил их внутренне.

Для этого вы используете jpeg_write_raw_data вместо jpeg_write_scanlinesи по умолчанию он будет обрабатывать ровно 16 строк развертки за раз. JPEG ожидает, что плоскости U и V будут в 2 раза понижены по умолчанию. Формат YUYV уже имеет горизонтальный размер с пониженной дискретизацией, но не вертикальный, поэтому я пропускаю U и V при каждом другом сканировании.

Инициализация:

cinfo.image_width = /* width in pixels */;
cinfo.image_height = /* height in pixels */;
cinfo.input_components = 3;
cinfo.in_color_space = JCS_YCbCr;
jpeg_set_defaults(&cinfo);

cinfo.raw_data_in = true;

JSAMPLE y_plane[16][cinfo.image_width];
JSAMPLE u_plane[8][cinfo.image_width / 2];
JSAMPLE v_plane[8][cinfo.image_width / 2];

JSAMPROW y_rows[16];
JSAMPROW u_rows[8];
JSAMPROW v_rows[8];

for (int i = 0; i < 16; ++i)
{
    y_rows[i] = &y_plane[i][0];
}

for (int i = 0; i < 8; ++i)
{
    u_rows[i] = &u_plane[i][0];
}

for (int i = 0; i < 8; ++i)
{
    v_rows[i] = &v_plane[i][0];
}

JSAMPARRAY rows[] { y_rows, u_rows, v_rows };

Сжимая:

jpeg_start_compress(&cinfo, true);

while (cinfo.next_scanline < cinfo.image_height)
{
    for (JDIMENSION i = 0; i < 16; ++i)
    {
        auto offset = (cinfo.next_scanline + i) * cinfo.image_width * 2;
        for (JDIMENSION j = 0; j < cinfo.image_width; j += 2)
        {
            y_plane[i][j] = image.data[offset + j * 2 + 0];
            y_plane[i][j + 1] = image.data[offset + j * 2 + 2];

            if (i % 2 == 0)
            {
                u_plane[i / 2][j / 2] = image_data[offset + j * 2 + 1];
                v_plane[i / 2][j / 2] = image_data[offset + j * 2 + 3];
            }
        }
    }

    jpeg_write_raw_data(&cinfo, rows, 16);
}

jpeg_finish_compress(&cinfo);

Благодаря этому методу мне удалось сократить время сжатия примерно на 33% по сравнению с ответом @JonWatte. Это решение не для всех, хотя; некоторые предостережения:

  • Вы можете сжимать изображения только с размерами, кратными 8. Если у вас есть изображения разных размеров, вам придется написать код для заполнения по краям. Если вы получаете изображения с камеры, они, скорее всего, будут такими.
  • Качество несколько ухудшается из-за того, что я просто пропускаю значения цвета для чередующихся строк развертки вместо чего-то более сложного, чем их усреднение. Для моего приложения скорость была важнее качества.
  • То, как оно написано прямо сейчас, выделяет тонну памяти в стеке. Это было приемлемо для меня, потому что мои изображения были маленькими (640x480) и было достаточно памяти.

Документация для libjpeg-turbo: https://raw.githubusercontent.com/libjpeg-turbo/libjpeg-turbo/master/libjpeg.txt

Другие вопросы по тегам