图像处理fpga实现

2018-06-07 20:07:55 woshifennu1234 阅读数 1976


用FPGA做图像处理最关键的一点优势就是:FPGA能进行实时流水线运算,能达到最高的实时性。因此在一些对实时性要求非常高的应用领域,做图像处理基本就只能用FPGA。例如在一些分选设备中图像处理基本上用的都是FPGA,因为在其中相机从看到物料图像到给出执行指令之间的延时大概只有几毫秒,这就要求图像处理必须很快且延时固定,只有FPGA进行的实时流水线运算才能满足这一要求。

所以要了解FPGA进行图像处理的优势就必须理解FPGA所能进行的实时流水线运算和DSP,GPU等进行的图像处理运算有何不同。DSP,GPU,CPU对图像的处理基本是以帧为单位的,从相机采集的图像数据会先存在内存中,然后GPU会读取内存中的图像数据进行处理。假如采集图像的帧率是30帧,那么DSP,GPU要是能在1/30秒内完成一帧图像的处理,那基本上就能算是实时处理。

FPGA对图像进行实时流水线运算是以行为单位的。FPGA可以直接和图像传感器芯片连接获得图像数据流,如果是RAW格式的则还可以进行差值以获得RGB图像数据。FPGA能进行实时流水线处理的关键是它可以用其内部的Block Ram缓存若干行的图像数据。这个Block Ram可以说是类似于CPU里面的Cache,但Cache不是你能完全控制的,但Block Ram是完全可控的,可以用它实现各种灵活的运算处理。这样FPGA通过缓存若干行图像数据就可以对图像进行实时处理,数据就这样一边流过就一边处理好了,不需要送入DDR缓存了之后再读出来处理。

这样的数据流处理显然是顺序读取数据的,那么也就只能实现那些顺序读取数据的算法,也就是图像处理中那一大类用3x3到NxN的算子进行的滤波、取边缘、膨胀腐蚀等算法。可能大家会觉得这些运算似乎都是最基本的图像处理运算,只是个前端的预处理,似乎用处不大。但问题是只有FPGA做这样的运算才是速度最快效率最高的,比如用CPU做一个取边缘的算法根本就达不到实时。另外别小看了这种NxN算子法,它可以有各种组合和玩法,可以实现分选多种颜色,甚至分辨简单形状等功能。FPGA进行的这种算子法处理是并行流水线算法,其延时是固定的,比如用3x3的算子进行处理其给出结果的延时是两行图像的时间。还有这个算子法和现在卷积神经网络中最前面的卷积层运算是类似的。


FPGA中的Block Ram是重要和稀缺资源,能缓存的图像数据行数是有限的,所以这个NxN的算子中的N不能特别大。当然FPGA也可以接DDR把图像缓存到其中再读出来进行处理,但这种处理模式就和CPU差不多了,达不到最高的实时性。其实有些我们认为需要随机读取数据的图像处理算法也是可以并行流水线化的。

在密集运算中,耽误时间和消耗功耗的操作往往不是运算本身,而是把数据从内存中搬来搬去。GPU,CPU在进行运算时要把数据从内存中取出来,算好了在放回去。这样内存带宽往往成了运算速度的瓶颈,数据搬运过程中的功耗占的比重也不会小。FPGA则可以通过堆很多计算硬件的方法把要做的运算都展开,然后数据从中流过,完成一个阶段的运算之后就直接流入第二个阶段,不需要把一个计算阶段完成后的数据再送回内存中,再读出来交给下一个阶段的运算。这样就会节省很多时间和功耗。现在用FPGA做图像处理就是这样干的,比如先用一个3x3的算子进行滤波,再用一个3x3的算子进行取边缘,在FPGA流水线算法中,滤波处理完了数据立即就会进行取边缘处理,是不需要像CPU那样存回内存再读出来的。


FPGA进行图像处理的前景还是挺广阔的,越来越多的工业应用场合都要求更高的实时性,而这正是FPGA所适合的。还有机器学习领域,神经网络这种层状的,不需要很随机的读取数据的运算是比较适合用FPGA来做的。不过FPGA不擅长浮点运算,如果能整出不需要浮点运算的神经网络,那么FPGA在这方面的应用将会更大。

前些年用matlab和opencv做图像处理。近一段时间通过FPGA处理摄像头视频数据,有太多感触,复杂的算法先不提及,单是上面文章中提到的一些处理手段及策略,非常受用。

感谢来自知乎网的图像处理讨论。非常受益!


版权所有权归卿萃科技 杭州FPGA事业部,转载请注明出处

作者:杭州卿萃科技ALIFPGA

原文地址:杭州卿萃科技FPGA极客空间 微信公众号


扫描二维码关注杭州卿萃科技FPGA极客空间


 



版权所有权归卿萃科技 杭州FPGA事业部,转载请注明出处

作者:杭州卿萃科技ALIFPGA

原文地址:杭州卿萃科技FPGA极客空间 微信公众号


扫描二维码关注杭州卿萃科技FPGA极客空间


 

2018-05-27 22:38:12 FPGAerClub 阅读数 4382

更多精彩内容,请微信搜索“FPGAer俱乐部”关注我们

基于软件的图像处理方法存在着一些局限性,尤其是计算速度和算法效率方面。所以大家很自然的就想到了FPGA作为嵌入式图像应用的平台。许多图像处理本身就是并行计算的,并且FPGA的编程硬件,本质上也是并行的。但是利用FPGA硬件进行图像处理存在很多的困难,需要学到很多的技巧。下面我介绍两几种比较基础的图像处理算法思想。

 

单幅图像的点操作是图像处理中最简单的操作,输出值只取决于输入值,与位置无关,可以看作是一个函数的映射。从硬件实现的角度来说,最简单的方式就是通过一个实现函数的模块对输入的每个像素进行依次处理,也就是流水化处理。每个像素都是单独处理的,可以把图像分为若干部分,每个部分单独处理,所以点操作容易并行实现。点操作可作为读取图像和后续处理之间的一个桥梁。A:亮度调节;为了使图像变亮,可以增大输出像素值,可以通过加一个常量实现。类似地,变暗减小像素值。但是实际中,调节亮度要复杂的多,因为人的视觉系统是非线性的。B:对比度调节;图像的对比度受映射函数的斜率影响。斜率大于1增强,小于1则降低,可以通过乘以一个大于或者小于1的常数实现。C:同时调节亮度和对比度;一个简单的调节它们的点操作是:Q=aI+b=a(I+b’),a,b是控制亮度和对比度的任意常数。当Q超出范围怎么办?例如用8位表示像素值时,Q超出0~255,那么输出怎么办?默认情况下只取8位最低有效位并且忽略任何会导致值超出范围的溢出位。通常还需要进行饱和或者裁剪到极值效果会更好。


直方图操作。使用直方图的图像处理有两个相关的主要步骤。第一步是建立直方图,第二步是从直方图中提取数据并用它来处理图像。A建立直方图:对每个像素值累计计数。通过计数器数组完成计算每个像素值出现的次数。这个方法的缺点是占用的硬件资源比较多,适合阈值后的直方图计算。可以使用双口存储器实现,可以大大减少逻辑资源的使用。B直方图均衡化(使用局部信息来分配那些在输出像素值的范围上具有大的计数值的输入值的像素来获得更平坦的直方图):实现直方图均衡化的映射是归一化累积直方图。直观地,如果输入点集合的计数值大于平均值,那么映射的斜率大于1,反之,小于1。

 

局部滤波器。局部滤波器扩展点操作,以一个局部窗口内像素值的函数运算结果作为输出。窗口的大小、形状可以随意,但是一般都是采用奇数正方形的,我见过最多的就是3x3,5x5,7x7,这样的话中心就很容易确定。局部滤波器有去噪、边缘检测、边缘增强等。线性去噪有排序去噪,均值去噪,加权均值去噪等,边缘检测可以利用Prewitt,Sobel算子等,将这些算法在3x3窗口中实现,相对来说就比较容易了。也可以改进这些算法,是写小论文比较好的创新点。还有一些形态学滤波器,颜色滤波器,大致思想都一样,就是实现的时候算法改一下。

 


本文载自http://www.eefocus.com/fpga/410120/r0,如涉及侵权,请私信小编删除。

============华 丽 的 分 割 线============


想加入我们FPGA学习交流群吗?可以长按或扫描以下二维码,审核通过后我们邀请您加入

这些微信群旨在打造一个提供给FPGA工程开发人员及兴趣爱好者(统称“FPGAer”)进行技术交流、答疑解惑和学习的平台。而且我们也将会通过网络举办FPGA技术讲座,分享相关研究文献 



了解更多FPGA知识可以长按或扫描以下二维码关注FPGAer俱乐部




2017-09-17 08:44:55 woshifennu1234 阅读数 4227

数字图像处理方法的重要性源于两个主要应用领域:

    • 改善图像信息以便解释。

    • 为存储、传输和表示而对图像数据进行处理,以便于机器自动理解。

图像处理(image processing):

用计算机对图像进行分析,以达到所需结果的技术。又称影像处理。一般指数字图像处理。数字图像是指用工业相机、摄像机、扫描仪等设备经过拍摄得到的一个大的二维数组,该数组的元素称为像素,其值称为灰度值。图像处理技术一般包括图像压缩,增强和复原,匹配、描述和识别3个部分。

常用方法:

    • 图像变换:由于图像阵列很大,直接在空间域中进行处理,涉及计算量很大。因此,往往采用各种图像变换的方法,如傅立叶变换、沃尔什变换、离散余弦变换等间接处理技术,将空间域的处理转换为变换域处理,不仅可减少计算量,而且可获得更有效的处理(如傅立叶变换可在频域中进行数字滤波处理)。目前新兴研究的小波变换在时域和频域中都具有良好的局部化特性,它在图像处理中也有着广泛而有效的应用。

    • 图像编码压缩:图像编码压缩技术可减少描述图像的数据量(即比特数),以便节省图像传输、处理时间和减少所占用的存储器容量。压缩可以在不失真的前提下获得,也可以在允许的失真条件下进行。编码是压缩技术中最重要的方法,它在图像处理技术中是发展最早且比较成熟的技术。

    • 图像增强和复原:图像增强和复原的目的是为了提高图像的质量,如去除噪声,提高图像的清晰度等。图像增强不考虑图像降质的原因,突出图像中所感兴趣的部分。如强化图像高频分量,可使图像中物体轮廓清晰,细节明显;如强化低频分量可减少图像中噪声影响。图像复原要求对图像降质的原因有一定的了解,一般讲应根据降质过程建立“降质模型”,再采用某种滤波方法,恢复或重建原来的图像。

    • 图像分割:图像分割是数字图像处理中的关键技术之一。图像分割是将图像中有意义的特征部分提取出来,其有意义的特征有图像中的边缘、区域等,这是进一步进行图像识别、分析和理解的基础。虽然目前已研究出不少边缘提取、区域分割的方法,但还没有一种普遍适用于各种图像的有效方法。因此,对图像分割的研究还在不断深入之中,是目前图像处理中研究的热点之一。

    • 图像描述:图像描述是图像识别和理解的必要前提。作为最简单的二值图像可采用其几何特性描述物体的特性,一般图像的描述方法采用二维形状描述,它有边界描述和区域描述两类方法。对于特殊的纹理图像可采用二维纹理特征描述。随着图像处理研究的深入发展,已经开始进行三维物体描述的研究,提出了体积描述、表面描述、广义圆柱体描述等方法。

    • 图像分类(识别):图像分类(识别)属于模式识别的范畴,其主要内容是图像经过某些预处理(增强、复原、压缩)后,进行图像分割和特征提取,从而进行判决分类。图像分类常采用经典的模式识别方法,有统计模式分类和句法(结构)模式分类,近年来新发展起来的模糊模式识别和人工神经网络模式分类在图像识别中也越来越受到重视。

图像分类:

在计算机中,按照颜色和灰度的多少可以将图像分为二值图像、灰度图像、索引图像和真彩色RGB图像四种基本类型。

二值图像:

一幅二值图像的二维矩阵仅由0、1两个值构成,“0”代表黑色,“1”代白色。由于每一像素(矩阵中每一元素)取值仅有0、1两种可能,所以计算机中二值图像的数据类型通常为1个二进制位。二值图像通常用于文字、线条图的扫描识别(OCR)和掩膜图像的存储。


二值图像

灰度图像:

灰度图像矩阵元素的取值范围通常为[0,255]。因此其数据类型一般为8位无符号整数的,这就是人们经常提到的256灰度图像。“0”表示纯黑色,“255”表示纯白色,中间的数字从小到大表示由黑到白的过渡色。二值图像可以看成是灰度图像的一个特例。


灰度图像

索引图像:

索引图像的文件结构比较复杂,除了存放图像的二维矩阵外,还包括一个称之为颜色索引矩阵MAP的二维数组。是一种把像素值直接作为RGB调色板下标的图像。索引图像可把像素值“直接映射”为调色板数值。索引图像一般用于存放色彩要求比较简单的图像,如Windows中色彩构成比较简单的壁纸多采用索引图像存放,如果图像的色彩比较复杂,就要用到RGB真彩色图像。索引图像细节不在赘述。

索引图像

 RGB彩色图像:

RGB图像与索引图像一样都可以用来表示彩色图像。与索引图像一样,它分别用红(R)、绿(G)、蓝(B)三原色的组合来表示每个像素的颜色。但与索引图像不同的是,RGB图像每一个像素的颜色值(由RGB三原色表示)直接存放在图像矩阵中,由于每一像素的颜色需由R、G、B三个分量来表示,M、N分别表示图像的行列数,三个M x N的二维矩阵分别表示各个像素的R、G、B三个颜色分量。 

彩色图像

数字图像空间域处理:

空间域处理分为灰度变换和空间滤波。

灰度变换:

灰度变换是指根据某种目标条件按一定变换关系逐点改变源图像中每一个像素灰度值的方法。目的是为了改善画质,使图像的显示效果更加清晰。 图像的灰度变换处理是图像增强处理技术中的一种非常基础、直接的空间域图像处理方法。

变换函数法:

    • 图像反转

    • 对数变换

    • 幂律(伽马)变换

    • 分段线性变换 

灰度变换函数曲线图

直方图处理法:

    • 直方图均衡

    • 直方图匹配

    • 局部直方图处理

    • 直方图统计


      matlab直方图均衡处理

      空间滤波:

      空间滤波,就是直接在灰度值上,做一些滤波操作。滤波一词,其实来源于频域,将某个频率成分滤除的意思。大部分线性的空间滤波器(比如均值滤波器),是在空间上进行一些灰度值上的操作,这个线性空间滤波器与频域滤波器有一一对应的关系(比如均值滤波器其本质就是低通滤波器)。

      通常分为低通滤波(平滑化)、高通滤波(锐化)。

      平滑滤波器:

      在空间域上考虑,所指的平滑滤波器,有平均滤波与加权平均滤波两种形式。


                                                                                          原始图像                                                                                         平均滤波


加权平均滤波

统计排序滤波器:

统计排序滤波器的运用也广泛,其是很典型的非线性滤波器。主要包括了,最大值滤波器,最小值滤波器,中央值滤波器等等。这里作为代表的,主要说中央值滤波器,中央值滤波对于去除椒盐噪声特别有效。

所谓中央值滤波器,就是将滤波器范围内的像素的灰度值,进行排序,选出中央值作为这个像素的灰度值。同理可解释最大值滤波器与最小值滤波器。


原始图像


椒盐噪声


中值滤波器

锐化滤波器:

所谓的锐化,即是将图像的细节强调出来。主要算子----拉普拉斯算子。

图像锐化

图像频率域滤波:

所谓的图像频率,就是这个图空间上的灰度变换的快慢。

为什么要在频率域中进行图像处理?

可以利用频率成分和图像外表之间的对应关系。一 些在空间域表述困难的增强任务,在频率域中变得非常普通 。

滤波在频率域更为直观,它可以解释空间域滤波的某些性质 。

可以在频率域指定滤波器,做反变换,然后在空间域使用结果滤波器作为空间域滤波器的指导。

主要变换为傅里叶变换。傅里叶变换是将时域信号分解为不同频率的正弦信号或余弦函数叠加之和。


时域与频域

频域滤波器分为:

    • 理想低通滤波器

    • 巴特沃斯低通滤波器

    • 高斯低通滤波器

    • 理想高通滤波器

    • 巴特沃斯高通滤波器

    • 高斯高通滤波器

    • 带阻滤波器

    • 带通滤波器

    • 陷波滤波器

其他图像处理相关:

图像处理与重建

彩色图像处理

小波和多分辨率处理

图像压缩

    霍夫曼编码

    Golomb编码

    算术编码

    块变换编码

    小波编码

    余弦变换

形态学图像处理

    腐蚀、膨胀

    边界提取

    空洞填充

    连通分量的提取

    细化与粗化

图像分割

    边缘检测

    阈值处理

    区域分割

目标识别 

FPGA技术:

一般地,图像处理大致可以分为低级处理和高级处理:低级处理的数据量大,算法简单,存在着较大的并行性;高级处理的算法复杂,数据量小。在图像处理的实现手段上,图像低级处理阶段,利用软件来实现是一个很耗时的过程,但是利用硬件实现,就可以对大量数据进行并行处理,能够极大的提高处理速度;而图像高级处理阶段,利用软件来实现则具有较高的性价比。因此,图像处理系统中可以利用高速硬件模块(如FPGA)承担图像低级处理任务。这样对大量图像数据进行了低级处理,使系统在减少数据传输量的同时还极大的提高了实时性能。

通用计算机:

通用计算机是基于冯.诺依曼结构的,通过高级语言(C,C++等)编写程序代码,整个执行过程是单指令单数据的串行处理过程,在很多情况下该系统结构上的局限性使它对低级图像的处理不能够满足高速处理的要求,它适合各种图像处理算法的验证,适用于一些实时性要求不高的场合。

并行处理机:

在许多场合下,单个CPU不能够实现实时数据处理的时候,采用多个CPU同时工作的并行处理为解决此问题提供了可能。各国学者在这方面作了大量的工作,并己经研究出多种并行结构和编程语言,它克服了单个处理器串行工作的局限性,提高了系统的性能。虽然对并行处理进行了大量的研究,但这个领域仍不成熟。处理单元负载不均衡,并行算法编程困难,理论上并行处理所应达到的性能和实际性能相比有较大差距。

专用集成电路:

专用集成电路是针对于某一固定算法或应用而专门设计的硬件芯片。许多图像处理算法采用通用处理器和DSP来实现难以满足速度需要,而必须采用ASIC来实现,在各种算法实现方案中使用ASIC来实现是最快的。但是,ASIC在实际应用中也有其缺点:ASIC从设计到应用需要较长的时间周期;ASIC因为属于专用硬件芯片,所以需求数量较少,成本也就非常高;由于ASIC是为专用目的设计的,当设计成型并且流片成功就不能改动,所以在设计中当算法因故需要改变时就要设计者重新设计芯片和硬件电路;当ASIC里存在硬件设计的错误时,并且在投入生产前未能发现的话,唯一解决的办法是把产品回收,而这样做的后果往往是芯片商付出沉重的经济代价。ASIC较低的灵活度往往使其局限于非常有限的应用中并容易产生性能瓶颈,因此由ASIC构建的图像处理系统,缺乏灵活性。

数字信号处理器:

数字信号处理器(DSP),是专门为快速实现各种数字信号处理算法而设计的、具有特殊结构的微处理器,通常使用C语言进行编程,其处理速度可达到2000MIPS,比最快的CPU还快10-50倍。数字信号处理器的内部采用专用硬件实现一些数字信号处理常用的运算,所以它进行这些运算速度非常快,如乘加(MAC)运算只需要一个时钟周期。但是从根本上来说,DSP只是对某些固定的运算提供硬件优化,其体系仍是串行指令执行系统,并且这些固定优化运算并不能够满足众多算法的需要,这使得它的使用受到限制。

现场可编程门阵列(FPGA):

FPGA器件是当今运用极为广泛的可编程逻辑器件,也被称为可编程ASIC。FPGA器件在结构上具有逻辑功能块排列,可编程的内部连线连接这些功能模块来实现一定的逻辑功能。FPGA器件的功能由逻辑结构的配置数据决定。工作时,这些配置数据存放在片内的SILAM中。使用SRAM的FPGA器件,在工作前需要从芯片外部加载配置数据,配置数据可以存储在片外的EPROM或其他存储体上,设计者可以控制加载过程,在现场修改器件的逻辑功能,即所谓现场编程。利用它用户不仅可以方便地设计出所需的硬件逻辑,而且可以进行静态重复编程和动态在系统重配置,使系统的硬件功能可以像软件一样编程来修改,从而可以实时地进行灵活而方便的更新和开发,大大提高了系统设计的灵活性和通用性。与此同时,FPGA自身也在迅速发展,其集成度、工作速度不断提高,包含的资源越来越丰富,可实现的功能也越来越强。

FPGA与图像处理
FPGA能在设计上实现硬件并行和流水线(pipeline)技术,而这些都不能在DSP上实现。因此,对于实时图像处理而言,与本质上仍然是依靠串行执行指令来完成相应图像处理算法的DSP系统相比,FPGA有很强的灵活性,可以根据需要进行重构配置,有较强的通用性,适于模块化设计;同时其开发周期短,系统易于维护和扩展,适合实时的信号处理,能够大大提高图像数据的处理速度,满足系统的实时性要求,因此采用FPGA器件是个不错的选择。

文章来源:卿萃科技FPGA极客空间 微信公众号

版权所有权归卿萃科技,转载请注明出处。

作者:卿萃科技ALIFPGA



2017-09-02 22:35:31 qq_39210023 阅读数 23817

FPGA图像处理之路,从此开始,接下来,让我们把时间交给“图像处理”。一休哥在动笔之前,一直在犹豫,反复思考着一个问题,这个问题一直困扰着我,“FPGA在图像处理领域中的地位?”
按照惯例,我们先暂且不直说这个问题的答案,让我们先谈谈图像处理技术。在一休哥我个人看来,图像处理就是对图像进行操作以得到自己想要的结果。图像处理,它是一个非常广义的概念,它包含图像增强,图像复原,图像重建,图像分析,模式识别,计算机视觉等N多个应用方向。这些应用技术有许多在本质上是相通的,但是不同应用领域的关注点往往是不同的。
当我们升入研究生阶段,或者进入公司,如果从事图像处理相关的工作,也就有了自己涉猎的领域了。在此,一休哥主动交代一下,一休哥现阶段主要关注的领域为图像重建。
好了,闲话不多说,我们来说说大实话。在我之见下的图像处理,作为一个“半职业”的FPGAer,用FPGA做图像处理相关的开发时,我们往往首先要考虑的就是FPGA处理板的性能了,因为做图像处理是一个十分消耗资源的事情。打开淘宝,我们可以搜索到很多图像处理FPGA开发板,有些开发板上的资源十分丰富,可以满足我们前期试验的需求。可是令人感到尴尬的是,用FPGA做图像处理也是一件十分困难的事。一休哥从网络上的开源情况来看,FPGA在图像处理方面的主要应用一直处于图像的预处理阶段。
什么叫图像的预处理?例如图像的畸变校正,滤波器处理,边缘检测、颜色检测和阈值处理等。这些预处理都有一些共同的特征,算法较为简单,操作重复性强等。但是,除了预处理,FPGA就不能做点别的吗?有的哦,图像处理类似一个三层金字塔,分为底层,中间层,高层。
这里写图片描述
图像处理金字塔有三层,分别针对的是像素级、特征级和目标级。一个成熟的图像处理应用应该同时涵盖这三层。
在像素层,我们可以对图像做一些变换,目的是增强图像的有用信息,同时抑制任何不相关的信息(如噪声)。然后通过对预处理后的图像做分割操作实现图像从像素级到特征级的过度,分割操作可以理解为检测图像中具有一些共同性质的区域。针对这些区域,依据一个或多个分类法则,将区域归类到一些预先设定的特征类型中作为后期识别的数据集。此时的数据已经不仅仅是图像了,其中包含了丰富的特征信息,如物体的位置信息等。在金字塔高层,依靠获取的特征,如有必要还可以将这些特征集作为学习的训练集来创建专用的模型,借助模型来实现识别,进而用来对实时采集的图像进行描述。
好了,刚才说了一大堆一休哥不擅长的大片理论的“教科书式”定义后,一休哥继续讲大实话。一言以蔽之,FPGA在图像处理中的应用尚处于未成熟状态,网络上可供借鉴的大多是预处理方面的资源,而关于使用FPGA去做特征和目标层次的处理还是十分复杂的,大多数高层次的应用还处于研发阶段,具体说吧,主要是高校和大公司会去做这方面的研究与应用,而且因为是研发阶段,所以使用的FPGA套件是十分昂贵的,不计成本。显然这种实现方式是不符合商用的。因此,当你真正用一个全新的“较低廉”的“高效”的硬件方式实现了一个效果十分出色的图像处理算法,那么你当真是非常了不起的了。
说了这么久,关于“FPGA在图像处理领域中的地位?”这个问题,想必各位心中已经有了属于你们自己的那份答案了。
不妨再来和一休哥的答案比较下。用FPGA做图像处理往往需要考虑除算法之外的更多问题,如时序约束,存储器带宽不足,资源不足,计算问题,这些问题都制约着FPGA在图像处理领域的发展。试想一下,本就复杂的图像处理算法,再加上这些有待解决的难题,这也就是我们经常遇到的困境。幸好,科技是在不断进步的,当现有的技术不能很好的解决当前的应用难题时,一定会新的技术诞生,专为处理这些问题而是的技术。这也就是,我们需要不断学习,不断充电的原因。
DDR2、DDR3、Zynq、PCIE、USB3.0、千兆网、LVDS、CORDIC,这些新技术的出现为我们提供了新的思路与解决方案,让FPGA拥有更大的潜能,FPGA将会在图像处理领域有更加卓越的表现。
在下期及以后的博文中,一休哥将主要介绍FPGA在图像处理中的应用实例。结合新的技术,通过具体应用实例的方式,讲述不一样但“绝对精彩”的图像处理故事,敬请期待!
这里写图片描述

2017-04-22 20:16:28 He11o_Liu 阅读数 6823

本文记录了利用FPGA加速图像处理中的卷积计算的设计与实现。实现环境为Altera公司的Cyclone IV型芯片,NIOS II软核+FPGA架构。
由于这是第一次设计硬件加速模块,设计中的瑕疵以及问题欢迎前来讨论。

更新记录:

  • D0423 记录FPGA核心计算模块和控制模块
  • D0426 记录FPGA核心计算模块的控制驱动,性能与功能测试

Part1 : 卷积相关

软件实现卷积

这里写图片描述

  • 卷积是将原来矩阵的对应部分与卷积核对位乘法再加起来,形成新的矩阵中的一个位。
  • 图中红色的框是卷积操作当前所在位置,对位乘法相加之后得到中间的小红框的值。
  • 利用C语言实现核心代码如下
void Conv(int filter[100][100], int arr[100][100], int res[100][100], int filterW, int filterH, int arrW, int arrH){
    int temp;

    for (int i=0; i<filterH+arrH-1; i++){
        for (int j=0; j<filterW+arrW-1; j++){
            printf("Start %d %d \n",i,j);
            temp = 0;
            for (int m=0; m<filterH; m++){
                for (int n=0; n<filterW; n++){
                    printf("m %d n %d  ",m,n);
                    if ((i-m)>=0 && (i-m)<arrH && (j-n)>=0 && (j-n)<arrW){
                        printf("%d * %d",filter[m][n],arr[i-m][j-n]);
                        temp += filter[m][n]*arr[i-m][j-n];
                    }
                    printf("\n");
                }
            }
            printf("End\n");
            res[i][j] = temp;
        }
    }
}
*********************************************** 
Filter: 
2 1 2 
0 5 0 
1 3 1 
*********************************************** 

*********************************************** 
Matrix: 
  17   24    1    8   15 
  23    5    7   14   16 
   4    6   13   20   22 
  10   12   19   21    3 
  11   18   25    2    9 
*********************************************** 

*********************************************** 
Result: 
   17    75    90    35    40    53    15 
   23   159   165    45   105   137    16 
   38   198   120   165   205   197    52 
   56    95   160   200   245   184    35 
   19   117   190   255   235   106    53 
   20    89   160   210    75    90     6 
   22    47    90    65    70    13    18 
*********************************************** 

针对具体的(3,4)

Start 3 4 
m 0 n 0  1 * 3
m 0 n 1  3 * 21
m 0 n 2  1 * 19
m 1 n 0  0 * 22
m 1 n 1  5 * 20
m 1 n 2  0 * 13
m 2 n 0  2 * 16
m 2 n 1  1 * 14
m 2 n 2  2 * 7
End

硬件实现思路和可能的加速

这里写图片描述

  • 这是一个卷积处理单元的顶层图。
  • 还是同滑动窗口思路类似,右边三位输入为控制器,使得该处理单元在3行上向右滑动。
  • 不同于软件实现,这里的乘法底层应该是查找表实现的,所有9个DFF的乘法,可以并行计算。
  • 计算结构用最下面的加法模块加上后输出当前位。
  • 这个加法可能会通过拆分加优化(加法树之类)

最简单的实现就是将这个模块运算玩一行后继续算下一行。但是这样没有充分利用这些数据不相关的特性,有两种思路。

  • 一个是如上图多放几个卷积模块
  • 另一个是通过级联将3*3的卷积模块扩大 比如说6*6的,可以通过4个3*3的处理模块级联实现。具体多大,和板子资源和具体矩阵尺寸有关。这个还要再考虑。

与软件比较

  • 3*3处理模块中软件9个乘法串行,这里并行。
  • 多个级联后可以达到36或者更高的优化

Part2 : FPGA卷积核心计算模块

Convolution Calculate Moudle

该模块是卷积计算的核心模块,模块顶层图如下:

这里写图片描述

输入输出端口说明

Din0 ~ Din8 是当前计算矩阵3*3的输入

参数Filter0 ~ Filter8 是卷积核3*3的参数。这里不选择动态输入是为了FPGA构造更小的LUT

StartRst是该模块的控制输入

Finish是该模块的完成输出,可以作为中断信号。

防止溢出,这里Dout给的16

详细设计与分析

该模块包含一个自动机,用于跟踪计算状态,提供与上层同步的时序依据。

这里写图片描述

在RUNING过程中包括两个子状态,用于并行计算乘与并行计算加,利用两个变量来控制。

该模块在Quartus 13RTL如下:

这里写图片描述

虽然看起来加法链那里由于没有优化拉的很长,但是由于与时序无关,下一个时钟周期来的时候计算一定会完成,所以没有太大影响。

测试该模块

书写Test Bench仿真测试

这里写图片描述

利用NIOS II 软核测试

这里写图片描述

烧录Cyclone IV JTAG输出测试

测试程序如下:

#include <stdio.h>
#include "system.h"
#include "altera_avalon_pio_regs.h"

int main() {
    //Test For Conv_cal
    int matrix[9] = { 3, 21, 19, 22, 20, 13, 16, 14, 7 };
    int i = 0;
    int finish;
    int result;
    IOWR_ALTERA_AVALON_PIO_DATA(CONV_CAL_CTL_BASE, 0x1);
    //RST
    IOWR_ALTERA_AVALON_PIO_DATA(DOUT0_BASE, matrix[i++]);
    IOWR_ALTERA_AVALON_PIO_DATA(DOUT1_BASE, matrix[i++]);
    IOWR_ALTERA_AVALON_PIO_DATA(DOUT2_BASE, matrix[i++]);
    IOWR_ALTERA_AVALON_PIO_DATA(DOUT3_BASE, matrix[i++]);
    IOWR_ALTERA_AVALON_PIO_DATA(DOUT4_BASE, matrix[i++]);
    IOWR_ALTERA_AVALON_PIO_DATA(DOUT5_BASE, matrix[i++]);
    IOWR_ALTERA_AVALON_PIO_DATA(DOUT6_BASE, matrix[i++]);
    IOWR_ALTERA_AVALON_PIO_DATA(DOUT7_BASE, matrix[i++]);
    IOWR_ALTERA_AVALON_PIO_DATA(DOUT8_BASE, matrix[i++]);
    IOWR_ALTERA_AVALON_PIO_DATA(CONV_CAL_CTL_BASE, 0x2);
    //START
    finish = IORD_ALTERA_AVALON_PIO_DATA(FINISH_BASE);
    printf("Finish %d\n", finish);
    while (finish & 0x01 == 0) {
        finish = IORD_ALTERA_AVALON_PIO_DATA(FINISH_BASE);
        usleep(10000);
        printf("Finish %d\n", finish);
    }
    result = IORD_ALTERA_AVALON_PIO_DATA(DIN_BASE);
    printf("Result %d\n", result);
    matrix[0] = 10;
    i = 0;
    IOWR_ALTERA_AVALON_PIO_DATA(DOUT0_BASE, matrix[i++]);
    IOWR_ALTERA_AVALON_PIO_DATA(DOUT1_BASE, matrix[i++]);
    IOWR_ALTERA_AVALON_PIO_DATA(DOUT2_BASE, matrix[i++]);
    IOWR_ALTERA_AVALON_PIO_DATA(DOUT3_BASE, matrix[i++]);
    IOWR_ALTERA_AVALON_PIO_DATA(DOUT4_BASE, matrix[i++]);
    IOWR_ALTERA_AVALON_PIO_DATA(DOUT5_BASE, matrix[i++]);
    IOWR_ALTERA_AVALON_PIO_DATA(DOUT6_BASE, matrix[i++]);
    IOWR_ALTERA_AVALON_PIO_DATA(DOUT7_BASE, matrix[i++]);
    IOWR_ALTERA_AVALON_PIO_DATA(DOUT8_BASE, matrix[i++]);
    IOWR_ALTERA_AVALON_PIO_DATA(CONV_CAL_CTL_BASE, 0x2);
    //START
    finish = IORD_ALTERA_AVALON_PIO_DATA(FINISH_BASE);
    printf("Finish %d\n", finish);
    while (finish & 0x01 == 0) {
        finish = IORD_ALTERA_AVALON_PIO_DATA(FINISH_BASE);
        usleep(10000);
        printf("Finish %d\n", finish);
    }
    result = IORD_ALTERA_AVALON_PIO_DATA(DIN_BASE);
    printf("Result %d\n", result);

    return 0;
}

JTAG 输出

这里写图片描述

存在问题 下版本优化

  • 从设计上来说 三个周期就已经可以输出结果了,当前状态机导致到了第五个时钟周期才通知取结果,状态机需要稍微优化一下。
  • 设计上一层分配3*3矩阵输入拆分模块时,状态能和当前状态机配合,甚至可以达到流水线的状态。

第二版本

第二版本相较于第一版本,将所有的非时序部分统一在一个时钟周期内完成。

这里写图片描述

综合实现RTL如下:

这里写图片描述

简化后的状态机为

这里写图片描述

Part3 : FPGA卷积计算控制模块&驱动

如何使用FPGA的卷积计算模块有两种思路

  • 一个是直接利用FPGA实现对内存的读取控制
  • 一个是利用AXI总线直接用上的模块

FPGA通过内存读写

先尝试第一种方法。开发难度非常大,最后写出来的模块也非常复杂。

由于本人第一次尝试设计一个功能模块,状态机中间可能有容易时钟周期,仅用于示意。状态图如下:

这里写图片描述

设计大体思路如下:

这里写图片描述

两个单向读取写入RAM分别用作

  • PS部分写入矩阵,PL部分读取处理
  • PL部分写入处理结果,PS部分读取结果

边界情况暂时略去。

波形仿真截取开始和结束

这里写图片描述

这里写图片描述

变量顾名思义,具体时序设计见代码。

生成RTL的实际电路如下:

这里写图片描述

其中绿色部分才是最核心的加速模块。

在设计过程中,很明显发现这样设计的特点:

  • FPGA真的不适合表达串行逻辑,或者逻辑复杂的时候,需要精心设计状态机。

  • 下面是两张综合后所用资源的图

这里写图片描述
这里写图片描述

上面一张是核心模块综合后的资源图,下面一张是利用内存之后的占用的资源图。

直接核心与PL交互的话输出端口占用比较多,而利用内存进行交互占用比较少。

这一点可以将核心模块的并行输入改为串行输入降低核心模块直接交互的端口数。

  • 代码不容易维护,扩展性极差。

  • 实际速度不好说,只有大约2/13的时钟周期在进行计算。这一点和直接利用PS核IO口传数据过来的速度比较可能优势也不大。

通过IO口读写

基本思路同上面写的软核测试类似。主要考虑两点

  • 并行输入该串行,IO口节约,或者写一个中间模块通过AXI总线交互的
  • 实际是将每次的9个乘法和一个加法优化到硬件部分实现

同样是上面的NIOS II工程,先利用软核软件实现:

void Conv_SW(int filter[100][100], int arr[100][100],int filterW, int filterH, int arrW, int arrH) {
    int temp;
    int i, j, m, n;
    for (i = 2; i < filterH + arrH - 3; i++) {
        for (j = 2; j < filterW + arrW - 3; j++) {
            temp = 0;
            for (m = 0; m < filterH; m++) {
                for (n = 0; n < filterW; n++) {
                    if ((i - m) >= 0 && (i - m) < arrH && (j - n) >= 0
                            && (j - n) < arrW) {
                        temp += filter[m][n] * arr[i - m][j - n];
                    }
                }
            }
            res[i][j] = temp;
        }
        if(i % 50 == 0) printf("=");
    }
}

然后是FPGA模块硬件驱动:

/**
 * 卷积硬件驱动示例
 * 将结果保存到全局变量res
 */
void Conv_HW(int filter[100][100], int arr[100][100],int filterW, int filterH, int arrW, int arrH) {
    int i, j;
    IOWR_ALTERA_AVALON_PIO_DATA(CONV_CAL_CTL_BASE, 0x1);
    for (i = 2; i < filterH + arrH - 3; i++) {
        for (j = 2; j < filterW + arrW - 3; j++) {
                 IOWR_ALTERA_AVALON_PIO_DATA(DOUT0_BASE, arr[i][j]);
                 IOWR_ALTERA_AVALON_PIO_DATA(DOUT1_BASE, arr[i][j-1]);
                 IOWR_ALTERA_AVALON_PIO_DATA(DOUT2_BASE, arr[i][j-2]);
                 IOWR_ALTERA_AVALON_PIO_DATA(DOUT3_BASE, arr[i-1][j]);
                 IOWR_ALTERA_AVALON_PIO_DATA(DOUT4_BASE, arr[i-1][j-1]);
                 IOWR_ALTERA_AVALON_PIO_DATA(DOUT5_BASE, arr[i-1][j-2]);
                 IOWR_ALTERA_AVALON_PIO_DATA(DOUT6_BASE, arr[i-2][j]);
                 IOWR_ALTERA_AVALON_PIO_DATA(DOUT7_BASE, arr[i-2][j-1]);
                 IOWR_ALTERA_AVALON_PIO_DATA(DOUT8_BASE, arr[i-2][j-2]);
            IOWR_ALTERA_AVALON_PIO_DATA(CONV_CAL_CTL_BASE, 0x2);
            while ((IORD_ALTERA_AVALON_PIO_DATA(FINISH_BASE) & 0x01) == 0)
                ;
            res[i][j] = IORD_ALTERA_AVALON_PIO_DATA(DIN_BASE);
        }
        if(i % 50 == 0) printf("=");
    }
}

主函数针对性能和功能进行测试。由于NIOS II无法获取Time,故直接将进度输出。(软件大致16s完成对500*500的矩阵计算,硬件大致3s完成相同计算)

void Conv_SW(int filter[100][100], int arr[100][100],
        int filterW, int filterH, int arrW, int arrH);
void Conv_HW(int filter[100][100], int arr[100][100],
        int filterW, int filterH, int arrW, int arrH);

int res[1000][1000];

int main() {

    int filterW = 3;
    int filterH = 3;
    int arrW = 5;
    int arrH = 5;
    int resW = filterW + arrW - 1;
    int resH = filterH + arrH - 1;
    int i, j;
    int pFilter[100][100];
    int arr[100][100];

    pFilter[0][0] = 1;
    pFilter[0][1] = 3;
    pFilter[0][2] = 1;
    pFilter[1][0] = 0;
    pFilter[1][1] = 5;
    pFilter[1][2] = 0;
    pFilter[2][0] = 2;
    pFilter[2][1] = 1;
    pFilter[2][2] = 2;

#ifdef TestSpeed
        arrW = 500;
        arrH = 500;
        resH = filterH + arrH - 1;
        resW = filterW + arrW - 1;
        printf("Software Start!\n");
        Conv_SW(pFilter, arr, filterW, filterH, arrW, arrH);
        printf("\nSoftware end!\n");
        printf("HardWare Start!\n");
        Conv_HW(pFilter, arr, filterW, filterH, arrW, arrH);
        printf("\nHardWare end!");
#else

    srand(10);
    arrW = 20;
    arrH = 20;
    resH = filterH + arrH - 1;
    resW = filterW + arrW - 1;
    for(i = 0; i < arrH; i++){
        for(j = 0; j< arrW;j++){
            arr[i][j] = rand()%20;
        }
    }
    printf("*********************************************** \n");
    printf("Filter: \n");
    for (i = filterH - 1; i >= 0; i--) {
        for (j = filterW - 1; j >= 0; j--) {
            printf("%d ", pFilter[i][j]);
        }
        printf("\n");
    }
    printf("*********************************************** \n");


    printf("Matrix: \n");
    for (i = 0; i < arrH; i++) {
        for (j = 0; j < arrW; j++) {
            printf("%4d ", arr[i][j]);
        }
        printf("\n");
    }
    printf("*********************************************** \n");
    printf("Software Start!\n");
    Conv_SW(pFilter, arr, filterW, filterH, arrW, arrH);
    printf("\nSoftware end!\n");

    printf("*********************************************** \n");
    printf("Result1: \n");
    for (i = 0; i < resH; i++) {
        for (j = 0; j < resW; j++) {
            printf("%5d ", res[i][j]);
        }
        printf("\n");
    }

    for (i = 0; i < resH; i++) {
        for (j = 0; j < resW; j++) {
            res[i][j] = 0;
        }
    }
    printf("*********************************************** \n");
    printf("HardWare Start!\n");
    Conv_HW(pFilter, arr, filterW, filterH, arrW, arrH);
    printf("\nHardWare end!");
    printf("Result2: \n");
    for (i = 0; i < resH; i++) {
        for (j = 0; j < resW; j++) {
            printf("%5d ", res[i][j]);
        }
        printf("\n");
    }
    printf("*********************************************** \n");
#endif
    return 0;
}

由于是示例驱动,这里忽略对四周的处理:

这里写图片描述

300*300 速度示例

这里写图片描述

附录 FPGA 部分源代码

通过IO口读写

基本思路同上面写的软核测试类似。主要考虑两点

  • 并行输入该串行,IO口节约,或者写一个中间模块通过AXI总线交互的
  • 实际将4重循环优化到了两重循环。
  • 测试驱动下周写一个例子

控制模块源代码

module conv_ctl(
    //控制信号输入输出
    clk,Start,Rst,Matrix_Length,Conv_Finish,
    //连接读取数据RAM
    Q,rdaddress,
    //连接写入数据RAM
    Wdata,wraddress,wren);

    //控制信号输入输出
    input clk,Start,Rst;
    input [7:0] Matrix_Length;
    output reg Conv_Finish;

    //连接读取数据RAM
    input [7:0] Q;
    output reg [7:0] rdaddress;

    //连接写入数据RAM
    output [7:0] Wdata;
    output reg [7:0] wraddress;
    output reg wren;

    //连接conv_cal
    // input[15:0] Cal_Dout;
    // input Cal_Finish;
    wire [15:0] Cal_Dout;
    wire  Cal_Finish;

    reg [7:0] Din0,Din1,Din2,Din3,Din4,Din5,Din6,Din7,Din8;
    reg Cal_Start;
    reg Cal_Rst;

    //状态机
    reg [5:0] cur_state,nxt_state;

    parameter IDLE = 6'b000001;
    parameter READING = 6'b000010;
    parameter CALCULATING = 6'b000100;
    parameter WRITING = 6'b001000;
    parameter CHECKING = 6'b010000;
    parameter FINISH = 6'b100000;


    //辅助参数
    reg Read_Finish;
    reg Write_Finish;
    reg [7:0] count,conv_x,conv_y;


     //调用模块
    conv_cal_2 my_cal(Cal_Dout,Din0,Din1,Din2,Din3,Din4,Din5,Din6,Din7,Din8,clk,Cal_Start,Cal_Rst,Cal_Finish);

    //状态自动转移
    always@(posedge clk)
    begin
        if(Rst)
            cur_state <= IDLE;
        else
            cur_state <= nxt_state;
    end

    //状态转移条件
    always@(cur_state or Start or Cal_Finish or Rst or Read_Finish or Write_Finish)
    begin
        if(Rst)
            nxt_state = IDLE;
        else
        begin
            case(cur_state)
                IDLE:nxt_state = Start?READING:IDLE;
                READING:nxt_state = Read_Finish?CALCULATING:READING;
                CALCULATING:nxt_state = Cal_Finish?WRITING:CALCULATING;
                WRITING: nxt_state = CHECKING;
                CHECKING: nxt_state = Write_Finish?FINISH:READING;
                FINISH: nxt_state = IDLE;
                default: nxt_state = IDLE;
            endcase
        end
    end

    //状态动作
    always@(posedge clk) 
    begin:b1
        case(cur_state)
            IDLE:
            begin
                Read_Finish = 1'b0;
                Write_Finish = 1'b0;
                count = 0;
                conv_x = 1;
                conv_y = 1;
                Cal_Start = 1'b0;
                Cal_Rst = 1'b1;
                wraddress = 8'd0;
                rdaddress = 8'd0;
                wren = 1'b0;
                Conv_Finish = 1'b0;
            end
            READING:
            begin
                wren = 1'b0;
                Cal_Start = 1'b0;

                //根据count对3*3输入进行赋值
                case(count)
                    0: 
                    begin
                        Din0 = Q;
                        rdaddress = conv_x - 1 + (conv_y-1) * Matrix_Length;
                    end
                    1: 
                    begin
                        Din1 = Q;
                        rdaddress = conv_x + (conv_y-1) * Matrix_Length;
                    end
                    2:
                    begin
                        Din2 = Q;
                        rdaddress = conv_x + 1 + (conv_y-1) * Matrix_Length;
                    end
                    3:
                    begin
                        Din3 = Q;
                        rdaddress = conv_x - 1 + conv_y * Matrix_Length;
                    end
                    4:
                    begin
                        Din4 = Q;
                        rdaddress = conv_x  + conv_y * Matrix_Length;
                    end
                    5:
                    begin
                        Din5 = Q;
                        rdaddress = conv_x + 1 + conv_y * Matrix_Length;
                    end
                    6:
                    begin
                        Din6 = Q;
                        rdaddress = conv_x - 1 + (conv_y+1) * Matrix_Length;
                    end
                    7:
                    begin
                        Din7 = Q;
                        rdaddress = conv_x + (conv_y+1) * Matrix_Length;
                    end
                    8:
                    begin
                        Din8 = Q;
                        rdaddress = conv_x + 1 + (conv_y+1) * Matrix_Length;
                    end
                endcase
                count = count + 7'd1;
                if(count == 7'd8) 
                    Read_Finish = 1'b1;
            end
            CALCULATING:
            begin
                Read_Finish = 1'b0;
                count = 0;
                Cal_Rst = 1'b0;
                Cal_Start = 1'b1;
            end
            WRITING:
            begin
                wren = 1'b1;
                if(conv_x == Matrix_Length - 2) //
                begin
                    conv_x = 1;
                    conv_y = conv_y + 1;
                end
                else
                begin
                    conv_x = conv_x + 1;
                end

                wraddress = wraddress + 1; //!!!这句话可能有问题

                if(conv_y == Matrix_Length - 1) // 
                begin
                    Write_Finish = 1'b1;
                end
                else
                begin
                    Write_Finish = 1'b0;
                end
            end
            FINISH:
            begin
                Conv_Finish = 1'b1;
            end
        endcase
    end


endmodule

核心计算模块 版本一

module conv_cal (Dout,Din0,Din1,Din2,Din3,Din4,Din5,Din6,Din7,Din8,clk,Start,Rst,Finish);
    input [7:0] Din0,Din1,Din2,Din3,Din4,Din5,Din6,Din7,Din8;
    input clk;
    input Start;
    input Rst;
    output reg [15:0] Dout;
    output reg Finish;
    reg [7:0] Add0,Add1,Add2,Add3,Add4,Add5,Add6,Add7,Add8;

    reg [2:0] state;
    reg mul_finish;
    reg add_finish;

    parameter IDLE = 3'b001;
    parameter RUNNING = 3'b010;
    parameter DONE = 3'b100;

    //Convolution Kernel
    parameter Filter0 = 8'd1;
    parameter Filter1 = 8'd3;
    parameter Filter2 = 8'd1;
    parameter Filter3 = 8'd0;
    parameter Filter4 = 8'd5;
    parameter Filter5 = 8'd0;
    parameter Filter6 = 8'd2;
    parameter Filter7 = 8'd1;
    parameter Filter8 = 8'd2;

    always@(posedge clk)
    begin
        if(Rst)
        begin
            state <= IDLE;
        end
        else
            case(state)
                IDLE: state <= Start ? RUNNING:IDLE;
                RUNNING: 
                begin
                    if(add_finish == 1) 
                    begin
                        state <= DONE;
                        Finish <= 1'b1;
                    end
                    else
                        state <= RUNNING;
                end
                DONE :
                begin
                    Finish <= 1'b0;
                    state <= IDLE;
                end
                default : state <= IDLE;
            endcase
    end


    always@(posedge clk)
    begin
        if(state == IDLE)
        begin
            mul_finish <= 1'b0;
            add_finish <= 1'b0;
            Finish <= 1'b0;
        end
        else if(state == RUNNING)
        begin
            if(!mul_finish)
            begin
                Add0 <= Din0 * Filter0;
                Add1 <= Din1 * Filter1;
                Add2 <= Din2 * Filter2;
                Add3 <= Din3 * Filter3;
                Add4 <= Din4 * Filter4;
                Add5 <= Din5 * Filter5;
                Add6 <= Din6 * Filter6;
                Add7 <= Din7 * Filter7;
                Add8 <= Din8 * Filter8;
                mul_finish <= 1;
            end
            else
            begin
                Dout <= Add0+Add1+Add2+Add3+Add4+Add5+Add6+Add7+Add8;
                add_finish <= 1;
            end
        end
    end
endmodule

核心计算模块 版本二

module conv_cal_2 (Dout,Din0,Din1,Din2,Din3,Din4,Din5,Din6,Din7,Din8,clk,Start,Rst,Finish);
    input [7:0] Din0,Din1,Din2,Din3,Din4,Din5,Din6,Din7,Din8;
    input clk;
    input Start;
    input Rst;
    output reg [15:0] Dout;
    output reg Finish;
    wire [7:0] Add0,Add1,Add2,Add3,Add4,Add5,Add6,Add7,Add8;
    wire [15:0] Add_all;

    reg [1:0] state;

    parameter IDLE = 2'b01;
    parameter DONE = 2'b10;

    //Convolution Kernel
    parameter Filter0 = 8'd1;
    parameter Filter1 = 8'd3;
    parameter Filter2 = 8'd1;
    parameter Filter3 = 8'd0;
    parameter Filter4 = 8'd5;
    parameter Filter5 = 8'd0;
    parameter Filter6 = 8'd2;
    parameter Filter7 = 8'd1;
    parameter Filter8 = 8'd2;



    assign Add0 = Din0 * Filter0;
    assign Add1 = Din1 * Filter1;
    assign Add2 = Din2 * Filter2;
    assign Add3 = Din3 * Filter3;
    assign Add4 = Din4 * Filter4;
    assign Add5 = Din5 * Filter5;
    assign Add6 = Din6 * Filter6;
    assign Add7 = Din7 * Filter7;
    assign Add8 = Din8 * Filter8;

    assign Add_all = Add0+Add1+Add2+Add3+Add4+Add5+Add6+Add7+Add8;

    always@(posedge clk )
    begin
        if(Rst)
        begin
            state <= IDLE;
            Finish <= 1'b0;
            Dout <= 16'b0;
        end
        else
            case(state)
                IDLE: 
                begin
                    Finish <= 1'b0;
                    if(Start)
                    begin
                        state <= DONE;
                    end
                    else
                        state <= IDLE;
                end
                DONE :
                begin
                    Finish <= 1'b1;
                    state <= IDLE;
                    Dout <= Add_all;
                end
                default : state <= IDLE;
            endcase
    end
endmodule

FPGA数字图像处理

阅读数 1136

FPGA图像处理之路

阅读数 2227