FPGA图像处理——老戏新说

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

    Sobel卷积因子为:FPGA图像处理——老戏新说_第1张图片

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

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

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

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

    归结起来,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里进行完成,从网上拉取一张图片:

 FPGA图像处理——老戏新说_第2张图片

仿真里调用javax.imageio.ImageIO等库读取图像,转换成灰度图:FPGA图像处理——老戏新说_第3张图片 

 随后将数据灌入到测试单元里,得到的数据流直接生成图片:FPGA图像处理——老戏新说_第4张图片 

 

你可能感兴趣的:(fpga开发,图像处理,人工智能)