JPEG Codec

算子效果

示意图像
VP5out_jpegcodec

原理

JPEG(Joint Photographic Experts Group)是一个广泛使用的有损压缩标准,由联合图像专家小组开发,并已成为国际通用的图像压缩标准。 JPEG不仅适用于静态图像编码,还被推广用于电视图像序列的帧内压缩。JPU硬件支持的JPEG标准是ISO/IEC 10918-1的Baseline Sequential模式。

JPEG编码原理

JPEG编码,将YUV格式图片编码成JPEG压缩格式的图片文件,例如*.jpg。

示意图像
VP5out_jpegencode

JPEG解码原理

JPEG解码,实现.jpg、.jpeg、.JPG、.JPEG图片文件的解码。

示意图像
VP5out_jpegdecode

以图像的某个8x8子区域为例,介绍JPEG编码的主要过程。8x8图像子区域数值如下所示:

[52556166706164736359559010985697262596811314410466736358711221541067069676168104126886870796560707768587585716459556165838779696865767894]\begin{aligned}\begin{bmatrix} 52 & 55 & 61 & 66 & 70 & 61 & 64 & 73\\ 63 & 59 & 55 & 90 & 109 & 85 & 69 & 72\\ 62 & 59 & 68 & 113 & 144 & 104 & 66 & 73\\ 63 & 58 & 71 & 122 & 154 & 106 & 70 & 69\\ 67 & 61 & 68 & 104 & 126 & 88 & 68 & 70\\ 79 & 65 & 60 & 70 & 77 & 68 & 58 & 75\\ 85 & 71 & 64 & 59 & 55 & 61 & 65 & 83\\ 87 & 79 & 69 & 68 & 65 & 76 & 78 & 94\\ \end{bmatrix}\end{aligned}

首先对8x8子区域进行二维的离散余弦变化(Discrete Cosine Transform,DCT),目的是将YUV颜色空间转换至频域空间。 由于离散余弦变化所接受的数值范围是[-128, 127],故将8x8图像子区域数值减去-128,得到如下矩阵:

[767367625867645565697338194359566669601516246255657057626225859616760242406058496368585160705343576469736763454149596063525034]\begin{aligned}\begin{bmatrix} -76&-73&-67&-62&-58&-67&-64&-55\\ -65&-69&-73&-38&-19&-43&-59&-56\\ -66&-69&-60&-15&16&-24&-62&-55\\ -65&-70&-57&-6&26&-22&-58&-59\\ -61&-67&-60&-24&-2&-40&-60&-58\\ -49&-63&-68&-58&-51&-60&-70&-53\\ -43&-57&-64&-69&-73&-67&-63&-45\\ -41&-49&-59&-60&-63&-52&-50&-34 \end{bmatrix}\end{aligned}

标准化后矩阵进行DCT,变换公式:

F(u,v)=14C(u)C(v)[m=07n=07f(m,n)cos(2m+1)uπ16cos(2n+1)vπ16]F(u,v)=\cfrac{1}{4}C(u)C(v)[\displaystyle\sum_{m=0}^{7} \displaystyle\sum_{n=0}^{7}f(m,n)cos\cfrac{(2m+1)u\pi}{16}cos\cfrac{(2n+1)v\pi}{16}]

变换后得到DCT系数矩阵:

[415306127562020422611013795477772529105649123415106221271342233832621421002134100141012]\begin{aligned}\begin{bmatrix} -415&-30&-61&27&56&-20&-2&0\\ 4&-22&-61&10&13&-7&-9&5\\ -47&7&77&-25&-29&10&5&-6\\ -49&12&34&-15&-10&6&2&2\\ 12&-7&-13&-4&-2&2&-3&3\\ -8&3&2&-6&-2&1&4&2\\ -1&0&0&-2&-1&-3&4&-1\\ 0&0&-1&-4&-1&0&1&2\end{bmatrix}\end{aligned}

其中(0,0)点处的系数称作直流分量(DC系数),其余63个点的系数称为交流分量(AC系数)。

其次对亮度和色度分量的DCT系数进行量化,即DCT系数除以量化表后按四舍五入取最接近的整数。 由于人眼对亮度信号比对色差信号更敏感,因此使用了亮度分量和色度分量两种量化表,默认的量化表是从广泛的实验中得出,也可自定义量化表。

亮度分量默认的量化表:

[1611101624405161121214192658605514131624405769561417222951878062182237566810910377243555648110411392496478871031211201017292959811210010399]\begin{aligned}\begin{bmatrix} 16& 11& 10& 16& 24& 40& 51& 61 \\ 12& 12& 14& 19& 26& 58& 60& 55 \\ 14& 13& 16& 24& 40& 57& 69& 56\\ 14& 17& 22& 29& 51& 87& 80& 62\\ 18& 22& 37& 56& 68& 109& 103& 77\\ 24& 35& 55& 64& 81& 104& 113& 92\\ 49& 64& 78& 87& 103& 121& 120& 101\\ 72& 92& 95& 98& 112& 100& 103& 99\\ \end{bmatrix}\end{aligned}

色度分量默认的量化表:

[17182447999999991821266699999999242656999999999947669999999999999999999999999999999999999999999999999999999999999999999999999999]\begin{aligned}\begin{bmatrix} 17 & 18 & 24 & 47 & 99 & 99 & 99 & 99\\ 18 & 21 & 26 & 66 & 99 & 99 & 99 & 99\\ 24 & 26 & 56 & 99 & 99 & 99 & 99 & 99\\ 47 & 66 & 99 & 99 & 99 & 99 & 99 & 99\\ 99 & 99 & 99 & 99 & 99 & 99 & 99 & 99\\ 99 & 99 & 99 & 99 & 99 & 99 & 99 & 99\\ 99 & 99 & 99 & 99 & 99 & 99 & 99 & 99\\ 99 & 99 & 99 & 99 & 99 & 99 & 99 & 99\\ \end{bmatrix}\end{aligned}

将前面所得到的DCT系数矩阵与默认值的亮度表进行相除,并四舍五入后可得到量化后的DCT系数:

[26362210002411000315110004121000010000000000000000000000000000000]\begin{aligned}\begin{bmatrix} -26 & -3 & -6 & 2 & 2 & -1 & 0 & 0\\ 0 & -2 & -4 & 1 & 1 & 0 & 0 & 0\\ -3 & 1 & 5 & -1 & -1 & 0 & 0 & 0\\ -4 & 1 & 2 & -1 & 0 & 0 & 0 & 0\\ 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\ \end{bmatrix}\end{aligned}

观察到量化后的DCT系数量化后的数据,直流系数相对于交流系数更大一些,并且交流系数种含有大量的0。 因此使用Z字形编码可将大量的0连接到一起,以减小编码后的大小。 主要思路就是从量化后的DCT系数左上角第一个像素开始以Z字形进行编排:

示意图像
VP5out_jpegzigzag

由于Z字形编码后的DCT系数的直流系数数值较大,并且相邻8x8图像区域的直流系数值变化不大,因此使用差分脉冲编码技术,对相邻图像区域之间DC系数的差值进行编码;对交流系数重复且连续出现多次的字符,使用行程长度编码。这两种编码方式都有中间格式,目的是进一步减小存储量。

得到 DC 系数的中间格式和 AC 系数的中间格式之后,为进一步压缩图像数据,需要对两者进行熵编码,通过对出现概率较高的字符采用较小的 bit 数编码达到压缩的目的。 JPEG 基本系统规定采用 Huffman 编码方法, Huffman 编码时 DC 系数与 AC 系数分别采用不同的 Huffman 编码表,对于亮度和色度也采用不同的 Huffman 编码表。 因此,需要 4 张 Huffman 编码表才能完成熵编码的工作,等到具体 Huffman 编码时再采用查表的方式来高效地完成。 然而,在 JPEG 标准中没有定义缺省的 Huffman 表,您可以根据实际应用自由选择,也可以使用 JPEG 标准推荐的 Huffman 表,或者预先定义一个通用的 Huffman 表,也可以针对一副特定的图像,在压缩编码前通过搜集其统计特征来计算 Huffman 表的值。

API接口

JPEG编码接口

// create JPEG encoding context
int32_t hbVPCreateJPEGEncContext(hbVPJPEGContext *context,
                                 hbVPJPEGEncParam *param);

// JPEG encoding API interface
int32_t hbVPJPEGEncode(hbUCPTaskHandle_t *taskHandle, 
                       hbVPImage const *srcImg,
                       hbVPJPEGContext context);

// get JPEG encoding ouptput buffer
int32_t hbVPGetJPEGEncOutputBuffer(hbVPArray *outBuf, 
                                   hbUCPTaskHandle_t taskHandle);

// release JPEG encoding context
int32_t hbVPReleaseJPEGEncContext(hbVPJPEGContext context);

JPEG解码接口

// create JPEG decoding context
int32_t hbVPCreateJPEGDecContext(hbVPJPEGContext *context, uint32_t outBufCount, uint8_t imageFormat);

// JPEG decoding API interface
int32_t hbVPJPEGDecode(hbUCPTaskHandle_t *taskHandle, 
                       hbVPArray const *srcBuf,
                       hbVPJPEGContext context);

// get JPEG decoding ouptput buffer
int32_t hbVPGetJPEGDecOutputBuffer(hbVPImage *outImg, 
                                   hbUCPTaskHandle_t taskHandle);

// release JPEG decoding context
int32_t hbVPReleaseJPEGDecContext(hbVPJPEGContext context);

详细接口信息请查看 hbVPJPEGEncodehbVPJPEGDecode

使用方法

JPEG编码使用方法

// Include the header
#include "hobot/hb_ucp.h"
#include "hobot/vp/hb_vp.h"
#include "hobot/vp/hb_vp_jpeg_codec.h"

// init image_buf, alloc memory for image data
hbUCPSysMem image_mem;
hbUCPMalloc(&image_mem, yuv_size, 0);
hbVPImage image_buf =  hbVPImage{HB_VP_IMAGE_FORMAT_YUV420,
                                 HB_VP_IMAGE_TYPE_U8C1,
                                 width,
                                 height,
                                 stride,
                                 image_mem.virAddr,
                                 image_mem.phyAddr,
                                 nullptr,
                                 0,
                                 0};
// init jpeg_buf
hbVPArray jpeg_buf{0};

// init encoding param
hbVPJPEGEncParam enc_param;
enc_param.qualityFactor = 50;
enc_param.extendedSequential = 0;
enc_param.width = width;
enc_param.height = height;
enc_param.imageFormat = image_format;
enc_param.outBufCount = 5;

// create encoding context and the JPU internally alloc the output buffer
hbVPJPEGContext enc_context{nullptr};
hbVPCreateJPEGEncContext(&enc_context, &enc_param);

// init task handle and schedule param
hbUCPTaskHandle_t task_handle{nullptr};
hbUCPSchedParam sched_param;
HB_UCP_INITIALIZE_SCHED_PARAM(&sched_param);
sched_param.backend = HB_UCP_JPU_CORE_0;
sched_param.priority = 0;

// create encoding task
hbVPJPEGEncode(&task_handle, &image_buf, enc_context);

// submit encoding task
hbUCPSubmitTask(task_handle, &sched_param);

// wait for encoding task done
hbUCPWaitTaskDone(task_handle, 0);

// get encoded buffer from JPU
hbVPGetJPEGEncOutputBuffer(&jpeg_buf, taskHandle);

// process jpeg data

// release task handle and dequeue output buffer from JPU
// since the dequeue output buffer process relies on the context,
// you must call the hbUCPReleaseTask interface before calling the hbVPReleaseJPEGEncContext interface
hbUCPReleaseTask(task_handle);

// release encoding context and the JPU internally release the output buffer
hbVPReleaseJPEGEncContext(enc_context);

// release memory
hbUCPFree(&image_mem);

JPEG解码使用方法

// Include the header
#include "hobot/hb_ucp.h"
#include "hobot/vp/hb_vp.h"
#include "hobot/vp/hb_vp_jpeg_codec.h"

// init jpeg_buf, alloc memory for jpeg data
hbUCPSysMem jpeg_mem;
hbUCPMalloc(&jpeg_mem, jpeg_size, 0);
hbVPArray jpeg_buf;
jpeg_buf.phyAddr = jpeg_mem.phyAddr;
jpeg_buf.virAddr = jpeg_mem.virAddr;
jpeg_buf.memSize = jpeg_mem.memSize;
jpeg_buf.size = jpeg_mem.memSize;
jpeg_buf.capacity = jpeg_mem.memSize;

// init image_buf
hbVPImage image_buf{0};

// create decoding context and the JPU internally alloc the output buffer
hbVPJPEGContext dec_context{nullptr};
hbVPCreateJPEGDecContext(&dec_context, 5, image_format);

// init task handle and schedule param
hbUCPTaskHandle_t task_handle{nullptr};
hbUCPSchedParam sched_param;
HB_UCP_INITIALIZE_SCHED_PARAM(&sched_param);
sched_param.backend = HB_UCP_JPU_CORE_0;
sched_param.priority = 0;

// create decoding task
hbVPJPEGDecode(&task_handle, &jpeg_buf, dec_context);

// submit decoding task
hbUCPSubmitTask(task_handle, &sched_param);

// wait for decoding task done
hbUCPWaitTaskDone(task_handle, 0);

// get decoded buffer from JPU
hbVPGetJPEGDecOutputBuffer(&image_buf, task_handle);

// process image data

// release task handle and dequeue output buffer from JPU
// since the dequeue output buffer process relies on the context,
// you must call the hbUCPReleaseTask interface before calling the hbVPReleaseJPEGEncContext interface
hbUCPReleaseTask(task_handle);

// release encoding context and the JPU internally release the output buffer
hbVPReleaseJPEGDecContext(dec_context);

// release memory
hbUCPFree(&jpeg_mem);