面对图像较大而带宽不足的情况,往往需要对图像进行压缩,使其变小后得以方便传输。
本文使用libjpeg(非libjpeg-turbo)实现了对图像的压缩,并且使用双线性插值进行了resize。
1.encode_by_libjpeg.h
#include <iostream>
#include <fstream>
#include <cstring>
#include <jpeglib.h>
#include <setjmp.h>
int yuv420sp_to_jpeg_resize(const char * filename, unsigned char* pdata,int image_width,int image_height, int out_w,int out_h,int quality);
int read_Image_from_raw_by_iostream(const std::string filename, unsigned char **buffer);
2.encode_by_libjpeg.cpp
#include "encode_by_libjpeg.h"
int yuv420sp_to_jpeg_resize(const char * filename, unsigned char* pdata,int image_width,int image_height, int out_w,int out_h,int quality)
{
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo); /*初始化*/
FILE * outfile; // target file
if ((outfile = fopen(filename, "wb")) == NULL) {
fprintf(stderr, "can't open %s\n", filename);
exit(1);
}
jpeg_stdio_dest(&cinfo, outfile); /*指定输出文件*/
/*设置压缩各项图片参数*/
cinfo.image_width = out_w; // image width and height, in pixels
cinfo.image_height = out_h;
cinfo.input_components = 3; // # of color components per pixel
cinfo.in_color_space = JCS_YCbCr; //colorspace of input image
jpeg_set_defaults(&cinfo);
/*设置压缩质量*/
jpeg_set_quality(&cinfo, quality, TRUE );
//
// cinfo.raw_data_in = TRUE;
cinfo.jpeg_color_space = JCS_YCbCr;
cinfo.comp_info[0].h_samp_factor = 2;
cinfo.comp_info[0].v_samp_factor = 2;
jpeg_start_compress(&cinfo, TRUE);
JSAMPROW row_pointer[1];
unsigned char *yuvbuf;
if((yuvbuf=(unsigned char *)malloc(out_w*3))!=NULL)
memset(yuvbuf,0,out_w*3);
unsigned char *ybase,*ubase;
ybase=pdata;
ubase=pdata+image_width*image_height;
int j=0;
//dest[dx+dy*dest_width] = src[(dx*src_width/dest_width)+(dy*src_height/dest_height)*src_width]
while (cinfo.next_scanline < out_h)
{
int idx=0;
for(int i=0;i<out_w;i++)
{
float srcX = i * ((float)(image_width - 1) / (out_w - 1));
float srcY = j * ((float)(image_height - 1) / (out_h - 1));
// 计算取图像坐标
int fx0 = srcX;
int fy0 = srcY;
int fx1 = srcX > fx0 ? fx0 + 1 : fx0;
int fy1 = srcY > fy0 ? fy0 + 1 : fy0;
// 计算取像素比例
float xProportion = srcX - fx0;
float yProportion = srcY - fy0;
// 四个输入坐标
int idx_in_y_00 = fy0 * image_width + fx0;
int idx_in_uv_00 = fy0 / 2 * image_width + fx0;
int idx_in_y_10 = fy1 * image_width + fx0;
int idx_in_uv_10 = fy1 / 2 * image_width + fx0;
int idx_in_y_01 = fy0 * image_width + fx1;
int idx_in_uv_01 = fy0 / 2 * image_width + fx1;
int idx_in_y_11 = fy1 * image_width + fx1;
int idx_in_uv_11 = fy1 / 2 * image_width + fx1;
// Y
yuvbuf[idx++] =
ybase[idx_in_y_00] * (1 - xProportion) * (1 - yProportion) +
ybase[idx_in_y_10] * xProportion * (1 - yProportion) +
ybase[idx_in_y_01] * (1 - xProportion) * yProportion +
ybase[idx_in_y_11] * xProportion * yProportion;
// U
yuvbuf[idx++] =
ubase[fx0 % 2 == 0 ? idx_in_uv_00 : idx_in_uv_00 - 1] * (1 - xProportion) * (1 - yProportion) +
ubase[fx0 % 2 == 0 ? idx_in_uv_10 : idx_in_uv_10 - 1] * xProportion * (1 - yProportion) +
ubase[fx1 % 2 == 0 ? idx_in_uv_01 : idx_in_uv_01 - 1] * (1 - xProportion) * yProportion +
ubase[fx1 % 2 == 0 ? idx_in_uv_11 : idx_in_uv_11 - 1] * xProportion * yProportion;
// V
yuvbuf[idx++] =
ubase[fx0 % 2 == 0 ? idx_in_uv_00 + 1 : idx_in_uv_00] * (1 - xProportion) * (1 - yProportion) +
ubase[fx0 % 2 == 0 ? idx_in_uv_10 + 1 : idx_in_uv_10] * xProportion * (1 - yProportion) +
ubase[fx1 % 2 == 0 ? idx_in_uv_01 + 1 : idx_in_uv_01] * (1 - xProportion) * yProportion +
ubase[fx1 % 2 == 0 ? idx_in_uv_11 + 1 : idx_in_uv_11] * xProportion * yProportion;
}
row_pointer[0] = yuvbuf;
/*逐行扫描压缩写入文件*/
jpeg_write_scanlines(&cinfo, row_pointer, 1);
j++;
}
/*完成压缩*/
jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);
fclose(outfile);
return 0;
}
int read_Image_from_raw_by_iostream(const std::string filename, unsigned char **buffer)
{
// open raw data
std::ifstream fin;
// 注意,这里要指定binary读取模式
fin.open(filename, std::ios::binary);
if (!fin) {
std::cerr << "open failed: " << filename << std::endl;
}
// seek函数会把标记移动到输入流的结尾
fin.seekg(0, fin.end);
// tell会告知整个输入流(从开头到标记)的字节数量
int length = fin.tellg();
// 再把标记移动到流的开始位置
fin.seekg(0, fin.beg);
//std::cout << "file length: " << length << std::endl;
// load buffer
char* temp_buf = new char [length];
// read函数读取(拷贝)流中的length各字节到buffer
fin.read(temp_buf, length);
*buffer=reinterpret_cast<u_char *>(temp_buf);
return 0;
}
3.main.cpp
#include "encode_by_libjpeg.h"
#include <iostream>
#include <algorithm>
#include <chrono>
struct Rawimage_t {
size_t input_w, input_h;
unsigned char *yuv420sp;
};
int main()
{
Rawimage_t image;
int input_width=3840 ;
int input_height=2176;
int output_width=1920 ;
int output_height=1088;
read_Image_from_raw_by_iostream("test.raw", &image.yuv420sp);
auto start = std::chrono::high_resolution_clock::now();
yuv420sp_to_jpeg_resize("output.jpg",image.yuv420sp,input_width,
input_height,output_width,output_height, 60);
auto stop = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(stop - start);
std::cout << "Time taken: " << duration.count() /1000.0<< " ms" << std::endl;
/* Free image memory */
free(image.yuv420sp);
return 0;
}
4.Makefile
demo : main.cpp encode_by_libjpeg.cpp
g++ main.cpp encode_by_libjpeg.cpp -I/opt/libjpeg-turbo/include -L/opt/libjpeg-turbo/lib64 -ljpeg