使用libjpeg将yuv数据转换为内存中的jpg

使用libjpeg将yuv数据转换为内存中的jpg 一休摸鱼 2023-10-18 16:27:33 332
最近在做人脸识别的东西,需要将海思采集到的YUV视频数据扔给第三方人脸sdk接口进行人脸识别,然后将人脸的小图抠出来发送给一个第三方的盒子。

libjpeg

下载连接

http://www.ijg.org/files/

此处用的版本是 jpegsrc.v9d.tar.gz

交叉编译

libjpeg 的交叉编译比较简单,没有报错

# root @ gentoo4wzy in /hisi_ext/3rd/test_jpeg/jpeg-9d [11:44:43]
$ ./configure --prefix=/hisi_ext/3rd/test_jpeg/_install --host=aarch64-himix100-linux

# root @ gentoo4wzy in /hisi_ext/3rd/test_jpeg/jpeg-9d [11:46:02]
$ make && make install

# root @ gentoo4wzy in /hisi_ext/3rd/test_jpeg/jpeg-9d [11:46:23]
$ ls ../_install/lib
libjpeg.a  libjpeg.la  libjpeg.so  libjpeg.so.9  libjpeg.so.9.4.0  pkgconfig

此后就可以在自己的代码中加上 #include "jpeglib.h",然后编译的时候加上 -ljpeg 参数

基本用法

具体的详细用法可以参考源码中的 cjpeg.c, example.c, djpeg.c

struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr);
/* 创建 */
jpeg_create_compress(&cinfo);

/* 存到文件和存到内存,二选一 */
/* 存到文件 */
jpeg_stdio_dest(&cinfo, jfp);
/* 存到内存 */
jpeg_mem_dest(&cinfo, jpeg_data, (size_t *)&jpeg_size);

cinfo.image_width = width;
cinfo.image_height = height;
cinfo.input_components = 3;
cinfo.in_color_space = JCS_YCbCr;    /* 此处是 YUV444 格式 */
cinfo.dct_method = JDCT_FLOAT;
jpeg_set_defaults(&cinfo);

/* 设置jpeg图像质量,范围 [0,100] */
jpeg_set_quality(&cinfo, 40, TRUE);

/* 开始 */
jpeg_start_compress(&cinfo, TRUE);

/* 处理数据 */
JSAMPROW row_pointer[1];
while (cinfo.next_scanline < cinfo.image_height)
{
  /* your code here */
  jpeg_write_scanlines( &jpeg,row_pointer, 1);
}

/* 停止 */
jpeg_finish_compress(&cinfo);
/* 销毁 */
jpeg_destroy_compress(&cinfo);

简单说一下, jpeg_mem_dest 函数的第二个参数是个二级指针,指向的是完成编码后的存放jpeg数据的地址,不需要自己分配内存,但是需要自己手动释放内存

YUV

格式

  • planar 平面格式
    • 先连续存Y分量,再连续存U(或V)分量,再连续存V(或U)分量
  • packed 打包格式
    • 每个像素点的 Y、U、V 分量连续交替存储

采样

  1. YUV420
    • YUV 4:2:0 采样,并不是指只采样 U 分量而不采样 V 分量。而是指,在每一行扫描时,只扫描一种色度分量(U 或者 V),和 Y 分量按照 2 : 1 的方式采样。比如,第一行扫描时,YU 按照 2 : 1 的方式采样,那么第二行扫描时,YV 分量按照 2:1 的方式采样。对于每个色度分量来说,它的水平方向和竖直方向的采样和 Y 分量相比都是 2:1 [1]
  2. YUV422
    • YUV 4:2:2 采样,意味着 UV 分量是 Y 分量采样的一半,Y 分量和 UV 分量按照 2 : 1 的比例采样。如果水平方向有 10 个像素点,那么采样了 10 个 Y 分量,而只采样了 5 个 UV 分量[2]
  3. YUV444
    • YUV 4:4:4 采样,意味着 Y、U、V 三个分量的采样比例相同,因此在生成的图像里,每个像素的三个分量信息完整,都是 8 bit,也就是一个字节[3]

YUV 在内存中的排列(不是对应关系)

易百纳社区

我这里海思编码出来的YUV格式是 YUV420SP,对于此格式来说:

  1. 听说只保留Y分量数据即可得到灰度图像(本人没存过)
  2. 在内存中的大小:
    • Y = w * h;
    • U = Y / 4;
    • V = Y / 4;
    • Total = Y+U+V = w * h * 3 / 2; 同时可根据偏移计算得到各个分量的地址

范例

yuv420sp_to_yuv420p

void yuv420sp_to_yuv420p(unsigned char* yuv420sp, unsigned char* yuv420p, int width, int height)
{
    int i, j;
    int y_size = width * height;

    unsigned char* y = yuv420sp;
    unsigned char* uv = yuv420sp + y_size;

    unsigned char* y_tmp = yuv420p;
    unsigned char* u_tmp = yuv420p + y_size;
    unsigned char* v_tmp = yuv420p + y_size * 5 / 4;

    // y
    memcpy(y_tmp, y, y_size);

    // u
    for (j = 0, i = 0; j < y_size/2; j+=2, i++)
    {
        u_tmp[i] = uv[j];
        v_tmp[i] = uv[j+1];
    }
}

yuv420p_to_yuv420sp

void yuv420p_to_yuv420sp(unsigned char* yuv420p, unsigned char* yuv420sp, int width, int height)
{
    int i, j;
    int y_size = width * height;

    unsigned char* y = yuv420p;
    unsigned char* u = yuv420p + y_size;
    unsigned char* v = yuv420p + y_size * 5 / 4;

    unsigned char* y_tmp = yuv420sp;
    unsigned char* uv_tmp = yuv420sp + y_size;

    // y
    memcpy(y_tmp, y, y_size);

    // u
    for (j = 0, i = 0; j < y_size/2; j+=2, i++)
    {
    // 此处可调整U、V的位置,变成NV12或NV21
#if 01
        uv_tmp[j] = u[i];
        uv_tmp[j+1] = v[i];
#else
        uv_tmp[j] = v[i];
        uv_tmp[j+1] = u[i];
#endif
    }
}

yuv422sp_to_yuv422p

void yuv422sp_to_yuv422p(unsigned char* yuv422sp, unsigned char* yuv422p, int width, int height)
{
    int i, j;
    int y_size;
    int uv_size;
    unsigned char* p_y1;
    unsigned char* p_uv;

    unsigned char* p_y2;
    unsigned char* p_u;
    unsigned char* p_v;

    y_size = uv_size = width * height;

    p_y1 = yuv422sp;
    p_uv = yuv422sp + y_size;

    p_y2 = yuv422p;
    p_u  = yuv422p + y_size;
    p_v  = p_u + width * height / 2;

    memcpy(p_y2, p_y1, y_size);

    for (j = 0, i = 0; j < uv_size; j+=2, i++)
    {
        p_u[i] = p_uv[j];
        p_v[i] = p_uv[j+1];
    }
}

yuv422p_to_yuv422sp

void yuv422p_to_yuv422sp(unsigned char* yuv422p, unsigned char* yuv422sp, int width, int height)
{
    int i, j;
    int y_size;
    int uv_size;
    unsigned char* p_y1;
    unsigned char* p_uv;

    unsigned char* p_y2;
    unsigned char* p_u;
    unsigned char* p_v;

    y_size = uv_size = width * height;

    p_y1 = yuv422p;

    p_y2 = yuv422sp;
    p_u  = p_y1 + y_size;
    p_v  = p_u + width * height / 2;

    p_uv = p_y2 + y_size;

    memcpy(p_y2, p_y1, y_size);

    for (j = 0, i = 0; j < uv_size; j+=2, i++)
    {
    // 此处可调整U、V的位置,变成NV16或NV61
#if 01
        p_uv[j] = p_u[i];
        p_uv[j+1] = p_v[i];
#else
        p_uv[j] = p_v[i];
        p_uv[j+1] = p_u[i];
#endif
    }
}

yuv420sp_to_jpg1

此函数可以直接把参数 jpeg_data 来写入文件,然后打开看是不是正常的jpg文件来验证正确性

static long yuv420sp_to_jpg1(int chn_id, int frm_num, int width, int height, unsigned char *yuv_data,unsigned char **jpeg_data, char *save_jpeg)
{
 int j = 0;
 long jpeg_size = 0;

 if (yuv_data == NULL)
 {
  fprintf(stderr, "\033[0;31m[%s]-%d: the input yuv data is NULL\033[0;39m\n", __FUNCTION__, __LINE__);
  return -1;
 }

 unsigned char *yuvbuf = NULL;
 if ((yuvbuf = (unsigned char *)malloc(width * 3)) == NULL)
 {
  fprintf(stderr, "\033[0;31m[%s]-%d: yuv buf malloc failed\033[0;39m\n", __FUNCTION__, __LINE__);
  return -1;
 }
 memset(yuvbuf, 0, width * 3);

 JSAMPROW row_pointer[1];

 unsigned char *ybase = NULL;
 unsigned char *ubase = NULL;

 struct jpeg_compress_struct cinfo;
 struct jpeg_error_mgr jerr;
 cinfo.err = jpeg_std_error(&jerr);
 jpeg_create_compress(&cinfo);

 FILE * jfp = NULL;
 char jpeg_name[128] = {0};

 if (save_jpeg != NULL)
 {
  snprintf(jpeg_name, sizeof(jpeg_name) - 1, "/mnt/mtd/data/test_chn%d_%d.jpg", chn_id, frm_num);
  if ((jfp = fopen(jpeg_name, "wb")) == NULL)
  {
   fprintf(stderr, "\033[0;31m[%s]-%d: can't open jpeg file %s\033[0;39m\n", __FUNCTION__, __LINE__, jpeg_name);
   goto EXIT0;
  }
  jpeg_stdio_dest(&cinfo, jfp);
 }
 else
 {
  // write to mem
  jpeg_mem_dest(&cinfo, jpeg_data, (size_t *)&jpeg_size);
 }

 cinfo.image_width = width;
 cinfo.image_height = height;
 cinfo.input_components = 3;
 cinfo.in_color_space = JCS_YCbCr;
 cinfo.dct_method = JDCT_FLOAT;
 jpeg_set_defaults(&cinfo);
 jpeg_set_quality(&cinfo, 40, 1);
 jpeg_start_compress(&cinfo, 1);

 ybase = yuv_data;
 ubase = yuv_data + width * height;

 while (cinfo.next_scanline < cinfo.image_height)
 {
  int idx = 0;
  for (int i = 0; i < width; i++)  /* 此处是yuv420sp转yuv444 */
  {
   yuvbuf[idx++] = ybase[i + j * width];
   yuvbuf[idx++] = ubase[j / 2 * width + (i / 2) * 2 + 1];
   yuvbuf[idx++] = ubase[j / 2 * width + (i / 2) * 2];
  }
  row_pointer[0] = yuvbuf;
  jpeg_write_scanlines(&cinfo, row_pointer, 1);
  j++;
 }

 jpeg_finish_compress(&cinfo);

 if (save_jpeg != NULL)
 {
  if (jfp)
  {
   fclose(jfp);
   jfp = NULL;
   remove(jpeg_name);
  }
 }

EXIT0:
 jpeg_destroy_compress(&cinfo);

 if (yuvbuf)
 {
  free(yuvbuf);
  yuvbuf = NULL;
 }

 return jpeg_size;
}

/* 注意:
  jpeg_data 是二级指针,
  libjpeg库会自动分配内存,
  我们用完后要记得手动释放 
*/
  • 还可以先把YUV数据转换为RGB数据,再设置 cinfo.in_color_space = JCS_RGB; 后进行压缩
声明:本文内容由易百纳平台入驻作者撰写,文章观点仅代表作者本人,不代表易百纳立场。如有内容侵权或者其他问题,请联系本站进行删除。
红包 点赞 收藏 评论 打赏
评论
0个
内容存在敏感词
手气红包
    易百纳技术社区暂无数据
相关专栏
置顶时间设置
结束时间
删除原因
  • 广告/SPAM
  • 恶意灌水
  • 违规内容
  • 文不对题
  • 重复发帖
打赏作者
易百纳技术社区
一休摸鱼
您的支持将鼓励我继续创作!
打赏金额:
¥1易百纳技术社区
¥5易百纳技术社区
¥10易百纳技术社区
¥50易百纳技术社区
¥100易百纳技术社区
支付方式:
微信支付
支付宝支付
易百纳技术社区微信支付
易百纳技术社区
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

审核成功

发布时间设置
发布时间:
是否关联周任务-专栏模块

审核失败

失败原因
备注
拼手气红包 红包规则
祝福语
恭喜发财,大吉大利!
红包金额
红包最小金额不能低于5元
红包数量
红包数量范围10~50个
余额支付
当前余额:
可前往问答、专栏板块获取收益 去获取
取 消 确 定

小包子的红包

恭喜发财,大吉大利

已领取20/40,共1.6元 红包规则

    易百纳技术社区