FPGA图像处理-Sobel边缘检测原理

时间:2025-11-01  作者:Diven  阅读:0

因为在做3*3卷积的时候,图像大小会变小,具体计算公式如下

FPGA图像处理-Sobel边缘检测原理

其中O是输出特征图的大小,I是输入特征图的大小,P是Padding的大小,K是卷积核的大小,S是指Stride的大小,当K的值是3,P的值是1,S的值也是1,的时候O的值和I的值相等。

为了保持输出图像的大小在经过卷积后和输入的大小一样,我们需要进行Padding操作,在这里我采用了复制周围一圈的方式来完成。

采用python完成Sobel算法的参考模型

 

import cv2 as cvimport numpy as npimg = cv.imread(r"G:shiyanIDc.jpg")img_gray = cv.cvtColor(img, cv.COLOR_RGB2GRAY)h, w = img_gray.shapeimg_padding = np.zeros((h + 2, w + 2), np.uint8)img_padding[1:h + 1, 1:w + 1] = img_grayimg_padding[0:1, 1:w + 1] = img_gray[0:1, :]img_padding[h + 1:h + 2, 1:w + 1] = img_gray[h - 1:h, :]img_padding[:, 0:1] = img_padding[:, 1:2]img_padding[:, w + 1:w + 2] = img_padding[:, w:w + 1]th = 200sobel_rf = np.zeros((h, w), np.uint8)for i in range(1, h): for j in range(1, w): gx1 = img_padding[i - 1][j + 1] + 2 * img_padding[i][j + 1] + img_padding[i + 1][j + 1] gx2 = img_padding[i - 1][j - 1] + 2 * img_padding[i][j - 1] + img_padding[i + 1][j - 1] gy1 = img_padding[i - 1][j - 1] + 2 * img_padding[i - 1][j] + img_padding[i - 1][j + 1] gy2 = img_padding[i + 1][j - 1] + 2 * img_padding[i + 1][j] + img_padding[i + 1][j + 1] gx = abs(gx1 - gx2) gy = abs(gy1 - gy2) if gx + gy > th: sobel_rf[i - 1][j - 1] = 255 else: sobel_rf[i - 1][j - 1] = 0cv.imshow("sobel_rf", sobel_rf)cv.imshow("src", img_gray)cv.waitKey()cv.destroyAllWindows()

 

根据算法模型完成HDL:提供SpinalHDL源码

 

import spinal.core._import spinal.lib._class Sobel(th: Int, imageColNum: Int, imageRowNum: Int) extends Component { val io = new Bundle { val dataIn = slave(ImageStream(8, imageColNum, imageRowNum, 1)) val dataOut = master(ImageStream(8, imageColNum, imageRowNum, 1)) } noIoPrefix() val genMatrix = new GenMatrix(scala.math.pow(2, log2Up(imageColNum)).toInt, imageColNum, imageRowNum) genMatrix.io.dataIn <> io.dataIn val genMatrixOut = ImageStream(8, imageColNum, imageRowNum, 9) genMatrixOut := genMatrix.io.dataOut val GX1 = RegNext(genMatrixOut.data(0).asUInt +^ (genMatrixOut.data(1) ## B"1'b0").asUInt +^ genMatrixOut.data(2).asUInt) val GX2 = RegNext(genMatrixOut.data(6).asUInt +^ (genMatrixOut.data(7) ## B"1'b0").asUInt +^ genMatrixOut.data(8).asUInt) val GX = Reg(UInt(11 bits)) val GY1 = RegNext(genMatrixOut.data(6).asUInt +^ (genMatrixOut.data(3) ## B"1'b0").asUInt +^ genMatrixOut.data(0).asUInt) val GY2 = RegNext(genMatrixOut.data(8).asUInt +^ (genMatrixOut.data(5) ## B"1'b0").asUInt +^ genMatrixOut.data(2).asUInt) val GY = Reg(UInt(11 bits)) when(GX1 > GX2) { GX := GX1 - GX2 } otherwise { GX := GX2 - GX1 } when(GY1 > GY2) { GY := GY1 - GY2 } otherwise { GY := GY2 - GY1 } val G = RegNext(GX + GY) val sobelOut = Reg(Bits(8 bits)) when(G > th) { sobelOut := 255 } otherwise { sobelOut := 0 } io.dataOut.data(0) := sobelOut io.dataOut.row := Delay(genMatrixOut.row, 4) io.dataOut.col := Delay(genMatrixOut.col, 4) io.dataOut.c.hsync := Delay(genMatrixOut.c.hsync, 4,init = False) io.dataOut.c.vsync := Delay(genMatrixOut.c.vsync, 4,init = False) io.dataOut.c.de := Delay(genMatrixOut.c.de, 4,init = False)}object Sobel extends App { SpinalConfig().generateVerilog(new Sobel(200, 640, 480))}

 

仿真代码:

 

import spinal.lib._import spinal.core._import spinal.core.sim._import scala.collection.mutable.Queueimport java.io.FileOutputStreamimport scala.io.Sourceclass tbSobelC(th: Int) extends Sobel(th, 430, 430) { var src = Array[String]() var destDut = Array[String]() var destRef = Array[String]() // var srcLen = 0 var width = Array[Int]() var high = Array[Int]() val dutData = Queue[Int]() val refData = Queue[Int]() var frameLen = 0 def init(srcFile: Array[String], destDutFile: Array[String], destRefFile: Array[String], imgShape: Array[(Int, Int)]) = { clockDomain.forkStimulus(10) io.dataIn.data(0) #= 0 io.dataIn.row #= 0 io.dataIn.col #= 0 src = srcFile destDut = destDutFile destRef = destRefFile io.dataIn.c.de #= false io.dataIn.c.vsync #= false io.dataIn.c.hsync #= false frameLen = src.length width = imgShape.map(i => i._1) high = imgShape.map(i => i._2) clockDomain.waitSampling(10) } def frame(src: String, width: Int, high: Int) = { val srcFile = Source.fromFile(src) val srcData = srcFile.getLines() var colCnt = 0 var rowCnt = 0 io.dataIn.row #= width io.dataIn.col #= high io.dataIn.c.de #= false io.dataIn.c.vsync #= false io.dataIn.c.hsync #= false clockDomain.waitSampling(20) while (srcData.hasNext) { val data = srcData.next() io.dataIn.data(0) #= data.toInt io.dataIn.c.de #= true if (colCnt == 0 && rowCnt == 0) { io.dataIn.c.vsync #= true println("xx") } else { io.dataIn.c.vsync #= false } if (colCnt == width - 1 && rowCnt == high - 1) { clockDomain.waitSampling(1) io.dataIn.c.de #= false clockDomain.waitSampling(200) } if (colCnt == 0) { io.dataIn.c.hsync #= true } else { io.dataIn.c.hsync #= false } if (colCnt == width - 1 && rowCnt != high - 1) { clockDomain.waitSampling(1) io.dataIn.c.de #= false clockDomain.waitSampling(20) } if (colCnt == width - 1) { colCnt = 0 if (rowCnt == high - 1) { rowCnt = 0 } else { rowCnt = rowCnt + 1 } } else { colCnt = colCnt + 1 } clockDomain.waitSampling() } clockDomain.waitSampling(1000) srcFile.close() } def driver = { val dri = fork { for (i <- 0 until frameLen) { println(s"frame = ${i}") frame(src(i), width(i), high(i)) } } } def dutOut = { val dutOutFile = new FileOutputStream(destDut(0)) val d = fork { while (true) { if (io.dataOut.c.de.toBoolean) { dutData.enqueue(io.dataOut.data(0).toInt) dutOutFile.write((io.dataOut.data(0).toInt.toString + "").getBytes()) } clockDomain.waitSampling() } } } def refFun = { val d = fork { while (true) { for (i <- 0 until frameLen) { val file = Source.fromFile(destRef(i)) val srcData = file.getLines() while (srcData.hasNext) { clockDomain.waitSampling() val data = srcData.next().toInt refData.enqueue(data) } } } } } def scoreBoard = { val d = fork { var index = 0 while (true) { while (dutData.nonEmpty && refData.nonEmpty) { clockDomain.waitSampling() val dut = dutData.dequeue() val ref = refData.dequeue() // if(dut != ref){ // println(s"i:${index} dutData:${dut} refData:${ref}") // } index = index + 1 assert(scala.math.ABS(ref - dut) < 5, s"index:${index}, dutData:${dut} refData:${ref}") // if (scala.math.ABS(ref - dut) != 0) { // println(s"ref = ${ref} , dut = ${dut}") // } } clockDomain.waitSampling() } } } def waitSimDone = { val d = fork { var index = 0 while (index < width(0) * high(0)) {        clockDomain.waitSampling()        if (io.dataOut.c.de.toBoolean) {          index = index + 1        }      }      clockDomain.waitSampling(3000)      simSuccess()    }.join()  }}class tbSobel {  val testFile = Array("testGray.txt")  val dutFile = Array("testDut.txt")  val refFile = Array("testSobel.txt")  val imgShape = Array((430, 430))    val dut = SimConfig.withConfig(SpinalConfig(inlineRom = true)).withWave.compile(new tbSobelC(100))  dut.doSim { dut => dut.init(testFile, dutFile, refFile, imgShape) dut.driver dut.refFun dut.dutOut dut.scoreBoard dut.waitSimDone }}object tbSobel extends App { val tb = new tbSobel}

 

经过分析之后,该代码可以跑到238MHz,占用330LUT,312FF。


审核编辑:刘清

猜您喜欢

8月3日,赛微电子发布最新调研纪要称,半导体厂的设计和建设具有严格的规范要求,包括防水,防闪电,防火,防爆,防静电等措施。北京mems工厂的生产经营没有受到近期...
2023-08-03 10:57:00

油嘴是机械设备中不可少的重要部件,主要用于润滑系统的油脂注入。主要作用是确保设备在运转过程中,能够及时、均匀地供油,从而减少摩擦、降低磨损,延长设备的使用寿命。...
2012-10-29 00:00:00

排阻作为重要的电阻元件,应用于各种电子设备中。万裕(SAMXON)作为知名的电子元器件品牌,其排阻产品因品质可靠、性能优越而受到市场青睐。那么,万裕(SAMXO...
2017-12-16 12:52:16

加热器是我们日常生活中常见的电器,主要用于提供温暖和舒适的居住环境。根据不同的使用需求和工作原理,加热器可以分为多种类型。按加热方式分类,可以分为电热水器和电暖...
2025-04-08 00:00:00

预售芯片的应用领域,涵盖了多个行业。在消费电子领域,预售芯片被应用于智能手机、平板电脑和智能家居设备中,提升了设备的性能和用户体验。在汽车行业,随着智能驾驶和电...
2012-01-05 00:00:00

防浪涌电阻作为保护电路免受瞬态高电压冲击的重要元件,是不可少的配件。随着电子产品对安全性和稳定性的要求不断提升,TDK作为全球领先的电子元器件制造商,其防浪涌电...
2019-02-16 20:06:23

电位器作为重要的调节元件,应用于音响设备、仪器仪表、家用电器等多个领域。冠佐(SUSCON)作为国内知名的电位器品牌,稳定的性能和多样化的产品线赢得了众多客户的...
2017-01-22 07:18:48

电子电路设计中,分流电路和合流电路是两种常见且基础的电路形式。在电流的分配和合并过程中有着着重要作用,应用于各种电子设备和系统中。本文将对分流电路与合流电路进行...
2025-11-01 00:00:03

脚手架是临时结构,主要用于建筑施工和维护,以便工人能够安全地在高处作业。通常由金属管、木材或其材料构成,通过合理的设计和搭建,形成一个稳定的平台,支持工人及其工...
2023-10-04 00:00:00

您是否想过,是什么让我们的手机、笔记本电脑和其电子设备充电更快,使用时间更长?答案就藏在这些设备内部,一个叫做同步整流IC的小巧元件,正在默默地提升着电子设备的...
2024-05-29 00:00:00