赞
踩
第一次写这种技术博客,发现整理清楚思路,把想法清楚的表达出来还是挺困难的。在努力表达清楚的过程中,发现觉得逻辑不太清晰的原因可能是自己的理解根本就没有深入。这篇博文是自己2019年的第一个Flag,自己要立好了!
在基于FPGA的图像处理算法中,对于算法实现的验证有两种方法:
找一张图,使用Python进行数据处理
640*512
分辨率的图像;# -*- coding:UTF-8-*- from cv2 import cv2 import numpy as np import struct from matplotlib import pyplot as plt width = 640 hidth = 512 pix_bit = 16#每个像素点有8位 IMG_ORIGINAL = './../data/lena.jpg' IMG_GRAY = './../data/lena_gray.jpg' IMG_GRAY_640x512 = './../data/lena_gray_640x512.jpg' IMG_RESULT = './../data/lena_result.jpg' IMG_RESULT_REVERSE = './../data/lena_result_reverse.jpg' sim_file = './../data/lena.txt' post_file = './../sim/post_108.txt' def read_img(img_file): img1 = cv2.imread(img_file) return img1 def rgb2gray(img_file): img1 = cv2.cvtColor(img_file,cv2.COLOR_RGB2GRAY) return img1 def show_img(image): cv2.imshow('image',image) cv2.waitKey(0) cv2.destroyAllWindows() def plt_show_img(image): plt.imshow(image) plt.xticks([]) plt.yticks([]) plt.show() # 图像缩放函数 def img_resize(image,post_x,post_y): fx=1.6 fy=1.2 # post_image = cv2.resize(image,(0,0),fx=fx,fy=fy,interpolation=cv2.INTER_CUBIC) post_image = cv2.resize(image,(post_x,post_y)) return post_image def raw2Txt(image): np.savetxt(sim_file, image, fmt='%d', delimiter='\n') def loadTxt(filename,nx,ny): a = np.loadtxt(filename,dtype=np.float64,usecols=(0), unpack=True).reshape((nx,ny)) post_image_8bit = np.rint(a).astype(dtype='uint8') cv2.imwrite(IMG_RESULT,post_image_8bit) cv2.imwrite(IMG_RESULT_REVERSE,255-post_image_8bit) return post_image_8bit def main(): img = read_img(IMG_ORIGINAL)#原始图像读入 img2 = rgb2gray(img)#RGB转灰度图像 cv2.imwrite(IMG_GRAY,img2)#保存灰度图像 img3 = img_resize(img2,640,512)#分辨率剪裁 cv2.imwrite(IMG_GRAY_640x512,img3) show_img(img3) raw2Txt(img3)#生成测试数据 # img4 = loadTxt(post_file,hidth,width)#加载Modelsim中产生的进行Sobel处理的图片 # show_img(img4) # show_img(255-img4)#极性做反向 if __name__ == "__main__": main()
这个小标题起的有点大,其实跟图像算法没有什么关系,不过是利用MATLAB生成图像数据、使用verilog读入数据提供源数据、再使用MATLAB读入modelsim中产生的输出数据,最后显示。
基本思路是:
把一副图片转换为320x256分辨率的灰度图片,然后利用fprintf
函数把图像矩阵写入txt文件。
这个m文件涉及到的函数都很简单,写的时候一遍看help,一遍运行着看结果对不对。MATLAB源码如下:
% /*----------------------------------------------------------------------- % CONFIDENTIAL IN CONFIDENCE % This confidential and proprietary software may be only used as authorized % by a licensing agreement from zjl (). % In the event of publication, the following notice is applicable: % Copyright (C) 2013-20xx zjl Corporation % The entire notice above must be reproduced on all authorized copies. % Author : zjl % Technology blogs : % Email Address : i540113104@gmail.com % Filename : .m % Data : 2018-12-30 % Description : 1.可以输入任意尺寸的图片,输出图片分辨率为320x256 % 2.输出rgb、灰度图像 % 3.生成txt文件,在modelsim仿真时使用 % 4.生成mif文件,在例化IP核时使用 % Modification History : % Data By Version Change Description % ========================================================================= % 30/12/18 zjl 1.0 Original % -----------------------------------------------------------------------*/ clc clear all close all img_ori = imread('lena.jpg');%加载图像 img_320x256 = imresize(img_ori,[256,320]);% 把图片转换为320x256分辨率 img_320x256_gray = rgb2gray(img_320x256);%图像转换为灰度图像 imwrite(img_320x256_gray,'lena_gray_320x256.jpg'); % --------------------------------------------------------------------------- figure(1) subplot(1,3,1),imshow(img_ori); subplot(1,3,2),imshow(img_320x256); subplot(1,3,3),imshow(img_320x256_gray); % --------------------------------------------------------------------------- %320*256大小的灰度图像生成TXT文档 fid = fopen('./lena.txt','wt');%打开空文件 for i = 1 : size(img_320x256_gray, 1)%行循环 for j = 1 : size(img_320x256_gray, 2)%列循环 fprintf(fid, '%d ', img_320x256_gray(i, j));%每个数据之间用空格分开% end fprintf(fid, '\n');%文件末尾加一个换行 end fid = fclose(fid);%关闭文件 I_data = load('./lena.txt');%加载txt文件到matlab工作区-可不要 % --------------------------------------------------------------------------- %生成mif文件 [m,n] = size( img_320x256_gray );% m行 n列 N = m*n; %%数据的长度,即存储器深度。 word_len = 8; %%每个单元的占据的位数,需自己设定 data = reshape(img_320x256_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'); %% 指定address为十进制 fprintf(fid, 'DATA_RADIX = HEX;\n'); %% 指定data为十六进制 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); %%关闭文件
$readmemh
把txt文件中的图像数据导入数组中,并在tb文件中产生行场信号;//*********************************************** //Project Name :Sobel //File Name :tb_x2.v //Author :ZJL //Date of Creation :20190920 //Functional Description :图像数据仿真 // //Revision History : //Change Log : //*********************************************** `timescale 1ns/1ns module tb_x2; //------------------------------------------ // `define 1280_1024_30 // `ifdef 1280_1024_30 // parameter FRAME_RATE = 30; //帧频 // parameter RES_WIDTH = 1280; //分辨率:宽度 // parameter RES_HEIGHT = 1024; //分辨率:高度 // `endif // `else//def 320_256_50 // parameter FRAME_RATE = 50; //帧频 // parameter RES_WIDTH = 320; //分辨率:宽度 // parameter RES_HEIGHT = 256; //分辨率:高度 // `endif parameter FRAME_RATE = 50 ; //帧频 parameter RES_WIDTH = 640 ; //分辨率:宽度 // parameter RES_HEIGHT = 512 ; //分辨率:高度 parameter RES_HEIGHT = 514 ; //分辨率:高度 parameter PERIOD_50MHZ = 20 ; //50MHz parameter LINE_BLANK = 100 ; //消隐期周期数 parameter EXTRA_LINES = 10 ; //多输出的行数 parameter FRAME_PERIOD = 1_000_000_000/FRAME_RATE; //帧周期-16,666,666.66666667ns parameter TOTAL_PIXEL = (RES_WIDTH + LINE_BLANK); //多输出的行数-1330 parameter TOTAL_LINES = (RES_HEIGHT + EXTRA_LINES); //多输出的行数-1040 parameter TEMP1 = FRAME_PERIOD/20; //多输出的行数-1040 parameter TEMP2 = TOTAL_PIXEL * TOTAL_LINES; //多输出的行数-1040 parameter TEMP3 = TEMP1 - TEMP2; //多输出的行数-1040 parameter CNT_WAIT_TIME = (FRAME_PERIOD/20 - (TOTAL_PIXEL * TOTAL_LINES))/2;//多输出的行数 //信号列表 reg clk; //clock reg rst_n; //reset @high voltage reg in_pulse; //input signal reg i_start; //to streamscale wire [15:0] o_data; //data after scale wire o_dvalid; //data valid after scale wire [15:0] data_tmp; wire [15:0] data_tmp2; integer i; //counter for random task reg [15:0] rand_data1; //random data out reg [15:0] rand_data2; //random data out integer N = 512; //random seed reg [15:0] reg_mem[0:RES_WIDTH*RES_HEIGHT-1];//={0}; //! memory integer addr; //memory address reg [1-1:0] field_rst; wire [1-1:0] wait_done; reg [1-1:0] flag_image; wire [1-1:0] wait_time_add ; reg [1-1:0] en_cnt_wait_time ; reg [32-1:0] cnt_wait_time ; reg [12-1:0] hcnt ; reg [12-1:0] vcnt ; wire [1-1:0] h_valid ; wire [1-1:0] v_valid ; //------------------------------------------ //系统时钟 initial begin clk = 0; forever #(PERIOD_50MHZ/2) clk = ~clk; end //------------------任务------------------------ //*任务:系统初始化 task task_sysinit; begin in_pulse = 0; i_start = 0; field_rst = 0; end endtask //*任务:Generate global reset task task_reset; begin rst_n = 0; repeat(2) @(negedge clk); rst_n = 1; end endtask //产生帧复位信号 task task_field_rst; begin @(posedge clk); field_rst = 1; @(posedge clk); field_rst = 0; #(FRAME_PERIOD); @(posedge clk); field_rst = 1; @(posedge clk); field_rst = 0; end endtask //task of generate random bit signal //*任务:产生随机数 task task_rand_bit; begin begin for( i = 0; i < 255; i=i+1 )begin @( posedge clk ); rand_data1 = { $random } % N;//随机数取值范围[0,N-1] rand_data2 = { $random } % N;//随机数取值范围[0,N-1] end end end endtask //*任务:读取文件到内存 task load_data2mem; begin $readmemh("./../data/lena.txt",reg_mem); end endtask //-----------------存储器地址------------------------ //生成存储器地址 always @ (posedge clk)begin if(!rst_n) begin addr <= 0; end else if( h_valid )begin addr <= addr +1; end end assign data_tmp = h_valid?(reg_mem[addr]):0;//从内存中读出数据 assign data_tmp2 = { data_tmp[7:0],data_tmp[15:8] };//从内存中读出数据 //------------------行计数----------------------- assign wait_time_add = field_rst; always @ ( posedge clk ) begin if( ~rst_n ) begin cnt_wait_time <= 'd0; end else if( en_cnt_wait_time )begin//只有在有效才计数 cnt_wait_time <= cnt_wait_time + 'd1; end else begin cnt_wait_time <= 'd0; end end always @ ( posedge clk ) begin if( ~rst_n ) begin en_cnt_wait_time <= 'd0; end else if( cnt_wait_time == CNT_WAIT_TIME - 1)begin//结束计数条件 en_cnt_wait_time <= 'd0; end else if( wait_time_add )begin //开始计数条件 en_cnt_wait_time <= 'd1; end else begin en_cnt_wait_time <= en_cnt_wait_time; end end assign wait_done = ( cnt_wait_time == CNT_WAIT_TIME - 1); always @ ( posedge clk ) begin if( ~rst_n ) begin flag_image <= 'd0; end else if( vcnt == RES_HEIGHT-1 && hcnt == RES_WIDTH-1 )begin//结束计数条件 flag_image <= 'd0; end else if( wait_done )begin //开始计数条件 flag_image <= 'd1; end else begin flag_image <= flag_image; end end always @ ( posedge clk ) begin if( ~rst_n ) begin hcnt <= 'd0; end else if( flag_image )begin//结束计数条件 if( hcnt == TOTAL_PIXEL-1) hcnt <= 'd0; else hcnt <= hcnt + 'd1; end else begin hcnt <= 'd0; end end assign h_valid = ( flag_image && hcnt<RES_WIDTH ); assign v_valid = ( flag_image && vcnt<RES_HEIGHT ); always @ ( posedge clk ) begin if( ~rst_n ) begin vcnt <= 'd0; end else if( flag_image )begin//结束计数条件 if( hcnt == TOTAL_PIXEL-1) vcnt <= vcnt + 'd1; else vcnt <= vcnt; end else begin vcnt <= 'd0; end end //----------------------系统初始化------------------------ initial begin task_sysinit; task_reset; load_data2mem; #5000 i_start = 1; #(PERIOD_50MHZ*2) i_start = 0; // generate_frame; #100 @ ( posedge clk ) in_pulse = 1; @ ( posedge clk ) in_pulse = 0; task_field_rst; end //----------------------模块例化------------------------ wire[15:0] data00; wire[15:0] data01; wire[15:0] data02; wire[15:0] data10; wire[15:0] data11; wire[15:0] data12; wire[15:0] data20; wire[15:0] data21; wire[15:0] data22; wire data_valid; window_3x3 #( .PIX_PER_LINE( 640 ), .PIX_DOUBLE_LINE( 1280 ) )inst_window_3x3( .clock ( clk ), .frame_reset ( field_rst ), .datain ( data_tmp ), .datain_en ( h_valid ), .data00 ( data00 ), .data01 ( data01 ), .data02 ( data02 ), .data10 ( data10 ), .data11 ( data11 ), .data12 ( data12 ), .data20 ( data20 ), .data21 ( data21 ), .data22 ( data22 ), .data_valid ( data_valid ) ); wire [1-1:0] Dvld ; wire [16-1:0] Data ; sobel inst_sobel( .i_Clk ( clk ), .i_Rst_n ( field_rst ), .i_Frame_rst ( field_rst ), .i_Data_valid ( data_valid ), .i_Line0_0 ( data00 ), //previous line .i_Line0_1 ( data01 ), .i_Line0_2 ( data02 ), .i_Line1_0 ( data10 ), //current line .i_Line1_1 ( data11 ), .i_Line1_2 ( data12 ), .i_Line2_0 ( data20 ), //next line .i_Line2_1 ( data21 ), .i_Line2_2 ( data22 ), .o_Dvld ( Dvld ), .o_Data ( Data ) ); //---------------------------------------------- wire [9:0] start_h; wire [9:0] start_v; wire [9:0] end_h; wire [9:0] end_v; //----------------------系统函数------------------------ //将仿真数据o_data写入外部文件中 integer file_df; initial begin //文件放置在"工程目录\simulation\modelsim"路径下 file_df = $fopen("post_108.txt"); if(!file_df)begin $display("could not open file!"); $finish; end end always @(posedge clk) begin if( Dvld )//一帧图像数据 $fdisplay(file_df,"%d",Data); end endmodule
window_3x3
Sobel
运算模块仿真波形如下图所示:
注意:因为是在testbench中产生数据的,所以可以使用延时语句让行有效信号严格对齐数据,这样更容易实现。
verilog操作文本的函数比较多,$display
就够用了。
//----------------------系统函数------------------------
//将仿真数据o_data写入外部TXT文件中(x1.txt)
integer file_df;
initial begin
//文件放置在"工程目录\simulation\modelsim"路径下
file_df = $fopen("x1.txt");
if(!file_df)begin
$display("could not open file!");
$finish;
end
end
always @(posedge clk) begin
if( data_valid )//一帧图像数据
$fdisplay(file_df,"%d",out);
end
最后一步是MATLAB读入仿真数据,显示图片
% /*----------------------------------------------------------------------- % CONFIDENTIAL IN CONFIDENCE % This confidential and proprietary software may be only used as authorized % by a licensing agreement from zjl (). % In the event of publication, the following notice is applicable: % Copyright (C) 2013-20xx zjl Corporation % The entire notice above must be reproduced on all authorized copies. % Author : zjl % Technology blogs : % Email Address : i540113104@gmail.com % Filename : .m % Data : 2018-12-30 % Description : 1.读入tb文件中生成的txt文件; % 2.txt文件中的数据可以是十进制、也可以是十六进制,在tb中设置; % 3.使用imshow函数显示仿真数据的时候有个坑,数据导入matlab后,查看数据范围是0~255,但是数据类型与imshow函数使用的不同,直接使用imshow(img)的时候,imshow会多余的处理数据,处理结果就是:大于0的像素认为该点灰度为1,否则为零,直观的观察就是窗口像是图像为纯白色; % Modification History : % Data By Version Change Description % ========================================================================= % 30/12/18 zjl 1.0 Original % -----------------------------------------------------------------------*/ % fid = fopen('./x1.txt','rt'); fid = fopen('./lena.txt','rt'); img_tmp=fscanf(fid,'%x',inf);%以十进制数据读入数据到matlab工作区 % img_tmp2 = reshape(img_tmp, 254,320);% 1行N列 % img_tmp3 = imresize(img_tmp2,[256,320]); % img_tmp4 = round(img_tmp3); % img_320x256=uint8(img_tmp3); p=1; for i = 1 : 254 %行数,取决于生成的txt文件像素数目 for j = 1 : 320 %列数,设置为固定吧 img_tmp2(i,j) = img_tmp(p); p=p+1; end end fclose(fid); figure(1) imshow(img_tmp2,[]); %! 注意:加上[]配置函数自动处理数据类型 % imwrite(uint8(img_320x256),'file_out.tif') % imshow(img_320x256);
显示图片有个比较坑的地方是,最后使用imshow
函数显示图片的时候,要使用imshow(mat,[])
来把数据调整到合适的灰度值范围内,否则直接现实的话会出现整帧图像全是白色的诡异图像(诡异的地方是matlab工作区的变量显示的取值范围就是0~255,我猜测是imshow函数在处理数据的时候把所有的数据判断为255,但是原因是什么还不清楚)。
下面两个图是verilog输入输出数据的图像显示。
盗用一下altera-ip-shift-register-usermanual中的一个图。
使用三个FIFO-Core实现行缓冲器。
缓冲器的关键点在于读出条件信号的控制。
module window_3x3#( parameter PIX_PER_LINE = 320, parameter PIX_DOUBLE_LINE = 640 )( input clock, input frame_reset, input[15:0] datain, input datain_en, output reg[15:0] data00, output reg[15:0] data01, output reg[15:0] data02, output reg[15:0] data10, output reg[15:0] data11, output reg[15:0] data12, output reg[15:0] data20, output reg[15:0] data21, output reg[15:0] data22, output reg data_valid ); reg line_buf_rden; reg line_buf_rden_d1; wire[15:0] line_buf_dout; wire[15:0] line_dout00; wire[15:0] line_dout01; wire[9:0] line_buf_data_count; wire[9:0] line_data_count00; wire[9:0] line_data_count01; wire line_rden0; wire line_rden1; reg[9:0] line_buf_rd_cnt; reg[10:0] line_rden_cnt; reg line_data_valid; reg line_data_valid_d1,line_data_valid_d2; reg [9:0] hcnt; reg [9:0] vcnt; // assign line_rden0 = (line_data_count00 >= PIX_PER_LINE - 1) ? line_buf_rden : 1'b0; // assign line_rden1 = (line_data_count01 >= PIX_PER_LINE - 1) ? line_buf_rden : 1'b0; assign line_rden0 = (line_data_count00 >= PIX_PER_LINE ) ? line_buf_rden : 1'b0; assign line_rden1 = (line_data_count01 >= PIX_PER_LINE ) ? line_buf_rden : 1'b0; always @(posedge clock) begin if(frame_reset) line_buf_rden <= 1'b0; else if(line_buf_data_count >= PIX_PER_LINE) line_buf_rden <= 1'b1; else if(line_buf_rd_cnt == PIX_PER_LINE - 1) line_buf_rden <= 1'b0; end always @(posedge clock) begin if(frame_reset) line_buf_rd_cnt <= 10'd0; else if(line_buf_rden) line_buf_rd_cnt <= line_buf_rd_cnt + 1'b1; else line_buf_rd_cnt <= 10'd0; end always @(posedge clock) begin line_buf_rden_d1 <= line_buf_rden; end always @(posedge clock) begin if(frame_reset) line_rden_cnt <= 11'd0; else if(line_buf_rden) begin if(line_rden_cnt < PIX_DOUBLE_LINE) line_rden_cnt <= line_rden_cnt + 1'b1; else line_rden_cnt <= line_rden_cnt; end else line_rden_cnt <= line_rden_cnt; end always @(posedge clock) begin if(frame_reset) line_data_valid <= 1'b0; else if(line_rden_cnt == PIX_DOUBLE_LINE) line_data_valid <= line_buf_rden; else line_data_valid <= 1'b0; end always @(posedge clock) begin if(frame_reset) begin data00 <= 16'h0; data01 <= 16'h0; data02 <= 16'h0; data10 <= 16'h0; data11 <= 16'h0; data12 <= 16'h0; data20 <= 16'h0; data21 <= 16'h0; data22 <= 16'h0; line_data_valid_d1 <= 1'b0; line_data_valid_d2 <= 1'b0; end else begin // data22 <= (vcnt<510) ? line_buf_dout : line_dout01; data22 <= line_buf_dout; data21 <= data22; data20 <= data21; data12 <= line_dout00; // data12 <= (vcnt<511) ? line_dout00 : line_dout01; data11 <= data12; data10 <= data11; data02 <= line_dout01; // data02 <= (vcnt<512) ? line_dout01 : 0; data01 <= data02; data00 <= data01; line_data_valid_d1 <= line_data_valid; line_data_valid_d2 <= line_data_valid_d1; // data_valid <= line_data_valid_d2; data_valid <= line_data_valid_d1; // data_valid <= line_data_valid; end end fifo #(.DATA_W(16),.DEPT_W(2048) )inst_board_sync1( .aclr ( frame_reset ), .wrclk ( clock ), .wrreq ( datain_en ), .data ( datain ), .rdclk ( clock ),//24mhz from test-board pll .rdreq ( line_buf_rden ), .q ( line_buf_dout ), .wrusedw ( line_buf_data_count ) ); // enfifo1024x16 line_buf( // .aclr (frame_reset), // .clock (clock), // .data (datain), // .rdreq (line_buf_rden), // .wrreq (datain_en), // .q (line_buf_dout), // .usedw (line_buf_data_count) // ); fifo #(.DATA_W(16),.DEPT_W(2048) )inst_board_sync2( .aclr ( frame_reset ), .wrclk ( clock ), // .wrreq ( line_buf_rden_d1 ), .wrreq ( line_buf_rden ), .data ( line_buf_dout ), .rdclk ( clock ),//24mhz from test-board pll .rdreq ( line_rden0 ), .q ( line_dout00 ), .wrusedw ( line_data_count00 ) ); // enfifo1024x16 line_00( // .aclr (frame_reset), // .clock (clock), // .data (line_buf_dout), // .rdreq (line_rden0), // .wrreq (line_buf_rden_d1), // .q (line_dout00), // .usedw (line_data_count00) // ); fifo #(.DATA_W(16),.DEPT_W(2048) )inst_board_sync3( .aclr ( frame_reset ), .wrclk ( clock ), // .wrreq ( line_buf_rden_d1 ), .wrreq ( line_rden0 ), .data ( line_dout00 ), .rdclk ( clock ),//24mhz from test-board pll .rdreq ( line_rden1 ), .q ( line_dout01 ), .wrusedw ( line_data_count01 ) ); // enfifo1024x16 line_01( // .aclr (frame_reset), // .clock (clock), // .data (line_dout00), // .rdreq (line_rden1), // .wrreq (line_buf_rden_d1), // .q (line_dout01), // .usedw (line_data_count01) // ); reg data_valid_r1; always @ ( posedge clock ) begin if( frame_reset ) begin data_valid_r1 <= 1'd0; end else begin data_valid_r1 <= data_valid; end end always @ ( posedge clock ) begin if( frame_reset ) begin hcnt <= 'd0; end else if( data_valid)begin hcnt <= hcnt + 'd1; end else begin hcnt <= 'd0; end end always @ ( posedge clock ) begin if( frame_reset ) begin vcnt <= 'd0; end else if( data_valid_r1 && (~data_valid) )begin vcnt <= vcnt + 'd1; end else begin vcnt <= vcnt; end end endmodule
未完待续……
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。