SpinalHDL里如何实现Sobel边缘检测

描述

 

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

Sobel边缘检测

 

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

    Sobel卷积因子为:

Verilog

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

Verilog

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

Verilog

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

Verilog

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

    归结起来,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的,可以完美实现整个仿真验证流程:从图片直接获取数据,然后进行仿真验证,仿真结果直接再次生成图片。  

      审核编辑:彭静
打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分