第一次写这种技术博客,发现整理清楚思路,把想法清楚的表达出来还是挺困难的。在努力表达清楚的过程中,发现觉得逻辑不太清晰的原因可能是自己的理解根本就没有深入。这篇博文是自己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 : [email protected]
% 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
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
未完待续……