文章目录
- 一、Modelsim联合Matlab进行FPGA图像仿真的步骤
- 二、具体实现方法
- 2.1 新建一个Modelsim项目并编写测试代码
- 2.2 新建v文件来编写待测试代码
- 2.3 建成项目后,我们采用matlab生成txt文本文件
- 2.4 编译仿真并进行波形分析
- 2.5 通过Matlab将txt文档转换成图像
- 三、matlab出错点
- 四、采用该方法实现Ycbcr的灰度转换
- 4.1 搭建FPGA图像处理工程
- 4.2 图像生成模块代码
- 4.3 图像处理模块——Ycbcr灰度转换
- 4.4 顶层top模块
- 4.5 测试tb文件
- 五、仿真调试中出现的问题
本节主要来完成Modelsim联合Matlab进行FPGA图像仿真。
参考咸鱼FPGA的方法进行学习,很便捷。
我这里主要根据之前介绍的独立采用Modelsim仿真的方法,以及运用Matlab实现图像和文本之间转换的方法进行彩色图像转灰度的仿真测试。
在学习本节之前,需要掌握如何独立采用Modelsim进行仿真,能更便利的进行图像的仿真验证,不用担心quartus代码不可综合情况的出现。还需要掌握Matlab实现图像与txt文本之间的转换。这些之前博文中已经写过,这里不再赘述。
一、Modelsim联合Matlab进行FPGA图像仿真的步骤
如下是咸鱼FPGA给出的联合仿真原理图,清晰明了。
对步骤的分析如下:
1、先采用Matlab来对原图进行处理,来生成txt文档(post_img.txt)。——img_data_gen.m。
2、通过Modelsim建立工程,为测试项目top_tb。——top_tb.v。其中待测试模块为top.v(内部包含图像处理模块、图像产生模块(相当于VGA模块))。
3、对于测试模块中top_tb,我们可以进行仿真波形的查看。另外通过测试文件中的代码,我们能将VGA数据逐像素的写入到post_img.txt文档中。
4、基于post_img.txt文档,用Matlab处理,将txt文档转换成图片的形式。——— img_data_show.m
5、此时,就完成了matlab和modelsim的联合FPGA图像处理仿真。
二、具体实现方法
2.1 新建一个Modelsim项目并编写测试代码
首先新建一个project,将项目名设为top_tb,语言为verilog。——该top_tb为tb测试文件。
测试文件中主要包括:
2.2 新建v文件来编写待测试代码
在project的基础上,新建source v文件进行待测试代码的编写。
这里的待测试代码是一个彩色图像进行灰度转换的项目。包含三个模块:顶层模块,图像产生模块(相当于VGA模块),灰度转换图像处理模块
2.3 建成项目后,我们采用matlab生成txt文本文件
% 彩色图片转为txt文本,格式为24bit的hex数据
clc;
clear all;
%--------------------------------------------------------------------------
pre_img = imread('pre_img.jpg'); %读取原图图片文件
[ROW,COL,N] = size(pre_img); %获得图片尺寸[宽度,长度,维度]
RGB_ij = uint64(zeros(ROW,COL)); %定义32位宽的RGB变量
%--------------------------------------------------------------------------
fid = fopen('pre_img.txt','w'); %打开txt文档,使得后续数据的写入该文档
for i = 1:ROW
for j = 1:COL
R = double(pre_img(i,j,1));
G = double(pre_img(i,j,2));
B = double(pre_img(i,j,3));
%-------------------------------------
RGB = R*(2^16) + G*(2^8) + B;
RGB_ij(i,j) = RGB;
RGB_hex = dec2hex(RGB);
%-------------------------------------
fprintf(fid,'%s\n',RGB_hex); %将字符打印到txt文件
end
end
fclose(fid);
将生成的txt文本文件放入到与测试项目同一文件夹中。
2.4 编译仿真并进行波形分析
此时我们有了待测试的文件以及测试文件,和图像数据txt文档。即可Cmopile,成功后进行Simulate,选中我们的top_tb工程即可。
添加波形并进行查看:
我们放大前面部分进行验证。img_data表示原图转换成txt文档的数据,我们可以将其与pre_img.txt文档中的数据进行比对。可看到第一个是e4915f,第二个数据de8b59,以此类推……
VGA_data是我们经过灰度处理后的数据,在VGA_de有效的时候,将其逐像素的写入到了post_img_txt中。
2.5 通过Matlab将txt文档转换成图像
将生成的post_img_txt放到img_data_show.m同一文件夹下,进行txt文档与图像之间的转换。
% txt文本中24bit的数据还原为彩色图片
clear all;
clc;
%--------------------------------------------------------------------------
ROW = 480; %宽度
COL = 640; %长度
N = 3; %维度
post_img = uint8(zeros(ROW,COL,N));
%--------------------------------------------------------------------------
fid = fopen('post_img.txt','r'); %打开post_img.txt文档,并向里面写数据
for i = 1:ROW
for j = 1:COL
value = fscanf(fid,'%s',1);
%------------------------------------------
post_img(i,j,1) = uint8(hex2dec(value(1:2)));
post_img(i,j,2) = uint8(hex2dec(value(3:4)));
post_img(i,j,3) = uint8(hex2dec(value(5:6)));
end
end
fclose(fid);
%--------------------------------------------------------------------------
pre_img = imread('pre_img.jpg'); %处理前面的图像进行展示比对
subplot(121);imshow(pre_img), title('处理前');
subplot(122);imshow(post_img),title('处理后');
%--------------------------------------------------------------------------
imwrite(post_img,'处理后的图片.jpg'); %保存处理后的图片输出为jpg格式
至此,经过咸鱼FPGA大佬的资料学习,成功完成了Modelsim和Matlab的联合仿真。本例程的灰度化是采用RGB分量转灰度的方式进行的,根据上面步骤可尝试进行Ycbcr转灰度或者图像翻转多种图像处理操作等操作。
三、matlab出错点
matlab 出现 “文件标识符无效。使用 fopen 生成有效的文件标识符。”主要有以下两个原因:
1、可能是路径或者文件名错了(我这里是路径错误,路径修改后可正常运行)
2、可能是在c盘下的目录,因为没有管理员权限所以报错。
四、采用该方法实现Ycbcr的灰度转换
我这里根据上面的学习,对Ycbcr灰度转换进行仿真验证,在此之前需要掌握RGB到Ycbcr灰度转换的流水线技术。
4.1 搭建FPGA图像处理工程
该部分我们首先搭建FPGA进行灰度转换处理的工程。
框图如下:
根据上面的学习,逐模块的进行代码编写即可。
4.2 图像生成模块代码
`timescale 1 ns/1 ns
//该模块用来生成图像数据,根据此数据进行后续的图像算法处理
module img_gen
//========================< 参数 >==========================================
// 640x480 @60Hz 25Mhz
#(
parameter H_ADDR = 640 , //行有效数据
parameter H_SYNC = 96 , //行同步
parameter H_BACK = 48 , //行显示后沿
parameter H_TOTAL = H_ADDR+H_SYNC+H_BACK , //行扫描周期784
parameter V_ADDR = 480 , //场有效数据
parameter V_SYNC = 2 , //场同步
parameter V_BACK = 33 , //场显示后沿
parameter V_TOTAL = V_ADDR+V_SYNC+V_BACK //场扫描周期515
)
//========================< 端口 >==========================================
(
//系统全局信号
input wire clk , //时钟
input wire rst_n , //复位,低电平有效
//img output ----------------------------------------
output wire img_hsync , //img行同步信号
output wire img_vsync , //img场同步信号
output reg [23:0] img_data , //img原图像数据信号
output reg img_de //img数据有效指示信号
);
//========================< 信号 >==========================================
reg [15:0] cnt_h ; //行有效数据计数器
wire add_cnt_h ; //计数器使能信号
wire end_cnt_h ;
reg [15:0] cnt_v ;
wire add_cnt_v ;
wire end_cnt_v ;
//---------------------------------------------------
reg [23:0] ram [H_ADDR*V_ADDR-1:0] ;
reg [31:0] i ;
//==========================================================================
//== 行、场计数
//==========================================================================
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cnt_h <= 0;
else if(add_cnt_h) begin
if(end_cnt_h)
cnt_h <= 0;
else
cnt_h <= cnt_h + 1;
end
end
assign add_cnt_h = 1;
assign end_cnt_h = add_cnt_h && cnt_h==H_TOTAL-1;//计数到783清零,说明一行像素计数完成
//---------------------------------------------------
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cnt_v <= 0;
else if(add_cnt_v) begin
if(end_cnt_v)
cnt_v <= 0;
else
cnt_v <= cnt_v + 1;
end
end
assign add_cnt_v = end_cnt_h; //每计数完一行,即可实现一场的+1.
assign end_cnt_v = add_cnt_v && cnt_v==V_TOTAL-1; //计数到515清零,说明一帧图像完成
//==========================================================================
//== 输出
//==========================================================================
//行场同步信号
//---------------------------------------------------
assign img_hsync = (cnt_h <= H_SYNC - 1) ? 0 : 1;
assign img_vsync = (cnt_v <= V_SYNC - 1) ? 0 : 1;
//数据有效信号,相当于数据输出使能
//行:144-784;场:35- 515
assign img_req = (cnt_h >= H_SYNC + H_BACK - 1) && (cnt_h < H_SYNC + H_BACK + H_ADDR - 1) &&
(cnt_v >= V_SYNC + V_BACK ) && (cnt_v < V_SYNC + V_BACK + V_ADDR );
//读取txt文件到数组中,格式为16进制(相当于往存储器中存数据)
//---------------------------------------------------
//如果是用quartus进行rtl查看,就需要将这里引去,因为不可综合。
initial begin
$readmemh("pre_img.txt", ram);
end
//当数据有效的时候,从ram中将数据逐像素的输出,输出后供后面图像处理模块使用
//---------------------------------------------------
always@(posedge clk or negedge rst_n) begin
if(!rst_n)begin
img_data <= 24'd0;
i <= 0;
end
else if(img_req) begin
img_data <= ram[i];
i <= i + 1;
end
else if(i==H_ADDR*V_ADDR) begin //全部像素读完后进行清零。
img_data <= 24'd0;
i <= 0;
end
end
//数据使能延迟一拍(读数据需要花费1个clk,因此延迟一拍)
always @(posedge clk) begin
img_de <= img_req;
end
endmodule
4.3 图像处理模块——Ycbcr灰度转换
//RGB分量转Gray灰度图
module RGB_Gray
//========================< 端口 >==========================================
(
input wire clk , //时钟
input wire rst_n , //复位
//原图 ----------------------------------------------
input wire RGB_hsync ,
input wire RGB_vsync ,
input wire [23:0] RGB_data ,
input wire RGB_de ,
//灰度转换图 ----------------------------------------------
output wire gray_hsync , //这里的行场同步信号也就是最终的VGA行场同步信号。
output wire gray_vsync , //需要根据实际消耗的时钟进行延迟
output wire [23:0] gray_data ,
output wire gray_de
);
wire [7:0] R0,G0,B0;
reg [15:0] R1,G1,B1;
reg [15:0] R2,G2,B2;
reg [15:0] R3,G3,B3;
reg [16:0] Y1,Cb1,Cr1;
reg [23:0] Y2,Cb2,Cr2;
reg [7:0] RGB_de_r ;
reg [7:0] RGB_hsync_r ;
reg [7:0] RGB_vsync_r ;
//将24位RGB分成三分量
assign R0 = RGB_data[23:16];
assign G0 = RGB_data[15:8];
assign B0 = RGB_data[7:0];
//=============根据RGB转Ycbcr的公式进行计算(三级流水线)==================
//---------------------------------------------------
//clk 1 第一级流水线完成所有的乘法计算
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
{R1,G1,B1} <= {16'd0, 16'd0, 16'd0};
{R2,G2,B2} <= {16'd0, 16'd0, 16'd0};
{R3,G3,B3} <= {16'd0, 16'd0, 16'd0};
end
else begin
{R1,G1,B1} <= { {R0 * 16'd77}, {G0 * 16'd150}, {B0 * 16'd29 } };
{R2,G2,B2} <= { {R0 * 16'd43}, {G0 * 16'd85}, {B0 * 16'd128} };
{R3,G3,B3} <= { {R0 * 16'd128}, {G0 * 16'd107}, {B0 * 16'd21 } };
end
end
//---------------------------------------------------
//clk 2 第二级流水线完成所有的加减法计算
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
Y1 <= 16'd0;
Cb1 <= 16'd0;
Cr1 <= 16'd0;
end
else begin
Y1 <= R1 + G1 + B1;
Cb1 <= B2 - R2 - G2 + 16'd32768; //128扩大256倍
Cr1 <= R3 - G3 - B3 + 16'd32768; //128扩大256倍
end
end
//---------------------------------------------------
//clk 3 第三级流水线完成所有的移位计算(缩小256倍) 右移8位
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
Y2 <= 8'd0;
Cb2 <= 8'd0;
Cr2 <= 8'd0;
end
else begin
Y2 <= Y1 >> 8;
Cb2 <= Cb1>> 8;
Cr2 <= Cr1>> 8;
end
end
// 取YcbCr三分量中的Y分量,将其赋值给RGB888通道即可。
assign gray_data = {Y2[7:0],Y2[7:0],Y2[7:0]};
//=================== 信号同步==================================
//为确保图像能正常显示,要保持数据与数据使能和行场有效信号同步
//前面我们花费了三个clk来计算,因此延迟三拍
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
RGB_de_r <= 3'b0;
RGB_hsync_r <= 3'b0;
RGB_vsync_r <= 3'b0;
end
else begin
RGB_de_r <= {RGB_de_r[1:0], RGB_de};
RGB_hsync_r <= {RGB_hsync_r[1:0], RGB_hsync};
RGB_vsync_r <= {RGB_vsync_r[1:0], RGB_vsync};
end
end
assign gray_de = RGB_de_r[2];
assign gray_hsync = RGB_hsync_r[2];
assign gray_vsync = RGB_vsync_r[2];
endmodule
4.4 顶层top模块
`timescale 1 ns/1 ns
module top
//========================< 参数 >==========================================
//将parameter常量传给调用实例
#(
parameter IMG_H = 640 , //图像长度
parameter IMG_W = 480 //图像宽度
)
//========================< 端口 >==========================================
//全局时钟和复位
(
input wire clk , //50MHZ时钟
input wire rst_n , //低电平复位
//VGA 端口
output wire VGA_hsync , //VGA行同步
output wire VGA_vsync , //VGA场同步
output wire [23:0] VGA_data , //数据
output wire VGA_de //数据有效
);
//========================信号 >==========================================
wire img_hsync ;
wire img_vsync ;
wire [23:0] img_data ;
wire img_de ;
//========================< 模块实例化 >===================================
// 图像数据产生模块 (VGA模块)
img_gen
#(
.H_ADDR (IMG_H ), //图像长度(行有效数据)
.V_ADDR (IMG_W ) //图像宽度(场有效数据)
)
u_img_gen
(
.clk (clk ),
.rst_n (rst_n ),
.img_hsync (img_hsync ), //img输出行同步
.img_vsync (img_vsync ), //img输出场同步
.img_data (img_data ), //img输出数据,该数据是原图的像素数据
.img_de (img_de ) //img输出数据有效信号
);
//==========================================================================
//== RGB分量转Gray灰度图
//==========================================================================
RGB_Gray u_RGB_Gray
(
.clk (clk ),
.rst_n (rst_n ),
//原图 ------------------------------------------
.RGB_hsync (img_hsync ),
.RGB_vsync (img_vsync ),
.RGB_data (img_data ),
.RGB_de (img_de ),
//灰度图 ----------------------------------------
.gray_hsync (VGA_hsync ),
.gray_vsync (VGA_vsync ),
.gray_data (VGA_data ), //灰度处理后的图像像素数据
.gray_de (VGA_de )
);
endmodule
4.5 测试tb文件
`timescale 1ns/1ns //时间精度
`define Clock 20 //时钟周期
module top_tb;
//========================< 参数 >==========================================
parameter IMG_H = 640 ; //图像长度
parameter IMG_W = 480 ; //图像高度
//========================< 信号 >==========================================
reg clk ; //时钟,50Mhz
reg rst_n ; //复位,低电平有效
wire VGA_hsync ; //VGA行同步
wire VGA_vsync ; //VGA场同步
wire [23:0] VGA_data ; //数据
wire VGA_de ; //数据有效
//==========================================================================
//== 模块例化
//==========================================================================
top
#(
.IMG_H (IMG_H ),
.IMG_W (IMG_W )
)
u_top
(
.clk (clk ),
.rst_n (rst_n ),
.VGA_hsync (VGA_hsync ),
.VGA_vsync (VGA_vsync ),
.VGA_data (VGA_data ),
.VGA_de (VGA_de )
);
//==========================================================================
//== 产生时钟激励和复位激励
//==========================================================================
initial begin
clk = 1;
forever
#(`Clock/2) clk = ~clk;
end
initial begin
rst_n = 0; #(`Clock*20+1);
rst_n = 1;
end
//==========================================================================
//== 图像数据转变为txt文本
//==========================================================================
//打开post_img.txt文件
//---------------------------------------------------
integer post_img_txt;
initial begin
post_img_txt = $fopen("post_img.txt");
end
//像素写入到txt中
//---------------------------------------------------
reg [20:0] pixel_cnt; //640*480 = 307200 ,对这些像素逐个写入
always @(posedge clk) begin
if(!rst_n) begin
pixel_cnt <= 0;
end
else if(VGA_de) begin
pixel_cnt = pixel_cnt + 1;
$fdisplay(post_img_txt,"%h",VGA_data);
if(pixel_cnt == IMG_H*IMG_W)
$stop;
end
end
endmodule
有了这几个文件即可建成modelism项目,后进行编译仿真。得到波形以及运行的时间。最重要的得到了处理后的图像数据,将其放到matlab文件夹中,使用img_data_show.m程度进行txt文档到图像的转换即可。
这就是我们采用verilog编写图像处理程序,然后结合matlab得到的最终仿真波形和效果图,验证了算法的正确性,便于上板验证。
黄色线处起就是在一行一行的处理图像数据,最终完成一帧图像的处理,具体的数据分析可根据公式计算验证。
五、仿真调试中出现的问题
1、在Ycbcr公式编写正确的情况下,却不能正确计算R1,G1……
这里主要是位数出现了错误:
比如当我们进行乘法的时候,两个八位相乘是16位。加减法的时候仍然是16位。
2、在计算的时候Y2也错误
同样还是位数的问题,当十进制显示的时候都是0我们不好观察,将数据均设置成二进制,就能清楚的看到数据是有的,但是位宽不正确,导致右移8位,也就是取高8位时候都是0.因此要特别注意位数的问题。
如下是针对Ycbcr仿真的结果,我们可以根据公式进行局部数据的计算。
例如:
首先读入24位数据,根据我们三分量的分配,看是否正确。
//将24位RGB分成三分量
assign R0 = RGB_data[23:16];
assign G0 = RGB_data[15:8];
assign B0 = RGB_data[7:0];
当输入第一个RGB888时,像素值是111001001001000101011111
高八位11100100 = 228 正确 ——R0
中八位 1001 0001 = 145 正确——G0
低八位 01011111 = 95 正确 —— B0
R1 = 77 * R0 = 77 ……逐个计算G1、B1、Y1等,验证均正确。