FPGA图像处理之三行缓存


一、前言

  在 FPGA 做图像处理时,行缓存是一个非常重要的一个步骤,因为图像输入还有输出都是一行一行进行的,即处理完一行后再处理下一行。行缓存可以存储当前行和前一行的数据以及多行的数据,使得在处理当前行时能够方便地访问周围像素。许多图像处理的算法都需要几行的图像数据进行处理,因此行缓存是非常重要的,本文实现三行缓存,多行缓存的思想也是一致的。

二、FPGA实现三行缓存的架构

  由于图像数据一般都是从上到下从左到右一个一个输入进来,因此我们优先考虑使用FIFO,先进先出。按照一般想法,我们只需要三个FIFO,每个FIFO存储一行数据即可实现三行缓存,这里可以节省资源只使用两个FIFO实现,具体实现框框架如下:

在这里插入图片描述

  开始时,图像数据的第0行写入到FIFO1中,图像数据的第1行写入到FIFO2中。

在这里插入图片描述
  当第2行数据到来时写入到FIFO2中,同时输出写入的数据作为第二行;同时读出FIFO2中的数据写入到FIFO1中,并输出作为第一行,同时读出FIFO1中的数据输出作为第0行。

在这里插入图片描述
  同理,当第三行数据来临时写入到FIFO2中,同时输出写入数据作为第二行,再同时读出FIFO2中的数据写入到FIFO1中并输出作为第一行,同时读出FIFO2中的数据输出作为第0行。后面的行以此类推。

三、Verilog代码实现

  先看输入接口,输入为像素数据和有效信号,输出为三行数据以及有效信号。

 	input                                               sys_clk ,
    input                                               sys_rst ,
    input           [23:0]                              i_img_data  ,
    input                                               i_img_data_valid    ,
    output          [23:0]                              o_img_data_1line  ,
    output          [23:0]                              o_img_data_2line  ,
    output          [23:0]                              o_img_data_3line  ,
    output                                              o_img_data_valid    

  再次例化两个FIFO,位宽就为一个像素位宽,深度为一行中最多的像素数量。

img_line_buffer_fifo u0_img_line_buffer_fifo (
  .clk  (sys_clk        ),  // input wire clk
  .srst (sys_rst        ),  // input wire srst
  .din  (fifo1_wr_data  ),  // input wire [23 : 0] din
  .wr_en(fifo1_wr_en    ),  // input wire wr_en
  .rd_en(rd_en          ),  // input wire rd_en
  .dout (fifo1_q        ),  // output wire [23 : 0] dout
  .full (),                 // output wire full
  .empty()                  // output wire empty
);

img_line_buffer_fifo u1_img_line_buffer_fifo (
  .clk  (sys_clk        ),  // input wire clk
  .srst (sys_rst        ),  // input wire srst
  .din  (fifo2_wr_data  ),  // input wire [23 : 0] din
  .wr_en(fifo2_wr_en    ),  // input wire wr_en
  .rd_en(rd_en          ),  // input wire rd_en
  .dout (fifo2_q        ),  // output wire [23 : 0] dout
  .full (),                 // output wire full
  .empty()                  // output wire empty
);

  然后根据架构图编写出剩下的代码,编写仿真代码。

四、仿真验证

  仿真我们先设置图像宽度为50*50,这样仿真可以跑快一点,然后写入数据流为每次都是0-49循环。就像一幅图像的第0行数据是0-49,第1行的数据也是0-49,每一行的数据都是0-49。按照想法,我们每次输出的数据就是前三行的像素,也就是3行的 0-49数据,仿真代码如下:

`timescale 1ns / 1ps

module tb_img_3line_buffer();

reg                                                 sys_clk ;
reg                                                 sys_rst ;
reg                                                 i_img_data_valid    ;
reg             [23:0]                              i_img_data  ;
reg             [12:0]                              cnt ;
wire            [23:0]                              o_img_data_1line    ;
wire            [23:0]                              o_img_data_2line    ;
wire            [23:0]                              o_img_data_3line    ;
wire                                                o_img_data_valid    ;

initial begin
    sys_clk =0;
    sys_rst = 1;
    i_img_data_valid = 0;
    i_img_data = 'd0;
    #200;
    sys_rst = 0;
end

always #5 sys_clk = ~sys_clk;

always @(posedge sys_clk) begin
    if(sys_rst == 1'b1)
        i_img_data_valid <= 1'b0;
    else if(cnt == 49)
        i_img_data_valid <= 1'b0;
    else
        i_img_data_valid <= 1'b1;
end

always @(posedge sys_clk) begin
    if(sys_rst == 1'b1)
        cnt <= 'd0;
    else if(cnt == 49)
        cnt <= 'd0;
    else if(i_img_data_valid == 1'b1)
        cnt <= cnt + 1'b1;
    else
        cnt <= cnt;
end

always @(posedge sys_clk) begin
    if(sys_rst == 1'b1)
        i_img_data <= 'd0;
    else if(i_img_data == 49)
        i_img_data <= 'd0;
    else if(i_img_data_valid == 1'b1)
        i_img_data <= i_img_data + 1'b1;
    else
         i_img_data <= i_img_data;
end

img_3line_buffer#(
    .IMG_WIDTH         ( 50 ),
    .IMG_HEIGHT        ( 50 )
)u_img_3line_buffer(
    .sys_clk           ( sys_clk           ),
    .sys_rst           ( sys_rst           ),
    .i_img_data        ( i_img_data        ),
    .i_img_data_valid  ( i_img_data_valid  ),
    .o_img_data_1line  ( o_img_data_1line  ),
    .o_img_data_2line  ( o_img_data_2line  ),
    .o_img_data_3line  ( o_img_data_3line  ),
    .o_img_data_valid  ( o_img_data_valid  )
);

endmodule

  运行仿真

在这里插入图片描述
  可以看到写入的每一行数据都是0-49,写入两行后,开始输出数据。

在这里插入图片描述
  我们可以看到输出的三行数据都是0-49的数据。符合预期。我们修改一下仿真代码,写入2500个数据,对应50*50的图像大小,数据为0-2499,这样第0行的数据就是0-49,第1行的数据就是50-99,第2行的数据就是100-149,第3行的数据就是150-199。输出的数据就应该是(0,50,100),(1,51,101)以此类推,仿真代码如下:

`timescale 1ns / 1ps

module tb_img_3line_buffer();

reg                                                 sys_clk ;
reg                                                 sys_rst ;
reg                                                 i_img_data_valid    ;
reg             [23:0]                              i_img_data  ;
reg             [12:0]                              cnt ;
wire            [23:0]                              o_img_data_1line    ;
wire            [23:0]                              o_img_data_2line    ;
wire            [23:0]                              o_img_data_3line    ;
wire                                                o_img_data_valid    ;

initial begin
    sys_clk =0;
    sys_rst = 1;
    i_img_data_valid = 0;
    i_img_data = 'd0;
    #200;
    sys_rst = 0;
end

always #5 sys_clk = ~sys_clk;

always @(posedge sys_clk) begin
    if(sys_rst == 1'b1)
        i_img_data_valid <= 1'b0;
    else if(cnt == 2499)
        i_img_data_valid <= 1'b0;
    else
        i_img_data_valid <= 1'b1;
end

always @(posedge sys_clk) begin
    if(sys_rst == 1'b1)
        cnt <= 'd0;
    else if(cnt == 2499)
        cnt <= 'd0;
    else if(i_img_data_valid == 1'b1)
        cnt <= cnt + 1'b1;
    else
        cnt <= cnt;
end

always @(posedge sys_clk) begin
    if(sys_rst == 1'b1)
        i_img_data <= 'd0;
    else if(i_img_data == 2499)
        i_img_data <= 'd0;
    else if(i_img_data_valid == 1'b1)
        i_img_data <= i_img_data + 1'b1;
    else
         i_img_data <= i_img_data;
end

img_3line_buffer#(
    .IMG_WIDTH         ( 50 ),
    .IMG_HEIGHT        ( 50 )
)u_img_3line_buffer(
    .sys_clk           ( sys_clk           ),
    .sys_rst           ( sys_rst           ),
    .i_img_data        ( i_img_data        ),
    .i_img_data_valid  ( i_img_data_valid  ),
    .o_img_data_1line  ( o_img_data_1line  ),
    .o_img_data_2line  ( o_img_data_2line  ),
    .o_img_data_3line  ( o_img_data_3line  ),
    .o_img_data_valid  ( o_img_data_valid  )
);

endmodule

  运行仿真

在这里插入图片描述
在这里插入图片描述
  验证完成和预期一致,后续一些图像处理算法需要用到这个行缓存。

五、输入图像数据进行仿真验证

  现在我们在仿真中输入一张图片,然后通过三行缓存输出,每次只取出第一行的数据写入到新的图片中:

在这里插入图片描述
  可以看出输出的图像和输入图像一模一样,文件大小也是一模一样,因此三行缓存是没问题的。

<think>嗯,用户想了解FPGA中的三缓存实现原理。我先回忆一下之前学过的相关知识。三缓存图像处理中很常见,比如3x3卷积核需要同时访问三数据。用户提到的引用里提到,三缓存通常用FIFO或缓存模块实现,可能还涉及边界处理。 首先,得解释三缓存的基本作用。为什么需要三缓存?因为像图像处理中的卷积操作,比如边缘检测,需要同时访问当前、上一和下一的像素数据。FPGA处理图像通常是流式处理,逐像素输入,所以需要缓存前两的数据,这样在处理第三的时候,三数据都准备好了。 然后,实现原理部分。根据引用[2],使用两个缓存模块,第一个缓存存第N,第二个存第N+1。当处理到第N+2时,同时从当前输入(第N+2)、第二个缓存(第N+1)和第一个缓存(第N)读取数据,形成三窗口。这样就能实时获取三像素。需要说明每个时钟周期,数据如何流动,比如当前直接进入处理,同时写入第一个缓存,而第一个缓存的输出又进入第二个缓存,形成延迟两的效果。 可能还需要提到边界处理,比如第一的时候,上一不存在,这时候可能用复制当前或者填充0。这在引用[2]里也有提到。另外,同步控制很重要,要确保三数据对齐,避免数据错位。 还要考虑硬件资源的优化,比如FIFO的深度如何设置,是否使用双端口RAM,或者乒乓缓存技术,如引用[3]提到的,交替写入和读取不同的缓冲区,提高吞吐量。多路视频的情况,引用[4]提到同步计数器的设置,这可能对多缓存的同步机制有参考价值。 可能需要对比不同的实现方式,比如使用两个FIFO级联,每个FIFO缓存数据,这样当前像素进入时,第一个FIFO输出上一,第二个FIFO输出上上,从而组合成三。或者使用缓存模块,每个模块存储一,依次传递数据。 最后,总结三缓存的优势,比如并处理、低延迟,以及应用场景,如图像滤波、边缘检测等。可能还要提到实现时的注意事项,比如资源占用、时序约束和边界处理策略。 需要确保回答结构清晰,分点说明原理、实现方式和关键点,并结合引用中的例子,比如代码结构或同步方法。同时,生成的相关问题要围绕扩展应用、优化方法、同步处理等,帮助用户深入理解。</think>### FPGA缓存实现原理 在FPGA图像处理中,**三缓存**是实现3×3卷积核、形态学操作等算法的核心组件,其核心目标是为流式像素数据提供相邻三的并访问能力。 #### 一、基本原理 1. **数据流需求** 对于3×3卷积核(如高斯滤波、边缘检测),需同时读取当前像素的**上一、当前、下一**数据。由于图像按顺序输入,FPGA缓存前两数据以实现三输出[^2]。 2. **缓存架构** 通过**两级缓存模块**实现延迟控制: - **第一级缓存**:存储当前输入的前一数据(即$N-1$) - **第二级缓存**:存储第一级缓存的前一数据(即$N-2$) - **实时数据流**:当前输入为$N$时,三缓存输出依次为$N-2$、$N-1$、$N$,构成三窗口[^1]。 3. **硬件实现** - **缓存模块**:通常采用FPGA的**Block RAM**或**分布式RAM**实现,深度为图像宽度(如1920像素) - **控制逻辑**:通过计数器同步、列坐标,确保三数据对齐[^2]。 #### 二、关键实现细节 1. **边界处理** - **首/末处理**:当处理首时,复制当前或填充0作为虚拟上一;末同理[^2]。 - 代码示例(Verilog): ```verilog always @(posedge clk) begin if (row_counter == 0) // 首 line1 <= (col_counter == 0) ? 0 : pixel_in; else line1 <= fifo1_out; // 正常缓存 end ``` 2. **同步机制** - 使用**全局计数器**控制读写时序,确保三数据相位一致 - 多路视频场景下,需通过帧同步计数器实现跨缓存同步[^4]。 3. **资源优化** - **乒乓缓存**:交替写入两个缓存区,实现无冲突读写(引用[3]的交替策略) - **FIFO级联**:两级FIFO构成延迟链,每级延迟一数据。 #### 三、典型应用场景 1. 图像滤波(中值滤波、高斯滤波) 2. 边缘检测(Sobel算子) 3. 形态学操作(膨胀、腐蚀) ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

爱奔跑的虎子

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

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

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

打赏作者

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

抵扣说明:

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

余额充值