十二 · 2021年10月27日

FPGA图像处理——老戏新说

书接上文,趁着今天休假,采用SpinalHDL做一个小的demo,看看在SpinalHDL里如何优雅的实现Sobel边缘检测。

Sobel边缘检测

Sobel边缘检测原理教材网上一大堆,核心为卷积处理。

Sobel卷积因子为:

d52c222fdc2a2930e2e1d55be8dd511f.png

该算子包含两组3x3的矩阵,分别为横向及纵向,将之与图像作平面卷积,即可分别得出横向及纵向的亮度差分近似值。如果以A代表原始图像,Gx及Gy分别代表经横向及纵向边缘检测的图像灰度值,其公式如下:

c9eb7f24b0f083836097c449c43a4c7d.png

图像的每一个像素的横向及纵向灰度值通过以下公式结合,来计算该点灰度的大小:

ad1526c651e0ed5925c25f26e501e912.png

通常,为了提高效率使用不开平方的近似值:

78c9ac89bf8e151fd343ea7d4da5824d.png

最后,当计算出来的值大于某一阈值时即认为为边缘像素点。

归结起来,Sobel边缘检测分为三大步:卷积计算、灰度计算、阈值比较处理。结合上文实现的bufWindow,在SpinalHDL里实现Sobel边缘检测也就几行代码的事情(如果是写Verilog我还是拒绝的)。

卷积计算

通过bufWindow,我们可以得到一个3x3的矩阵窗口,拿到结果第一步即是计算卷积,由于卷积因子是带符号的,而在做卷积时又需要考虑位宽扩展的事情,在写Verilog时还是需要小心的设计下的,而在SpinalHDL里,两行代码:

val Gx=(windowbuf.io.dataOut.payload(0)(2).expand.asSInt-^windowbuf.io.dataOut.payload(0)(0).expand.asSInt)+|
      ((windowbuf.io.dataOut.payload(1)(2).expand.asSInt-^windowbuf.io.dataOut.payload(1)(0).expand.asSInt)<<1)+|
      (windowbuf.io.dataOut.payload(2)(2).expand.asSInt-^windowbuf.io.dataOut.payload(2)(0).expand.asSInt)
val Gy=(windowbuf.io.dataOut.payload(0)(0).expand.asSInt-^windowbuf.io.dataOut.payload(2)(0).expand.asSInt)+|
       ((windowbuf.io.dataOut.payload(0)(1).expand.asSInt-^windowbuf.io.dataOut.payload(2)(1).expand.asSInt)<<1)+|
       (windowbuf.io.dataOut.payload(0)(2).expand.asSInt-^windowbuf.io.dataOut.payload(2)(2).expand.asSInt)

首先将bufWindow输出的窗口矩阵值扩展一位位宽转换为有符号值,然后进行计算卷积。计算卷积运用了两个运算符“-^”,"+|"来处理加减运算时的位宽处理(可参照SpinalHDL手册或本公众号的《SpinalHDL—数据类型:UInt/SIn》)。最终得到Gx、Gy。

灰度计算

灰度计算这里采用近似值,通过取绝对值的方式进行实现,在SpinalHDL里也就一行代码的事情:

sobelResult.payload:= (sobelConv.payload(0).abs+| sobelConv.payload(1).abs).fixTo(cfg.dataWidth-1 downto 0,RoundType.ROUNDUP)

由于在卷积计算时有扩展位宽,这里计算最后调用fixTo进行高位饱和处理。最终得到位宽与输入保持一致(想想你在Veirlog里实现这一步要做多少事情,少年😎)。

阈值比较

阈值比较就很简单了,比较两个值大小取两个极端:

when(sobelResult.payload>io.thresholdValue){
      io.dataOut.payload:=(default->true)
    }otherwise{
      io.dataOut.payload:=(default->false)
    }

最终实现Sobel边缘检测代码如下:

case class sobelProc(cfg:lineBufferCfg) extends Component{
  require(cfg.lineNum==3)
  val io=new Bundle{
    val thresholdValue =in UInt(cfg.dataWidth bits)
    val dataIn=slave Flow(UInt(cfg.dataWidth bits))
    val dataOut=master Flow(UInt(cfg.dataWidth bits))
    dataOut.valid.setAsReg().init(False)
    dataOut.payload.setAsReg().init(0)
  }
  noIoPrefix()
  val sobel=new Area{
    val windowbuf=bufWindow(cfg)
    val sobelConv=Reg(Flow(Vec(SInt(),2)))
    val sobelResult=Reg(Flow(UInt(cfg.dataWidth bits)))
    sobelConv.valid.init(False)
    sobelResult.valid.init(False)
    io.dataIn<>windowbuf.io.dataIn
    val Gx=(windowbuf.io.dataOut.payload(0)(2).expand.asSInt-^windowbuf.io.dataOut.payload(0)(0).expand.asSInt)+|
      ((windowbuf.io.dataOut.payload(1)(2).expand.asSInt-^windowbuf.io.dataOut.payload(1)(0).expand.asSInt)<<1)+|
      (windowbuf.io.dataOut.payload(2)(2).expand.asSInt-^windowbuf.io.dataOut.payload(2)(0).expand.asSInt)
    val Gy=(windowbuf.io.dataOut.payload(0)(0).expand.asSInt-^windowbuf.io.dataOut.payload(2)(0).expand.asSInt)+|
           ((windowbuf.io.dataOut.payload(0)(1).expand.asSInt-^windowbuf.io.dataOut.payload(2)(1).expand.asSInt)<<1)+|
           (windowbuf.io.dataOut.payload(0)(2).expand.asSInt-^windowbuf.io.dataOut.payload(2)(2).expand.asSInt)
    sobelConv.valid:=windowbuf.io.dataOut.valid
    sobelConv.payload(0):=Gx
    sobelConv.payload(1):=Gy
    sobelResult.valid:=sobelConv.valid
    sobelResult.payload:= (sobelConv.payload(0).abs+| sobelConv.payload(1).abs).fixTo(cfg.dataWidth-1 downto 0,RoundType.ROUNDUP)
    io.dataOut.valid:=sobelResult.valid
    when(sobelResult.payload>io.thresholdValue){
      io.dataOut.payload:=(default->true)
    }otherwise{
      io.dataOut.payload:=(default->false)
    }
  }
}

区区不到四十行代码,简洁而优雅,基本上就是描述算法,出错概率应该很小吧!

仿真

做图像处理的小伙伴想想在做仿真验证时需要怎么搞,matlab生成灰度图像二进制数据放在文件里,然后仿真时再导入,仿真完成后将结果保存到文件里,最后再在matlab里做对比。

太麻烦。SpinalHDL提供了仿真支持,而SpinalHDL是基于Scala的,可以完美实现整个仿真验证流程:从图片直接获取数据,然后进行仿真验证,仿真结果直接再次生成图片。这里仿真在SpinalHDL里进行完成,从网上拉取一张图片:

e9477e27ac29a1755001268598cefe23.jpg

仿真里调用javax.imageio.ImageIO等库读取图像,转换成灰度图:

fe1ac3bc65fd1f37f80a97374e90651e.jpg

随后将数据灌入到测试单元里,得到的数据流直接生成图片:

b1921588e949757f77840cf75d692fff.jpg

 title=

END

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

推荐阅读

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