如何使用libjpeg将YUYV原始数据压缩为JPEG?

vdm*_*vdm 5 c c++ yuv libjpeg subsampling

我正在寻找一个如何使用该libjpeg库将YUYV格式帧保存为JPEG文件的示例.

Jon*_*tte 7

在典型的计算机API中,"YUV"实际上表示YCbCr,"YUYV"表示存储为Y0,Cb01,Y1,Cr01,Y2的"YCbCr 4:2:2"...

因此,如果您有"YUV"图像,则可以使用JCS_YCbCr颜色空间将其保存到libjpeg.

如果您有422图像(YUYV),则必须在将扫描线写入libjpeg之前将Cb/Cr值复制到需要它们的两个像素.因此,这个写循环将为您完成:

// "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;
Run Code Online (Sandbox Code Playgroud)

如果您的错误或写入函数可以抛出/ longjmp,请使用您喜欢的auto-ptr以避免泄漏"buf".

直接向libjpeg提供YCbCr更适合转换为RGB,因为它将以该格式直接存储它,从而节省了大量的转换工作.当图像来自网络摄像头或其他视频源时,在某种类型的YCbCr中获取图像通常也是最有效的(例如YUYV.)

最后,"U"和"V"在模拟分量视频中意味着略有不同,因此YUV在计算机API中的命名实际上意味着YCbCr非常混乱.


rvi*_*hne 5

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 };
Run Code Online (Sandbox Code Playgroud)

压缩:

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);
Run Code Online (Sandbox Code Playgroud)

与@JonWatte 的回答中的方法相比,我能够使用这种方法将压缩时间减少约 33%。但是,此解决方案并不适合所有人。一些警告:

  • 您只能压缩尺寸为 8 的倍数的图像。如果您有不同尺寸的图像,则必须编写代码来填充边缘。但是,如果您从相机获取图像,它们很可能是这种方式。
  • 由于我只是跳过交替扫描线的颜色值,而不是像平均它们那样更高级的东西,因此质量在某种程度上受到了影响。不过,对于我的应用程序,速度比质量更重要。
  • 它现在的编写方式在堆栈上分配了大量内存。这对我来说是可以接受的,因为我的图像很小(640x480)并且有足够的可用内存。

libjpeg-turbo 的文档:https : //raw.githubusercontent.com/libjpeg-turbo/libjpeg-turbo/master/libjpeg.txt