一 概述
图像处理算法一般是用matla或OpenCV实现的,若是用FPGA实现,设计思路差别极大。matlab和opencv的优势:这些工具的优势在于可以方便地载入图像文件,或输出数据到图像文件,同时提供了大量的API函数,便于使用者快速实现想要的功能,同时又能通过查看图像文件直观地看到预想结果。将算法直接在FPGA实现是有难度和漫长的,在matlab中,一个直方图处理和双边滤波器,引导图像滤波,仅仅一行代码即可,有现成的函数调用,十分简单。而在FPGA实现则需要考虑帧缓存,算法的设计结构与硬件相符合,时序等问题。很有必要对FPGA实现图像处理算法的基本思路和方法进行学习!
二 FPGA实现图像处理算法的基本思路
(一) 需求分析及问题描述
问题描述应该清楚地描述问题而不是解决方法。为使描述更具体,至少需要讨论三个方面。第一是系统功能,也就是系统需要做什么。在一个图像处理应用中,需要详细说明图像处理后的预期结果。第二,必须讨论系统的性能,即说明系统完成这些功能的指标是什么。对 千实时图像处理来说,允许的最大延时和每秒需要处理的帧数是两个很重要的指标。第三个需要考虑的方面是系统将要运行的环境。应用图像处理不仅仅包含图像处理算法,它是一个需要对整个系统进行考虑和说明的系统工程问题。其他需要 考虑的重要方面包括照明、光学及所支持的硬件和机械接口。图像处理系统之间及其与整个工程系统其他部分之间的联系也需要认真地说明和定义。比如,要做一个分辨率为640*480@60Hz的视频处理系统,要求提升每一帧图像的亮度和对比度。这就是明确的需求:即明确输入图像或视频的要求和最终的评价指标。
(二)软件算法设计及验证
软件开发及验证会适当地在硬件设计之前进行,比如在用matlab或OpenCV等工具验证算法的视觉效果和客观指标,这主要是由千在FPGA上调试算法周期过长,即使仅仅做仿真工作所消耗的时间也远远比软件多。如果在硬件上进行映射,其综合、编译和布局布线的时间花费更是无法令人接受。大部分情况下,FPGA更多的是仅仅作为一个映射工具。一般实现某个算法如红外图像细节增强,都是先花两周看经典的SCI论文,选两三篇经典的(一是权威的作者:如知名企业三星,学术机构IEEE的院士,知名大学中科院教授;二是看被引量,如60次以上,这是很多相关算法的基础,很完善成熟。三是发表时间五年之前的比较靠谱的,新的论文很少有人验证。四是看论文的实验仿真,设计是否严谨和多种指标衡量。),在对这两三篇经典的论文算法理解透了,用matlab复现。通过自己matlab仿真不同算法,从实验结果选择一种最佳的作为硬件实现。算法的精度,涉及到浮点转换为定点运算,FPGA不支持小数运算。
(三) 硬件平台设计
硬件平台的设计往往会和软件开发同时进行。通常 个算法的测试及改进是 个周期很长的任务。硬件平台的设计在算法开 发基本功能验证之后就可以对其进行整体评估。
- 软件与硬件的划分
硬件平台设计的第 步是合理地划分硬件和软件。 这里的硬件是指算法由 FPGA逻辑实现,软件是指算法由 DSP、ARM 或MCU软件编程实现。规则的底层图像处理操作(如形态学滤波、 Sobel 算子、均值滤波等)具有计算数据量大、 结构规则并行等特点,非常适合千用 FPGA 硬件实现。 不规则的底层图像处理操作(如具有动态可变长度循环的算法) 和串行顶层图像处理操作(如弹道计算、任务判决融合等)用 FPGA实现会非常繁琐且效率较低, 此类操作用软件实现效率较高, 开发难度较低。无论怎么划分层级,清楚地定义软件与硬件之间的接口与通信机制是基本的要求。尤其有必要设计同步和数据交换机制来促进数据流的平滑。如常见的LCD屏显示,通常是FPGA处理像素数据后通过SPI总线发送给MCU驱动显示。
2. 资源评估与FPGA选型
在硬件方案确定之后, 在确定具体的FPGA型号之前, 对整个系统所消耗的资源 进行预估是十分必要的。对千图像处理系统来讲, 比较敏感的资源是存储器资源。此外,FPGA 所拥有的些高速接口资源也是重要的考虑因素, 这主要考虑到视频处理的高带宽特点。如系统需求为2560*1600,帧率为90Hz的RGB888视频流输入,则帧缓存的带宽为2560*1600*90*24/8=1.105GB/s,而一般DDR3的带宽为800M,需要两片DDR3才行。
(四) FPGA映射
FPGA映射是将软件算法转换为FPGA设计的过程。这个在书籍<<基于FPGA的数字图像处理原理及应用>>的第四章,有讲解到映射技术,如下图所示,需要时可以看书籍进一步了解
(五) 仿真验证
在FPGA映射之后,接下来的重点工作是对设计的系统进行仿真和验证。在FPGA代码撰写完毕时对其进行功能测试是十分有必要的。一般是搭建视频的modelsim仿真平台,即编写一个Verilog文件模拟符合标准视频时序的视频输入源,提供给我们的设计模块,仿真观察波形,将算法处理的结果用txt文本存储,再用matlab观察对比效果。本文的后面会介绍一个vga的模拟输入。在硬件中的在线调试也是十分必要的。最简单的方法是将主信号布线到不用的IO,口上, 使得它们从FPGA外部是可观测的, 在外部使用 个示波器或是逻辑分析仪来监控信号。 此外, Xlinx和Altera厂商提供的IDE中也提供了虚拟的逻辑分析仪来辅助调试。 不过, 辅助调试手段需要占用片内的存储器资源。
三 VGA
上面介绍图像处理算法的fpga实现的基本思路,现在通过搭建一个vga时序来实现搭建图像仿真平台的第一步。vga时序模拟图像算法的视频输入,第二步就是设计图像处理算法,如直方图统计。第三步把算法对图像数据的处理结果用matlab直观显示,也可以和matlab实现算法的处理结果对比。通过这三步就很直观显示图像输入输出的效果,对于验证图像算法的有效性很方便。
(一) 外部接口
1.VGA原理图及端口
上面是vga接口的电路图,可以看出总共五个信号,分成两类,一是控制VGA驱动的行同步信号VGA_HS(HSync) 和场同步信号(VSync);二是控制像素数据输出的RGB信号,根据vga接口和RGB的组合,常见RGB格式有RGB888和RGB565.
2. VGA扫描方式
显示是用逐行扫描的方式解决,阴极射线枪发出电子束打在涂有荧光粉的荧光屏上,产生RGB 三基色,合成一个彩色像素。扫描从屏幕的左上方开始,从左到右,从上到下,进行扫描,每扫完一行,电子束回到屏幕的左边下一行的起始位置,在这其间CRT 对电子束进行消隐。每行结束时,用行同步信号进行同步;扫描完所有行,用场同步信号进行同步,并使扫描回到屏幕左上方,同时进行场消隐,预备下一场的扫描。
(二) 640*480@60Hz的VGA实现
1.VESA显示标准
下面我们以实现640*480@60Hz的vga驱动来掌握VGA的实现参数和驱动。对于vga的驱动,首先有一个官方的标准,VESA视频标准显示手册,这是做视频显示的权威指南,下面我们给出这个分辨率的vesa标准:
首先是红色方框的图像有效显示的分辨率和帧数(一秒内显示的图像数),即640*480为有效区域和屏幕的总分辨率800*525区别,大致理解有效区域为总屏幕的一部分,这是理解重点,牵涉到后面的像素有效信号test_dvalid(de)的理解。像素的时钟由分辨率和帧率决定,pixel clock = (1/帧数)/屏幕分辨率(800*525)=25M左右
2. VGA屏幕一帧的时序参数图
其他的时序参数如行场同步,场前肩,场后肩,行左边界,行右边界。要结合下面的两张图才能深刻理解,光看上面的vesa标准还是很困惑这些时序参数的含义。
上面的第二张为从<VESA Display Monitor Timing Standard>手册中截取的屏幕一帧的时序参数图,从图中可知红框表示图像的有效区域640*480对应着它上面图片的图像显示区。绿框为整个显示屏幕,分辨率大小800*525为标准手册的Hor Total Time=800 pixels和Ver Total Time = 525 lines,分别表示总行时间,总列时间。分别以像素时钟的周期和行的时间为单位。这个分辨率对应着它上面图2的整个屏幕的大小800*525.故后面代码中的像素有效标志信号test_dvalid为图像像素在红框的有效区域时,行同步信号有效的值,表示输出的像素有效。
时序参数可以参考上面的解释。
3. VGA 显示的时序图
(三) VGA时序模拟的设计思路及代码
1.设计思路
上面是vga行场扫描的时序图,通过这个时序图结合我们上面的分析,就能编写Verilog代码驱动vga显示。行同步信号(Hsync)与场同步信号都可以每行/帧一开始拉高/低,场同步信号为每一帧的起始点。行同步信号则为每一行像素的起始点。设计思路,结合手册,先编写行计数器cnt_h和垂直计数器cnt_v,分别是计数到800-1,525-1,垂直计数器cnt_v是以每行计数完加1的。接着是像素有效信号test_dvalid,它是在有效区域640*480内,在像素时钟的驱动下拉高。场同步信号test_vsync_temp根据时序参数知为2lines的高电平,故在垂直计数器cnt_v在0-2范围则拉高,其余时间为零。故场同步信号表示每一帧的开始,即新帧的到来,它满足每一秒60帧,即场同步信号的周期为1/60秒,约为16.6毫秒,故可判读时序是否正确。
2. 代码
1 `timescale 1 ns/1 ns 2 3 `define SEEK_SET 0 4 `define SEEK_CUR 1 5 `define SEEK_END 2 6 //the simulation of image conform to the VESA stand 7 //so modified the parameters 8 //Timing Name = 640 x 480 @ 60Hz; 9 //acquired from vesa 10 module ima_src( 11 reset_l, 12 clk, 13 src_sel, 14 test_vsync, 15 test_dvalid,//pixel valid 16 test_data, 17 clk_out 18 ); 19 20 parameter iw = 640;//640*2 2 bytes per pixels 21 parameter ih = 480;//512 plus one command line 22 parameter dw = 8; 23 24 parameter h_total = 800;//Hor Total Time = 800 pixel 25 parameter v_total = 525 ;//Ver Total Time = 16.683; // (msec) = 525 lines 26 27 parameter sync_b = 2;//V Front Porch/ Ver Sync Time=2 lines 28 parameter sync_e = 2;//Ver Sync 29 parameter vld_b = (2+25+8);//V Back Porch=35 30 //Ver Addr Time =480 lines 31 parameter h_b = (96+40+8);//Hor Sync Time(96)+H Back Porch(40)+H Left Border(8) =144 32 input reset_l,clk;//clock,reset 33 input [3:0]src_sel;//to select the input file 34 output test_vsync,test_dvalid,clk_out; 35 output [dw-1:0]test_data; 36 37 reg [dw-1:0]test_data_reg; 38 reg test_vsync_temp; 39 reg test_dvalid_tmp; 40 reg [1:0]test_dvalid_r; 41 42 reg [10:0] h_cnt; 43 reg [10:0] v_cnt; 44 45 integer fp_r; 46 integer cnt=0; 47 48 assign clk_out = clk;//output the dv clk 49 50 assign test_data = test_data_reg;//test data output 51 52 //read data from file 53 54 always @(posedge clk or posedge test_vsync_temp ) 55 56 if ((((~test_vsync_temp))) == 1'b0) 57 cnt<=0;//clear file pointer when a new frame comes 58 else 59 begin 60 if (test_dvalid_tmp == 1'b1) 61 begin 62 case (src_sel) 63 4'b0000 :fp_r = $fopen("../poc/ln.txt","r"); 64 4'b0001 :fp_r = $fopen("../poc/ln.txt","r");//very error 65 4'b0010 :fp_r = $fopen("../poc/recovery/e_640x480_hex.txt","r"); 66 4'b0011 :fp_r = $fopen("txt_source/test_src3.txt", "r"); 67 4'b0100 :fp_r = $fopen("txt_source/test_src4.txt", "r"); 68 4'b0101 :fp_r = $fopen("txt_source/test_src5.txt", "r"); 69 4'b0110 :fp_r = $fopen("txt_source/test_src6.txt", "r"); 70 4'b0111 :fp_r = $fopen("txt_source/test_src7.txt", "r"); 71 4'b1000 :fp_r = $fopen("txt_source/test_src8.txt", "r"); 72 4'b1001 :fp_r = $fopen("txt_source/test_src9.txt", "r"); 73 4'b1010 :fp_r = $fopen("txt_source/test_src10.txt", "r"); 74 4'b1011 :fp_r = $fopen("txt_source/test_src11.txt", "r"); 75 4'b1100 :fp_r = $fopen("txt_source/test_src12.txt", "r"); 76 4'b1101 :fp_r = $fopen("txt_source/test_src13.txt", "r"); 77 4'b1110 :fp_r = $fopen("txt_source/test_src14.txt", "r"); 78 4'b1111 :fp_r = $fopen("txt_source/test_src15.txt", "r"); 79 default :fp_r = $fopen("../poc/ln.txt","r"); 80 endcase 81 82 $fseek(fp_r,cnt,0); 83 $fscanf(fp_r, "%02x\n", test_data_reg); 84 cnt <= cnt + 4 ; 85 $fclose(fp_r); 86 //$display("%02x",test_data_reg); //for debug use 87 end 88 end 89 90 //horizon counter 91 always @(posedge clk or posedge reset_l) 92 if (((~(reset_l))) == 1'b1) 93 h_cnt <= #1 {11{1'b0}}; 94 else 95 begin 96 if (h_cnt == ((h_total - 1))) 97 h_cnt <= #1 {11{1'b0}}; 98 else 99 h_cnt <= #1 h_cnt + 11'b00000000001; 100 end 101 102 //vertical counter 103 always @(posedge clk or posedge reset_l) 104 if (((~(reset_l))) == 1'b1) 105 v_cnt <= #1 {11{1'b0}}; 106 else 107 begin 108 if (h_cnt == ((h_total - 1))) 109 begin 110 if (v_cnt == ((v_total - 1))) 111 v_cnt <= #1 {11{1'b0}}; 112 else 113 v_cnt <= #1 v_cnt + 11'b00000000001; 114 end 115 end 116 117 //field sync 118 always @(posedge clk or posedge reset_l) 119 if (((~(reset_l))) == 1'b1) 120 test_vsync_temp <= #1 1'b1; 121 else 122 begin 123 if (v_cnt >= 0 & v_cnt < (sync_b )) 124 test_vsync_temp <= #1 1'b1; 125 else 126 test_vsync_temp <= #1 1'b0; 127 end 128 129 assign test_vsync = (test_vsync_temp); 130 131 //horizon sync 132 always @(posedge clk or posedge reset_l) 133 if (((~(reset_l))) == 1'b1) 134 test_dvalid_tmp <= #1 1'b0; 135 else 136 begin 137 if (v_cnt >= vld_b & v_cnt < ((vld_b + ih)-1)) 138 begin 139 if (h_cnt >= h_b & h_cnt < ((h_b + iw)-1)) 140 test_dvalid_tmp <= #1 1'b1; 141 else 142 test_dvalid_tmp <= #1 1'b0; 143 end 144 end 145 assign test_dvalid = test_dvalid_tmp; 146 147 always @(posedge clk or posedge reset_l) 148 if (((~(reset_l))) == 1'b1) 149 test_dvalid_r <= #1 2'b00; 150 else 151 test_dvalid_r <= #1 ({test_dvalid_r[0], test_dvalid_tmp}); 152 153 endmodule
明显的差别是数据的输出,这次的设计,通过文件操作来获得图像的数据,为了和matlab联合仿真。在硬件描述语言仿真平台中简单地载入图像文件和输出图像文件,那么对图像类处理的仿真将会带来极大的方便。方式通过用matlab把图像转换为txt文本,用Verilog的$fopen,$fclose,$fscanf,$fread,$fwrite等进行文件操作,注意点是只能用来仿真,不可综合。后面Verilog算法处理后存储为txt文本还能用来作为matlab的图像输入进行显示。这样很方便能验证算法的处理效果,不要很麻烦到硬件平台去观察。
3.MATLAB代码
通过matlab代码将图像转换为8比特的16进制形式作为设计的输入数据。MATLAB处理结果如下:
matlab功能:把640*480的lean原图转换640*480=307200个8比特的像素数据,存储为txt文本。后面modelsim仿真,打开这个文件作为vga模拟视频源的输入数据。
clc; clear; %% 数据获取 RGB = imread('lean640_480.bmp'); %rgb原始图像 GRAY = rgb2gray(RGB); %Matlab变换灰度图像 fid = fopen('./lean640_480_hex.txt','wt'); for i = 1:size(RGB,1) for j = 1:size(RGB,2) fprintf(fid,'%2x\n',RGB(i,j));%每个数据之间用空格分开 end end %% 画图显示 figure(1); subplot(1,3,1); imshow(RGB); title('lena原始图像'); subplot(1,3,2); imshow(GRAY); title('Matlab变换灰度图像');
再附上lean原图,可以调整为640*480,供图像处理算法使用,这是图像领域的经典,好好收藏!
(四) 仿真
1.testbench代码设计
1 `timescale 1ns/1ps 2 3 module tb_top; 4 5 //======================================================== 6 //parameters 7 parameter CLK_FREQ = 25.200;//ddr reference clock frequency, unit: MHz 8 parameter CLK_PERIOD = 1000.0/CLK_FREQ; //unit: ns 9 //pixel_total=h_total*v_total*60=25_200_000 10 // parameter FREQ = 100_000_000 ; 11 parameter sim_num = 5_000_000 ; 12 // parameter BAUDRATE = 115200 ; 13 //ima parameter 14 parameter iw = 640;//640*2 2 bytes per pixels 15 parameter ih = 480;//512 plus one command line 16 parameter dw = 8; 17 18 parameter h_total = 800;//Hor Total Time = 800 pixel 19 parameter v_total = 525 ;//Ver Total Time = 16.683; // (msec) = 525 lines 20 21 parameter sync_b = 2;//V Front Porch 22 parameter sync_e = 2;//Ver Sync 23 parameter vld_b = 25+8;//V Back Porch+V top Borch 24 25 //======================================================= 26 reg clk; // 50M 27 reg rst_n ; 28 reg [3:0] de ; 29 30 31 wire test_vsync; 32 wire test_dvalid; 33 wire[7:0] test_data; 34 wire clk_out; 35 36 37 //======================================================== 38 GSR GSR(.GSRI(1'b1)); 39 40 41 initial 42 begin 43 rst_n = 1'b0; 44 de =4'd0; 45 #200; 46 de = 4'd1; 47 rst_n = 1'b1; 48 49 50 51 end 52 53 //---------------------------------------------------- 54 //ref clk 55 initial 56 begin 57 clk = 1'b0; 58 end 59 60 always #(CLK_PERIOD/2.0) clk = ~clk; 61 62 //================================================== 63 //ima_src 64 //parameter must be connected to constant 65 ima_src inst_ima_src ( 66 .reset_l (rst_n), 67 .clk (clk), 68 .src_sel (de), 69 .test_vsync (test_vsync), 70 .test_dvalid (test_dvalid), 71 .test_data (test_data), 72 .clk_out (clk_out) 73 ); 74 75 endmodule
2.波形
从仿真波形看出一帧的时间为16.6ms左右,即场同步信号的周期, 符合每秒60帧的时序。并且像素数据在像素有效信号拉高是输出。
四 总结
一是至少看三种以上的参考资料。如本次学习搭建图像仿真平台,就是<<基于FPGA的数字图像处理原理及应用>>,然后是网上博客如咸鱼FPGA;V3学院就业办的基础课程第17讲VGA,OpensLee, 开源骚客,vesa标准手册等。
二是,循序渐进搭仿真。如刚开始是直接给时钟复位,让仿真跑起来。其次是理解别人的设计逻辑。这时候通过上面的各种资料,这一步可能花两三天,最后就是修改逻辑和不断仿真调试,直到符合vesa时序图。
三是,在实践过程中记录。遇到问题及解决过程。
参考资料:
1.<<基于FPGA的数字图像处理原理及应用>>
2.V3学院--第十七讲、VGA 接口驱动
3. vesa标准手册
4.https://www.cnblogs.com/huangwei0521/