摘要
关于连通域提取算法的FPGA设计。与传统基于CPU的设计比具有低延时、资源消耗少的优点。
什么是连通域提取
连通域提取一般是针对二值图像(只有0和1),它就是把连通着的1的部分标记出来。至于什么是连通,又分为4连通与8连通。我们采用8连通,更符合人的直觉。
可以看到,总共有12个区域。顺序排列是从图像的最左边开始,面积依次为38,93,167,32…
中心为[50.6579,147.6842],[57.2581,15.8495]… 坐标原点为图像左上角,先X后Y。
你可以下载下面的图像,在本地的matlab上,跑下面这段.m脚本,获得更直观的理解,matlab将此功能封装在regionprops函数,可以说是非常简单了。
matlab脚本如下:你需要更改第3行的图像路径
1 | clc; clear all; close all; |
CPU处理存在的问题
- 需要加载完一张图像,才开始处理,算法延时高
- 需要存储一张图像,存储资源消耗大
FPGA能解决这样的问题吗
- 图像边传输,边处理,一个一个像素进行处理
- 事实上连通域只与周围的像素有关,存储周围的像素即可
问题就这么简单吗
我们看这样一张图片,80即图像中亮的部分
从左上开始,按照行方向进行逐像素扫描。我们会得到这样的结果(2,2)(第一个连通域)、(2,8)(第二个连通域),(3,3)(第一个连通域,与(2,2)连通)、(3,7)(第二个连通域,与(2,8)连通)…
这样一直下去,到(5,5)就会有一个神奇的变化。(5,5)起到了纽带作用,将两片独立的连通域连接到了一起,我们前面标记的如(2,2)与(2,8)实际是连通的,但是在第二行的局部看,我们不可能知道。
这样就需要一个连通域合并判断。合并之后需要将所有第二个连通域(简称标号为2)转成标号是1,这依然有可能需要大量的存储器(极限是一幅图),这对于FPGA设计是不现实的!
深入思考之后😳反复尝试之后,可以发现,只需要对上一行的标号2连通域(而不是所有的标号2连通域)更新为标号1就可以了,之后对两个连通域的信息进行合并(如面积、灰度和、边界),而其他内部的元素标号改不改是没有影响的。
这样就变成了需要一行的存储器,即便对于1080p这样的分辨率,一行1920个像素,FPGA存储器是足够的且微不足道的(这都不是事儿)。
我相信大部分人看完这样的描述,依然是一头雾水😅。对于一个算法的理解,一般是这样的过程。这里只是初步看一下,看不懂没有关系(非常正常),这里提供可信的已经完成的代码,先确定代码是好使的,至于为什么这样做就能简单好使,需要你自己去调一调、试一试。
如何知道设计是可行的(强大的测试)
代码好不好使,当然是看测试用例了。给几组输入,看看结果,与MATLAB或者人的直观感觉,对比一下就ok。
测试用例1
我们用前面的V形图像作为输入,来看一下iverilog仿真的结果
这里注意几点
- 所有数据是在fs下降沿(一帧图像完成后)间隔2个时钟开始输出,处理是边接收边处理的,延时低。
- e_label是连通域标号,e_upm(外接矩形上边界),e_dw(外接矩形下边界),e_le(外接矩形左边界),e_ri(外接矩形右边界),e_num_gray(连通域像素个数),e_sum_gray(连通域像素总和)。
- 这里输出了两个连通域,但标号2像素个数是0,这样的连通域是被合并过的连通域,后续可以删除。
- 连通域1的信息是最终的结果,7个像素,总和560(7*80),上边界2,下边界5,左边界2,右边界8。结果是正确的。
我们深入细节,看一下算法的过程:
标号1(label1)的连通域像素个数(num_gray1)变化为1-2-3-7。标号2(label2)的连通域像素个数(num_gray1)变化为1-2-3-0:起到了连通域合并的效果。
进一步分析,外接矩形边界(upm,dw,le,ri)的变化,理解程序的执行过程。
觉得这样的测试,Too Young Too Simple? 可能程序的正确只是一种巧合?
测试用例2
我们采用lena图二值化后进行测试。
考虑到iverilog仿真的速度问题,我们采用32*32低一点的分辨率及进行测试,但程序是适用于1080p的,从逻辑的角度讲,两者没有差别,只是资源消耗有差别。
这个结果,通过直觉来看,总共应该有7个连通域。像素个数分别是61,1,222,98,10,1,2。Looks not bad。
我们跟matlab对比一下
- Area 对应像素个数,两者是一致的。
- Bounding Box 与这里的格式不一致,matlab里的Bounding Box [x1,x2,x3,x4] , x3, x4 表示的是矩形的长和宽像素数=(dw-upm+1)or =(ri-le+1)。x1,x2是矩形左上角位置。matlab上向上取整后,与(le,upm)一致。个人感觉左上角不应是小数。对matlab机制,这里不做深入讨论(其实我也不懂😳)。
是时候安利一波源码及仿真使用了
模块架构
文件依赖
注:(蓝字部分是需要使用的设计文件,黑字部分为xilinx ip,只需要指定参数并调用xilinx ip core)。
模块依赖
模块端口
connect_domain_get
端口类型 | 名称 | 描述 | 备注 |
输入 | clk | 数据时钟 | |
rst_n | 复位信号,低有效 | ||
fs | 场同步 | ||
hs | 行同步 | ||
data | 图像数据 | ||
hang_cnt_out | 当前像素是第几行 | ||
lie_cnt_out | 当前像素是第几列 | ||
输出 | e_label | 连通域标号 | |
e_le | 连通域外接矩形左边界 | ||
e_ri | 连通域外接矩形右边界 | ||
e_upm | 连通域外接矩形上边界 | ||
e_dw | 连通域外接矩形下边界 | ||
e_sum_gray | 连通域图像灰度总和 | ||
e_num_gray | 连通域图像像素个数 |
u_get_around
端口类型 | 名称 | 描述 | 备注 |
输入 | clk | 数据时钟 | |
rst_n | 复位信号,低有效 | ||
fs | 场同步 | ||
hs | 行同步 | ||
data | 图像数据 | ||
输出 | middle | 当前像素(正中间) | |
up_right1 | 右上像素 | ||
up_middle1 | 中上像素 | ||
up_left1 | 左上像素 | ||
left | 左边像素 | ||
fs_neg | 场同步下降沿 | ||
hs_neg | 行同步下降沿 |
源码使用
所有的代码均共享在Github。verilog设计文件在zhangxin6/iverilog_testbench,xilinx ip core文件在zhangxin6/manage_ip
如果你会使用Github,推荐使用命令行中的Git下载
1 | git clone git@github.com:zhangxin6/iverilog_testbench.git |
反之,你可以通过上面的链接,通过Download ZIP下载。
这里提供file_io_testbench.v及iverilog仿真的file_io_testbench.bat供参考。双击file_io_testbench.bat即可实现仿真!
file_io_testbench.bat
1 | @echo off |
里面绝对路径iverilog_path,gtkwave_path,vivado_dir需要根据软件实际安装位置进行调整。
各个文件的相对路径如有修改,则file_io_testbench.bat需要做相应修改。
zhang可以替换成不含中文的路径,其他建议按照下图处理。
设计思路细节
挖坑待填😳
软件依赖
- 仿真必需
- vivado > 2015
- iverilog (免费开源HDL 仿真软件)
iverilog安装使用教程推荐
- 推荐
- Notepad++(查看源码)或Atom
- VHDL
- 如果你更熟悉VHDL而不是Verilog,可以使用XHDL软件进行转换,你可以支持正版或淘宝购买
- 操作系统
- Windows 没有任何问题
- 理论上Linux也是支持的,但iverlog的设置会有不同(参考iverilog官方文档),文件路径也和Windows不一样,需要探索
最后
如果你有任何问题,有以下3种方式给我反馈:
- 在下面评论
- 去相应的github repo提issue
- 发邮件给我,zhangxin_mail163@163.com
Just feel free to raise a question and have some fun with using this code 😄