2019-03-15 09:56:56 jiyotin 阅读数 874
  • 基于Simulink的FPGA代码自动生成技术

    课程主要讲解基于simulink的hdl coder模块组的使用方法,学会使用hdl coder搭建算法模型,校验模型并能自动生成可以下载到FPGA运行的Verilog或VHDL代码,学会testbench文件的自动生成和modelsim模型的验证。对于初学者,能掌握基于simulink的FPGA代码自动生成技术,会加速初学者开发复杂的FPGA算法的本领。

    6 人正在学习 去看看 顾华勇

该FPGA项目旨在详细说明如何使用Verilog处理图像,读取Verilog中的输入位图图像(.bmp),处理并将处理结果用Verilog写入输出位图图像。提供了用于读取图像,图像处理和写入图像的完整Verilog代码 。

在这个FPGA Verilog项目中,Verilog实现了一些简单的处理操作,例如反转,亮度控制和阈值操作。通过“parameter.v”文件选择图像处理操作,然后将处理后的图像数据写入位图图像output.bmp以进行验证。所述图像 Verilog读取代码作为图像传感器/摄像机的Verilog模型,它在实时FPGA图像处理项目中,对功能验证很有用。当您想要以BMP格式查看输出图像时,图像写入部分对于测试也非常有用。在这个项目中,我在阅读部分添加了一些简单的图像处理代码,以便进行图像处理,但您可以轻松删除它以获取原始图像数据。首先,Verilog无法直接读取图像,要在Verilog中读取.bmp图像,需要将图像从位图格式转换为十六进制格式。下面是一个Matlab示例代码,用于将位图图像转换为.hex文件。输入图像大小为768x512,图像.hex文件包含位图图像的R,G,B数据。

b=imread('kodim24.bmp'); % 24-bit BMP image RGB888 

k=1;
for i=512:-1:1 % image is written from the last row to the first row
for j=1:768
a(k)=b(i,j,1);
a(k+1)=b(i,j,2);
a(k+2)=b(i,j,3);
k=k+3;
end
end
fid = fopen('kodim24.hex', 'wt');
fprintf(fid, '%x\n', a);
disp('Text file write done');disp(' ');
fclose(fid);
% fpga4student.com FPGA projects, Verilog projects, VHDL projects

完整代码及验证结果,阅读国外课栈网“电子物语”专栏主题

2016-05-27 11:31:20 u013701860 阅读数 14621
  • 基于Simulink的FPGA代码自动生成技术

    课程主要讲解基于simulink的hdl coder模块组的使用方法,学会使用hdl coder搭建算法模型,校验模型并能自动生成可以下载到FPGA运行的Verilog或VHDL代码,学会testbench文件的自动生成和modelsim模型的验证。对于初学者,能掌握基于simulink的FPGA代码自动生成技术,会加速初学者开发复杂的FPGA算法的本领。

    6 人正在学习 去看看 顾华勇


FPGA verilog做基于模板的图像处理的方法

 

首先,在如今CPU和GPU具有极强计算能力的情况下,为什么还要考虑用FPGA做图像处理呢?我觉得原因可能有:1,FPGA可以通过硬件接口直接连相机,从而实现实时在线处理。而基于CPU和GPU需要操作相机厂商依赖的SDK,比较难做到实时在线。2,FPGA的计算特点能够很好的匹配某些图像处理模式,处理性能比CPU和GPU更强(比如基于固定模板的图像处理,也称二维卷积。图像与一个固定大小和内容的模板进行卷积,每次操作只涉及到卷积模板覆盖的局部像素区域,如均值、高斯滤波等等)。也就是说选用哪种处理器,还得看具体的应用,不同的处理器适合不同的处理场合。

 

对于FPGA,我觉得,学会语法仅仅是入门,而要在FPGA的世界里自由的翱翔,必须熟练掌握另外两种核心的思想:流水线(pipeline)和状态机(state machine)。有了这两个强大的工具,往往才能高效、高性能的处理复杂的应用。这里FPGA做图像处理,就要用到pipeline的方式。

首先简单介绍下pipeline是什么。对于一个计算f=(a+b)*(c+d),编写后的模块如图1所示。


图1

其中,每个方框指一个寄存器,+和x指相应的组合逻辑运算。如果你有好几组数据需要计算,不像C语言那样,第二组数据必须等待第一组数据计算完成之后才能进行。而流水线的模式则是:第一组a,b,c,d完成step1的加法运算后进入step2,这个时候step1的寄存器已经空出来,第二组数据即可马上进入。第一组数据结果进入到step3的时候,第二组数据已经进入step2,而第三组数据则可以进入step1进行计算。这样原始数据可以源源不断连续进行输入,无需等待前一次的数据计算完成。这个例子的代码如下:

<span style="font-size:14px;">module admul1 (
    input [15:0] ia,
    input [15:0] ib,
    input [15:0] ic,
    input [15:0] id,
 
    output [31:0] of,
 
 
    input clk,    // Clock
    input rst  // Synchronousreset active high
);
 
reg [15:0] a,b,c,d;
 
reg [16:0] ta1=0;
reg [16:0] ta2=0;
 
reg [31:0] f=0;
 
assign of = f;
 
always @(posedge clk ) begin
    // clk1
    a <= ia;
    b <= ib;
    c <= ic;
    d <= id;
 
    // clk2
    ta1 <= a + b;
    ta2 <= c + d;
 
    // clk3
    f <= ta1 * ta2;
end
 
endmodule</span>

言归正传,接下来说明FPGA如何做基于模板的图像处理,这里以一个3x3的均值滤波为例。在这之前,你必须了解相机的数据是怎么传到FPGA中的。图像是一个矩阵,如图2所示,这里假设该图像只有4x4个像素。


图2

但是由于像素的读出一般不是能够一下全部读出的,而是一个一个或者几个像素为一组,从一个输出端口加上行、场信号(Line valid, Frame valid)进行输出,类似图3,这里假设每次只读出一个像素.


图3

要实现基于模板的处理,我们需要一些移位寄存器来构成行缓冲器,一般都有相应的IP可用(如果没有好用的IP可以用FIFO自己搭建)。其效果如图4,特点是每个时钟右移一个像素,其长度刚好可以储存一行图像,不能多不能少。当第二行的图像的第一个像素开始传入时,第一行的第一个像素则开始移出。

图4

要实现3x3的均值滤波,需要3个行缓冲器加一些外部寄存器,完整实现框图如图5.


图5

特点是上一个行缓冲器的数据在流入下一个行缓冲器的时候,还要输出给外部的寄存器,用来获取当前位置的像素值。假设刚好图像的前三行像素填满了这三个行缓冲器,然后每个时钟每个行缓冲器移出一个像素,那么效果就是每一个时钟就实现了一次卷积。当然这个必须要结合行有效和帧有效信号来判断当前是不是处理的有效数据。该滤波代码如下所示,不包含line buffer的实现。

<span style="font-size:14px;">module AveFilter_core (
         // 3 pixel input
         input [47:0] imgLine3,
         input [47:0] imgLine2,
         input [47:0] imgLine1,
         //1 pixel
         output [15:0] opixel,
 
         input  ival, // keepSynchronous with imgLine2
         output oval,
 
         input clk,    // Clock
         input rst  //Synchronous reset active high
        
);
 
 
reg [15:0] DataLine1 [2:0];
reg [15:0] DataLine2 [2:0];
reg [15:0] DataLine3 [2:0];
 
// clk1
always @(posedge clk ) begin
         {DataLine3[2],DataLine3[1],DataLine3[0]}<=imgLine3;
         {DataLine2[2],DataLine2[1],DataLine2[0]}<=imgLine2;
         {DataLine1[2],DataLine1[1],DataLine1[0]}<=imgLine1;
end
 
// clk2
reg [16:0] AddDatL11[4:0];
 
// clk3
reg [17:0] AddDatL12[2:0];
 
// clk4
reg [18:0] AddDatL13[1:0];
 
// clk5
reg [18:0] AddDatL14=0;
 
// clk6
reg [24:0] AddDatL15=0;
 
// clk7
reg [15:0] AddDatL16=0;
 
 
// output valid
reg [6:0] validarry=0;
 
assign opixel   =      AddDatL16;
assign oval      =      validarry[6];
 
always @(posedge clk or posedgerst) begin
         if(rst) begin
                   validarry<=7'd0;
         end else begin
                   validarry<={validarry[5:0], ival};
         end
end
 
 
// clk2
always @(posedge clk ) begin
         AddDatL11[0] <= DataLine1[0] + DataLine1[1];
         AddDatL11[1] <= DataLine1[2] + DataLine2[0];
         AddDatL11[2] <= DataLine2[1] + DataLine2[2];
         AddDatL11[3] <= DataLine3[0] + DataLine3[1];
         AddDatL11[4] <= DataLine3[2] ;
end
 
// clk3
always @(posedge clk ) begin
         AddDatL12[0] <= AddDatL11[0] + AddDatL11[3];
         AddDatL12[1] <= AddDatL11[1] + AddDatL11[4];
         AddDatL12[2] <= AddDatL11[2] ;
 
end
 
// clk4
always @(posedge clk ) begin
         AddDatL13[0] <= AddDatL12[0] + AddDatL12[1];
         AddDatL13[1] <= AddDatL12[2] ; // for round off ofAddDatL15
end
 
// clk5
always @(posedge clk ) begin
         AddDatL14 <= AddDatL13[0] + AddDatL13[1];
end
 
// clk6
always @(posedge clk ) begin
         AddDatL15 <= AddDatL14*8'd57; //  9=512/57
end
 
// clk7
always @(posedge clk ) begin
         AddDatL16 <= AddDatL15[24:9]; //
end
 
/*
average filter
 
f1=[
         1      1      1
         1      1      1
         1      1      1      
 
]*1/9;
 
*/
 
Endmodule</span>

上面讨论的是每次读出一个像素的情况,但实际中以camera link接口为例,每个时钟可以同时传入5个像素,也就是说每次也必须同时处理5个像素的卷积运算。这就需要实例化5个滤波核心。以每次并行两个像素为例的实现框图如下,这需要两个滤波模块并行处理:


图6


参考文献

http://www.cnblogs.com/oomusou/archive/2008/08/25/verilog_sobel_edge_detector.html


Luchang Li

2016.05.27


2013-05-01 20:36:05 renshengrumenglibing 阅读数 21468
  • 基于Simulink的FPGA代码自动生成技术

    课程主要讲解基于simulink的hdl coder模块组的使用方法,学会使用hdl coder搭建算法模型,校验模型并能自动生成可以下载到FPGA运行的Verilog或VHDL代码,学会testbench文件的自动生成和modelsim模型的验证。对于初学者,能掌握基于simulink的FPGA代码自动生成技术,会加速初学者开发复杂的FPGA算法的本领。

    6 人正在学习 去看看 顾华勇
之前一直用Xilinx公司的SysGen搭建图像处理的算法,然后进行仿真,也可以直接编译下载到FPGA开发板上直接运行。但是算法实现之后却很难和其他模块一块使用。经过一段时间的反思,决定用Verilog直接写算法。

Verilog进行图像处理的难点:
1、图片获取
    C语言或者Matlab进行图像处理,图片的文件读取获取或者摄像头读取都能一个函数搞定,但是FPGA进行图像处理,如果从摄像头获取需要考虑很多时序问题,如果从存储区获取图片又不能太大,因为FPGA内部的RAM个数很有限,想存一张图片都比较困难。
//C
IplImage *src = cvLoandImage("lena.png",0);
//Matlab
src = imread('lena.png');

2、算法的编写
FPGA本身的并行性可以使处理速度大大提高,但是算法需要面对的时序问题很难解决
大部分的算法都需要对邻域进行操作,如中值滤波和均值滤波:
//中值滤波和均值滤波
#include<cv.h>
#include<highgui.h>

int main(){
	IplImage * image,*image2,*image3;
	image = cvLoadImage("E:\\image\\Dart.bmp",0);//以灰度图像的形式读入图片
	cvNamedWindow("image",CV_WINDOW_AUTOSIZE);
	cvNamedWindow("image2",CV_WINDOW_AUTOSIZE);
	cvNamedWindow("image3",CV_WINDOW_AUTOSIZE);
	//cvSaveImage("E:\\image\\moon.jpg",image,0);
	cvShowImage("image",image);
	//cvWaitKey(0);
	unsigned char * ptr,*dst;
	int i,j,m,n,sum,temp,r,s;
	image2 = cvCreateImage(cvGetSize(image),image->depth,1);
	image3 = cvCreateImage(cvGetSize(image),image->depth,1);
	//模板1 均值 
	int tem[9] = {1,1,1,1,1,1,1,1,1}; 
	//也可以使用改进的高斯模板,但是效果相近 
	int tem2[9] = {0};//获取中值时用于排序

	//均值滤波3*3模板的均值
	for( i = 0 ; i < image->height;i++){
		for( j = 0; j< image->width;j++){
			
			//边界处理
			if(i == 0 || i == image->height || j == 0 || j == image->width){
				ptr = (unsigned char *)image->imageData + i*image->widthStep + j;
				dst = (unsigned char *)image2->imageData+ i*image2->widthStep+ j;
			 	*dst = *ptr; //边界值赋予源图像的值
			}
			else {
				sum = 0;
				for( m = -1 ; m <= 1; m++  ){
					for( n = -1 ; n <= 1 ; n++){
						ptr = (unsigned char *)image->imageData + (i + m)*image->widthStep + j + n;
						
						sum += (*ptr) * tem[3*(m+1) + n+1];
					}
				}
				dst = (unsigned char *)image2->imageData+ i *image2->widthStep+ j;	
				*dst = (unsigned char)((sum +4)/9);//赋新值,四舍五入
			} 
		
		}
	}
//中值滤波 在去除噪声的同时,图像的模糊程度比较小,比均值滤波更加适合
//冲击噪声或者称为椒盐噪声
	for( i = 0 ; i < image->height;i++){
		for( j = 0; j< image->width;j++){

			//边界处理
			if(i == 0 || i == image->height || j == 0 || j == image->width){
				ptr = (unsigned char *)image->imageData + i*image->widthStep + j;
				dst = (unsigned char *)image3->imageData+ i*image3->widthStep+ j;
				*dst = *ptr; //边界值赋予源图像的值
			}
			else {
				temp = 0;
				//将3*3模板覆盖的值拷贝进数组,一边查找中值
				for( m = -1 ; m <= 1; m++  ){
					for( n = -1 ; n <= 1 ; n++){
						ptr = (unsigned char *)image->imageData + (i + m)*image->widthStep + j + n;
						tem2[3*(m+1) +n +1] = *ptr;
						//printf("%d",*ptr);
					
					}
				}
				//对数组进行冒泡排序
				for(r = 0 ; r <8; r ++){
					for(s = 0 ; s< r -1; s++ ){
						if(tem2[s] > tem2[s+1]){
							temp = tem2[s];
							tem2[s] = tem2[s+1];
							tem2[s+1] = temp;
						}
					}
				}
				//printf("%d",tem2[4]);
				//对新图赋予新值
				dst = (unsigned char *)image3->imageData+ i *image3->widthStep+ j;	
				*dst = (unsigned char)(tem2[4]);//赋新值
			} 

		}
	}

	cvShowImage("image2",image2);
	cvShowImage("image3",image3);
	cvWaitKey(0);
	cvSaveImage("E:\\image\\Dart2.bmp",image2,0);
	cvSaveImage("E:\\image\\Dart3.bmp",image3,0);
	return 0;
}

3、处理效果的显示
 FPGA处理速度快,但是能进行显示的方法却比较少,一般的FPGA开发板(指的是价格低廉的)虽然带有VGA接口,但是一般都是IO直驱,导致显示的颜色一般只有8种而已,都无法进行灰度图的显示


我试着写一些简单的处理算法,第一次写的是中值滤波,在解决问题的过程中发现其实并没有特别的复杂。
测试平台:黑金动力社区的FPGA开发板,cyclone IV 
1、图片的获取
虽然Verilog读取CMOS摄像头的源码网上很多,但是我还是希望算法测试阶段,图片数据是固定的。因此将图片压缩到32*32 = 1024个像素。
Cyclone IV的M9K RAM的大小刚好是1024Byte,图片大一些也是可以的,只是这里没有必要。

QuartusII提供了很多现成的模块,这里使用ROM模块来存储原始图片,ROM的初始化使用mif文件,mif文件制定了ROM的深度,位宽,以及数据等。至于mif文件怎么生成,首选当然是matlab了。
matlab生成mif文件:
%mcode to create a mif file
src = imread('lena.jpg');
gray = rgb2gray(src);
[m,n] = size( gray );                  % m行 n列

N = m*n;                               %%数据的长度,即存储器深度。
word_len = 8;                          %%每个单元的占据的位数,需自己设定
data = reshape(gray', 1, N);% 1行N列



fid=fopen('gray_image.mif', 'w');       %打开文件
fprintf(fid, 'DEPTH=%d;\n', N);
fprintf(fid, 'WIDTH=%d;\n', word_len);

fprintf(fid, 'ADDRESS_RADIX = UNS;\n'); %% 指定地址为十进制
fprintf(fid, 'DATA_RADIX = HEX;\n');    %% 指定数据为十六进制
fprintf(fid, 'CONTENT\t');
fprintf(fid, 'BEGIN\n');
for i = 0 : N-1
    fprintf(fid, '\t%d\t:\t%x;\n',i, data(i+1));
end
fprintf(fid, 'END;\n');                 %%输出结尾
fclose(fid);                            %%关闭文件

mif文件的格式大致如此:
DEPTH=1024;
WIDTH=8;
ADDRESS_RADIX = UNS;
DATA_RADIX = HEX;
CONTENT	BEGIN
	0	:	9e;
	1	:	97;
...................................
	1020	:	50;
	1021	:	65;
	1022	:	58;
	1023	:	3b;
END;

使用megaWizard生成一个ROM模块,
ROM模块的调用格式:
gray_image_ROM	gray_image_ROM_inst (
	.address ( address_sig ),
	.clock ( clock_sig ),
	.q ( q_sig )
	);

可以通过如下的方式逐个获取像素值:
	//rom 		
	reg [9:0] rd_addr = 10'b0_000_000_000;
	wire [7:0] raw_data; 
	//address increase
	always @(posedge rCLK_1Hz or negedge iRST_n)
		if(!iRST_n)
			rd_addr <= 10'b0_000_000_000;
		else if(rd_addr == 10'b1_111_111_111)
			rd_addr <= 10'b0_000_000_000;
		else rd_addr <= rd_addr + 1'b1;
	
	//read from rom
	gray_image_ROM	gray_image_ROM_inst 
	(
		.address ( rd_addr ),
		.clock ( rCLK_1Hz ),
		.q ( raw_data )
	);

raw_data就会每个周期更新一次,逐次将所有的数据流出。下面就是怎么处理了。


2、数据在ROM中,需要让数据一个一个的流出来,不能像C那样想用谁用谁了。
数据一个一个的流出,但是均值滤波需要的是邻域操作,需要每次知道一个邻域内的所有值。
假设中值滤波使用的邻域为3*3,那么就需要知道9个数据。
同一行相邻的数据可以通过设置多个寄存器获取。
同一列的相邻数据的获取可以使用一个行缓存LineBuffer获取,行缓存的头部是ROM中流出的数据,行缓存的3个末端是相邻的三行,这样就能每个周期得出相邻行的三个数据。
这样每个周期就能获取邻域内的9个数据。
如果邻域更大只需要调整行缓存的末端个数以及寄存器个数即可。
LineBuffer可以用megaWizard生成,调用格式如下:
shift_line_buffer	shift_line_buffer_inst (
	.clock ( clock_sig ),
	.shiftin ( shiftin_sig ),
	.shiftout ( shiftout_sig ),
	.taps0x ( taps0x_sig ),
	.taps1x ( taps1x_sig ),
	.taps2x ( taps2x_sig )
	);
可以通过如的方式获取邻域内的9个数据:
	wire	[7:0]	wData0;
	wire	[7:0]	wData1;
	wire	[7:0]	wData2;

	
	reg	[7:0]	wData0_d1,wData0_d2;
	reg	[7:0]	wData1_d1,wData1_d2;
	reg	[7:0]	wData2_d1,wData2_d2;
	
	//TODO 
	shift_line_buffer	S1	(
					.clock(wMeanFilter_clk),
					.shiftin(iData),
					.shiftout(),
					.taps2x(wData0),
					.taps1x(wData1),
					.taps0x(wData2)
				);
	//get data in the window
	always@(posedge wMeanFilter_clk or negedge iRST_n)
	begin
		if (!iRST_n)
			begin
				wData0_d1<=0;
				wData0_d2<=0;
				wData1_d1<=0;
				wData1_d2<=0;
				wData2_d1<=0;
				wData2_d2<=0;				
			end
		else
			begin
				{wData0_d2,wData0_d1}<={wData0_d1,wData0};
				{wData1_d2,wData1_d1}<={wData1_d1,wData1};
				{wData2_d2,wData2_d1}<={wData2_d1,wData2};
			end
	end	
此时wData0 wData1 wData2 wData0_d1 ,wData0_d2, wData1_d1,wData2_d1,wData2_d2即为邻域内的9个数据,可以随便进行处理了。

3、均值滤波算法
由于是第一次用Verilog写算法,不敢写太复杂的,上个简单的吧,均值滤波,由于FPGA不擅长算乘除法(感觉好弱啊),因此将算法稍微改进,变成加权的均值滤波,权值如下
1 2 1
2 4 2
1 2 1
乘法可以用移位代替,最后的除法(除数刚好是16哦亲)也可以用移位来代替。
算法模块:
module meanFilter
(	
	input [7:0] p00, 
	input [7:0] p01, 
	input [7:0] p02, 
	input [7:0] p10, 
	input [7:0] p11, 
	input [7:0] p12, 
	input [7:0] p20, 
	input [7:0] p21, 
	input [7:0] p22,
	output [7:0] oMeanVal
); 
	//weights
	//1 2 1
	//2 4 2
	//1 2 1
	wire [8:0] p01_w, p10_w, p12_w,p21_w;
	wire [9:0] p11_w;	
	wire [11:0] sum;
	
	wire [10:0] sum2;
	wire [10:0] sum1;
	assign p01_w = { p01, 1'b0};
	assign p10_w = { p10, 1'b0};
	assign p12_w = { p12, 1'b0};
	assign p21_w = { p21, 1'b0};	
	assign p11_w = { p11, 2'b0};	
	assign sum1 = p00 + p02 + p20 + p22 + p11_w;
	assign sum2 = p01_w + p10_w + p12_w + p21_w;
	// assign sum4 = p11_w;
	assign sum = sum1 + sum2;	
	assign oMeanVal = sum[11:4];
endmodule 	

4、处理效果的显示
本来打算用VGA或者LCD显示处理后的图片,但是由于我的板子比较屌丝,VGA接口只能显示8种颜色,LCD只能显示2种颜色,因此决定将数据发给上位机,在上位机进行显示。
串口速率比较低,可能很多数据来不及发送诶上位机,因此决定换个高端的,买了个USB2.0的模块,测试通信速度可以达到40Mb/s,这个速度我还是很满意的。
这个必须秀一下:

在算法和USB模块之间加一个FIFO,当FIFO不满时就计算并往里塞数据,当FIFO慢时就停止计算,这样就能够避免低丢数的问题。
为了随时记录算法是否在进行,加上一个数码管,显示处理之后的数据的低四位。
由于计算的频率比较高,数码管一直显示8,假如上位机不接收的话,当FIFO满时,停止计算,此时数码管就不动了。

5、对比数据
用matlab进行一次均值滤波,然后和FPGA均值滤波的结果进行比较看看效果如何:
第一行是上位机均值滤波的结果,第二行是FPGA进行均值滤波的结果。


上位机使用matlab处理的:
%mcode to create a mif file
src = imread('lena.jpg');
gray = rgb2gray(src);
imwrite(gray, 'gray.png');
[m,n] = size( gray );                  % m行 n列
dst = gray;
sum = 0;
gray = double(gray);
for i = 2 : m -1 %  i 行
    for j = 2 : n -1 %j 列
        sum = gray(i,j)*4 + gray(i, j -1)*2 + gray(i-1, j)*2 + gray(i, j + 1)*2 + gray(i +1, j) *2 + gray(i-1, j-1)+ gray(i-1,j+1) + gray(i+1, j-1) + gray(i +1, j+1);
        dst(i,j) = uint8(sum/16);
    end
end

%将数据写入文件
fid = fopen('meanFilter.txt','w');
for i = 1 : m
   for j = 1 :n
        fprintf(fid, '%x ',dst(i,j));
   end
end
fclose(fid);
imwrite(dst, 'meanFilter.png');

注意到有些数据并不一致,有两个原因,一是Matlab处理时会有舍入,二是二者对边界的处理方式不同。



2018-08-26 21:46:04 qq_40261818 阅读数 3052
  • 基于Simulink的FPGA代码自动生成技术

    课程主要讲解基于simulink的hdl coder模块组的使用方法,学会使用hdl coder搭建算法模型,校验模型并能自动生成可以下载到FPGA运行的Verilog或VHDL代码,学会testbench文件的自动生成和modelsim模型的验证。对于初学者,能掌握基于simulink的FPGA代码自动生成技术,会加速初学者开发复杂的FPGA算法的本领。

    6 人正在学习 去看看 顾华勇

项目:【基于FPGA verilog 边缘检测图像处理sobel算子实现

项目介绍:Sobel 边缘检测算子是一阶导数,Sobel边缘检测算法的实现是利用3*3个上下左右相邻的像素点进行计算的,根据上下左右像素点的值计算出图像的水平和垂直的梯度,然后将水平和垂直梯度根据对应的算法计算出梯度值,如果这个梯度值大于设定的阀值,那么这个像素点就是边缘部分,否则就认为该像素点不是图像的边缘部分。如图1为3×3区域相邻的像素点,图2为Sobel水平梯度算子,图3为Sobel垂直梯度算子。

GX = (G3+2 *G6 + G9 ) - (G1 +2 *G4 + G7 )

GY = (G7+2* G8 + G9 ) - (G1 +2* G2 + G3 )

| G | =(GX^2+ GY^2)^1/2

判断| G |是否大于阈值,若大于阈值让他为FF否则为00.

项目构架:

RTL图:

结果展示:

                 原图

 

 

 

                 结果图

因为阈值不同,所以显示的效果不一样,有兴趣的同学可以调一下阈值自己实现一下。

 

本文为原创博客,未经允许请勿盗用谢谢!  转载请标注地址:https://blog.csdn.net/qq_40261818/article/details/82084701

2018-04-15 11:18:55 jayash 阅读数 446
  • 基于Simulink的FPGA代码自动生成技术

    课程主要讲解基于simulink的hdl coder模块组的使用方法,学会使用hdl coder搭建算法模型,校验模型并能自动生成可以下载到FPGA运行的Verilog或VHDL代码,学会testbench文件的自动生成和modelsim模型的验证。对于初学者,能掌握基于simulink的FPGA代码自动生成技术,会加速初学者开发复杂的FPGA算法的本领。

    6 人正在学习 去看看 顾华勇

版权所有:转载请注明 https://blog.csdn.net/jayash/article/details/79947314

      基于FPGA的图像处理中,rtl代码的仿真验证一直是重中之重, 之前也在我们的书中《基于FPGA的数字图像处理原理及应用》(电子工业出版社)中提出了基于VC和verilog的仿真验证平台。该验证平台仅能提供简单的图像数据的交互,并且需要VC平台的交互。VC可提供较好的UI功能,但是对于一些中间结果的验证还是比较麻烦。最近刚好学习了system verilog,其面对对象的设计特性很类似于C++,可完整实现更加方便和功能更加全面的testbench平台。

        今天主要介绍bmp文件的读取及解析。bmp文件的格式详解可参考以下链接:

        https://www.cnblogs.com/l2rf/p/5643352.html

        bmp文件解析主要是bmp文件头的解析以及图像数据内容的重定位。bmp文件头主要类型定义如下:

typedef struct{
        u32   biSize;
        u32   biWidth;
        u32   biHeight;
        u16   biPlanes;
        u16   biBitCount;
        u32   biCompression;
        u32   biSizeImage;
        u32   biXPelsPerMeter;
        u32   biYPelsPerMeter;
        u32   biClrUsed;
        u32   biClrImportant;
    } BITMAPINFOHEADER;

    typedef struct {
        u8    rgbBlue;
        u8    rgbGreen;
        u8    rgbRed;
        u8    rgbReserved;
    } RGBQUAD;


    typedef struct {
        BITMAPINFOHEADER    bmiHeader;
        RGBQUAD             bmiColors[1];
    } BITMAPINFO;

    typedef struct  {
        u16    bfType;
        u32    bfSize;
        u16    bfReserved1;
        u16    bfReserved2;
        u32    bfOffBits;
    } BITMAPFILEHEADER;

        bmp文件主要有两个header,包括一个14字节的header BITMAPFILEHEADER,包含了bmp文件头信息,以及 一个40字节的header BITMAPINFOHEADER,包含了bmp文件的图像相关信息。

        解析的主要要点有:

    1 内存存放问题:分辨率不为4的倍数的每一行之后会补零,保证每一行的占用内存是4的倍数;

    2 数据组织问题:bmp的组织原则是首先存放最后一行,接着倒数第二行,以此类推,解析时需还原;

    解析代码如下:(代码为system verilog)

class spk_bmp;
    
    function void trans_16(reg [15:0] _reg,ref u16 dat);
        dat = (_reg[7:0]<<8) + _reg[15:8];
    endfunction
    
    function void trans_32(reg [31:0] _reg,ref u32 dat);
        dat = (_reg[7:0]<<24)+ (_reg[15:8]<<16)+ (_reg[23:16]<<8) + _reg[31:24];
    endfunction
    
    function  int LoadVectorFromBMPFile(string name,ref u8 data[], ref u16 height,ref u16 width,ref u16 flag);
        u32 skip,position;
        u32 dwSize;
	    u32 dReadWidth;
        reg [8*14-1:0] header1;
        reg [8*40-1:0] header2;
        reg [8-1:0]    data_tmp; 
        u32 off = 8*14-1;
        BITMAPFILEHEADER bfh;
        BITMAPINFO bmi;
        
        //判断文件是否存在
        int fp = $fopen(name, "r");
        if(!fp) begin 
            $display("Error: can not open file: %s!",name);
            $stop;
            return -1;
        end 
       
        //读header1
        $fread(header1,fp,0,14); off = 14*8 - 1;
        trans_16(header1[off -: 2*8],bfh.bfType);       off -= 2*8;
        trans_32(header1[off -: 4*8],bfh.bfSize);       off -= 4*8;
        trans_16(header1[off -: 2*8],bfh.bfReserved1);  off -= 2*8;
        trans_16(header1[off -: 2*8],bfh.bfReserved2);  off -= 2*8;
        trans_32(header1[off -: 4*8],bfh.bfOffBits);    off -= 4*8;
        
        if(bfh.bfType != 16'h4d42) begin  
            $display("Error: '%s' is not a bmp file!",name);
            $stop;
            return -1;
        end 
        
        //读header2
        $fread(header2,fp,14,40); off = 40*8 - 1;
        trans_32(header2[off -: 4*8],bmi.bmiHeader.biSize);          off -= 4*8;
        trans_32(header2[off -: 4*8],bmi.bmiHeader.biWidth);         off -= 4*8;
        trans_32(header2[off -: 4*8],bmi.bmiHeader.biHeight);        off -= 4*8;
        trans_16(header2[off -: 2*8],bmi.bmiHeader.biPlanes);        off -= 2*8;
        trans_16(header2[off -: 2*8],bmi.bmiHeader.biBitCount);      off -= 2*8;
        trans_32(header2[off -: 4*8],bmi.bmiHeader.biCompression);   off -= 4*8;
        trans_32(header2[off -: 4*8],bmi.bmiHeader.biSizeImage);     off -= 4*8;
        trans_32(header2[off -: 4*8],bmi.bmiHeader.biXPelsPerMeter); off -= 4*8;
        trans_32(header2[off -: 4*8],bmi.bmiHeader.biYPelsPerMeter); off -= 4*8;
        trans_32(header2[off -: 4*8],bmi.bmiHeader.biClrUsed);       off -= 4*8;
        trans_32(header2[off -: 4*8],bmi.bmiHeader.biClrImportant);  off -= 4*8;
        
        height = bmi.bmiHeader.biHeight;
        width  = bmi.bmiHeader.biWidth;
        flag   = bmi.bmiHeader.biBitCount; 
        
        if(flag == 24) width *= 3; //真彩图
		dwSize     = height * width; 
        dReadWidth = (width+3)/4*4;         
        data       = new[dwSize];
        skip       = dReadWidth - width; 
        position   = dwSize - width;
        off        = bfh.bfOffBits; //第一行图像数据偏移 
        
        $fseek(fp,off,0);
        for(int i = 0;i < height;i++) begin 
            for(int j = 0;j < width;j++) begin
                $fread(data_tmp,fp,off,1); 
                data[position] = data_tmp;
                off++;
                $fseek(fp,off,0);
                position++;
            end 
            position -= 2*width;
            off += skip;            
        end 
        
        $fclose(fp);
        if(flag==24) width /= 3;
    endfunction

endclass : spk_bmp
                                
没有更多推荐了,返回首页