如何使用libjpeg压缩YUYV原始数据到JPEG
How to compress YUYV raw data to JPEG using libjpeg?
我正在寻找如何使用libjpeg
库将YUYV格式帧保存为JPEG文件的示例。
在典型的计算机api中,"YUV"实际上表示YCbCr, "YUYV"表示"YCbCr 4:2:2"存储为Y0, Cb01, Y1, Cr01, Y2…
因此,如果您有一个"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;
使用你喜欢的auto-ptr来避免泄漏"但是",如果你的错误或写函数可能抛出/longjmp。
直接向libjpeg提供YCbCr比转换为RGB更好,因为它将直接以该格式存储它,从而节省了大量转换工作。当图像来自网络摄像头或其他视频源时,通常以某种形式的YCbCr(如YUYV)获取它也是最有效的。
最后,"U"answers"V"在模拟分量视频中的意思略有不同,因此在计算机api中YUV的命名实际上意味着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);
与@JonWatte的答案相比,我能够用这种方法减少33%的压缩时间。但这个解决方案并不适合所有人;一些警告:
- 您只能压缩尺寸为8的倍数的图像。如果您有不同大小的图像,您将不得不编写代码来填充边缘。如果你从相机中获取图像,它们很可能是这种方式。
- 由于我简单地跳过交替扫描线的颜色值,而不是像平均它们这样更花哨的东西,所以质量有些受损。对于我的应用来说,速度比质量更重要。
- 它现在写的方式在堆栈上分配了大量的内存。这对我来说是可以接受的,因为我的图像很小(640x480),并且有足够的内存可用。
libjpeg-turbo文档:https://raw.githubusercontent.com/libjpeg-turbo/libjpeg-turbo/master/libjpeg.txt
- C++:将值赋值给原始数据类型(例如布尔值)是原子操作吗?
- 从USBPcap库读取原始数据
- BoostSpirit.Qi-针对原始数据类型进行边界检查
- Qt - 将空指针(原始数据)转换为 QImage 并将其显示在标签上
- 使用 QDataStream 对原始数据进行反序列化
- 当原始数据是常量时,修改指针指向的位置是 UB 吗?
- IMAGE_SECTION_HEADER的虚拟地址和指针到原始数据的差异
- 直接从 RAM 发送原始数据
- MEX C++原始数据访问
- 服务器客户端通过原始数据错误C 发送接收结构
- 如何从C 中的卷曲中获取原始数据
- 在C/C 中创建和发送原始数据包
- 如何捕获麦克风缓冲区原始数据
- 用原始数据填充 std::vector
- opencv::Mat,从原始数据中获取像素值
- 使用 Qt 通过蓝牙发送多个原始数据包
- 将sqlite原始数据读取到qbyTearray
- 矢量调整大小抛出bad_alloc会使原始数据无效
- 从原始数据就地创建 std::vector
- 如何使用libjpeg压缩YUYV原始数据到JPEG