对于图像处理的知识,个人仅限于研究生时上过的数字图像处理课程,而对于FPGA的数字图像处理,一直仅限于略(yan)有(gao)耳(shou)闻(di)。国庆躺尸看了点儿简单的FPGA图像处理,发现在FPGA图像处理中矩阵提取是个有趣的东东,本文仅限个人DIY,是否具有工程价值不做论述,权当一乐。
无处不在的line buffer
现在FPGA图像处理中,凡事牵涉到矩阵运算的算法里面基本都有line buffer的身影,而line buffer往往采用Xilinx的Ram-based Shift Register或者Altrea的Shift Register来实现:
嗯,简单明了,似乎没什么难的,随后翻了翻两个厂商的IP,发现有点儿别扭:
我把数据从IP的din灌入进去了,但输出数据什么时候有效没告诉我啊,虽然仿真时可以看到数据有效时的状态但设计里你还要我手动去控制判断什么时候是有效输出是不是有点儿过分了……
当年老师也讲过在进行矩阵处理时对于边缘像素提取矩阵时往往需要填充像素。软件里感觉还好但FPGA里实时地你要我补像素貌似有点儿麻烦哎……
也许个人太懒了吧,这种事情做起来都不复杂,但要在RTL里做这些我还是有点儿拒绝的。另辟蹊径,借助SpinalHDL里方便的电路描述符方式,魔改实现一个IP:
串行输入像素,带Valid指示,输入前无需用户进行边缘像素填充。
输出nxn矩阵用于做图像处理,输出数据有效带Valid标签。
对于3x3矩阵,用户调用的RTL接口列表将会如下所示(起名有点儿随意):
line buffer实现
首先要实现的是行缓冲器,说白了就是一连串移位寄存器,这里使用了SpinalHDL里提供的Histroy工具,它的定义是:
天然的实现行缓冲器的有效工具啊!!!几行代码就可以实现一个多抽头行缓冲器:
val sram=History(io.dataIn.payload,cfg.lineDepth*cfg.lineNum+1,init=U(0,cfg.dataWidth bits))
这里有一点需要注意的是History最后一个元素是延迟了length-1拍结果,故这里length为缓冲的像素数+1。
同时为了实现对边缘像素的处理,避免填补像素,在对外给输出Valid信号时需数据在到达缓存像素个数中间时给出有效标志(在矩阵处理时待处理像素都是在矩阵中间),同样需要借助一个缓冲器缓冲Valid标签:
至此行缓冲器的整体结构便有了(几行代码的事情)。
bufWindow实现
有了line buffer的实现,bufWindow的实现就水到渠成了,line buffer按序输出了n行待处理的元素,接下来在bufWIndow里通过寄存器缓存实现矩阵窗口及数据有效标志位处理即可。
针对矩阵窗口的实现,由于line buffer输出已缓存的一排排数据,故在bufWidnow里只需再缓存nx(n-1)个元素即可,不多说,移位寄存(也可调整元素顺序,使得可算法描述里一一对应)~
而数据有效位的处理,则需对line buffer输出延迟(n+1)/2-1拍即可。
实现结果
对于行宽为3,行数为3,提取3x3矩阵仿真结果为(按行输入元素从左到右依次填充1,2,3……25) :
对于行宽为5,行数为5,提取5x5矩阵仿真结果为(按行输入元素从左到右依次填充1,2,3……25) :
可以看到,bufWindow能够为每个元素提取相应的矩阵,不过在处理边缘像素时填充的元素不一定全为0,部分为其它边缘像素值,若在工程中这点可接收那么在处理时将减少很多工作量了~
当然只是DIY,这里矩阵大小n只支持奇数。
原作者:玉骐