由于图像阈值的直观性和易于实现的性质,使它在图像分割应用中处于中心地位。阈值分割方法实际上是输入图像
f
f
f到输出图像
g
g
g的变换,如式所示。
g
(
i
,
j
)
=
{
1
,
f(i,j)>=T
0
,
f(i,j)<T
g(i,j) = \begin{cases} 1, & \text{f(i,j)>=T} \\ 0, & \text{f(i,j)<T} \\ \end{cases}
g(i,j)={1,0,f(i,j)>=Tf(i,j)<T
其中
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=0∑Tnk)(k=T∑L−1nk)(∑k=0Tnk∑k=0Tk×nk−∑k=TL−1nk∑k=TL−1k×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=TL−1nk 可由
∑
k
=
0
L
−
1
n
k
−
∑
k
=
0
T
n
k
\sum_{k=0}^{L-1}n_k-\sum_{k=0}^Tn_k
∑k=0L−1nk−∑k=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=TL−1k×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=0L−1k×nk−∑k=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,用来存储直方图数据,其配置界面如下:
该模块会在一帧图像结束时,输出该帧图像的直方图数据,提供给累积直方图和累积灰度图模块以及计算阈值模块,其模块代码如下:
`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的数字图像处理原理及应用;
阮秋琦.数字图像处理(MATLAB版)(第二版);