前言/背景/废话

这是一个长期更新的项目,一开始找不到现成的代码,只能说国内的生态环境太差了。一方面是对于去年 FPGA 创新赛没写出来代码的不甘心,另一方面是这个学期的通关项目老师不满足于我对去年项目的更新,他并不认为画电路板是有什么创新的事,而事实也确实如此。在此基础下,就开始了新建文件夹的工程。既然要开始写,那就要先知其所以然。不得不说,在看完 JPEG 的编码过程后,不得不感慨前人的智慧。接下来一点点更新吧。

之前在做FPGA以太网图传,如果不进行图像压缩的话,对摄像头的原始图像数据进行传输fps很有限。最初的分析和实测,百兆网对于 640_480 分辨率的 RGB565 的数据进行传输,最高帧数不会超过 20fps;即使是千兆网络,对于 1920_1080 分辨率的 RGB565 图像,最高帧数也不会超过 28fps。对于视频流,有 H2.64 编码,我们这里只进行图像的压缩,很容易就会想到 JPEG 格式,如此就开始了代码的编写。

JPEG 格式的压缩率是目前各种图像文件格式中最高的,压缩比率通常在 10:1 到 40:1 之间。它用有损压缩的方式去除图像的冗余数据,但存在着一定的失真。由于其高效的压缩效率和标准化要求,目前已广泛用于彩色传真、静止图像、电话会议、印刷及新闻图片的传送。由于各种浏览器都支持 JPEG 这种图像格式,因此它也被广泛用于图像预览和制作 HTML 网页。利用 FPGA 来实现视频图像压缩,可以充分发挥 FPGA 并行计算能力的优势,实现更快速、高效的图像压缩与解压缩操作。因此,深入研究 FPGA 在视频图像压缩与解压缩中的应用,对于提高视频传输效率、节省存储空间以及提升图像处理性能具有重要意义。

这种压缩面对的一种情况是,当采集端的输出图片数据量太大,而接收端没有这么大的带宽,例如:摄像头的设置为720P60fps且进行简单压缩的 RGB565 图像,如这样的数据量,已经快到达千兆网的级别了,当你接收端只有百兆网来进行数据传输时候,如果传输原始数据,那么帧数就会很低,同时也是不连续的,如果对每一帧的 RGB 行实时的 JPEG 压缩,那么在同样的帧数下,就只需要传输压缩后的图像数据,尽管因为要对原始数据进行处理导致数据慢了帧,但这并不是什么太重要的事情,我们只需要期望其不要太长,而对于如今的硬件运算,延时并不会很高,尽管输出的压缩数据不像原始的 RGB 流那样连续有条理,但这已经大大降低了数据量,使得相对低带宽传输成为了可能。

图1 数据转换示意图

而另一种情况则是需要硬件来加速压缩速率,尽管我们可以直接在 PC 机上进行这种处理,但是 FPGA 也提供了一种灵活的嵌入式可能。

JPEG 压缩原理

JPEG 其主要是采用预测编码(DPCM)、离散余弦变换(DCT)以及熵编码的联合编码方式,以去除冗余的图像和彩色数据,属于有损压缩格式,它能够将图像压缩在很小的储存空间,一定程度上会造成图像数据的损伤。尤其是使用过高的压缩比例,将使最终解压缩后恢复的图像质量降低,如果追求高品质图像,则不宜采用过高的压缩比例。

RGB 转换 YCBCR

假定输入的图像为长宽已知的 RGB888 数据流

首先将每个像素的 RGB888 转为 YCBCR 444(主要是为了硬件采样便于实现)或其他。

  • RGB888YCBCR444公式如下:
    • imay=0.256789×imar+0.504129×imag+0.097906×imab+16ima_y = 0.256789 \times ima_r + 0.504129 \times ima_g + 0.097906 \times ima_b + 16
    • imacb=0.148223×imar0.290992×img+0.439215×imab128ima_{cb} = - 0.148223 \times ima_r - 0.290992 \times im_g + 0.439215 \times ima_b 128
    • imacr=0.439215×imar0.367789×imag0.071426×imab+128ima_cr = 0.439215 \times ima_r - 0.367789 \times ima_g - 0.071426 \times ima_b + 128
  • 为方便硬件实现,所有数据需要左移若干位取整以便于运算。

PS:在编码过程中,笔者采用另一公式换算

8×8 DCT

  • 将图像划分为 8x8 的块,单独处理每个块,从左到右,从上到下,顺序处理。对于熵编码部分,当采样为 444 时,块编码顺序为 Y、Cb、Cr、Y、Cb、Cr……当采样顺序为 422 时,以每 16x16 为单位,编码顺序为 Y、Y、Y、Y、Cb、Cb、Cr、Cr以此类推。
  • 块内每个像素值原 [0,255] 减去 128,形成 [-128,127] 的对称范围。
  • 8x8 的块进行 DCT 处理,DCT 矩阵AA如下,一般为了方便硬件计算会左移若干位取整。

A(i,j)=c(i)cos[(j+0.5)πNi],N=8c(i)={1 N,i=02 N,i0\begin{split} &A(i,j)=c(i)cos[\frac{(j+0.5)π}{N}i],N=8\\ &c(i)=\left\{\begin{array}{ll} \sqrt{\frac{1}{\mathrm{~N}}}, & \mathrm{i}=0 \\ \sqrt{\frac{2}{\mathrm{~N}}}, & \mathrm{i} \neq 0 \end{array}\right. \end{split}

  • 令:

a=1/8b=12cos(π8)c=12cos(3π8)d=12cos(π16)e=12cos(316π)f=12cos516πg=12cos716π\begin{array}{l} \mathrm{a}=\sqrt{1 / 8} \\ \mathrm{b}=\frac{1}{2} \cos \left(\frac{\pi}{8}\right) \\ \mathrm{c}=\frac{1}{2} \cos \left(\frac{3 \pi}{8}\right) \\ \mathrm{d}=\frac{1}{2} \cos \left(\frac{\pi}{16}\right) \\ \mathrm{e}=\frac{1}{2} \cos \left(\frac{3}{16} \pi\right) \\ \mathrm{f}=\frac{1}{2} \cos \frac{5}{16} \pi \\ \mathrm{g}=\frac{1}{2} \cos \frac{7}{16} \pi \end{array}

  • 有 DCT 矩阵:

[aaaaaaaadefggfedbccbbccbegdffdgeaaaaaaaafdgeegdfcbbccbbcgfeddefg]\begin{bmatrix} a& a& a& a& a& a& a& a \\ d& e& f& g& -g& -f& -e& -d \\ b& c& -c& -b& -b& -c& c& b \\ e& -g& -d& -f& f& d& g& -e \\ a& -a& -a& a& a& -a& -a& a\\ f& -d& g& e& -e& -g& d& -f \\ c& -b& b& -c& -c& b& -b& c \\ g& -f& e& -d& d& -e& f& -g \end{bmatrix}

  • 在FPGA实现中,都是采用拆成2个一维DCT的方式进行计算。因此,2维DCT便拆分为2次行、列方向的一维DCT变换,在FPGA中只需要实现1维DCT变换,然后复用2次即可。

量化编码

  • DCT 后的数据按照量化表一对一整除,进行量化。
    根据人眼的视觉特性,人眼对高频信息的不敏感,在 JPEG 中采用低频细量化,高频粗量化,以降低视觉冗余。同时由于人眼对色度信号的敏感度低于亮度信号,故对于亮度和色度信号有不同的量化系数 Q(u,v)Q ( u , v )
    量化结果可表示为:

L(u,v)=[F(u,v)Q(u,v)+0.5]\mathrm{L}(\mathrm{u}, \mathrm{v})=\left[\frac{\mathrm{F}(\mathrm{u}, \mathrm{v})}{\mathrm{Q}(\mathrm{u}, \mathrm{v})}+0.5\right]

根据不同质量的要求,建立不同的量化系数表。

表1 通用的8亮度量化表
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 113 77
24 35 55 64 81 104 120 92
49 64 78 87 103 121 120 101
72 92 95 98 112 100 103 99
表2 通用的8x8色度量化表
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

DC 系数差分编码

  • 8×8 图像块经过DCT变换之后得到的 DC 直流系数有两个特点
    • 系数的数值比较大
    • 相邻8×8图像块的 DC 系数值变化不大:冗余
  • 对相邻图像块之间量化 DC 系数的差值 DIFF 进行编码:

DIFF=DCkDCk1DIFF = DC_k - DC_{k-1}

AC 系数 Zigzag 扫描编码

  • 量化后的小块按照 Zigzag 扫描成一维排列的数据流。
图1 Zigzag 扫描输出示意
  • 尤其在最后, 如果都是零,给出 EOB 即可。
    在 JPEG 中的游程编码规定为:(run,level)
    表示连续 run 个 0 ,后面跟值为 level 的系数

如: −3 0 −3 −2 −6 2 −4 1 −4 1 1 5 1 2 −1 1 −1 2 0 0 0 0 0 −1 −1 EOB
表示为:
(0,-3) ; (1,-3) ; (0,-2) ; (0,-6) ; (0,2) ; (0,-4) ; (0,1) ;(0,-4) ; (0,1) (0,1) ; (0,5) ; (0,1); (0,2) ; (0,-1) ; (0,1) ; (0,-1); (0,2) ; (5,-1) ; (0,-1) ; EOB
编码:

  • Run: 最多 15 个,用 4 位表示 Z
  • Level:类似 DC ,分成 16 个类别,用 4 位表示表示类别号 C
  • 对(Z, C)联合用 Huffman 编码
  • 对类内索引用定长码编码

霍夫曼编码

  • 将一维排列进行 Huffman 熵编码。
  • 查表参数 Run/size = 非零数前面 0 的个数 / 非零数值编码 Bit 长度。
    比如(0, 6, 57)对应编码值为 0/6 对应的 111100,那么最后 57 的编码结果为 1111000 + 111001,单独处理的亮度 DC 值 50 会放在这串数据前面,即 1110 + 110010,最后以对应的 EOB 结尾,这样就完成了一个块(亮度)的编码,按每 8 位一个字节进行输出,有空余补 0,然后对 Cb、Cr 分块进行同样的处理。
  • 读取下一个块,重复进行直至完成所有分块;对于最后一个数据可以不给 EOB,由 FFD9 直接结束。

数据打包

  • 将编码数据和参数打包为标准 JPEG 格式(段 + 段的格式)输出,具体见下段的 JPEG 编码结构

JPEG 编码结构

JPEG 文件以 segment 为数据流的组织形式,定义了一系列的标记(Marker)放在每个 segment 的开头。Marker 均以 0xFF 开始,后跟 1 字节的标记标识符和 2 字节的标记长度以及该标记所对应 payload。标记长度部分高位在前,低位在后,不包含该标记头两个字节。
常见 Maker 如下:

表3 JPEG 编码结构说明表
Name Bytes Payload Comments
SOI (Start of Image) 0xFFD8 none 文件开头
APPn (Application Specific) 0xFFEn variable size Exif JPEG 使用APP1,JFIF JPEG 使用 APP0
DQT(Define Quantization Table) 0xFFDB variable size 指定量化表
SOF0 (Start of Image Baseline DCT) 0xFFC0 variable size Baseline DCT 所用的开头
SOF2(Start of Image Progressive DCT) 0xFFC2 variable size Progressive DCT 所用的开头
DHT(Define Huffman Table) 0xFFC4 variable size 指定 Huffman 表
SOS(Start of Scan) 0xFFDA variable size 扫描的开头,包含编码通道顺序以及图像压缩数据等信息
EOI(End of Image) 0xFFD9 none 文件结束
COM (Comment) 0xFFFE variable size 注释
DRI (Define Restart Interval) 0xFFDD 2 bytes RST 中的 marker

另外不常见的段类型不展开。另外为避免编码过程中产生 FFXX 触发段类型,当数据内出现 FF 时,在后面强制插入 00 再接后面的数据。这一部分的输出并不是编码的核心,但是也是必要的一环。还是很需要对这部分进行编写。方便进一步的仿真综合验证,此处给出必要的段类型说明,如下。

SOI

表4 SOI 结构说明表
字节编号 16进制值 说明
1 FF 固定,段标志
2 D8 固定,段类型

APP0

表5 APP0 结构说明表
字节编号 16进制值 说明
1 FF 固定,段标志
2 E0 固定,段类型
3-4 00 10(默认) + 3 x N(缩略图像素数) 自定义,段长度,如果没有缩略图就是00 10
5-9 4A46494600 固定,JFIF字符串
10-11 0101(默认) 自定义,版本号,一般为0101,即JFIF版本 1.1
12 01 自定义,密度单位(0:无单位;1:点数/英寸;2:点数/厘米)
13-14 XX 自定义,图像横向像素数
15-16 XX 自定义,图像纵向像素数
17 00(默认) 自定义,缩略图横向像素数,没有就是00
18 00(默认) 自定义,缩略图纵向像素数,没有就是00
19…… XX 自定义,缩略图RGB数据,每3个字节为一个像素,没有就不写

DQT

表6 DQT 结构说明表
字节编号 16进制值 说明
1 FF 固定,段标志
2 DB 固定,段类型
3-4 00 43(默认) 自定义,段长度,对于8b的数据表来说,默认43,16b的数据此处为83
4 XX 自定义,字节高四位(0:8b 数据 1:16b数据),低四位(0:亮度表 1:色度表),例如此处截图的为亮度表
5…… XX 自定义,8x8共64个表格值,对于8b的格式,一个字节对应一个数据;对于16b的格式则需要两个字节。按照Zigzag顺序从左上往右下扫描。

SOF

表7 SOF 结构说明表
字节编号 16进制值 说明
1 FF 固定,段标志
2 C0 固定,段类型,C2等硬件不方便实现的类型暂不说明,此处只说明C0,即Baseline类型
3-4 00 11(默认) 自定义,段长度
5 08 自定义,采样精度,Baseline只有08
6~7 XX XX 自定义,图像横向像素数
8~9 XX XX 自定义,图像纵向像素数
10 03(默认) 自定义,图像分量,对于YUV来说只有3个分量,灰度就只有1个
11~13 XX XX XX 自定义,分量ID 0 8b+H0分量水平采样 4b+V0分量垂直采样 4b+T0当前分量量化表ID 8b
14~16 XX XX XX 自定义,分量ID 1 8b+H1分量水平采样 4b+V1分量垂直采样 4b+T1当前分量量化表ID 8b
17~19 XX XX XX 自定义,分量ID 2 8b+H2分量水平采样 4b+V2分量垂直采样 4b+T2当前分量量化表ID 8b
20…… XX 自定义,YUV此处已经结束,如果其他编码方式可能还有后续的分量编码

注:对于11~19来说,主要定义了YUV444还是422还是420采样,并且采用哪个量化表,当然这里一般是固定的Y U V 分量编码 0 1 2,并且Y使用量化表0,U V共同使用量化表1;对于其他颜色空间等,这里可能还有别的分量,所以看起来这个描述比较复杂

DHT

表8 DHT 结构说明表
字节编号 16进制值 说明
1 FF 固定,段标志
2 C4 固定,段类型
3-4 XX XX 自定义,段长度
5 XX 自定义,第8~6位:固定0,第五位:0:DC直流 1:AC交流,低四位:0 亮度 1色度,因此一共有00 10 (亮度DC\AC),10 11(色度DC\AC)四张表
6~21 XX…… 自定义,Huffman Table 位表,共16个字节,表示在Huffman 表中,第1~第16bit的各有多少个数据,顺序摆放。

如上图所示,将数据分为6~21部分和22~37部分,第6个字节00表示长度bit 01的有0个数据;第7个字节01表示长度bit 01的有1个数据,将第22字节放入;第8个字节05表示长度bit 01的有5个数据,将23~27字节放入,以此类推可以得到下图值表;
22…… XX…… 自定义,Huffman Table 值表

SOS

表9 SOS 结构说明表
字节编号 16进制值 说明
1 FF 固定,段标志
2 DA 固定,段类型
3-4 00 0C 固定,段长度
5 XX 自定义,扫描行内组件,1:灰度图,3:YCBCR or YIQ,4:CMYK,一般为1
6,8,10 XX 自定义,组件ID,01:Y,02:CB,03:CR,后面45 YUV模式用不上
7,9,11 XX 自定义,Hufffman表号,高四位是AC表ID,低四位是DC表
12 00 3F 00 自定义,频谱选择的三个参数,在简化模式下可以当做常量
13…… XX…… 压缩的图像数据,顺序为从左到右,从上到下

EOI:

表9 EOI 结构说明表
字节编号 16进制值 说明
1 FF 段标志
2 D9 段类型

工程实现

工程结构

jpeg_top.v

u26 (clock_div) clock_div.v
u19 (fifo_out) fifo_out.v

u14 (pre_fifo) pre_fifo.v
u4 (RGB2YCBCR) rgb2ycbcr.v
u11 (crd_q h) crd_q_h.v

u8 (cr_dct) cr_dct.v
u9 (cr_quantizen) cr_quantizer.v
u10 (cr_huff) cr_huff.v

u12 (cbd_q h) cbd_q_h.v

u5 (cb_dct) cb_dct.v
u6 (cb_quantizen) cb_quantizer.v
u7 (cb_huff) cb_huff.v

u13 (yd_q h) yd_q_h.v

u1 (y_dct) y_dct.v
u2 (y_quantizer) y_quantizer.v
u3 (y_huff) y_huff.v

u15 (sync_fifo_32) sync_fifo_32.v
u25 (sync_fifo_32) sync_fifo_32.v
u16 (sync_fifo_32) sync_fifo_32.v
u24 (sync_fifo_32) sync_fifo_32.v
u17 (sync_fifo_32) sync_fifo_32.v

u20 (ff_checker) ff_checker.v

u18 (sync_fifo_ff) sync_fifo_ff.v

u28 (parallel_to_serial) parallel_to_serial.v
u29 (jpeg_package) jpeg_package.v

主要module

jpeg_top

图2 jpeg_top module

下表给出 module 说明:

表10 jpeg_top module Port
Port name Direction Type Description
clk input 时钟
rst input 复位
end_of_file_signal input 结束标志
enable input 输入 有效
Compress_data_rdy input 打包编码流输入有效
data_in input [23:0] 输入RGB_block信号
JPEG_bitstream output [31:0] 输出编码流
data_ready output 输出信号有效
end_of_file_bitstream_count output [4:0] 编码流剩余数
eof_data_partial_ready output 输出编码流结束
jpeg_data output [7:0] JPEG数据流
jpeg_data_vaild output JPEG数据流有效
enda1 output JPEG数据流结束标志

RGB2YCBCR

图3 RGB2YCBCR module

下表给出 module 说明:

表11 RGB2YCBCR module Port
Port name Direction Type Description
clk input 时钟
rst input 复位
enable input 输入有效
data_in input [23:0] 输入信号
data_out output [23:0] 输出信号
enable_out output 输出有效

y/Cb/Cr_DCT

图4 y/Cb/Cr_DCT module

下表给出 module 说明:

表12 DCT module Port
Port name Direction Type Description
clk input 时钟
rst input 复位
enable input 输入有效
data_in input [7:0] 输入信号
Zijij_final output [10:0] 输出矩阵信号
output_enable output 输出有效

其中i,j=18i,j = 1\sim 8

y/Cb/Cr_huff

图5 y/Cb/Cr_huff module

下表给出 module 说明:

表13 huff module Port
Port name Direction Type Description
clk input 时钟
rst input 复位
enable input 输入使能
Yijij input [10:0] 输入矩阵信号
JPEG_bitstream output [31:0] 输出编码流
data_ready output 输出开始
output_reg_count output [4:0] 输出计数
end_of_block_output output 结束标志
end_of_block_empty output 结束清空标志

其中i,j=18i,j = 1\sim 8

y/Cb/Cr_quantizer

图6 y/Cb/Cr_quantizer module

下表给出 module 说明:

表14 quantizer module Port
Port name Direction Type Description
clk input 时钟
rst input 复位
enable input 输入有效
Zijij input [10:0] 输入矩阵信号
Qijij output [10:0] 输出矩阵信号
out_enable output 输出有效

其中i,j=18i,j = 1\sim 8

jpeg_package

图7 jpeg_package module

下表给出 module 说明:

表15 jpeg_package module Generics
Generic name Type Value Description
YUV_MODE 编码模式
IMG_WIDTH 图片宽度
IMG_HEIGHT 图片高度
表16 jpeg_package module Port
Port name Direction Type Description
clk input 时钟
rst input 复位
data_ready1 input 输入开始标志
data_en input 输入有效
Compress_data_rdy input 编码流有效
Compress_data_last input 编码流结束标志
Compress_data input [7:0] 编码流
Compress_data_rden output 打包开始标志
jpeg_data output [7:0] JPEG输出
jpeg_data_vaild output 输出有效
jpeg_data_last output 输出结束
  • 缺 状态转移图

待补充

仿真验证

RGB块数据流的生成

这一部分使用了 matlab 来进行图片数据的读取、转换、生成数据流和延时,并生成 tb.v 文件便于直接仿真。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
clc
clear
rgb_image=imread(['test.jpg']);%读取图片
fR=rgb_image(:, :, 1);%读取红色分量
fG=rgb_image(:, :, 2);%读取绿色分量
fB=rgb_image(:, :, 3);%读取蓝色分量
R1 = dec2hex(fR);
R2 = dec2hex(fG);
R3 = dec2hex(fB);
WIDTH = 256; //img宽度
HEIGHT = 256; //img高度
% l = sqrt(length(R1));
fileID = fopen('tb.v','w');


fprintf(fileID,"`timescale 1ps / 1ps\n" + ...
'`include "jpeg_top.v"\n' + ...
"module jpeg_top_tb;/n"+ ...
"wire [7:0] jpeg_data;\n" + ...
"wire jpeg_data_vaild;\n" + ...
"reg end_of_file_signal;\n" + ...
"reg [23:0] data_in;\n" + ...
"reg clk;\nreg rst;\n" + ...
"reg enable;\n" + ...
"reg Compress_data_rdy;\n" + ...
"wire [31:0] JPEG_bitstream;\n" + ...
"wire data_ready;\n" + ...
"wire [4:0] end_of_file_bitstream_count;\n" + ...
"wire eof_data_partial_ready;\n" + ...
"wire enda1;" +...
"jpeg_top #(\n" +...
".YUV_MODE (2 ),\n" +...
".IMG_WIDTH (16'd%d ),\n" +...
".IMG_HEIGHT (16'd%d ) \n" +...
")UUT (\n" +...
".end_of_file_signal (end_of_file_signal ),\n" +...
".data_in (data_in ),\n" +...
".clk (clk ),\n" +...
".rst (rst ),\n" +...
".enable (enable ),\n" +...
".JPEG_bitstream (JPEG_bitstream ),\n" +...
".data_ready (data_ready ),\n" +...
".end_of_file_bitstream_count (end_of_file_bitstream_count),\n" +...
".eof_data_partial_ready (eof_data_partial_ready ),\n" +...
".Compress_data_rdy (Compress_data_rdy ),\n" +...
".jpeg_data (jpeg_data ),\n" +...
".jpeg_data_vaild (jpeg_data_vaild ),\n" +...
".enda1 (enda1 ) \n" +...
");\n" +...
"initial begin : STIMUL\n" +...
" #0 rst = 1'b1;\n" +...
" enable = 1'b0;\n" +...
" Compress_data_rdy = 0;\n" +...
" end_of_file_signal = 1'b0;\n" +...
" #100;\n" +...
" rst = 1'b0;\n" +...
" Compress_data_rdy = 1;\n" +...
" #300;\n" +...
" enable = 1'b1;\n",WIDTH,HEIGHT);
for i = 1: 8: HEIGHT
for j = 1: 8: WIDTH
R = fR(i: i + 8 - 1, j: j + 8 - 1);
G = fG(i: i + 8 - 1, j: j + 8 - 1);
B = fB(i: i + 8 - 1, j: j + 8 - 1);
R2 = dec2hex(R', 2);
G2 = dec2hex(G', 2);
B2 = dec2hex(B', 2);
x1 = strcat(B2, G2, R2);
for k = 1: 64
fprintf(fileID, " data_in <= 24'h");
fprintf(fileID, '%s;\n',x1(k,:));
fprintf(fileID, ' #100 \n');
end
fprintf(fileID,' #1300;\n');
fprintf(fileID," enable <= 1'b0;\n");
if((i == (HEIGHT - 7)) && (j == (WIDTH - 7)) )
fprintf(fileID," Compress_data_rdy = 0;\n");
else
fprintf(fileID,' #100;\n');
fprintf(fileID," enable <= 1'b1;\n");
end
end
end
fprintf(fileID," #70000;\n" +...
" $finish;\n" +..." + ...
"end\n" +...
"always begin : CLOCK_clk\n" +...
" clk = 1'b0;\n" +...
" #10;\n" +...
" clk = 1'b1;\n" +...
" #10;\n" +...
" end\n" +...
" integer test ;\n" +...
"initial begin\n" +...
' test = $fopen("tst4.jpeg", "wb");\n' +...
' if (test) $display("open file success");\n' +...
' else $display("open file fail");\n' +...
"end\n" +...
"always @(posedge clk) begin : JPEG\n" +...
"if (jpeg_data_vaild == 1'b1)\n" +...
"begin\n" +...
'// $write("%%h", jpeg_data);\n' +...
' $fwrite(test, "%%c", jpeg_data);\n' +...
"end\n" +...
"// if (jpeg_data ==24'hffd9) \n" +...
"// begin\n" +...
"// $fclose(test);\n" +...
'// $display("close success");\n' +...
"// $finish;\n" +...
"// end\n" +...
"end\n" +...
"/*生成仿真所需的vcd文件*/\n" +...
"//initial begin\n" +...
'// $dumpfile("wave.vcd");\n' +...
"// $dumpvars(0, UUT );\n" +...
"//end\n" +...
"endmodule");
fclose(fileID);

Testbench

宏定义图像尺寸,对于小尺寸的图片进行波形文件的绘制,以及将输出的数据直接写入 JPEG 以方便直接验证;大尺寸的图像直接写入 JPEG。
对于 128×128 的图片仿真时间只有 14s 左右,而对于 1080×720 的图片仿真时间相当之长,连 VScode 也救不了,要达到 7min 之久。
此处为了节约版面给出 8×8 分辨率的一个 tb.v

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
`timescale 1ps / 1ps
`include "jpeg_top.v"
module jpeg_top_tb;
wire [7:0] jpeg_data;
wire jpeg_data_vaild;
reg end_of_file_signal;
reg [23:0] data_in;
reg clk;
reg rst;
reg enable;
reg Compress_data_rdy;
wire [31:0] JPEG_bitstream;
wire data_ready;
wire [4:0] end_of_file_bitstream_count;
wire eof_data_partial_ready;
wire enda1;jpeg_top #(
.YUV_MODE (2 ),
.IMG_WIDTH (16'd8 ),
.IMG_HEIGHT (16'd8 )
)UUT (
.end_of_file_signal (end_of_file_signal ),
.data_in (data_in ),
.clk (clk ),
.rst (rst ),
.enable (enable ),
.JPEG_bitstream (JPEG_bitstream ),
.data_ready (data_ready ),
.end_of_file_bitstream_count (end_of_file_bitstream_count),
.eof_data_partial_ready (eof_data_partial_ready ),
.Compress_data_rdy (Compress_data_rdy ),
.jpeg_data (jpeg_data ),
.jpeg_data_vaild (jpeg_data_vaild ),
.enda1 (enda1 )
);
initial begin : STIMUL
#0 rst = 1'b1;
enable = 1'b0;
Compress_data_rdy = 0;
end_of_file_signal = 1'b0;
#100;
rst = 1'b0;
Compress_data_rdy = 1;
#300;
enable = 1'b1;
data_in <= 24'h1D1410;
#100
data_in <= 24'h1D1410;
#100
data_in <= 24'h1D1410;
#100
data_in <= 24'h1D1410;
#100
data_in <= 24'h1E1511;
#100
data_in <= 24'h1E1511;
#100
data_in <= 24'h1D1410;
#100
data_in <= 24'h1C130F;
#100
data_in <= 24'h1D1410;
#100
data_in <= 24'h1C130F;
#100
data_in <= 24'h1C130F;
#100
data_in <= 24'h1D1410;
#100
data_in <= 24'h1D1410;
#100
data_in <= 24'h1D1410;
#100
data_in <= 24'h1D1410;
#100
data_in <= 24'h1C130F;
#100
data_in <= 24'h1E1511;
#100
data_in <= 24'h1D1410;
#100
data_in <= 24'h1C130F;
#100
data_in <= 24'h1C130F;
#100
data_in <= 24'h1C130F;
#100
data_in <= 24'h1D1410;
#100
data_in <= 24'h1D1410;
#100
data_in <= 24'h1D1410;
#100
data_in <= 24'h201713;
#100
data_in <= 24'h1E1511;
#100
data_in <= 24'h1D1410;
#100
data_in <= 24'h1C130F;
#100
data_in <= 24'h1C130F;
#100
data_in <= 24'h1D1410;
#100
data_in <= 24'h1D1410;
#100
data_in <= 24'h1D1410;
#100
data_in <= 24'h221915;
#100
data_in <= 24'h201713;
#100
data_in <= 24'h1E1511;
#100
data_in <= 24'h1C130F;
#100
data_in <= 24'h1C130F;
#100
data_in <= 24'h1D1410;
#100
data_in <= 24'h1D1410;
#100
data_in <= 24'h1D1410;
#100
data_in <= 24'h211814;
#100
data_in <= 24'h201713;
#100
data_in <= 24'h1E1511;
#100
data_in <= 24'h1D1410;
#100
data_in <= 24'h1C130F;
#100
data_in <= 24'h1D1410;
#100
data_in <= 24'h1D1410;
#100
data_in <= 24'h1D1410;
#100
data_in <= 24'h1F1612;
#100
data_in <= 24'h1E1511;
#100
data_in <= 24'h1D1410;
#100
data_in <= 24'h1C130F;
#100
data_in <= 24'h1C130F;
#100
data_in <= 24'h1C130F;
#100
data_in <= 24'h1D1410;
#100
data_in <= 24'h1C130F;
#100
data_in <= 24'h1D1410;
#100
data_in <= 24'h1C130F;
#100
data_in <= 24'h1B120E;
#100
data_in <= 24'h1B120E;
#100
data_in <= 24'h1C130F;
#100
data_in <= 24'h1C130F;
#100
data_in <= 24'h1D1410;
#100
data_in <= 24'h1C130F;
#100
#1300;
enable <= 1'b0;
Compress_data_rdy = 0;
#70000;
$finish;
end
always begin : CLOCK_clk
clk = 1'b0;
#10;
clk = 1'b1;
#10;
end
integer test ;
initial begin
test = $fopen("tst4.jpeg", "wb");
if (test) $display("open file success");
else $display("open file fail");
end
always @(posedge clk) begin : JPEG
if (jpeg_data_vaild == 1'b1)
begin
// $write("%h", jpeg_data);
$fwrite(test, "%c", jpeg_data);
end
// if (jpeg_data ==24'hffd9)
// begin
// $fclose(test);
// $display("close success");
// $finish;
// end
end
/*生成仿真所需的vcd文件*/
//initial begin
// $dumpfile("wave.vcd");
// $dumpvars(0, UUT );
//end
endmodule

验证结果

图为编码输出打包的 JPEG,vcd 文件因为太大了就没有生成。同时给出 96×96 的关键仿真波形,因为量化编码方式的区别,JPEG 的输出可能会有差异。

图8 工程输出 1080×720 图像
图9 工程输出 96×96 图像
图10 JPEG 开始字节
图11 JPEG 编码流开始
图12 JPEG 编码流结束
图13 JPEG 结束字节

总结

在进行了几个月的摸鱼、调研查找编码原理以及相关算法例程,最后经过不屑的缝合,终于是搓出来一个看的过去的工程,性能可能不是很好,因为没有借助任何 Xlinx 和其他家的相关 IP 核。
全部的编写验证在 Vscode 上完成,包括仿真波形以及输出数据流。

工程主要借鉴了猪蹄的 GitHub 工程(虽然他并没有写完),但是 package 模块被拿过来用了。同时借鉴(使用了)一个意外发现的开源核。整个工程没有外部 fifo 以及 RAM 之类的缓存,所以每次进入的是 8×8 数据块,需要在外写 8 行 RAM 缓存来进行原生态 RGB 图像硬件转换。考虑到输出数据的位宽大多规定为 8bit,而开源核输出位宽为 32bit,被迫给时钟分频降速 5 倍(实际上应该降 4 倍)然后再进行并串转换输出。在实际使用中可以使用锁相环进行系统时钟倍频,来给编码模块提供时钟,然后再输出接入 fifo 或 RAM 一类,进行异步转换。只是因为没有下板验证,尚且不知倍频上去的数据误码率怎么样。要注意每个数据块之间的间隔应该大于 13 个时钟周期,以保证输出数据的正确性。输出数据为打包好的 JPEG ,以 FFD8 开始,FFD9 结束,是标准的 JPEG 格式。然而压缩率并不是很高,只降低了 2 倍左右,也可以说图像质量没有下降很多。只能说给自己了一个交代吧,不算是什么都没有做出来了。

附录

工程链接:

  • 待更新

参考资料

其他

表17 Huffman编码表
Value Size Bits
0 0 -
-1, 1 1 0, 1
-3, -2, 2, 3 2 00, 01, 10, 11
-7, -6, -5, -4, 4, 5, 6, 7 3 000, 001, 010, 011, 100, 101, 110, 111
-15, …, -8, 8, …, 15 4 0000, …, 0111, 1000, …, 1111
-31, …, -16, 16, …, 31 5 0 0000, …, 0 1111, 1 0000, …, 1 1111
-63, …, -32, 32, …, 63 6 00 0000, …, …, 11 1111
-127, …, -64, 64, …, 127 7 000 0000, …, …, 111 1111
-255, …, -128, 128, …, 255 8 0000 0000, …, …, 1111 1111
-511, …, -256, 256, …, 511 9 0 0000 0000, …, …, 1 1111 1111
-1023, …, -512, 512, …, 1023 10 00 0000 0000, …, …, 11 1111 1111
-2047, …, -1024, 1024, …, 2047 11 000 0000 0000, …, …, 111 1111 1111
-4095, …, -2048, 2048, …, 4095 12 0000 0000 0000, …, …, 1111 1111 1111
-8191, …, -4096, 4096, …, 8191 13 0 0000 0000 0000, …, …, 1 1111 1111 1111
-16383, …, -8192, 8192, …, 16383 14 00 0000 0000 0000, …, …, 11 1111 1111 1111
-32767, …, -16348, 16348, …, 32767 15 000 0000 0000 0000, …, …, 111 1111 1111 1111