FPGA图像处理_OTSU算法的实现(含源码)

  由于图像阈值的直观性和易于实现的性质,使它在图像分割应用中处于中心地位。阈值分割方法实际上是输入图像 f f f到输出图像 g g g的变换,如式所示。
g ( i , j ) = { 1 , f(i,j)>=T 0 , f(i,j)=T} \\ 0, & \text{f(i,j)g(i,j)={1,0,f(i,j)>=Tf(i,j)
  其中 T T T为阈值,对于物体的图像元素 g ( i , j ) = 1 g(i,j)=1 g(i,j)=1,对于背景的图像元素 g ( i , j ) = 0 g(i,j)=0 g(i,j)=0
  由此可见,阈值分割算法的关键是确定阈值。如果能够确定一个合适的阈值就可准确地将图像分割开来。阈值确定后,将阈值与像素点的灰度值逐个进行比较,并且像素分割可对各像素并行地进行,分割的结果直接给出图像区域。阈值分割的有点是计算简答,运算效率高、速度快,它在重视运算效率的应用场合(如用于硬件实现)得到了广泛应用。阈值分割技术又可分为全局阈值分割和局部自适应阈值分割。本文重点介绍全局阈值分割中的最大类间方差分割算法(OTSU)。
  OSTU算法是按图像的灰度特性,将图像分成背景和目标两部分。背景和目标之间的类间方差越大,说明构成图像的两部分的差别越大,当部分目标错分成背景或者部分背景错分成目标都会导致两部分差别越小。因此,使类间方差最大的分割意味着错分概率最小。
对于图像 I ( x , y ) I(x,y) I(x,y),前景(目标)和背景的分割阈值记作 T T T,属于前景的像素点数占整幅图像的比例记为 w 0 w_0 w0,其平均灰度为 μ 0 \mu_0 μ0;背景像素点数占整幅图像的比例为 w 1 w_1 w1,其平均灰度为 μ 1 \mu_1 μ1。图像的总平均灰度记为 μ \mu μ,类间方差记为 g g g。假设背景的背景较暗,并且图像的大小为 M × N M\times N M×N,图像中像素的灰度值小于阈值 T T T的像素个数记作 N 0 N_0 N0。若把像素灰度大于阈值 T T T的像素个数记作 N 1 N_1 N1,则有
w 0 = N 0 / M × N w 1 = N 1 / M × N N 0 + N 1 = M × N w 0 + w 1 = 1 μ = w 0 × μ 0 + w 1 × μ 1 g = w 0 ( μ 0 − μ ) 2 + w 1 ( μ 1 − μ ) 2 \begin{align} w_0& = N_0/M\times N & \tag 1 \\ w_1 & = N_1/M\times N & \tag 2 \\ N_0 + N_1 & = M\times N &\tag 3 \\ w_0 + w_1 & = 1&\tag 4 \\ \mu& = w_0\times\mu_0 + w_1\times\mu_1 &\tag 5 \\ g& = w_0(\mu_0-\mu)^2 + w_1(\mu_1-\mu)^2 &\tag 6 \\ \end{align} w0w1N0+N1w0+w1μg=N0/M×N=N1/M×N=M×N=1=w0×μ0+w1×μ1=w0(μ0μ)2+w1(μ1μ)2(1)(2)(3)(4)(5)(6)
将式(4)代入式(5)得到等价公式:
g = w 0 w 1 ( μ 0 − μ 1 ) 2 (7) g = w_0w_1(\mu_0-\mu_1)^2\tag7 g=w0w1(μ0μ1)2(7)
  采用遍历的方法即可得到使类间方差最大的阈值 T T T,即为所求值。
  以上就是OTSU算法的基本原理,我们需要将公式改写,从而更适合移植到FPGA平台。现添加设定,假设 n k n_k nk为图像中出现第 k k k级灰度级的像素数, L L L为图像具有的灰度级总数,则公式改为
g = ( ∑ k = 0 T n k ) ( ∑ k = T L − 1 n k ) ( ∑ k = 0 T k × n k ∑ k = 0 T n k − ∑ k = T L − 1 k × n k ∑ k = T L − 1 n k ) 2 (8) g = (\sum_{k=0}^Tn_k ) (\sum_{k=T}^{L-1}n_k )(\frac {\sum_{k=0}^Tk\times n_k}{\sum_{k=0}^Tn_k} -\frac {\sum_{k=T}^{L-1}k\times n_k}{\sum_{k=T}^{L-1}n_k})^2\tag8 g=(k=0Tnk)(k=TL1nk)(k=0Tnkk=0Tk×nkk=TL1nkk=TL1k×nk)2(8)
  由公式(8)不难看出, ∑ k = 0 T n k \sum_{k=0}^Tn_k k=0Tnk为累积直方图统计, ∑ k = T L − 1 n k \sum_{k=T}^{L-1}n_k k=TL1nk 可由 ∑ k = 0 L − 1 n k − ∑ k = 0 T n k \sum_{k=0}^{L-1}n_k-\sum_{k=0}^Tn_k k=0L1nkk=0Tnk 计算得出; ∑ k = 0 T k × n k \sum_{k=0}^Tk\times n_k k=0Tk×nk为累积灰度统计,同理 ∑ k = T L − 1 k × n k \sum_{k=T}^{L-1}k\times n_k k=TL1k×nk可由 ∑ k = 0 L − 1 k × n k − ∑ k = 0 T k × n k \sum_{k=0}^{L-1}k\times n_k-\sum_{k=0}^{T}k\times n_k k=0L1k×nkk=0Tk×nk计算得出。通过公式(8)我们便可知道,OTSU算法的实现大概包含如下模块:1、直方图统计模块;2、累积直方图与累积灰度图模块;3、计算阈值模块。
本文以320*240大小的图像为例,通过FPGA搭建的图像仿真平台进行效果展示,由于是后期对所做的内容进行总结,在做的过程中可能代码有所改动,而注释并未改动,因此代码中有些注释可能有误,不明白的同学可以自己进行仿真看时序图来加深理解,直方图统计模块代码如下:

`timescale 1ns / 1ps

module VIP_Histogram_Get(

    input 	wire 			clk 		,  //时钟信号
	input	wire 			rst_n 		,  //低电平复位信号
	input 	wire 			cam_href	,  //行同步信号
	input	wire 			cam_vsync	,  //场同步信号
	input	wire 			cam_valid	,  //像素数据有效信号
	input 	wire 	[7:0]	cam_gray 	,  //灰度数据

	output 	wire 			po_histo_vld,  //输出数据有效信号
	output 	wire 	[31:0]	po_histo_data  //输出直方图统计结果.虽然图像大小为320X240=76800个数据,理论上单个灰度级最多有76800个数据,用17位足以表达,但是一般设定为2的整数次幂

    );
    
//==================================================================
//parameter define
//==================================================================
parameter   IMG_WIDTH   =   9'd320 ;   //图像宽度
parameter   IMG_HEIGHT  =   8'd240 ;   //图像高度
parameter   GRAY_LEVEL  =   9'd256 ;   //图像灰度级

parameter   IDLE 		 =   4'b0001;//空闲状态
parameter   CLEAR		 =   4'b0010;//清空RAM中数据状态
parameter   CALCULATE   =   4'b0100;//统计图像直方图状态
parameter   GET_HISTO   =   4'b1000;//输出直方图
    
//==========================================
//内部信号
//==========================================
reg 	[3:0]	state 		     ;//状态寄存器


//==========================================
//清空RAM阶段
//==========================================
reg 	[7:0]	cnt_clear 	;
wire			add_cnt_clear;
wire 			end_cnt_clear;
reg 			clear_flag	;//清空RAM指示信号

//==========================================
//统计直方图阶段
//==========================================
reg 	[7:0]	cnt_row 	;
wire 			add_cnt_row ;
wire 			end_cnt_row	;

reg 			cam_valid_dly0;//数据有效延时信号
reg 			cam_valid_dly1;//数据有效延时信号
reg 	[7:0]	cam_gray_dly0 ;//有效数据延时
reg 	[7:0]	cam_gray_dly1 ;//有效数据延时
reg 	[31:0]	cal_pixle	;//相同的像素统计值
reg 	[31:0]	cal_value 	;//写入RAM的统计值
reg 			cal_value_vld;//写入RAM数据有效信号
reg 			cal_one_row_done	;//统计一行图像数据结束
wire 	[7:0]	cal_wr_ram_addr;//统计状态下写RAM的地址
wire 	[7:0]	cal_rd_ram_addr;//统计状态下读RAM的地址

//==========================================
//读出数据阶段
//==========================================
reg 			get_data_flag 	;
reg 	[7:0]	cnt_get 		;
wire 			add_cnt_get 	;
wire 			end_cnt_get 	;
reg 			histo_data_vld 	;
wire 	[31:0]	histo_data 		;

//==========================================
//Block RAM 相关信号
//==========================================
reg 			wr_ram_en 	;//写RAM使能信号
reg 	[7:0]	wr_ram_addr	;//写RAM地址
reg 	[31:0]	wr_ram_data ;//写入RAM的数据
reg  	[7:0]	rd_ram_addr ;//读RAM的地址
wire	[31:0]	rd_ram_data	;//从RAM中读出的数据

//==========================================
//对场同步信号延时两个时钟周期,用于确定上升沿和下降沿
//上升沿是进入清空状态的标志位
//==========================================
reg 	[1:0]	cam_vsync_dly    ;

always @ (posedge clk or negedge rst_n) begin
    if(!rst_n)
        cam_vsync_dly   <=  2'd0;
    else
        cam_vsync_dly   <=  {cam_vsync_dly[0],cam_vsync};
end

//=========================================================================
//during the clear state
//=========================================================================
//在清空状态时,wr_ram_en = clear_flag 
assign  add_cnt_clear = (state == CLEAR) && (wr_ram_en == 1'b1)       ;
assign  end_cnt_clear = add_cnt_clear && (cnt_clear == GRAY_LEVEL - 1);

//---------clear_flag(写使能信号)-----------------------
always @ (posedge clk or negedge rst_n) begin
    if(!rst_n)
        clear_flag  <=  1'b0;
    else if(state == CLEAR) begin
        if(end_cnt_clear == 1'b1) 
            clear_flag  <=  1'b0;
        else
            clear_flag  <=  1'b1;
    end
    else
        clear_flag  <=  1'b0;
end

//---------cnt_clear(写地址信号)-----------------------
//用于清空RAM的计数器,目的就是清空256个地址的数据
always @ (posedge clk or negedge rst_n) begin
    if(!rst_n)
        cnt_clear   <=  8'd0;
    else if(add_cnt_clear) begin
        if(end_cnt_clear)
            cnt_clear   <=  8'd0;
        else
            cnt_clear   <=  cnt_clear + 1'b1;
    end
    else
        cnt_clear   <=  8'd0;
end

//=========================================================================
//during the calculate state
//=========================================================================
assign cal_wr_ram_addr  =   cam_gray_dly1;  //写入数据RAM的地址,写入数据落后于cam_valid信号2个时钟周期
assign cal_rd_ram_addr  =   cam_gray     ;  //读出数据RAM的地址
//对输入的灰度数据和数据有效信号延时2拍,主要是为了比较相邻的像素灰度值是否相同
always @ (posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        cam_valid_dly0  <=  1'b0            ;
        cam_valid_dly1  <=  1'b0            ;
        cam_gray_dly0   <=  8'd0            ;
        cam_gray_dly1   <=  8'd0            ;  
    end
        cam_valid_dly0  <=  cam_valid       ;
        cam_valid_dly1  <=  cam_valid_dly0  ;
        cam_gray_dly0   <=  cam_gray        ;
        cam_gray_dly1   <=  cam_gray_dly0   ;
end

//-------------cal_pixle-----------------计算相邻且相等的像素灰度值个数
always @ (posedge clk or negedge rst_n) begin
    if(!rst_n)
        cal_pixle   <=  32'd1;  //初始值设置为1
    else if((state == CALCULATE) && (cam_valid_dly0 == 1'b1)) begin
        //从第2个像素灰度值开始进行比较,相邻两个像素点的灰度值不同,统计值回到1
        if(cam_gray != cam_gray_dly0)
            cal_pixle   <=  32'd1;
        //当像素数据有效信号拉低时,清空计数个数,cal_pixle和cam_valid_dly0信号都落后于cam_valid信号一个时钟周期,保持同步
        else if(cam_valid == 1'b0)
            cal_pixle   <=  32'd1;
        //相邻两个像素灰度值相同
        else if(cam_gray == cam_gray_dly0)
            cal_pixle   <=  cal_pixle + 1'b1;
    end
    else 
        cal_pixle   <=  32'd1;
end

//-------------cal_value-----------------将cal_pixle写入RAM地址
always @ (posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        cal_value       <=  32'd0;  //落后于cam_valid信号2个时钟周期,与写地址保持同步
        cal_value_vld   <=  1'b0;   //落后于cam_valid信号2个时钟周期,与写地址保持同步
    end
    else if(state == CALCULATE) begin
        //相邻两个像素灰度值不同时,就将当前统计值结果写入
        if((cam_gray != cam_gray_dly0) && (cam_valid_dly0 == 1'b1)) begin
            //从RAM中读出的数据,有一拍的延时,这里保证了数据对齐
            cal_value       <=  rd_ram_data + cal_pixle;
            cal_value_vld   <=  1'b1;   //该有效信号仅仅是指示在什么时候写入数据,并不等同于cam_valid_dly0
        end
        //当检测到cam_valid信号下降沿时,把当前结果写入RAM中
        else if((cam_valid == 1'b0) && (cam_valid_dly0 == 1'b1)) begin
            cal_value       <=  rd_ram_data + cal_pixle;                         
            cal_value_vld   <=  1'b1;   //该有效信号仅仅是指示在什么时候写入数据,并不等同于cam_valid_dly0
        end
        else begin
            cal_value       <=  32'd0;
            cal_value_vld   <=  1'b0; 
        end
     end
     else begin
        cal_value       <=  32'd0;
        cal_value_vld   <=  1'b0;
     end
end

//----------cal_one_row_done-----------------------------------------
//表明当cam_valid信号有效期间的像素灰度值已经统计完毕,检测到valid下降沿时,表明一行已经统计完成
always @ (posedge clk or negedge rst_n) begin
    if(!rst_n) 
        cal_one_row_done    <=  1'b0;
    else if((state == CALCULATE) && (cam_valid == 1'b0) && (cam_valid_dly0 == 1'b1)) 
        cal_one_row_done    <=  1'b1;
    else
        cal_one_row_done    <=  1'b0;
end

//---------cnt_row---------------------------------------------------
always @ (posedge clk or negedge rst_n) begin
    if(!rst_n)
        cnt_row <=  8'd0;
    else if(add_cnt_row) begin
        if(end_cnt_row)
            cnt_row <=  8'd0;
        else
            cnt_row <=  cnt_row + 1'b1;
    end
end

assign add_cnt_row = (cal_one_row_done == 1'b1);    //计算有多少个行结束信号
assign end_cnt_row = add_cnt_row && cnt_row == IMG_HEIGHT - 1;

//=========================================================================
//during the getting histogram data state                                               
//=========================================================================
//--------------------get_data_flag----------------------------------该信号在获取直方图统计值期间一直有效
always @ (posedge clk or negedge rst_n) begin
    if(!rst_n)
        get_data_flag   <=  1'b0;
    else if(state == GET_HISTO) begin
        if(end_cnt_get == 1'b1) 
            get_data_flag   <=  1'b0;
        else
            get_data_flag   <=  1'b1;
    end
    else
         get_data_flag   <=  1'b0;
end

//-----------------cnt_get-------------------------------------------作为读取RAM数据的地址
assign add_cnt_get = (get_data_flag == 1'b1);
assign end_cnt_get = add_cnt_get && (cnt_get == GRAY_LEVEL - 1);

always @ (posedge clk or negedge rst_n) begin
    if(!rst_n)
        cnt_get <=  8'd0;
    else if(add_cnt_get) begin
        if(end_cnt_get)
            cnt_get <=  8'd0;
        else
            cnt_get <=  cnt_get + 1'b1;
    end
    else
        cnt_get <=  8'd0;
end

//-----------histo_data_vld--------------
always @ (posedge clk or negedge rst_n) begin
    if(!rst_n)
        histo_data_vld  <= 1'b0;
    else
        histo_data_vld  <= get_data_flag;   //数据落后于地址一个时钟周期,因此将有效信号也延迟1个时钟周期
end

assign histo_data = (histo_data_vld)? rd_ram_data : 32'd0;

//------------------state machine describe---------------------------
always @ (posedge clk or negedge rst_n) begin
    if(!rst_n)
        state   <=  IDLE;
    else begin
        case(state)
            IDLE: begin
                    //当检测到场同步信号的下降沿时,进入清空RAM状态,若是用上升沿,清空RAM的操作还没完成,就要往里面写数据
                    if(cam_vsync_dly[0] == 1'b0 && cam_vsync_dly[1] == 1'b1)
                        state   <=  CLEAR       ;
                    else
                        state   <= IDLE         ;
                   end
             
            CLEAR: begin
                   //当前RAM中的数据已经清空时,进入统计状态
                    if(end_cnt_clear == 1'b1)
                        state   <=   CALCULATE  ;
                    else
                        state   <=  CLEAR       ;
                    end
                   
            CALCULATE: begin
                    //当一帧图像统计完成后,进入输出直方图数据的状态
                    if(end_cnt_row == 1'b1)
                        state   <=  GET_HISTO   ;
                    else
                        state   <=   CALCULATE  ;
                    end
                    
            GET_HISTO: begin
                    //将RAM中的直方图数据全部读出
                    if(end_cnt_get == 1'b1)
                        state   <= IDLE         ;
                    else
                        state   <=  GET_HISTO   ; 
                    end
                    
             default:   state   <= IDLE         ;
        endcase
    end
end

//******************************************************************
//此为伪双口RAM,A端口写入数据,B端口可以读出数据,端口选择总是使能,
//暂时选择的是Read_first模式,会先读再写,读取地址中的旧数据
//wea = 1时,为写使能有效
//******************************************************************

histogram_ram histogram_ram_inst(
  .clka(clk),    // input wire clka
  .wea(wr_ram_en),      // input wire [0 : 0] wea
  .addra(wr_ram_addr),  // input wire [7 : 0] addra
  .dina(wr_ram_data),    // input wire [31 : 0] dina
  .clkb(clk),    // input wire clkb
  .addrb(rd_ram_addr),  // input wire [7 : 0] addrb
  .doutb(rd_ram_data)  // output wire [31 : 0] doutb
); 



//---------------wr_ram_addr,wr_ram_data,wr_ram_en----------------
always @ (*) begin
    if(state == CLEAR) begin
        wr_ram_addr = cnt_clear;
        wr_ram_en   = clear_flag;
        wr_ram_data = 32'd0;
    end
    else if(state == CALCULATE) begin
        wr_ram_addr = cal_wr_ram_addr;
        wr_ram_en   = cal_value_vld;
        wr_ram_data = cal_value;
    end
    else begin
        wr_ram_addr = 8'd0;
        wr_ram_en   = 1'b0;
        wr_ram_data = 32'd0;
    end
end

//-------------rd_ram_addr------------------------------------------
always @ (*) begin
    if(state == CALCULATE) 
        rd_ram_addr = cal_rd_ram_addr;
    else if(state == GET_HISTO) 
        rd_ram_addr = cnt_get;
    else
        rd_ram_addr = 8'd0;
end
   
assign po_histo_data = (histo_data_vld) ? histo_data : 32'd0;
assign po_histo_vld = histo_data_vld;

endmodule

  其中调用了双口RAM,用来存储直方图数据,其配置界面如下:
FPGA图像处理_OTSU算法的实现(含源码)_第1张图片
FPGA图像处理_OTSU算法的实现(含源码)_第2张图片
FPGA图像处理_OTSU算法的实现(含源码)_第3张图片
  该模块会在一帧图像结束时,输出该帧图像的直方图数据,提供给累积直方图和累积灰度图模块以及计算阈值模块,其模块代码如下:

`timescale 1ns / 1ps

//**********************************************************************************
//该模块有两个功能:1、统计累积直方图;2、统计累积灰度图;3、求阈值
//**********************************************************************************
module VIP_Cumulative_Histogram(
    input               clk,
    input               rst_n,
    input               per_histogram_valid,        //输入直方图有效信号
    input   [31:0]      per_histogram_data,          //输入直方图数据
    output  [7:0]       gresh_data

    );
    
//==================================================================
//parameter define
//==================================================================  
parameter   GRAY_LEVEL  =   9'd256 ;   //图像灰度级
parameter   IDLE        =   4'b0001;   //空闲状态
parameter   CUMULATIVE  =   4'b0010;   //统计状态
parameter   COMPUTE     =   4'b0100;   //计算过程,通过前一帧直方图数据得到阈值
parameter   CLEAR       =   4'b1000;   //清空状态

//==================================================================
//内部信号
//==================================================================
reg 	[3:0]	state 		     ;//状态寄存器

//==================================================================
//清空状态所需控制和指示信号
//==================================================================
reg 	[7:0]	cnt_clear 	;  //作为清空阶段的写地址信号
reg 			clear_flag	; //作为清空阶段的写使能信号
wire			add_cnt_clear;
wire 			end_cnt_clear;

//==================================================================
//统计状态所需控制和指示信号
//==================================================================
reg [7:0]  cumulative_data_cnt;
reg [31:0] cumulative_data;             //统计累积直方图数据
reg [31:0] gray_data;             //统计累积灰度图数据
reg [7:0]  cumulative_gray_cnt;

reg        add_cnt_cumulative;
wire       end_cnt_cumulative;
wire       end_cnt_gray;

//==================================================================
//计算阈值状态所需控制和指示信号
//首先将RAM中的数据读出来,需要wea\ena\addr,读行为在什么时候终止呢?
//根据公式,由于分母不能为0,所以当前K级的累积直方图等于255级的累积直方图时,终止读行为
//==================================================================
reg         compute_flag;       //作为计算开始的信号
reg         compute_flag_dly;
reg         compute_flag_dly1;
reg [7:0]   cnt_compute;        //作为PORT A端口的地址
reg [31:0]  compute_his_sub;    //用来存储累积直方图的差值
reg [31:0]  compute_gray_sub;   //用来存储累积灰度图的差值
wire        add_cnt_compute;
wire        end_cnt_compute;
reg [31:0]  his_ram_douta_dly;
reg [31:0]  gray_ram_douta_dly;

//==================================================================
//存储累计直方图数据的RAM相关信号
//==================================================================
//port A的控制和数据信号
reg             his_ram_ena;
reg             his_ram_wea;
reg     [7:0]   his_ram_addra;
reg     [31:0]  his_ram_dina;
wire    [31:0]  his_ram_douta;

//port B的控制和数据信号
reg             his_ram_enb;
reg             his_ram_web;
reg     [7:0]   his_ram_addrb;
reg     [31:0]  his_ram_dinb;
wire    [31:0]  his_ram_doutb;

//==================================================================
//存储累计灰度图数据的RAM相关信号
//==================================================================
//port A的控制和数据信号
reg             gray_ram_ena;
reg             gray_ram_wea;
reg     [7:0]   gray_ram_addra;
reg     [31:0]  gray_ram_dina;
wire    [31:0]  gray_ram_douta;

//port B的控制和数据信号
reg             gray_ram_enb;
reg             gray_ram_web;
reg     [7:0]   gray_ram_addrb;
reg     [31:0]  gray_ram_dinb;
wire    [31:0]  gray_ram_doutb;

//==================================================================
//数据有效信号延时1个时钟周期,通过上升沿判断有效数据到来,进入统计状态
//对数据也延时一个时钟周期,用作统计累积直方图数据
//==================================================================
reg         per_histogram_valid_dly;
reg [31:0]  per_histogram_data_dly;

always @ (posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        per_histogram_valid_dly <=  1'b0;  
        per_histogram_data_dly  <=  32'd0;
    end
    else begin
        per_histogram_valid_dly <=  per_histogram_valid;
        per_histogram_data_dly  <=  per_histogram_data;
    end
end

//==================================================================
//during the CUMULATIVE state
//==================================================================
assign  end_cnt_cumulative = (cumulative_data_cnt == GRAY_LEVEL - 8'd1);
assign  end_cnt_gray       = (cumulative_gray_cnt == GRAY_LEVEL - 8'd1);
//add_cnt_cumulative信号落后valid信号两个时钟周期,在统计完的第一个数据时拉高,直至统计完最后一个数据时拉低,因此可以作为写使能信号
always @ (posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        add_cnt_cumulative  <=  1'b0;
    end
    else if(state == CUMULATIVE) begin
        if(end_cnt_cumulative)
            add_cnt_cumulative  <=  1'b0;
        else
            add_cnt_cumulative  <=  1'b1;
    end
    else
        add_cnt_cumulative  <=  1'b0;
end

//对前K个灰度数目值进行累加,作为累积直方图统计模块RAM的数据
//cumulative_data落后于per_histogram_data两个时钟周期
always @ (posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        cumulative_data <=  32'd0;
        gray_data       <=  32'd0;
    end
    else if(state == CUMULATIVE) begin
        if(end_cnt_cumulative) begin
            cumulative_data <=  32'd0; 
            gray_data       <=  32'd0; 
        end
        else begin
            cumulative_data <=  cumulative_data + per_histogram_data_dly;  
            gray_data       <=  gray_data + per_histogram_data_dly * cumulative_gray_cnt;     
        end     
    end 
    else begin
        cumulative_data <=  32'd0;   
        gray_data       <=  32'd0;
    end       
end

//计数已经统计的数目,作为累积直方图统计模块RAM的地址
always @ (posedge clk or negedge rst_n) begin
    if(!rst_n) 
        cumulative_data_cnt <=  8'd0;   
    else if(add_cnt_cumulative) begin
        if(end_cnt_cumulative) 
            cumulative_data_cnt <=  8'd0;
       else 
            cumulative_data_cnt <=  cumulative_data_cnt +8'd1;       
    end
    else 
        cumulative_data_cnt <=  8'd0;      
end

//计数器,作为累积灰度图的因子
always @ (posedge clk or negedge rst_n) begin
    if(!rst_n)
        cumulative_gray_cnt <=  8'd0;
    else if(state == CUMULATIVE) begin
        if(end_cnt_gray)
            cumulative_gray_cnt <=  8'd0;
        else      
            cumulative_gray_cnt <= cumulative_gray_cnt + 8'd1;
    end
    else
        cumulative_gray_cnt <=  8'd0;
end

//==================================================================
//during the COMPUTE state
//==================================================================
assign  add_cnt_compute = (state == COMPUTE) && (compute_flag == 1'b1)      ;
assign  end_cnt_compute = add_cnt_compute && (cnt_compute == GRAY_LEVEL - 1);
//---------compute_flag(读使能信号)-----------------------
always @ (posedge clk or negedge rst_n) begin
    if(!rst_n)
        compute_flag  <=  1'b0;
    else if(state == COMPUTE) begin
        if(end_cnt_compute == 1'b1) 
            compute_flag  <=  1'b0;
        else
            compute_flag  <=  1'b1;
    end
    else
        compute_flag  <=  1'b0;
end

//---------cnt_compute(读地址信号)-----------------------
always @ (posedge clk or negedge rst_n) begin
    if(!rst_n)
        cnt_compute   <=  8'd0;
    else if(add_cnt_compute) begin
        if(end_cnt_compute)
            cnt_compute   <=  8'd0;
        else
            cnt_compute   <=  cnt_compute + 1'b1;
    end
    else
        cnt_compute  <=  8'd0;
end

always @ (posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        compute_flag_dly    <= 1'b0;
        compute_flag_dly1   <= 1'b0;
        his_ram_douta_dly   <=  32'd0;
        gray_ram_douta_dly  <=  32'd0;
    end
    else begin
        compute_flag_dly    <= compute_flag;        //compute_flag_dly1用来指示累积直方图的差值是否有效
        compute_flag_dly1   <= compute_flag_dly;
        his_ram_douta_dly   <= his_ram_douta;
        gray_ram_douta_dly  <=  gray_ram_douta;
    end
end

always @ (posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        compute_his_sub <= 32'd0;
        compute_gray_sub<= 32'd0;
    end
    else if(compute_flag_dly) begin  //用来指示his_ram_doutb\his_ram_douta是否是有效值
        compute_his_sub <= his_ram_doutb - his_ram_douta;
        compute_gray_sub<= gray_ram_doutb - gray_ram_douta;
    end   
    else begin
        compute_his_sub <= 32'd0;
        compute_gray_sub<= 32'd0;
    end
end

//以上过程得到了计算类间方差所需的四个因子:his_ram_douta_dly、gray_ram_douta_dly、compute_his_sub、compute_gray_sub
//在计算类间方差的过程中,需要注意的是compute_his_sub作为分母,不能为0
//step1:先计算his_ram_douta_dly与compute_his_sub的乘积
wire [31:0]  mul_data0; //输出当前灰度级的累积直方图与剩余累积直方图的乘积结果
wire         div_data0_valid;    //除法结果有效的标志位
wire [39:0]  div_data0; //输出当前灰度级的累积灰度与累积直方图的除法结果
wire         div_data1_valid;    //除法结果有效的标志位
wire [39:0]  div_data1; //输出当前灰度级的剩余累积灰度与剩余累积直方图的除法结果

//乘法IP核,输出延迟于输入1个时钟周期
mult_gen_0 mul_inst0(
  .CLK(clk),  // input wire CLK
  .A(his_ram_douta_dly),      // input wire [31 : 0] A
  .B(compute_his_sub),      // input wire [31 : 0] B
  .CE(compute_flag_dly1),    // input wire CE
  .P(mul_data0)      // output wire [31 : 0] P
);

//除法IP核,输出延迟于输入1个时钟周期
div_gen_0 div_inst0(
  .aclk(clk),                                      // input wire aclk
  .s_axis_divisor_tvalid(compute_flag_dly1),    // 除数有效信号
  .s_axis_divisor_tdata(his_ram_douta_dly),      // input wire [31 : 0] s_axis_divisor_tdata
  .s_axis_dividend_tvalid(compute_flag_dly1),  // 被除数有效信号
  .s_axis_dividend_tdata(gray_ram_douta_dly),    // input wire [31 : 0] s_axis_dividend_tdata
  .m_axis_dout_tvalid(div_data0_valid),          // output wire m_axis_dout_tvalid
  .m_axis_dout_tdata(div_data0)            // output wire [63 : 0] m_axis_dout_tdata
);

div_gen_0 div_inst1(
  .aclk(clk),                                      // input wire aclk
  .s_axis_divisor_tvalid(compute_flag_dly1),    // 除数有效信号
  .s_axis_divisor_tdata(compute_his_sub),      // input wire [31 : 0] s_axis_divisor_tdata
  .s_axis_dividend_tvalid(compute_flag_dly1),  // 被除数有效信号
  .s_axis_dividend_tdata(compute_gray_sub),    // input wire [31 : 0] s_axis_dividend_tdata
  .m_axis_dout_tvalid(div_data1_valid),          // output wire m_axis_dout_tvalid
  .m_axis_dout_tdata(div_data1)            // output wire [63 : 0] m_axis_dout_tdata
);

//step2:计算差值平方,然后取整,消耗1个时钟周期
reg [39:0] sub_data;   //取绝对值
always @ (posedge clk or negedge rst_n) begin
    if(!rst_n)
        sub_data    <=  40'd0;
    else if(div_data0_valid) begin
        if(div_data0 >= div_data1)
            sub_data    <=  div_data0 - div_data1;
        else if(div_data0 < div_data1)
            sub_data    <=  div_data1 - div_data0;
        else
            sub_data    <=  sub_data;
    end
    else
        sub_data    <=  40'd0;
end

//同时对有效信号也延时1个时钟周期
reg div_data0_valid_dly;
always @ (posedge clk or negedge rst_n)  begin
    if(!rst_n)
        div_data0_valid_dly <= 1'b0;
    else
        div_data0_valid_dly <= div_data0_valid;
end

//step3:计算绝对差的平方结果
wire    [63:0]  mul_data1;  //输出绝对差的平方结果
mult_gen_1 mul_inst1(
  .CLK(clk),  // input wire CLK
  .A(sub_data),      // input wire [39 : 0] A
  .B(sub_data),      // input wire [39 : 0] B
  .CE(div_data0_valid_dly),    // input wire CE
  .P(mul_data1)      // output wire [63 : 0] P
);

//step4:计算最大类间方差
reg         div_data0_valid_dly1;
reg [31:0]  mul_data0_dly;
reg [31:0]  mul_data0_dly1;
always @ (posedge clk or negedge rst_n)  begin
    if(!rst_n) begin
        div_data0_valid_dly1 <= 1'b0;
        mul_data0_dly        <= 32'd0; 
        mul_data0_dly1       <= 32'd0;
    end
    else begin
        div_data0_valid_dly1 <= div_data0_valid_dly;
        mul_data0_dly        <= mul_data0;
        mul_data0_dly1       <= mul_data0_dly;
    end
end

wire [95:0] mul_data3;
mult_gen_2 mult_gen_inst3(
  .CLK(clk),  // input wire CLK
  .A(mul_data0_dly1),      // input wire [31 : 0] A
  .B(mul_data1),      // input wire [63 : 0] B
  .CE(div_data0_valid_dly1),    // input wire CE
  .P(mul_data3)      // output wire [95 : 0] P    出来的数据就是计算好的类间方差结果,根据MATLAB仿真可以看出是一个波峰的形状,先递增,再递减
);

//step5:将计算好的类间方差数据延时1拍,做到当前值与前一个值做比较,若是大于前一个值,则加1,直到停止加1为止
reg [95:0]  mul_data3_dly;
reg         div_data0_valid_dly2;
reg         div_data0_valid_dly3;
always @ (posedge clk or negedge rst_n)  begin
    if(!rst_n) begin
        mul_data3_dly <= 96'd0;
        div_data0_valid_dly2    <=  1'b0;
    end
    else begin
        mul_data3_dly <= mul_data3;  
        div_data0_valid_dly2    <=  div_data0_valid_dly1;       
    end
end

always @ (posedge clk or negedge rst_n) begin
    if(!rst_n) 
        div_data0_valid_dly3    <= 1'b0;
    else if(div_data0_valid_dly1 == 1'b1 && div_data0_valid_dly2 == 1'b0)
        div_data0_valid_dly3    <= 1'b1;
    else if(mul_data3 == 96'd0 && mul_data3_dly > 96'd0)
        div_data0_valid_dly3    <= 1'b0;
    else
        div_data0_valid_dly3    <= div_data0_valid_dly3;
end

//存放阈值
reg   [7:0]    max_cnt;
always @ (posedge clk or negedge rst_n) begin
    if(!rst_n)
        max_cnt <=  8'd0;
    else if(div_data0_valid_dly3) begin
        if(mul_data3 >= mul_data3_dly)
            max_cnt <=  max_cnt + 1'b1;
        else 
            max_cnt <=  max_cnt;
    end
    else if(div_data0_valid_dly1 == 1'b1 && div_data0_valid_dly2 == 1'b0)
        max_cnt <=  8'd0;
    else
        max_cnt <=  max_cnt;
end

assign gresh_data = max_cnt;

//==================================================================
//during the CLEAR state
//==================================================================
//在清空状态时,wr_ram_en = clear_flag 
assign  add_cnt_clear = (state == CLEAR) && (clear_flag == 1'b1)      ;
assign  end_cnt_clear = add_cnt_clear && (cnt_clear == GRAY_LEVEL - 1);
//---------clear_flag(写使能信号)-----------------------
always @ (posedge clk or negedge rst_n) begin
    if(!rst_n)
        clear_flag  <=  1'b0;
    else if(state == CLEAR) begin
        if(end_cnt_clear == 1'b1) 
            clear_flag  <=  1'b0;
        else
            clear_flag  <=  1'b1;
    end
    else
        clear_flag  <=  1'b0;
end

//---------cnt_clear(写地址信号)-----------------------
//用于清空RAM的计数器,目的就是清空256个地址的数据
always @ (posedge clk or negedge rst_n) begin
    if(!rst_n)
        cnt_clear   <=  8'd0;
    else if(add_cnt_clear) begin
        if(end_cnt_clear)
            cnt_clear   <=  8'd0;
        else
            cnt_clear   <=  cnt_clear + 1'b1;
    end
    else
        cnt_clear   <=  8'd0;
end

//------------------state machine describe---------------------------
always @ (posedge clk or negedge rst_n) begin
    if(!rst_n)
        state   <=  IDLE;
    else begin
        case(state)
            IDLE: begin
                    //检测到直方图数据有效信号上升沿时,进入累计状态,获得累计直方图和灰度统计
                    if((per_histogram_valid == 1'b1) && (per_histogram_valid_dly == 1'b0))
                        state   <=  CUMULATIVE;
                    else
                        state   <=  IDLE;
                  end
                  
            CUMULATIVE:begin
                    //统计完成时,进入计算方差最小模块
                    if(end_cnt_cumulative)
                        state   <=  COMPUTE;
                    else
                        state   <=  CUMULATIVE;
                   end
                   
            COMPUTE: begin
                    //将统计的数据读出后,清零RAM
                    if(end_cnt_compute)
                        state   <=  CLEAR       ;
                    else
                        state   <=  COMPUTE;
                    end
             
            CLEAR:begin
                    //当前RAM中的数据已经清空时,进入统计状态
                    if(end_cnt_clear == 1'b1)
                        state   <=   IDLE  ;
                    else
                        state   <=  CLEAR       ;
                   end 
             
             default: state   <=   IDLE  ;
        endcase
    end
end

//use truth dual port ram to cumulative histogram data   
dual_port_ram histogram_data_inst(
  .clka(clk),    // input wire clka
  .ena(his_ram_ena),      // input wire ena
  .wea(his_ram_wea),      // input wire [0 : 0] wea
  .addra(his_ram_addra),  // input wire [7 : 0] addra
  .dina(his_ram_dina),    // input wire [31 : 0] dina
  .douta(his_ram_douta),  // output wire [31 : 0] douta
  .clkb(clk),    // input wire clkb
  .enb(his_ram_enb),      // input wire enb
  .web(his_ram_web),      // input wire [0 : 0] web
  .addrb(his_ram_addrb),  // input wire [7 : 0] addrb
  .dinb(his_ram_dinb),    // input wire [31 : 0] dinb
  .doutb(his_ram_doutb)  // output wire [31 : 0] doutb
);

//use truth dual port ram to cumulative gray data 
dual_port_ram gray_data_inst(
  .clka(clk),    // input wire clka
  .ena(gray_ram_ena),      // input wire ena
  .wea(gray_ram_wea),      // input wire [0 : 0] wea
  .addra(gray_ram_addra),  // input wire [7 : 0] addra
  .dina(gray_ram_dina),    // input wire [31 : 0] dina
  .douta(gray_ram_douta),  // output wire [31 : 0] douta
  .clkb(clk),    // input wire clkb
  .enb(gray_ram_enb),      // input wire enb
  .web(gray_ram_web),      // input wire [0 : 0] web
  .addrb(gray_ram_addrb),  // input wire [7 : 0] addrb
  .dinb(gray_ram_dinb),    // input wire [31 : 0] dinb
  .doutb(gray_ram_doutb)  // output wire [31 : 0] doutb
);

//==================================================================
//清空状态:仅使用RAM的PORT_A端口往地址里面写0
//统计状态:使用RAM的PORT_A端口往地址里面写统计好的数据,使用PORT_B端口来读取数据
//计算阈值状态:将RAM的PORT A端口和PORT B端口都作为读端口,PORT A口从地址0开始读,PORT B口一直读取地址255上的数据,然后二者开始相减比较
//空闲状态:让所有端口都停用
//==================================================================
//---------------wr_ram_addr,wr_ram_data,wr_ram_en----------------
always @ (*) begin
    if(state == CLEAR) begin
        his_ram_ena = clear_flag;      
        his_ram_wea = clear_flag;      
        his_ram_addra = cnt_clear;   
        his_ram_dina = 32'd0;  
        
        his_ram_enb = 1'b0;          
        his_ram_web = 1'b0;          
        his_ram_addrb = 8'd0;        
        his_ram_dinb = 32'd0;    
          
        gray_ram_ena = clear_flag;  
        gray_ram_wea = clear_flag;  
        gray_ram_addra = cnt_clear; 
        gray_ram_dina = 32'd0;   
        
        gray_ram_enb = 1'b0;          
        gray_ram_web = 1'b0;          
        gray_ram_addrb = 8'd0;        
        gray_ram_dinb = 32'd0;                
    end
    else if(state == CUMULATIVE) begin
        his_ram_ena = add_cnt_cumulative;
        his_ram_wea = add_cnt_cumulative;
        his_ram_addra = cumulative_data_cnt;
        his_ram_dina = cumulative_data;  
               
        his_ram_enb = add_cnt_cumulative;          
        his_ram_web = ~add_cnt_cumulative;          
        his_ram_addrb = cumulative_data_cnt;   
        
        gray_ram_ena = add_cnt_cumulative;    
        gray_ram_wea = add_cnt_cumulative;    
        gray_ram_addra = cumulative_data_cnt; 
        gray_ram_dina = gray_data;    
                
        gray_ram_enb = add_cnt_cumulative;     
        gray_ram_web = ~add_cnt_cumulative;    
        gray_ram_addrb = cumulative_data_cnt;           
    end
    else if(state == COMPUTE) begin
        his_ram_ena = compute_flag;
        his_ram_wea = ~compute_flag;
        his_ram_addra = cnt_compute;
        his_ram_dina = 32'd0;  
               
        his_ram_enb = compute_flag;          
        his_ram_web = ~compute_flag;          
        his_ram_addrb = 8'd255;   
        
        gray_ram_ena = compute_flag;
        gray_ram_wea = ~compute_flag;
        gray_ram_addra = cnt_compute;
        gray_ram_dina = 32'd0; 
        
           
        gray_ram_enb = compute_flag;          
        gray_ram_web = ~compute_flag;          
        gray_ram_addrb = 8'd255;       
    end
    else begin
        his_ram_ena = 1'b0;  
        his_ram_wea = 1'b0;  
        his_ram_addra = 8'd0; 
        his_ram_dina = 32'd0;   
        his_ram_enb = 1'b0;          
        his_ram_web = 1'b0;          
        his_ram_addrb = 8'd0;        
        his_ram_dinb = 32'd0;              
        
        gray_ram_ena = 1'b0;       
        gray_ram_wea = 1'b0;       
        gray_ram_addra = 8'd0;     
        gray_ram_dina = 32'd0;     
        gray_ram_enb = 1'b0;       
        gray_ram_web = 1'b0;       
        gray_ram_addrb = 8'd0;     
        gray_ram_dinb = 32'd0;                   
    end
end

endmodule

  该代码前半段主要是调用了两个RAM的IP核,用于存储累积直方图信息以及累积灰度图信息,其配置可参考直方图统计模块,与之不同的是该处设置的是真双口RAM模式,需要注意!此外我想说的是计算模块,我们读出RAM的数据相当于获得了公式(8)中需要的所有自变量数据,然后对公式(8)的乘积项进行拆解,通过调用乘法器IP核(Multiplier)和除法器IP核(Divider Generator),来计算类间方差,乘法器IP核与除法器IP核的配置比较简单,只需要配置输入输出的位宽,注意输出是否设置有延迟即可,在此不多做介绍。下面我们来看仿真效果:
FPGA图像处理_OTSU算法的实现(含源码)_第4张图片

FPGA图像处理_OTSU算法的实现(含源码)_第5张图片 FPGA图像处理_OTSU算法的实现(含源码)_第6张图片   第一幅图为原图,第二幅图为Y分量图像,第三幅图为阈值分割后的图像。这幅图之所以分割效果不明显的原因是Y分量的目标与背景灰度值差异并不大,其直方图并未呈现出双波峰的形状,因此阈值不够准确,故而分割效果不好,但是这并不是代码移植有问题,而是OTSU算法原理自身的缺陷导致的,目前有基于OTSU算法的改进型算法,感兴趣的同学可以研究一下。

参考文献:牟新刚,周晓,郑晓亮.基于FPGA的数字图像处理原理及应用;
阮秋琦.数字图像处理(MATLAB版)(第二版);

你可能感兴趣的:(#,基于FPGA的图像处理,图像处理,算法,matlab)