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

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

5

Решение

В типичных компьютерных 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 очень запутанно.

7

Другие решения

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. Если у вас есть изображения разных размеров, вам придется написать код для заполнения по краям. Если вы получаете изображения с камеры, они, скорее всего, будут такими.
  • Качество несколько ухудшается из-за того, что я просто пропускаю значения цвета для чередующихся строк развертки вместо чего-то более сложного, чем их усреднение. Для моего приложения скорость была важнее качества.
  • То, как оно написано прямо сейчас, выделяет тонну памяти в стеке. Это было приемлемо для меня, потому что мои изображения были маленькими (640×480) и было достаточно памяти.

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

4

По вопросам рекламы [email protected]