I'm looking for an example of how to save a YUYV format frame to a JPEG file using the libjpeg
library.
我正在寻找一个如何使用libjpeg库将YUYV格式框架保存到JPEG文件的示例。
2 个解决方案
#1
5
In typical computer APIs, "YUV" actually means YCbCr, and "YUYV" means "YCbCr 4:2:2" stored as Y0, Cb01, Y1, Cr01, Y2 ...
在典型的计算机api中,“YUV”实际上表示YCbCr,“YUYV”表示“YCbCr:2:2”,存储为Y0、Cb01、Y1、Cr01、Y2…
Thus, if you have a "YUV" image, you can save it to libjpeg using the JCS_YCbCr color space.
因此,如果有一个“YUV”图像,可以使用JCS_YCbCr颜色空间将其保存到libjpeg中。
When you have a 422 image (YUYV) you have to duplicate the Cb/Cr values to the two pixels that need them before writing the scanline to libjpeg. Thus, this write loop will do it for you:
当您有一个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 pointercinfo.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;
Use your favorite auto-ptr to avoid leaking "buf" if your error or write function can throw / longjmp.
如果你的错误或写函数可以抛出/ longjmp,使用你最喜欢的自动ptr来避免泄漏“buf”。
Providing YCbCr to libjpeg directly is preferrable to converting to RGB, because it will store it directly in that format, thus saving a lot of conversion work. When the image comes from a webcam or other video source, it's also usually most efficient to get it in YCbCr of some sort (such as YUYV.)
直接向libjpeg提供YCbCr可以转换为RGB,因为它将直接以这种格式存储它,从而节省了大量的转换工作。当图像来自网络摄像头或其他视频来源时,通常在某种YCbCr(比如YUYV)中获取图像最有效。
Finally, "U" and "V" mean something slightly different in analog component video, so the naming of YUV in computer APIs that really mean YCbCr is highly confusing.
最后,“U”和“V”在模拟组件视频中有些许不同,因此在计算机api中命名YUV实际上意味着YCbCr是非常令人困惑的。
#2
2
libjpeg also has a raw data mode, whereby you can directly supply the raw downsampled data (which is almost what you have in the YUYV format). This is more efficient than duplicating the UV values only to have libjpeg downscale them again internally.
libjpeg还有一个原始数据模式,您可以直接提供原始的下采样数据(YUYV格式中的数据几乎就是这样)。这比只让libjpeg在内部降低UV值更有效。
To do so, you use jpeg_write_raw_data
instead of jpeg_write_scanlines
, and by default it will process exactly 16 scanlines at a time. JPEG expects the U and V planes to be 2x downsampled by default. YUYV format already has the horizontal dimension downsampled but not the vertical, so I skip U and V every other scanline.
为此,您使用jpeg_write_raw_data而不是jpeg_write_scanlines,默认情况下,它一次只处理16个scanline。JPEG预计U和V飞机在默认情况下会下降2倍。YUYV格式已经有了水平方向的向下采样,但不是垂直的,所以我跳过U和V其他的扫描线。
Initialization:
初始化:
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 };
Compressing:
压缩:
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);
I was able to get about a 33% decrease in compression time with this method compared to the one in @JonWatte's answer. This solution isn't for everyone though; some caveats:
与@JonWatte的答案相比,我用这个方法压缩时间减少了33%。但这个解决方案并不适合所有人;一些说明:
- You can only compress images with dimensions that are a multiple of 8. If you have different-sized images, you will have to write code to pad in the edges. If you're getting the images from a camera though, they will most likely be this way.
- 你只能压缩图片的维数是8的倍数。如果您有不同大小的图像,您将不得不编写代码来填充边缘。如果你从相机上获取图像,它们很可能是这样的。
- The quality is somewhat impaired by the fact that I simply skip color values for alternating scanlines instead of something fancier like averaging them. For my application though, speed was more important than quality.
- 我只是简单地跳过了交替扫描线的颜色值,而不是像求平均值这样更花哨的东西,这对质量有一定的影响。对于我的应用来说,速度比质量更重要。
- The way it's written right now it allocates a ton of memory on the stack. This was acceptable for me because my images were small (640x480) and enough memory was available.
- 它现在的书写方式是在堆栈上分配大量的内存。这对我来说是可以接受的,因为我的图像很小(640x480),并且有足够的内存可用。
Documentation for libjpeg-turbo: https://raw.githubusercontent.com/libjpeg-turbo/libjpeg-turbo/master/libjpeg.txt
文档libjpeg-turbo:https://raw.githubusercontent.com/libjpeg-turbo/libjpeg-turbo/master/libjpeg.txt
#1
5
In typical computer APIs, "YUV" actually means YCbCr, and "YUYV" means "YCbCr 4:2:2" stored as Y0, Cb01, Y1, Cr01, Y2 ...
在典型的计算机api中,“YUV”实际上表示YCbCr,“YUYV”表示“YCbCr:2:2”,存储为Y0、Cb01、Y1、Cr01、Y2…
Thus, if you have a "YUV" image, you can save it to libjpeg using the JCS_YCbCr color space.
因此,如果有一个“YUV”图像,可以使用JCS_YCbCr颜色空间将其保存到libjpeg中。
When you have a 422 image (YUYV) you have to duplicate the Cb/Cr values to the two pixels that need them before writing the scanline to libjpeg. Thus, this write loop will do it for you:
当您有一个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 pointercinfo.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;
Use your favorite auto-ptr to avoid leaking "buf" if your error or write function can throw / longjmp.
如果你的错误或写函数可以抛出/ longjmp,使用你最喜欢的自动ptr来避免泄漏“buf”。
Providing YCbCr to libjpeg directly is preferrable to converting to RGB, because it will store it directly in that format, thus saving a lot of conversion work. When the image comes from a webcam or other video source, it's also usually most efficient to get it in YCbCr of some sort (such as YUYV.)
直接向libjpeg提供YCbCr可以转换为RGB,因为它将直接以这种格式存储它,从而节省了大量的转换工作。当图像来自网络摄像头或其他视频来源时,通常在某种YCbCr(比如YUYV)中获取图像最有效。
Finally, "U" and "V" mean something slightly different in analog component video, so the naming of YUV in computer APIs that really mean YCbCr is highly confusing.
最后,“U”和“V”在模拟组件视频中有些许不同,因此在计算机api中命名YUV实际上意味着YCbCr是非常令人困惑的。
#2
2
libjpeg also has a raw data mode, whereby you can directly supply the raw downsampled data (which is almost what you have in the YUYV format). This is more efficient than duplicating the UV values only to have libjpeg downscale them again internally.
libjpeg还有一个原始数据模式,您可以直接提供原始的下采样数据(YUYV格式中的数据几乎就是这样)。这比只让libjpeg在内部降低UV值更有效。
To do so, you use jpeg_write_raw_data
instead of jpeg_write_scanlines
, and by default it will process exactly 16 scanlines at a time. JPEG expects the U and V planes to be 2x downsampled by default. YUYV format already has the horizontal dimension downsampled but not the vertical, so I skip U and V every other scanline.
为此,您使用jpeg_write_raw_data而不是jpeg_write_scanlines,默认情况下,它一次只处理16个scanline。JPEG预计U和V飞机在默认情况下会下降2倍。YUYV格式已经有了水平方向的向下采样,但不是垂直的,所以我跳过U和V其他的扫描线。
Initialization:
初始化:
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 };
Compressing:
压缩:
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);
I was able to get about a 33% decrease in compression time with this method compared to the one in @JonWatte's answer. This solution isn't for everyone though; some caveats:
与@JonWatte的答案相比,我用这个方法压缩时间减少了33%。但这个解决方案并不适合所有人;一些说明:
- You can only compress images with dimensions that are a multiple of 8. If you have different-sized images, you will have to write code to pad in the edges. If you're getting the images from a camera though, they will most likely be this way.
- 你只能压缩图片的维数是8的倍数。如果您有不同大小的图像,您将不得不编写代码来填充边缘。如果你从相机上获取图像,它们很可能是这样的。
- The quality is somewhat impaired by the fact that I simply skip color values for alternating scanlines instead of something fancier like averaging them. For my application though, speed was more important than quality.
- 我只是简单地跳过了交替扫描线的颜色值,而不是像求平均值这样更花哨的东西,这对质量有一定的影响。对于我的应用来说,速度比质量更重要。
- The way it's written right now it allocates a ton of memory on the stack. This was acceptable for me because my images were small (640x480) and enough memory was available.
- 它现在的书写方式是在堆栈上分配大量的内存。这对我来说是可以接受的,因为我的图像很小(640x480),并且有足够的内存可用。
Documentation for libjpeg-turbo: https://raw.githubusercontent.com/libjpeg-turbo/libjpeg-turbo/master/libjpeg.txt
文档libjpeg-turbo:https://raw.githubusercontent.com/libjpeg-turbo/libjpeg-turbo/master/libjpeg.txt