十二 · 2021年10月27日

FPGA图像处理中矩阵提取

对于图像处理的知识,个人仅限于研究生时上过的数字图像处理课程,而对于FPGA的数字图像处理,一直仅限于略(yan)有(gao)耳(shou)闻(di)。国庆躺尸看了点儿简单的FPGA图像处理,发现在FPGA图像处理中矩阵提取是个有趣的东东,本文仅限个人DIY,是否具有工程价值不做论述,权当一乐。

无处不在的line buffer

闲暇逛知乎,发现在FPGA图像处理中,凡事牵涉到矩阵运算的算法里面基本都有line buffer的身影,而line buffer往往采用Xilinx的Ram-based Shift Register或者Altrea的Shift Register来实现:

f847d961e1db4faaef66126c4f8fec2a.png

嗯,简单明了,似乎没什么难的,随后翻了翻两个厂商的IP,发现有点儿别扭:

  1. 我把数据从IP的din灌入进去了,但输出数据什么时候有效没告诉我啊,虽然仿真时可以看到数据有效时的状态但设计里你还要我手动去控制判断什么时候是有效输出是不是有点儿过分了😒……
  2. 当年老师也讲过在进行矩阵处理时对于边缘像素提取矩阵时往往需要填充像素。软件里感觉还好但FPGA里实时地你要我补像素貌似有点儿麻烦哎😁……

也许个人太懒了吧,这种事情做起来都不复杂,但要在RTL里做这些我还是有点儿拒绝的。另辟蹊径,借助SpinalHDL里方便的电路描述符方式,魔改实现一个IP:

  1. 串行输入像素,带Valid指示,输入前无需用户进行边缘像素填充。
  2. 输出nxn矩阵用于做图像处理,输出数据有效带Valid标签。

对于3x3矩阵,用户调用的RTL接口列表将会如下所示(起名有点儿随意😂):

module bufWindow (
  input               dataIn_valid,
  input      [15:0]   dataIn_payload,
  output              dataOut_valid,
  output     [15:0]   dataOut_payload_0_0,
  output     [15:0]   dataOut_payload_0_1,
  output     [15:0]   dataOut_payload_0_2,
  output     [15:0]   dataOut_payload_1_0,
  output     [15:0]   dataOut_payload_1_1,
  output     [15:0]   dataOut_payload_1_2,
  output     [15:0]   dataOut_payload_2_0,
  output     [15:0]   dataOut_payload_2_1,
  output     [15:0]   dataOut_payload_2_2,
  input               clk,
  input               reset
);
endmodule

line buffer实现

首先要实现的是行缓冲器,说白了就是一连串移位寄存器,这里使用了SpinalHDL里提供的Histroy工具,它的定义是:

a8f289673310c822f0cdd8a882b0e63c.png

天然的实现行缓冲器的有效工具啊!!!几行代码就可以实现一个多抽头行缓冲器:

val sram=History(io.dataIn.payload,cfg.lineDepth*cfg.lineNum+1,init=U(0,cfg.dataWidth bits))

这里有一点需要注意的是History最后一个元素是延迟了length-1拍结果,故这里length为缓冲的像素数+1。

同时为了实现对边缘像素的处理,避免填补像素,在对外给输出Valid信号时需数据在到达缓存像素个数中间时给出有效标志(在矩阵处理时待处理像素都是在矩阵中间),同样需要借助一个缓冲器缓冲Valid标签:

val validRam=History(io.dataIn.valid,(cfg.lineNum+1)*cfg.lineDepth/2+1,init=False)

至此行缓冲器的整体结构便有了(几行代码的事情)。

bufWindow实现

有了line buffer的实现,bufWindow的实现就水到渠成了,line buffer按序输出了n行待处理的元素,接下来在bufWIndow里通过寄存器缓存实现矩阵窗口及数据有效标志位处理即可。

针对矩阵窗口的实现,由于line buffer输出已缓存的一排排数据,故在bufWidnow里只需再缓存nx(n-1)个元素即可,不多说,移位寄存(也可调整元素顺序,使得可算法描述里一一对应)~

而数据有效位的处理,则需对line buffer输出延迟(n+1)/2-1拍即可。

val windowValid=History(lineBuffers.io.dataOut.valid,(cfg.lineNum+1)/2,init=False)
io.dataOut.valid:=windowValid.vec.last

实现结果

对于行宽为3,行数为3,提取3x3矩阵仿真结果为(按行输入元素从左到右依次填充1,2,3……25) :

buffer window 1
0  0  0
0  1  2
0  4  5
buffer window 2
0  0  0
1  2  3
4  5  6
buffer window 3
0  0  1
2  3  4
5  6  7
buffer window 4
0  1  2
3  4  5
6  7  8
buffer window 5
1  2  3
4  5  6
7  8  9
buffer window 6
2  3  4
5  6  7
8  9  0
buffer window 7
3  4  5
6  7  8
9  0  0
buffer window 8
4  5  6
7  8  9
0  0  0
buffer window 9
5  6  7
8  9  0
0  0  0

对于行宽为5,行数为5,提取5x5矩阵仿真结果为(按行输入元素从左到右依次填充1,2,3……25) :

buffer window 1
0  0  0  0  0
0  0  0  0  0
0  0  1  2  3
0  0  6  7  8
0  0  11  12  13
buffer window 2
0  0  0  0  0
0  0  0  0  0
0  1  2  3  4
0  6  7  8  9
0  11  12  13  14
buffer window 3
0  0  0  0  0
0  0  0  0  0
1  2  3  4  5
6  7  8  9  10
11  12  13  14  15
buffer window 4
0  0  0  0  0
0  0  0  0  1
2  3  4  5  6
7  8  9  10  11
12  13  14  15  16
buffer window 5
0  0  0  0  0
0  0  0  1  2
3  4  5  6  7
8  9  10  11  12
13  14  15  16  17
buffer window 6
0  0  0  0  0
0  0  1  2  3
4  5  6  7  8
9  10  11  12  13
14  15  16  17  18
buffer window 7
0  0  0  0  0
0  1  2  3  4
5  6  7  8  9
10  11  12  13  14
15  16  17  18  19
buffer window 8
0  0  0  0  0
1  2  3  4  5
6  7  8  9  10
11  12  13  14  15
16  17  18  19  20
buffer window 9
0  0  0  0  1
2  3  4  5  6
7  8  9  10  11
12  13  14  15  16
17  18  19  20  21
buffer window 10
0  0  0  1  2
3  4  5  6  7
8  9  10  11  12
13  14  15  16  17
18  19  20  21  22
buffer window 11
0  0  1  2  3
4  5  6  7  8
9  10  11  12  13
14  15  16  17  18
19  20  21  22  23
buffer window 12
0  1  2  3  4
5  6  7  8  9
10  11  12  13  14
15  16  17  18  19
20  21  22  23  24
buffer window 13
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
buffer window 14
2  3  4  5  6
7  8  9  10  11
12  13  14  15  16
17  18  19  20  21
22  23  24  25  0
buffer window 15
3  4  5  6  7
8  9  10  11  12
13  14  15  16  17
18  19  20  21  22
23  24  25  0  0
buffer window 16
4  5  6  7  8
9  10  11  12  13
14  15  16  17  18
19  20  21  22  23
24  25  0  0  0
buffer window 17
5  6  7  8  9
10  11  12  13  14
15  16  17  18  19
20  21  22  23  24
25  0  0  0  0
buffer window 18
6  7  8  9  10
11  12  13  14  15
16  17  18  19  20
21  22  23  24  25
0  0  0  0  0
buffer window 19
7  8  9  10  11
12  13  14  15  16
17  18  19  20  21
22  23  24  25  0
0  0  0  0  0
buffer window 20
8  9  10  11  12
13  14  15  16  17
18  19  20  21  22
23  24  25  0  0
0  0  0  0  0
buffer window 21
9  10  11  12  13
14  15  16  17  18
19  20  21  22  23
24  25  0  0  0
0  0  0  0  0
buffer window 22
10  11  12  13  14
15  16  17  18  19
20  21  22  23  24
25  0  0  0  0
0  0  0  0  0
buffer window 23
11  12  13  14  15
16  17  18  19  20
21  22  23  24  25
0  0  0  0  0
0  0  0  0  0
buffer window 24
12  13  14  15  16
17  18  19  20  21
22  23  24  25  0
0  0  0  0  0
0  0  0  0  0
buffer window 25
13  14  15  16  17
18  19  20  21  22
23  24  25  0  0
0  0  0  0  0
0  0  0  0  0

可以看到,bufWindow能够为每个元素提取相应的矩阵,不过在处理边缘像素时填充的元素不一定全为0,部分为其它边缘像素值,若在工程中这点可接收那么在处理时将减少很多工作量了~

当然只是DIY,这里矩阵大小n只支持奇数。

 title=

END

作者:玉骐
原文链接:https://mp.weixin.qq.com/s/mwxTc5YE1ExM7zxHBj3zpQ
微信公众号:
 title=

推荐阅读

更多SpinalHDL技术干货请关注Spinal FPGA专栏。
推荐阅读
关注数
1581
内容数
133
用SpinalHDL提升生产力
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息