JPEG Codec
算子效果
| 示意图像 |
|---|
 |
原理
JPEG(Joint Photographic Experts Group)为国际通用标准,是一种针对照片影像而广泛使用的有损压缩标准方法,由联合图像专家小组开发。
其适用范围广泛,除用于静态图像编码外,还推广到电视图像序列的帧内图像压缩。
JPEG编码原理
JPEG编码,将YUV格式图片编码成JPEG压缩格式的图片文件,例如*.jpg。
| 示意图像 |
|---|
 |
JPEG解码原理
JPEG解码,实现.jpg、.jpeg、.JPG、.JPEG图片文件的解码。
| 示意图像 |
|---|
 |
以图像的某个8x8子区域为例,介绍JPEG编码的主要过程。8x8图像子区域数值如下所示:
52636263677985875559595861657179615568716860646966901131221047059687010914415412677556561851041068868617664696670685865787372736970758394
首先对8x8子区域进行二维的离散余弦变化(Discrete Cosine Transform,DCT),目的是将YUV颜色空间转换至频域空间。
由于离散余弦变化所接受的数值范围是[-128, 127],故将8x8图像子区域数值减去-128,得到如下矩阵:
−76−65−66−65−61−49−43−41−73−69−69−70−67−63−57−49−67−73−60−57−60−68−64−59−62−38−15−6−24−58−69−60−58−191626−2−51−73−63−67−43−24−22−40−60−67−52−64−59−62−58−60−70−63−50−55−56−55−59−58−53−45−34
标准化后矩阵进行DCT,变换公式:
F(u,v)=41C(u)C(v)[m=0∑7n=0∑7f(m,n)cos16(2m+1)uπcos16(2n+1)vπ]
变换后得到DCT系数矩阵:
−4154−47−4912−8−10−30−22712−7300−61−617734−1320−12710−25−15−4−6−2−45613−29−10−2−2−1−1−20−710621−30−2−952−344105−6232−12
其中(0,0)点处的系数称作直流分量(DC系数),其余63个点的系数称为交流分量(AC系数)。
其次对亮度和色度分量的DCT系数进行量化,即DCT系数除以量化表后按四舍五入取最接近的整数。
由于人眼对亮度信号比对色差信号更敏感,因此使用了亮度分量和色度分量两种量化表,默认的量化表是从广泛的实验中得出,也可自定义量化表。
亮度分量默认的量化表:
1612141418244972111213172235649210141622375578951619242956648798242640516881103112405857871091041211005160698010311312010361555662779210199
色度分量默认的量化表:
17182447999999991821266699999999242656999999999947669999999999999999999999999999999999999999999999999999999999999999999999999999
将前面所得到的DCT系数矩阵与默认值的亮度表进行相除,并四舍五入后可得到量化后的DCT系数:
−260−3−41000−3−2110000−6−452000021−1−1000021−100000−100000000000000000000000
观察到量化后的DCT系数量化后的数据,直流系数相对于交流系数更大一些,并且交流系数种含有大量的0。
因此使用Z字形编码可将大量的0连接到一起,以减小编码后的大小。
主要思路就是从量化后的DCT系数左上角第一个像素开始以Z字形进行编排:
| 示意图像 |
|---|
 |
由于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);
详细接口信息请查看 hbVPJPEGEncode 及 hbVPJPEGDecode 。
使用方法
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);