赞
踩
参考咸鱼FPGA的方法进行学习,很便捷。
我这里主要根据之前介绍的独立采用Modelsim仿真的方法,以及运用Matlab实现图像和文本之间转换的方法进行彩色图像转灰度的仿真测试。
在学习本节之前,需要掌握如何独立采用Modelsim进行仿真,能更便利的进行图像的仿真验证,不用担心quartus代码不可综合情况的出现。还需要掌握Matlab实现图像与txt文本之间的转换。这些之前博文中已经写过,这里不再赘述。
如下是咸鱼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图像处理仿真。
首先新建一个project,将项目名设为top_tb,语言为verilog。——该top_tb为tb测试文件。
测试文件中主要包括:
在project的基础上,新建source v文件进行待测试代码的编写。
这里的待测试代码是一个彩色图像进行灰度转换的项目。包含三个模块:顶层模块,图像产生模块(相当于VGA模块),灰度转换图像处理模块
% 彩色图片转为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文本文件放入到与测试项目同一文件夹中。
此时我们有了待测试的文件以及测试文件,和图像数据txt文档。即可Cmopile,成功后进行Simulate,选中我们的top_tb工程即可。
添加波形并进行查看:
我们放大前面部分进行验证。img_data表示原图转换成txt文档的数据,我们可以将其与pre_img.txt文档中的数据进行比对。可看到第一个是e4915f,第二个数据de8b59,以此类推……
VGA_data是我们经过灰度处理后的数据,在VGA_de有效的时候,将其逐像素的写入到了post_img_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 出现 “文件标识符无效。使用 fopen 生成有效的文件标识符。”主要有以下两个原因:
1、可能是路径或者文件名错了(我这里是路径错误,路径修改后可正常运行)
2、可能是在c盘下的目录,因为没有管理员权限所以报错。
我这里根据上面的学习,对Ycbcr灰度转换进行仿真验证,在此之前需要掌握RGB到Ycbcr灰度转换的流水线技术。
该部分我们首先搭建FPGA进行灰度转换处理的工程。
框图如下:
根据上面的学习,逐模块的进行代码编写即可。
`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
//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
`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
`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等,验证均正确。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。