FPGA视觉从入门到放弃——Canny算子
一. FPGA视觉从入门到放弃简介
本笔记仅适合实验室内部的FPGA图像采集卡,并不适合计算机视觉中的高大上场合,但功耗和速度很阔怕,同时方法简单得阔怕。毕竟没有什么方法或工具可以永远时尚下去,所以从入门到放弃也算是进步之选嘛~
二. Prefix
1.常用变量术语
CLK 时钟20ns
LLC 经过锁相环的时钟37ns
oddfield 采集奇场图像时输出高电平
Y_flag 与像素灰度值同步的高电平脉冲
m2_*** wishbone总线的控制信号与数据信号
2.贴士
reg默认为无符号,有符号为reg signed;
Camera_AB.v模块为最高层模块;
FIFO数据达到16个,准备写SRAM;写完SRAM后从IDLE分支调到WRITE_END分支,主动放弃总线。
3.FPGA切换SRAM访问权
SRAM_A和B有容量512K=2^9*2^10=2^19,SAVEADDR为19位地址线;数据线16位宽。
FPGA先给SRAM_A写当前帧结果,DSP从SRAM_B读上一帧结果;下一时刻,FPGA再给SRAM_A写当前帧结果,DSP从SRAM_A读上一帧结果。
DSP的地址空间CE2设置为SRAM,空间地址0xA0000000开始;CE3设置为FPGA内部寄存器,空间地址0xB0000000开始。DSP与SRAM导线连接,读写更快。
FPGA_A_DSP_B为高电平时,FPGA访问SRAM_A,DSP访问SRAM_B。低电平时切换。
4.DSP读写CE3数据
(1)DSP读
DSP_nCE3为低电平且DSP_nRE(或DSP_nOE)为高电平时,FPGA内部16位导线dsp_data_read_ce3与DSP_DATA双向端口连接。
DSP_ADDR的地址为8位,DSP读FPGA内部参数最多为256个字节,这样只会用到CE3空间很小的一部分。
(2)DSP写
DSP_nCE3为低电平且DSP_nWE为高电平时,取出DSP_ADDR地址处的DSP_DATA写入FPGA内部参数。
三. FPGA代码分析——Canny算子
问题:大小为720*576的图像在哪里降采样为360*288的?
答:原图像大小为720*576,odd_field和even_field同时存在时为该分辨率;只用odd_field或even_field则为360*288。
1.Canny算子相对于高斯滤波在顶层文件Camera_AB.v中区别
Canny算子的sysreg0中多出1条8位线threshold_lower_limit,“为防止白纸上检测到无数边缘点”。
除了save_Y模块,还有save_Cb和save_Cr模块。
wishbone总线的master 7未用。
(1)Canny:
添加13位线G_VPO_Y;
Gaussian_filter_Y模块滤波(
726个Y_flags),模块输入为原图像VPO_out,输出为G_VPO_Y。
添加1位线max_flag;
添加8位线gradient;
Sobel_Y模块提取梯度极大值(
729个Y_flags),模块输入为Gaussian_filter_Y模块的输出G_VPO_Y,输出为max_flag和gradient。
添加
17位线ram_read_data和ram_write_data;
添加1位线ram_write_enable;
添加8位线ram_addr;
RAM_17bits_256depth模块建立Histogramof the Gradients(?),
输入ram_write_data和ram_write_enable,输出ram_read_data。
//=============================================================================
compare_gradient_count模块输入为Sobel模块的输出max_flag和gradient,以及RAM_17bits_256depth模块的输入输出。强边缘横坐标,强边缘纵坐标,弱边缘横坐标,弱边缘纵坐标分别占用master 3,4,5,6。
SAVEADDR_ROW_X为19'h20000,FPGA的SAVEADDR_***地址的2倍为DSP中对应的SAVEADDR_***地址。同理SAVEADDR_COL_Y为19'h28000,DSP地址为0x28000*2=0x50000,即SAVEADDR_STRONG_COL_Y。地址赋给mi_addr(i=3,4,5,6)。
默认高阈值high_threshold(8位reg)为240,最大强弱边缘像素数为32768。当梯度大于高阈值时,则记录为强边缘坐标;当梯度小于高阈值且大于高阈值的1/4时,则记录为弱边缘坐标。高阈值可以用DSP修改,先送给threshold_lower_limit。
//=============================================================================
添加8位线VPO_Y;
median_filter_Y模块(
731个时钟脉冲)输入为VPO_out,输出为VPO_Y。
//=============================================================================
compare_Y_count模块用Y阈值比较像素值,保存二值化图像中的每个“亮”像素。输入为VPO_Y和Y_threshold,输出bright_pix_num,又向wishbone总线的master3,4写入强边缘横坐标和强边缘纵坐标。
默认Y阈值DEFAULT_Y_THRESHOLD为240。
//=============================================================================
问题:中值滤波的输入图像是原图像而不是gradient,滤波结果与边缘提取没关系吧?再写1遍m3_data_write和m4_data_write是为什么?
已经注释掉了中值滤波的部分。
(2)Gaussian:
添加8位线VPO_median_Y;有不同大小高斯模板的滤波模块。
Interrupt the DSP和wishbone总线部分没变。保存原图像都占用master 0。
四. DSP代码分析——Canny算子
获得强边缘的坐标存入堆栈数组,在强边缘的8邻域内搜索弱边缘。如果存在弱边缘,将弱边缘修改为强边缘,完成强弱边缘的连接。
注:DSP每个地址的数据类型为unsigned int 16。
1. clear_Y_data()
Y_data和Label清0。
2. get_the_edge_points()
(1) 强边缘
STRONG_EDGE_PIX_NUM_ADDR为0xB0000062,从该地址取边缘像素数edge_pix_num;
MAX_EDGE_PIX_NUM为32768,用于对edge_pix_num限幅。
SAVEADDR_STRONG_ROW_X为0xA0040000,从该地址提取强边缘的横坐标;
SAVEADDR_STRONG_COL_Y为0xA0050000,从该地址提取强边缘的纵坐标。
如果4<横坐标x<285且4<纵坐标y<357,则Y_data在该点的像素值为255,实际边缘像素数actual_edge_num加1,把x-1和y-1存入栈数组stack_row_x和stack_row_y。
strong_edge_pix_num记录DSP从强边缘的存储地址中提取的横纵坐标位于图像内的有效像素数。
注:强边缘坐标最多有0x10000=65536个坐标,图像大小为360*288=103680个坐标。强边缘像素数占8个字节,感觉3个字节就可以。
(2) 弱边缘
弱边缘同理。WEAK_EDGE_PIX_NUM_ADDR为0xB0000066;
SAVEADDR_WEAK_ROW_X为0xA0060000;
SAVEADDR_WEAK_COL_Y为0xA0070000。
如果满足边界条件,则Y_data在该点的像素值为1。并未存入栈数组。
weak_edge_pix_num记录有效的弱边缘像素数。
(3) 连接边缘
索引从强边缘坐标数组的末端开始往前扫描。假设存在强边缘坐标(x0,y0)(Y_data在该点处的像素值为255)。寻找强边缘坐标的8近邻区域是否存在弱边缘坐标(x0+x1,y0+y1)(Y_data在该点处的像素值为1),如果存在,则该相邻点坐标(x0+x1,y0+y1)处的像素值改为255,同时实际边缘数加1,坐标(x0+x1,y0+y1)存入栈数组。
五.主机与DSP通信——Canny算子
1.发送边缘阈值
51 0xFF value_edge_threshold U8_data[0]=0x00U8_data[1]=0x00
2.读原图像
m_original->imageData的大小为1080*1080,选择前96*1080字节:360*288=103680=96*1082。
六. FPGA代码分析——腐蚀
1.腐蚀相对于Canny算子在顶层文件Camera_AB.v中区别
LLC,VSYNC,FID,VPO后缀增加_S:LLC->LLC_S。
master 5,6,7未用,而Canny算子只有master 7未用。
//=============================================================================
Average_filter_Y_f模块同时选择oddfield和evenfield,输入图像VPO_out,输出滤波结果A_VPO_Y_f。
taps为2位移位寄存器的水龙头输出(8位线),输入为VPO_in。
问题:均值滤波的对应原理?
Y11p<-Y12p<-Y13p
Y31p<-Y31p<-Y32p
taps缓存图像序列VPO_in上一行位置的值。
我被文件标题欺骗:这里不是腐蚀,是2*3均值滤波。所以后面的腐蚀带双引号~
//=============================================================================
compare_Y_count模块输入为VPO_in;Canny对应VPO_Y,增加bright_pix_num。
七. DSP代码分析——“腐蚀”
虽然FPGA处理的图像大小为720*576,get_original_image依然获得大小为360*288的图像。get_laser_image应该是获取结果图像,Y_even_buffer地址为0xA0040000;Y_odd_buffer地址为0xA0046540=25920(字节)。
灰度图像大小为720*576=414720(字节),奇场图像为一半:414720/2=207360(字节)<25920(字节)。
八. FPGA代码制作——Canny算子 + “腐蚀”
1.复制Average_filter_Y_f.v,taps_2_lines.bsf/.qip/.v至src文件夹下;
2.在Camera_AB中添加模块Average_filter_Y,输入VPO_in为gradient,Y_f_flag改为Y_flag;
3.修改compare_gradient_count中的输入为A_VPO_Y_f。
九. 实验结果
左图为Canny算子的边缘提取结果;右图为Canny算子 + “腐蚀”的边缘提取结果。