基于FPGA的Sobel算法实现

基于FPGA的Sobel算法实现

第一次写这种技术博客,发现整理清楚思路,把想法清楚的表达出来还是挺困难的。在努力表达清楚的过程中,发现觉得逻辑不太清晰的原因可能是自己的理解根本就没有深入。这篇博文是自己2019年的第一个Flag,自己要立好了!


在基于FPGA的图像处理算法中,对于算法实现的验证有两种方法:

  • 使用显示器
  • 使用matlab

仿真图象生成

Python图像处理方案

找一张图,使用Python进行数据处理

  1. RGB转灰度图像;
  2. 对图像进行裁剪,得到640*512分辨率的图像;
  3. 把图像数据写入txt文件中;
# -*- 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图像处理平台搭建

这个小标题起的有点大,其实跟图像算法没有什么关系,不过是利用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);                            %%关闭文件

Modelsim仿真

  1. 使用$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.66666667‬ns
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

显示图片有个比较坑的地方是,最后使用imshow函数显示图片的时候,要使用imshow(mat,[])来把数据调整到合适的灰度值范围内,否则直接现实的话会出现整帧图像全是白色的诡异图像(诡异的地方是matlab工作区的变量显示的取值范围就是0~255,我猜测是imshow函数在处理数据的时候把所有的数据判断为255,但是原因是什么还不清楚)。

下面两个图是verilog输入输出数据的图像显示。

基于FPGA的Sobel算法实现_第1张图片

基于FPGA的Sobel算法实现_第2张图片

行缓冲器的verilog实现

盗用一下altera-ip-shift-register-usermanual中的一个图。

基于FPGA的Sobel算法实现_第3张图片
使用三个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 

未完待续……

你可能感兴趣的:(FPGA)