FPGA图像处理_图像仿真平台的搭建(含源码)

一个完善稳定的仿真测试系统对于图像处理算法的设计至关重要。这个测试系统至少要完成以下功能:

  1. 模拟可配置的视频流(单帧的视频视为一幅图像);
  2. 模拟视频捕获,生成视频数据;
  3. 对FPGA的处理结果进行验证。
    本文以960*480分辨率的图像为例,通过RGB转YCbCr的算法得到与彩色图像的Y分量灰度图像。仿真代码如下:
`timescale 1ns / 1ps
 
module picture_display_tb;
 
integer iBmpFileId;                 //输入BMP图片
integer oBmpFileId;                 //输出BMP图片     
integer iIndex = 0;                 //输出BMP数据索引
integer pixel_index = 0;            //输出像素数据索引 
        
integer iCode;      
integer iBmpSize;                   //输入BMP 字节数
   
reg [ 7:0] rBmpData [2000000:0];    //用于寄存输入BMP图片中的字节数据(包括54字节的文件头)
reg [ 7:0] Vip_BmpData [2000000:0]; //用于寄存视频图像处理之后 的BMP图片 数据 

reg clk;
reg rst_n; 
localparam pic_height = 480;
localparam pic_width = 960;
reg [ 7:0] vip_pixel_data [pic_height*pic_width*3-1:0];   //960x480x3

initial begin

    //分别打开 输入/输出BMP图片,以及输出的Txt文本
	iBmpFileId = $fopen("picture_960_480.bmp","rb"); //此处返回的并不是图像数组,而是一个文件描述符,专门指向此次打开文件的操作
//  iBmpFileId = $fopen("E:\\project\\modelsim\\bmp_sim_test\\PIC\\fengjing_320x240.bmp","rb");
	oBmpFileId = $fopen("output_file_1.bmp","wb+");
    //将输入BMP图片加载到数组中
	iCode = $fread(rBmpData,iBmpFileId);
    //根据BMP图片文件头的格式,分别计算出图片的 宽度 /高度 /像素数据偏移量 /图片字节数
	iBmpSize        = {rBmpData[ 5],rBmpData[ 4],rBmpData[ 3],rBmpData[ 2]};   //指的是图文件的大小,而不是专指图片的字节数  
    //关闭输入BMP图片
	$fclose(iBmpFileId);
    
    //延迟5ms,等待第一帧VIP处理结束
    #5000000    
    //加载图像处理后,BMP图片的文件头和像素数据
	for (iIndex = 0; iIndex < iBmpSize; iIndex = iIndex + 1) begin
		if(iIndex < 54)
            Vip_BmpData[iIndex] = rBmpData[iIndex];     //处理过后的图片文件头信息与原图像保持一致
        else
            Vip_BmpData[iIndex] = vip_pixel_data[iIndex-54];   //图像数据就需要重新赋值,vip_pixel_data就是存储的像素数据
	end
    
    for(iIndex = 0; iIndex < iBmpSize;iIndex= iIndex+1 )begin
        $fwrite(oBmpFileId,"%c",Vip_BmpData[iIndex]);
    end
    //关闭输出BMP图片
	$fclose(oBmpFileId);   
end
 
//初始化时钟和复位信号
initial begin
    clk     = 1;
    rst_n   = 0;
    #110
    rst_n   = 1;
end 

//产生50MHz时钟(周期是20ns)
always #5 clk = ~clk;
 
产生摄像头时序 

wire		cmos_vsync ;  //场同步信号
reg			cmos_href;
wire        cmos_clken;   //
reg	[23:0]	cmos_data;    //摄像头数据 

reg [31:0]  cmos_index;

parameter [10:0] IMG_HDISP = 11'd960;
parameter [10:0] IMG_VDISP = 11'd480;

localparam H_SYNC = 11'd5;		
localparam H_BACK = 11'd5;		
localparam H_DISP = IMG_HDISP;	
localparam H_FRONT = 11'd5;		
localparam H_TOTAL = H_SYNC + H_BACK + H_DISP + H_FRONT;	//5+5+320+5=335个时钟周期

localparam V_SYNC = 11'd1;		
localparam V_BACK = 11'd0;		
localparam V_DISP = IMG_VDISP;	
localparam V_FRONT = 11'd1;		
localparam V_TOTAL = V_SYNC + V_BACK + V_DISP + V_FRONT;    //1+0+240+1=242个行同步信号

//---------------------------------------------
//水平计数器
reg	[10:0]	hcnt;
always@(posedge clk or negedge rst_n) begin
	if(!rst_n)
		hcnt <= 11'd0;
	else
		hcnt <= (hcnt < H_TOTAL - 1'b1) ? hcnt + 1'b1 : 11'd0;
end

//---------------------------------------------
//竖直计数器,竖直计数器+1的判决条件就是一行的结束,计数的是行同步信号
reg	[10:0]	vcnt;
always@(posedge clk or negedge rst_n) begin
	if(!rst_n)
		vcnt <= 11'd0;		
	else begin
		if(hcnt == H_TOTAL - 1'b1)
			vcnt <= (vcnt < V_TOTAL - 1'b1) ? vcnt + 1'b1 : 11'd0;
		else
			vcnt <= vcnt;
    end
end

//---------------------------------------------
//场同步,场同步信号就是一帧的开始,当竖直计数器达到规定值后,就让场同步信号拉低
reg	cmos_vsync_r;
always@(posedge clk or negedge rst_n) begin
	if(!rst_n)
		cmos_vsync_r <= 1'b0;			//H: Vaild, L: inVaild
	else begin
		if(vcnt <= V_SYNC - 1'b1)     //V_SYNC为11'd1
			cmos_vsync_r <= 1'b0; 	//H: Vaild, L: inVaild
		else
			cmos_vsync_r <= 1'b1; 	//H: Vaild, L: inVaild
    end
end
assign	cmos_vsync	= cmos_vsync_r;


//---------------------------------------------
//Image data href vaild  signal,当frame_valid_ahead信号拉高时,说明此时的数据为有效的像素数据
wire	frame_valid_ahead =  ( vcnt >= V_SYNC + V_BACK  && vcnt < V_SYNC + V_BACK + V_DISP
                            && hcnt >= H_SYNC + H_BACK  && hcnt < H_SYNC + H_BACK + H_DISP ) 
						? 1'b1 : 1'b0;
      
reg			cmos_href_r;      
always@(posedge clk or negedge rst_n) begin
	if(!rst_n)
		cmos_href_r <= 0;
	else begin
		if(frame_valid_ahead)
			cmos_href_r <= 1;
		else
			cmos_href_r <= 0;
    end
end

always@(posedge clk or negedge rst_n) begin
	if(!rst_n)
		cmos_href <= 0;
	else
        cmos_href <= cmos_href_r;
end

assign cmos_clken = cmos_href;

//-------------------------------------
//从数组中以视频格式输出像素数据
wire [10:0] x_pos;
wire [10:0] y_pos;

assign x_pos = frame_valid_ahead ? (hcnt - (H_SYNC + H_BACK )) : 0;
assign y_pos = frame_valid_ahead ? (vcnt - (V_SYNC + V_BACK )) : 0;


always@(posedge clk or negedge rst_n)begin
   if(!rst_n) begin
       cmos_index   <=  0;
       cmos_data    <=  24'd0;
   end
   else begin
       cmos_index   <=  (y_pos * pic_width  + x_pos)*3 + 54;        //  3*(y*320 + x) + 54
       cmos_data    <=  {rBmpData[cmos_index], rBmpData[cmos_index+1] , rBmpData[cmos_index+2]};    //从rBmpData中读取数据时,索引低的是B,高的是R
   end
end

//-------------------------------------------------------------------------------------------------------------------
//开始算法部分的仿真
wire 		per_frame_vsync	=	cmos_vsync ;	
wire 		per_frame_href	=	cmos_href;	
wire 		per_frame_clken	=	cmos_clken;	
wire [7:0]	per_img_red		=	cmos_data[7:0];	   	
wire [7:0]	per_img_green	=	cmos_data[15: 8];   	            
wire [7:0]	per_img_blue	=	cmos_data[23:16];   

//算法1:RGB转YCbCr,得到Cb和Cr分量,消耗3个CLK   
wire 		post1_frame_vsync;   
wire 		post1_frame_href ;   
wire 		post1_frame_clken;  
wire [7:0]	post1_img_Y       ;   
wire [7:0]	post1_img_Cb      ;   
wire [7:0]	post1_img_Cr      ;
VIP_RGB888_YCbCr VIP_RGB888_YCbCr_inst(
    .clk  (clk) ,
    .rst_n(rst_n) ,
    
    //Image data prepred to be processd
	.per_frame_vsync (per_frame_vsync)  ,	//Prepared Image data vsync valid signal
	.per_frame_href  (per_frame_href)  ,		//Prepared Image data href vaild  signal
	.per_frame_clken (per_frame_clken)  ,	//Prepared Image data output/capture enable clock	
	.per_img_red  (per_img_red),		//Prepared Image red data to be processed
	.per_img_green(per_img_green),		//Prepared Image green data to be processed
	.per_img_blue (per_img_blue),		//Prepared Image blue data to be processed
	
	//Image data has been processd
	.post_frame_vsync (post1_frame_vsync) ,	//Processed Image data vsync valid signal
	.post_frame_href  (post1_frame_href) ,	//Processed Image data href vaild  signal
	.post_frame_clken (post1_frame_clken) ,	//Processed Image data output/capture enable clock	
	.post_img_Y (post1_img_Y),		//Processed Image brightness output
	.post_img_Cb(post1_img_Cb),	//Processed Image blue shading output
	.post_img_Cr(post1_img_Cr)	//Processed Image red shading output

    );
    
//输出转灰度之后的结果
wire 		vip_out_frame_vsync;   
wire 		vip_out_frame_href ;   
wire 		vip_out_frame_clken;    
wire [7:0]	vip_out_img_R     ;   
wire [7:0]	vip_out_img_G     ;   
wire [7:0]	vip_out_img_B     ;

assign vip_out_frame_vsync = post1_frame_vsync;   
assign vip_out_frame_href  = post1_frame_href ;   
assign vip_out_frame_clken = post1_frame_clken;    
assign vip_out_img_R = post1_img_Y;
assign vip_out_img_G = post1_img_Y;
assign vip_out_img_B = post1_img_Y;

reg [31:0] vip_cnt;
 
reg         vip_vsync_r;    //寄存VIP输出的场同步 
reg         vip_out_en;     //寄存VIP处理图像的使能信号,仅维持一帧的时间

always@(posedge clk or negedge rst_n)begin
   if(!rst_n) 
        vip_vsync_r   <=  1'b0;
   else 
        vip_vsync_r   <=  post1_frame_vsync;
end

reg    [7:0]   vip_vsync_cnt;
always@(posedge clk or negedge rst_n) begin
    if(!rst_n)
        vip_vsync_cnt   <=  8'd0;
    else if(post1_frame_vsync & (!vip_vsync_r))
        vip_vsync_cnt   <=  vip_vsync_cnt + 8'd1;
    else
        vip_vsync_cnt   <=  vip_vsync_cnt;
end

//选择第几帧图像数据输出
always@(posedge clk or negedge rst_n)begin
   if(!rst_n) 
        vip_out_en    <=  1'b0;
   else if(vip_vsync_cnt == 8'd1)  //只将第1帧数据写入文件
        vip_out_en    <=  1'b1;
   else if(vip_vsync_cnt == 8'd2)
        vip_out_en    <=  1'b0;
end

always@(posedge clk or negedge rst_n)begin
   if(!rst_n) begin
        vip_cnt <=  32'd0;
   end
   else if(vip_out_en) begin
        if(vip_out_frame_href & vip_out_frame_clken) begin
            vip_cnt <=  vip_cnt + 3;
            vip_pixel_data[vip_cnt+0] <= vip_out_img_B;
            vip_pixel_data[vip_cnt+1] <= vip_out_img_G;
            vip_pixel_data[vip_cnt+2] <= vip_out_img_R;
        end
   end
end
endmodule

仿真结果如图所示:
原图:
在这里插入图片描述
灰度图:
在这里插入图片描述

本人在编写代码时,对如下几点进行了考量:

  1. 图像的大小(分辨率)不同:当输入的图像大小与代码设定的图像大小不一致时,会报错,解决办法只需改动代码中两处声明的图像的高度与宽度值
  2. 图像的处理流程不同:以RGB转灰度算法和直方图均衡化算法为例,前者当像素数据“流过”时,能够直接对像素点的8bitR\G\B数据进行计算,得到灰度数据,只需要在规定时间有一帧完整的图像输入后,就可输出,而后者需要统计图像的灰度数据,因此只有当一帧图像数据完整“流过”时,统计完成后才能进行下一步图像处理,故输出的是图像处理过后的第一帧数据,而不是输入的第一帧图像,可能我的解释比较迷,但是加粗字体是关键,理解了,就可以在代码中initial块语句中改变延时时间,在输出完整的处理过后的图像前提下缩短仿真时间,提升效率。
    参考文献:牟新刚 周晓 郑晓亮《基于FPGA的数字图像处理原理及应用》
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值