JPEG Codec

算子效果

示意图像
VP5out_jpegcodec

原理

JPEG(Joint Photographic Experts Group)为国际通用标准,是一种针对照片影像而广泛使用的有损压缩标准方法,由联合图像专家小组开发。 其适用范围广泛,除用于静态图像编码外,还推广到电视图像序列的帧内图像压缩。

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编码接口

// 创建JPEG编码的context int32_t hbVPCreateJPEGEncContext(hbVPJPEGContext *context, hbVPJPEGEncParam *param); // JPEG编码API接口 int32_t hbVPJPEGEncode(hbUCPTaskHandle_t *taskHandle, hbVPImage const *srcImg, hbVPJPEGContext context); // 获取存储编码数据的输出buffer int32_t hbVPGetJPEGEncOutputBuffer(hbUCPTaskHandle_t taskHandle, hbVPArray *dstBuf); // 释放JPEG编码的context int32_t hbVPReleaseJPEGEncContext(hbVPJPEGContext context);

JPEG解码接口

// 创建JPEG解码的context int32_t hbVPCreateJPEGDecContext(hbVPJPEGContext *context, uint32_t outBufCount, uint8_t imageFormat); // JPEG解码API接口 int32_t hbVPJPEGDecode(hbUCPTaskHandle_t *taskHandle, hbVPArray const *srcBuf, hbVPJPEGContext context); // 获取存储解码数据的输出buffer int32_t hbVPGetJPEGDecOutputBuffer(hbUCPTaskHandle_t taskHandle, hbVPImage *dstImg); // 释放JPEG解码的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 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, &jpeg_buf, &image_buf, enc_context); // submit encoding task hbUCPSubmitTask(task_handle, &sched_param); // wait for encoding task done hbUCPWaitTaskDone(task_handle, 0); // get encoded buffer hbVPGetJPEGEncOutputBuffer(taskHandle, &jpeg_buf); // process jpeg data // release task handle hbUCPReleaseTask(task_handle); // release encoding context 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 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, &image_buf, &jpeg_buf, dec_context); // submit decoding task hbUCPSubmitTask(task_handle, &sched_param); // wait for decoding task done hbUCPWaitTaskDone(task_handle, 0); // get decoded buffer hbVPGetJPEGDecOutputBuffer(task_handle, &image_buf); // process image data // release task handle hbUCPReleaseTask(task_handle); // release decoding context hbVPReleaseJPEGDecContext(dec_context); // release memory hbUCPFree(&jpeg_mem);