目录
在图像分割的过程中,通常需要进行二值化处理。所谓的二值化,就是把图像分成黑和白两部分,用于凸显图像的某些特征。在毕设的项目中,鉴于图像的背景是白色的,原定是想把图像转化为灰度图像在进行分离,但是实测过程中,白色背景也会出现阴影,或者是水果的颜色过浅色(比如黄色的香蕉),这些都会导致分割失败。因此为了针对这个现象,我使用HSV的S域来进行分离,因为无论是背景的白色还是灰色S域的大小都是很小,因此可以通过这个来将其进行分离操作,同时使用大律法算法进一步提高分离的效率。
一、什么是OTSU
最大类间方差法是由日本学者大津(Nobuyuki Otsu)于1979年提出的,是一种自适合于双峰情况的自动求取阈值的方法,又叫大津法,简称OTSU。它是按图像的灰度特性,将图像分成背景和目标2部分。背景和目标之间的类间方差越大,说明构成图像的两部分的差别越大,当部分目标错分为背景或部分背景错分为目标都会导致2部分差别变小。因此,使类间方差最大的分割意味着错分概率最小。
二、OTSU的实现原理
它按图像的灰度特性,将图像分成背景和前景两部分。因方差是灰度分布均匀性的一种度量,背景和前景之间的类间方差越大,说明构成图像的两部分的差别越大,因此分割的效果也就最好。
假设ω(D0)为背景D0出现的概率,ω(D1)为目标出现的概率,u(D0)和u(D1)分别为D0和D1的灰度均值。ω(D0)、ω(D1)、u(D0)以及u(D1)计算公式如下所示:
其中P(i)为灰度级i出现的概率,T为背景和目标的分界灰度级,L为最大灰度级。则整幅图像的背景与目标之间的类间方差g为:
三、OTSU的FPGA实现
这里为了适应FPGA流水线的计算逻辑,先将公式进行转换,尽可能少的使用乘除法逻辑资源:
其中,N为总的像素个数,G为整幅图像的平均灰度,因此我们只需要计算和
,而这两个可以通过流水线进行计算,依次计算T从1到256的所有数值。
该模块由直方图统计模块和类间方差计算模块两部分构成,系统整体架构框图如下:
OTSU全局阈值分割的实现首先需要对当前图像进行灰度直方图统计。具体通过双口RAM架构实现像素频次统计:当灰度图像输入时,像素值作为地址信号输入RAM地址总线,同时触发灰度计数器工作。该计数器用于统计各灰度值连续出现的频次,在灰度值切换时,将当前计数值与RAM对应地址的存储数据进行累加后回写,这也是使用双口RAM的原因。通过这种地址映射机制,RAM的0-255地址空间最终将精确存储对应灰度级的像素数量分布。代码如下:
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 CALCULATE = 4'b0010;//统计图像直方图状态
parameter GET_HISTO = 4'b0100;//输出直方图
//内部信号
reg [3:0] state ;//状态寄存器
//统计直方图阶段
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 ;
reg histo_data_vld_d;
wire [31:0] histo_data ;
//Block RAM 相关信号
reg ram_choose ;
wire [31:0] rd_ram_data ;
reg wr_0_ram_en ;//写RAM使能信号
reg [7:0] wr_0_ram_addr ;//写RAM地址
reg [31:0] wr_0_ram_data ;//写入RAM的数据
reg [7:0] rd_0_ram_addr ;//读RAM的地址
wire [31:0] rd_0_ram_data ;//从RAM中读出的数据
reg wr_1_ram_en ;//写RAM使能信号
reg [7:0] wr_1_ram_addr ;//写RAM地址
reg [31:0] wr_1_ram_data ;//写入RAM的数据
reg [7:0] rd_1_ram_addr ;//读RAM的地址
wire [31:0] rd_1_ram_data ;//从RAM中读出的数据
reg [1:0] cam_vsync_dly ;
/*************************打拍操作***************************/
//对输入的灰度数据和数据有效信号延时2拍,主要是为了比较相邻的像素灰度值是否相同
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
cam_vsync_dly <= 2'd0 ;
cam_valid_dly0 <= 1'b0 ;
cam_valid_dly1 <= 1'b0 ;
cam_gray_dly0 <= 8'd0 ;
cam_gray_dly1 <= 8'd0 ;
histo_data_vld_d<= 1'b0 ;
end
cam_valid_dly0 <= cam_valid ;
cam_valid_dly1 <= cam_valid_dly0 ;
cam_gray_dly0 <= cam_gray ;
cam_gray_dly1 <= cam_gray_dly0 ;
histo_data_vld_d<= histo_data_vld ;
cam_vsync_dly <= {cam_vsync_dly[0],cam_vsync};
end
/*************************统计阶段***************************/
assign cal_wr_ram_addr = cam_gray_dly1; //写入数据RAM的地址,写入数据落后于cam_valid信号2个时钟周期
assign cal_rd_ram_addr = cam_gray ; //读出数据RAM的地址
//-------------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
if(cam_gray != cam_gray_dly0) //相邻两个像素点的灰度值不同,统计值回到1
cal_pixle <= 32'd1;
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地址)
assign rd_ram_data = ram_choose ? rd_1_ram_data : rd_0_ram_data;
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
cal_value <= 32'd0;
cal_value_vld <= 1'b0;
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;
end else if((cam_valid == 1'b0) && (cam_valid_dly0 == 1'b1)) begin //当检测到cam_valid信号下降沿写入
cal_value <= rd_r