基于FPGA的数字图像处理【3.2】

6.5 基于FPGA的直方图操作

6.5.1 FPGA直方图统计

        我们知道,在软件中,直方图统计工作非常简单,实际上仅仅一行代码即可实现统计工作。但是,对于FPGA来说,这个统计工作是相当复杂的,需要考虑以下几点:
(1)统计工作至少要等到当前图像“流过”之后才能完成。此限制决定了我们不可能对统计工作进行流水统计与输出。
(2)必须对前期的统计结果进行缓存。这点是毋庸置疑的。
(3)在下一次统计前需要将缓存结果清零。
        在直方图统计中,我们一般选择片内双口RAM作为缓存存储器。对于8位的深度图来说,统计结果的数据量并不大,因此选择片内存储。此外,一方面统计模块需要与其他时序进行配合,因此需提供双边读写接口;另一方面,统计过程中需要地址信息,因此选择RAM形式的存储器。接下来的工作就是确定双口RAM的参数,主要包括数据位宽及地址位宽。假定输入图像宽度为IW,高度为IH,数据位宽为DW。那么待统计的像素总数为
PixelTotal =IW*IH
像素灰度值的理论最大值为

直方图统计步骤如下:
(1)将当前统计值读出,加1后重新写入RAM。
(2)重复以上步骤,直到当前图像统计完毕。
(3)在下一幅图像到来之前将结果读出。
(4)读出之后对RAM内容进行清零。
因此,要完成直方图统计,需要至少设计三个电路:统计电路、读出电路和清零电路。
1.统计电路
在实际的图像中,连续的像素点灰度值为相同值的情况非常常见,如果每来一个像素都对双口RAM进行一次寻址和写操作,显然降低了统计效率而提高了功耗。本书中给出一种优化的统计方式:采用一个相同灰度值计数器进行优化,统计电路如图6-16所示。
图6-16中各个元件的说明:
(1)DPRAM:存放统计结果。分为A口和B口,A口负责统计结果写入,不输出。B口负责统计结果读出和清零,无输入。
(2)CNT:相同像素计数器。负责对连续相同灰度值的像素进行计数,复位值为1。
(3)ADD(+):统计值加法器。对当前统计值和新的统计值进行加法运算,重新写入RAM。
(4)B_ADDR MUX:B口地址mux,很明显,B口需要完成读出前一个统计值和清零的分时操作。因此,需要一个mux对读出地址和清零地址进行选通。
(5)reg:将输入数据打两拍以确保读出的是当前的统计值。

统计原理如下:
        当前灰度值的统计值由B口读出,与相同灰度值计数器(图6-16中所示为CNT)进行相加后重新写入RAM。CNT会不断检测当前像素和前一个像素是否一致,若不一致,则重置为1,实现统计值加1目的;若一致,则将计数器加1,直到不一致之后将一致的总数写入RAM,并在每一行图像的最后一个像素统一执行一次写入操作,这样可大大减少读写RAM操作。
        下面以几个关键信号的设计电路来说明统计电路的工作原理。首先将输入信号din,输入有效dvalid打两拍,分别为din_r,din_r2及dvalid_r,dvalid_r2。
1)inc_en
        此信号负责递增计数器的递增使能。当前待统计数据din_r2有效,且与前一个已经统计完成的数据din_r相同时,将递增计数器加1。否则计数器会复位到1,如图6-17和图6-18所示。

2)rst_cnt
        此信号为递增计数器的复位信号。除了当前待统计灰度值与上一个统计过的灰度值不相同的情况下会复位计数器,第一个有效数据到来时也会复位递增计数器,为新的一轮统计工作做准备。
3)we_a
        此信号为DPRAM写入信号,也是分两种情况:若当前待统计灰度值与之前待统计值不同,则直接写入RAM。否则,就一直累加直到数据无效时统一写入RAM,如图6-19所示。
4)count_en
        此信号为统计使能,很明显,在统计阶段此信号需要一直保持有效,统计完成后(也即当前图像遍历完毕),在读出和清零阶段,需要将此信号失能,这是由于B口在此阶段需要读出和清零地址。
        此信号的产生可以通过对图像进行行计数来实现,当到达一幅图像的高度时,失能信号。新的行同步到来时使能信号。
2.读出电路设计
        首先,本书讨论的统计值读出方法是顺序读出,即灰度值为0的统计值首先输出,其次是灰度值为1的统计值输出,最后是灰度值为255的统计值输出。如果读者想要实现指定输出,可自行设计相关逻辑。

        读出和清零操作并不一定是在统计完成之后立即进行的,可以根据需要由外部时序控制,输入读出请求。当然在统计阶段,模块是不允许读出的,此时读出电路出处于复位状态。在读出阶段,需设计读出像素计数器对读出时序进行控制。读出电路(地址发生器)设计如图6-20所示。

        可见,只有当计数完成,并且外部时序申请读出时,输出地址才会进行递增。否则,将会被钳位到0。图中没有标识出来的是,当一次读出完成之后此地址发生器复位,也就是count_en会重新使能,直到下一次统计完成。
3.清零电路设计
        一种简单的方法是在所有数据输出完成之后整体清零,另外一种思路是在输出完的下个时钟清零,清零地址为递增,由B口输入。在本书中给出反相清零的方法,即在读出时钟的下一个时钟进行清零。因此,每个像素的统计数据输出和清零操作均需占用1个时钟,奇数时钟输出,偶数时钟清零。
        在某些场合,为了配合外部存储器位宽或是本地数据位宽,一般不能直接处理32位的直方图数据,这时就需要拆分位宽处理。在本书中,我们将32位的统计及读出清零工作拆分为高低16位进行,这样我们就需要连个16位宽的DPRAM来存储直方图数据。同时,输出时分两个时钟输出32位的统计数据,我们规定低16位先输出。
        这样一来,对于一个灰度值的输出操作,需要两个时钟,清零操作也需要两个时钟,每个像素需要4个时钟来完成读出和清零操作。是不是哪里不对?的确,我们忽略了FPGA的并行特性。实际上就算是拆分成为高低16位分时输出,也可以做到2个时钟内完成读出和清零操作。

        我们来看一下这两个时钟内电路都需要完成哪些工作,如表6-1所示。

        由此可见,在这两个时钟内,读出地址需保持为当前灰度值,清零地址则是慢一拍。清零地址产生电路如图6-21所示。

                                                                图6-21 清零地址发生器
之所以需要右移1位是因为每次需要两个时钟。
4.Verilog代码设计

//模块定义如下:
module histogram_2d(
rst_n,
clk,
din_valid, //输入有效
din, //输入待统计的数据
dout, //统计输出
vsync, //输入场同步
dout_valid, //输出有效
int_flag, //中断输出
rdyOutput //数据读出请求
);
//模块入口参数
parameter DW = 14; //数据位宽
parameter IH = 512; //图像高度
parameter IW = 640; //图像宽度
parameter TW = 32; //直方图统计数据位宽
localparam TOTAL_CNT = IW * IH; //像素总数
localparam HALF_WIDTH = (TW>>1); //将32位的数据位宽拆分
为高低16位
//输入输出声明
input rst_n;
input clk;
input din_valid;
input [DW-1:0]din;
input rdyOutput;
output reg [HALF_WIDTH:0]dout;
input vsync;
output reg dout_valid;
output reg int_flag;
//变量声明
reg vsync_r;
reg dvalid_r;
reg dvalid_r2;
reg [DW-1:0]din_r;
reg [DW-1:0]din_r2;
wire hsync_fall;
wire hsync_rise;
reg [9:0]hsync_count;
reg count_en;
wire [DW-1:0]mux_addr_b;
wire [DW-1:0]mux_addr_b2;
wire [TW-1:0]q_a;
wire [TW-1:0]q_b;
reg [TW-1:0]counter;
wire [TW-1:0]count_value;
wire rst_cnt; //统计计数器复位信号
wire inc_en; //递增使能信号
//DPRAM信号
wire we_a;
wire we_b;
wire we_b_l;
reg we_b_h;
wire [DW-1:0]addr_a;
//中断寄存器
reg int_r;
wire [DW-1:0]clr_addr; //清零地址
reg [DW-1:0]clr_addr_r;
reg [DW:0]out_pixel; //输出计数
reg count_all; //统计完成信号
//reg count_all_r;
reg count_en_r;
reg [TW-1:0]hist_cnt; //直方图统计累加寄存器
wire rstOutput; //读出电路复位信号
wire [TW-1:0]dataTmp2;
wire clr_flag; //全局清零信号
//将输入数据打两拍
always @(posedge clk or negedge rst_n)
if (((~(rst_n))) == 1'b1)
begin
vsync_r <= #1 1'b0;
dvalid_r <= #1 1'b0;
dvalid_r2 <= #1 1'b0;
din_r <= #1 {DW{1'b0}};
din_r2 <= #1 {DW{1'b0}};
end
else
begin
vsync_r <= #1 vsync;
dvalid_r <= #1 din_valid;
dvalid_r2 <= #1 dvalid_r;
din_r <= #1 din;
din_r2 <= #1 din_r;
end
//输入行同步计数,确定统计的开始和结束时刻
assign #1 hsync_fall = dvalid_r & (~(din_valid));
assign #1 hsync_rise = (~(dvalid_r)) & din_valid;
always @(posedge clk or negedge rst_n)
if (((~(rst_n))) == 1'b1)
hsync_count <= #1 {10{1'b0}};
else
begin
if (vsync_r == 1'b1)
hsync_count <= #1 {10{1'b0}};
else if (hsync_fall == 1'b1)
hsync_count <= hsync_count + 10'b1;
end
//一帧图像结束后停止统计 下一帧图像到来时开始计数
always @(posedge clk or negedge rst_n)
if (((~(rst_n))) == 1'b1)
count_en <= #1 1'b0;
else
begin
if (hsync_count >= IH)count_en <= #1 1'b0;
else if (hsync_rise == 1'b1)
count_en <= #1 1'b1;
else
count_en <= #1 count_en;
end
assign mux_addr_b = ((count_en == 1'b1)) ? din_r :
clr_addr;
assign mux_addr_b2 = ((count_en == 1'b1)) ? din_r :
clr_addr_r;
//统计递增计数器
always @(posedge clk)
begin
if (rst_cnt == 1'b1)
counter <= #1 {{TW-1{1'b0}},1'b1};//复位值为1
else if (inc_en == 1'b1)
counter <= #1 counter + {{TW-1{1'b0}},1'b1};
else
counter <= #1 counter;
end
assign#1 rst_cnt=((din_r!=din_r2)|((dvalid_r2==1'b1)&
(dvalid_r== 1'b0))) ? 1'b1 :1'b0;
assign #1 inc_en = (((din_r == din_r2) & (dvalid_r2 ==
1'b1))) ? 1'b1 :1'b0;
assign#1 we_a=((((din_r!=din_r2)&(dvalid_r2==1'b1))|
((dvalid_r2== 1'b1) & (dvalid_r == 1'b0)))) ? 1'b1 : 1'b0;assign#1 count_value=((count_en==1'b1))?counter+q_b:
{TW{1'b0}};
assign #1 addr_a = din_r2;
//直方图存储器 分高16位和低16位分别存储
hist_buffer dpram_bin_l(
.address_a(addr_a), //输入地址为像素灰度值
.address_b(mux_addr_b), //读出和清零地址
.clock(clk), //同步时钟
.data_a(count_value[HALF_WIDTH - 1:0]),//当前计数值
.data_b({HALF_WIDTH {1'b0}} ), //清零数据
.wren_a(we_a),
.wren_b(we_b_l),
.q_a(q_a[HALF_WIDTH - 1:0]),
.q_b(q_b[HALF_WIDTH - 1:0])
);
defparam dpram_bin_l.AW = DW;
defparam dpram_bin_l.DW = HALF_WIDTH;
hist_buffer dpram_bin_h(
.address_a(addr_a),
.address_b(mux_addr_b2),
.clock(clk),
.data_a(count_value[TW - 1:HALF_WIDTH]),
.data_b({HALF_WIDTH {1'b0}}),
.wren_a(we_a),
.wren_b(we_b_h),
.q_a(q_a[TW - 1:HALF_WIDTH]),
.q_b(q_b[TW - 1:HALF_WIDTH]));
defparam dpram_bin_h.AW = DW;
defparam dpram_bin_h.DW = HALF_WIDTH;
always @(posedge clk or negedge rst_n)
if (((~(rst_n))) == 1'b1)
count_en_r <= #1 1'b0;
else
count_en_r <= #1 count_en;
//读出电路逻辑 计数时不能输出,读出请求时才输出
assign rstOutput = count_en_r | (~(rdyOutput));
//输出像素计数
always @(posedge clk)
if (rstOutput == 1'b1)
out_pixel <= {DW+1{1'b0}};
else
begin
if ((~count_all) == 1'b1)
begin
if (out_pixel == (((2 ** (DW + 1)) - 1)))
out_pixel <= #1 {DW+1{1'b0}};//输出完毕
else
out_pixel <= #1 out_pixel + 1'b1;
end
end
//统计结束信号
always @(posedge clk)
begin//count_all_r <= (~rstOutput);
we_b_h <= we_b_l;
if (out_pixel == (((2 ** (DW + 1)) - 1)))
count_all <= #1 1'b1;
else if (count_en == 1'b1)
count_all <= #1 1'b0;
end
//全局清零信号
assign clr_flag = vsync;
//中断输出 信号读出操作完成
always @(posedge clk or negedge rst_n)
if ((~(rst_n)) == 1'b1)
begin
int_flag <= 1'b1;
int_r <= 1'b1;
end
else
begin
int_flag <= #1 int_r;
if (clr_flag == 1'b1)
int_r <= #1 1'b1;
else if (out_pixel >= (((2 ** (DW + 1)) - 1)))
int_r <= #1 1'b0;
end
assign we_b_l=(((out_pixel[0]==1'b1)&
(count_all==1'b0)))?1'b1:1'b0;
//清零地址,与读出地址反相assign clr_addr = out_pixel[DW:1];
wire dout_valid_temp;
wire [HALF_WIDTH-1:0]dout_temp;
always @(posedge clk or negedge rst_n)
if ((~(rst_n)) == 1'b1)
begin
dout <= {HALF_WIDTH{1'b0}};
dout_valid <= 1'b0;
end
else
begin
dout <= #1 dout_temp;
dout_valid <= #1 dout_valid_temp;
end
assign dout_temp = (we_b_l == 1'b1) ? q_b[HALF_WIDTH -
1:0]:
q_b[TW - 1:HALF_WIDTH];
assign dout_valid_temp = we_b_h |we_b_l;//输出使能

6.统计电路
我们以din_valid有效时刻作为分析起点统计电路关键信号,如表6-2所示。

        在时刻2,统计电路进入就绪状态,复位rst_cnt。在时刻3开始进 行 统 计 , 此 时 输 入 的 像 素 值 din_r2 为 155 , 同 时 读 出 其 统 计 值 counter_value为32,计数器加1,不执行写入;在时刻4,发现输入同 样为155,同样将计数器加1,不执行写入;直到时刻6,发现与上一个 值不同,将计数值counter(也就是3,表示3个连续相同的像素)与前 一个计数统计值32相加后写入,新的统计值变为35。我们也可在时刻8 看到,此时读出的统计值已经更新为35。

7.统计结果
TestBench设计如下所示:

/*hist data*/
wire hist_dvalid;
wire [16 - 1:0]hist_data;
wire hist_vsync;
/*hist data input*/
wire hist_dvalid_in;
wire [local_dw - 1:0]hist_data_in;wire hist_vsync_in;
reg hist_req;//hist data read req
wire hist_int;
histogram_2d hist(
.rst_n(reset_l),
.clk(cap_clk),
.din_valid(hist_dvalid_in),
.din(hist_data_in[7:0]),
.dout(hist_data),
.vsync(hist_vsync_in),
.dout_valid(hist_dvalid),
.int_flag(hist_int),
.rdyOutput(hist_req)
);
defparam hist.DW = 8;
defparam hist.IH = ih;
defparam hist.IW = iw;
assign hist_data_in = cap_data;
assign hist_vsync_in = cap_vsync;
assign hist_dvalid_in = cap_dvalid

        为了验证直方图统计的正确性,我们首先生成一幅分辨率为 256×256的8位灰度渐变图,对其进行直方图统计。可以预见的是,其 直方图均匀分布在0~255,并且每个灰度级的统计值应该是256,统计 结果如图6-23所示。可见,统计电路设计正确。

图6-23 统计结果输出 为了进一步验证设计的正确性,我们输入如图6-24所示的测试 图,分别用FPGA和VC对其进行直方图统计,然后再将两个统计结果进 行比对。 FPGA和VC的统计结果分别如图6-25(a)和图6-25(b)所示。可 见,统计结果完全正确。

  • 30
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BinaryStarXin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值