How to compress YUYV source data to JPEG using libjpeg?

I am looking for an example of how to save a YUYV format frame into a JPEG file using the libjpeg library.

+5
source share
3 answers

In typical computer APIs, β€œYUV” actually means YCbCr, and β€œYUYV” means β€œYCbCr 4: 2: 2,” stored as Y0, Cb01, Y1, Cr01, Y2 ...

Thus, if you have a "YUV" image, you can save it to libjpeg using the JCS_YCbCr color space.

When you have image 422 (YUYV), you must duplicate the Cb / Cr values ​​by the two pixels they need before writing the scan line to libjpeg. So this write loop will do it for you:

 // "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; 

Use your favorite auto-ptr to avoid a buf leak if your error or write function might call / longjmp.

Providing YCbCr in libjpeg directly is preferable to convert to RGB, because it will store it directly in this format, thereby saving a lot of conversion work. When an image comes from a webcam or other video source, it is also usually most effective to get some kind of YCbCr (for example, YUYV.)

Finally, β€œU” and β€œV” mean something a little different from analog component video, so the name YUV in computer APIs, which really means YCbCr, is very confusing.

+7
source

libjpeg also has a raw data mode through which you can directly provide raw data with downsampling (which is almost what you have in YUYV format). This is more efficient than duplicating UV values ​​only so that libjpeg reduces them again inside.

To do this, you use jpeg_write_raw_data instead of jpeg_write_scanlines , and by default it will process exactly 16 scan lines at a time. JPEG expects U and V to be equal 2 times by default by default. The YUYV format already has a horizontal dimension with reduced sampling, but not vertical, so I skip U and V every other scan line.

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 }; 

Compression:

 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% reduction in compression time using this method compared to what was in @JonWatte's answer. This solution is not for everyone; some reservations:

  • You can only compress images with sizes that are multiples of 8. If you have images of different sizes, you will have to write code to lay along the edges. If you get images from the camera, they are likely to be like that.
  • The quality deteriorates somewhat due to the fact that I just skip the color values ​​for alternating scan lines, and not something more exciting than averaging them. However, for my application, speed was more important than quality.
  • Now it writes a ton of memory on the stack. This was acceptable to me because my images were small (640x480) and there was enough memory.

Documentation for libjpeg-turbo: https://raw.githubusercontent.com/libjpeg-turbo/libjpeg-turbo/master/libjpeg.txt

+4
source

@rvighne Can I not reduce the UV in the vertical direction by setting cinfo.comp_info [0] .h_sample_factor = 2 and cinfo.comp_info [0] = 1.h_sample_factor.

0
source

Source: https://habr.com/ru/post/1479287/


All Articles