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

C++ 如何使用libjpeg将YUYV原始数据压缩为JPEG?,c++,c,yuv,libjpeg,subsampling,C++,C,Yuv,Libjpeg,Subsampling,我正在寻找一个如何使用libjpeg库将YUYV格式帧保存到JPEG文件的示例。在典型的计算机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 u

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

在典型的计算机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;
/“base”是带有YUYV数据的无符号字符常量*
//jrow是一个1行指针的libjpeg样本行数组
cinfo.image_width=width&-1;
cinfo.image_height=高度和-1;
cinfo.input_components=3;
cinfo.in_color_space=JCS_YCbCr;
jpeg\u set\u默认值(&cinfo);
jpeg_集_质量(&cinfo,92,真);
jpeg\u start\u compress(&cinfo,TRUE);
无符号字符*buf=新的无符号字符[宽度*3];
而(cinfo.next_扫描线<高度){
对于(int i=0;i
如果您的错误或写入函数可能抛出/longjmp,请使用您最喜欢的自动ptr避免泄漏“buf”

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


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

libjpeg还有一种原始数据模式,您可以直接提供原始下采样数据(这几乎是YUYV格式的数据)。这比仅在libjpeg内部再次缩小UV值时复制UV值更有效

为此,您使用
jpeg\u write\u raw\u data
而不是
jpeg\u write\u scanlines
,默认情况下,它一次只处理16条扫描线。JPEG希望U和V平面在默认情况下为2x下采样。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 };
cinfo.image_width=/*像素宽度*;
cinfo.image_height=/*像素高度*/;
cinfo.input_components=3;
cinfo.in_color_space=JCS_YCbCr;
jpeg\u set\u默认值(&cinfo);
cinfo.raw_data_in=true;
J样本y_平面[16][cinfo.图像宽度];
J样本u_平面[8][cinfo.image_宽度/2];
J样本v_平面[8][cinfo.image_width/2];
JSAMPROW y_行[16];
JSAMPROW u_行[8];
JSAMPROW v_行[8];
对于(int i=0;i<16;++i)
{
y_行[i]=&y_平面[i][0];
}
对于(int i=0;i<8;++i)
{
u_行[i]=&u平面[i][0];
}
对于(int i=0;i<8;++i)
{
v_行[i]=&v_平面[i][0];
}
JSAMPARRAY行[]{y_行、u_行、v_行};
压缩:

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);
jpeg\u start\u compress(&cinfo,true);
while(cinfo.next\u扫描线
与@JonWatte的答案中的方法相比,使用这种方法,我能够减少大约33%的压缩时间。但这种解决方案并不适合所有人;一些警告:

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

libjpeg-turbo文档:

请描述失真情况。缩放、垃圾、一致错误的颜色重新分配。?也许可以尝试将您的输入和输出图像发布到此处或imgur。以下是两个图像示例(第一个示例通过Cheese网络摄像头播客进行catrured,第二个示例通过libjpeg从流中创建):YUYV必须在将其写入JPEG之前转换为RGB。如果YUYV与YUV422相同,您可能会发现这个答案很有用:Mark,谢谢您的评论。我认为libjpeg允许直接从yuyv422保存jpeg图像。