FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训
登录
首页-技术文章/快讯-技术分享-正文

FPGA图像处理:Sobel边缘检测的流水线优化与资源权衡

二牛学FPGA二牛学FPGA
技术分享
13小时前
0
0
1

Quick Start

  • 下载并安装 Vivado 2023.2(或更高版本),创建一个新的 RTL 工程,目标器件选择 Xilinx Artix-7 XC7A35T(或其他常用 FPGA)。
  • 在工程中添加本文提供的 Sobel 核心 RTL 文件(sobel_top.v、line_buffer.v、sobel_core.v)以及一个简单的 testbench(tb_sobel.v)。
  • 编写一个 640×480 灰度图像(.hex 或 .coe 格式)作为测试激励,或使用 testbench 内部生成的随机像素流。
  • 运行行为仿真(Behavioral Simulation),观察 sobel_out 信号与 golden 参考值是否一致(可使用 MATLAB 预先计算标准 Sobel 结果)。
  • 综合(Synthesize)并查看资源利用率报告,确认 LUT、FF、BRAM 使用量在目标器件的 60% 以内。
  • 实现(Implement)并生成比特流,下载到开发板(如 Nexys A7),通过 VGA/HDMI 输出显示边缘检测结果,或通过串口回传像素数据验证。

预期结果:仿真波形中 sobel_out 与参考值误差小于 1 LSB;上板后图像边缘清晰连续,无撕裂或延迟超过 1 行。

前置条件与环境

项目推荐值说明替代方案
器件/板卡Xilinx Artix-7 XC7A35T资源适中,BRAM 够缓存 2 行图像Intel Cyclone IV / Lattice ECP5
EDA 版本Vivado 2023.2支持 SystemVerilog-2012,综合优化好Vivado 2021.1+ / Quartus Prime 20+
仿真器Vivado Simulator / ModelSim SE-64 2020.4支持波形对比与自动化检查Verilator(仅仿真,不支持综合)
时钟/复位主时钟 25 MHz(VGA 像素时钟)或 75 MHz(HDMI 倍频)Sobel 流水线每个时钟处理 1 像素50 MHz 需调整时序约束
接口依赖VGA 或 HDMI 输出(分辨率 640×480@60Hz)像素流同步信号(hsync, vsync, de)LVDS / DVP 摄像头输入
约束文件XDC 时序约束(主时钟周期 40 ns 或 13.33 ns)必须约束输入输出延迟,否则时序难收敛SDC(Quartus)

目标与验收标准

  • 功能正确:对于任意 3×3 窗口,输出 Sobel 梯度幅值(G = |Gx| + |Gy|)与 MATLAB 参考值误差 ≤ 1(像素值范围 0–255)。
  • 流水线延迟:从像素输入到对应输出延迟 ≤ 2 行 + 3 个时钟周期(含行缓冲与流水线级)。
  • 资源上限:LUT ≤ 600,FF ≤ 800,BRAM18K ≤ 4(640×480 灰度图像);Fmax ≥ 100 MHz(Artix-7 speed grade -1)。
  • 上板验收:输出图像边缘清晰,无水平条纹或错位;VGA 输出无闪烁,帧率稳定 60 Hz。

实施步骤

1. 工程结构与顶层模块

创建以下文件结构:

sobel_edge_detector/
├── rtl/
│   ├── sobel_top.v       # 顶层:包含行缓冲 + 3×3 窗口 + Sobel 核
│   ├── line_buffer.v     # 行缓冲:使用 BRAM 实现双行延迟
│   └── sobel_core.v      # 核心:Gx/Gy 卷积 + 幅值计算 + 阈值
├── sim/
│   ├── tb_sobel.v
│   └── test_image.hex
├── constr/
│   └── sobel_top.xdc
└── vivado/
    └── sobel.xpr

逐行说明

  • 第 1 行:根目录 sobel_edge_detector,存放所有工程文件。
  • 第 2–4 行:rtl 目录下三个 RTL 文件,分别对应顶层、行缓冲、Sobel 核心。
  • 第 5–7 行:sim 目录下 testbench 与测试图像文件(hex 格式,每行 8 位灰度值)。
  • 第 8 行:约束文件目录,存放时序与物理约束。
  • 第 9 行:Vivado 工程文件,双击可打开工程。

2. 行缓冲模块(line_buffer.v)

行缓冲负责缓存当前行与上一行像素,输出三行数据供 3×3 窗口使用。采用 BRAM 实现双端口 RAM,深度等于图像宽度(640),位宽 8 位。

module line_buffer #(
    parameter WIDTH = 640,
    parameter DATA_BITS = 8
) (
    input  wire                clk,
    input  wire                rst_n,
    input  wire                we,          // 写使能(像素有效时拉高)
    input  wire [DATA_BITS-1:0] din,        // 当前像素
    output wire [DATA_BITS-1:0] dout_line0, // 上一行像素
    output wire [DATA_BITS-1:0] dout_line1  // 上两行像素
);

    reg [DATA_BITS-1:0] ram [0:WIDTH-1];
    reg [$clog2(WIDTH)-1:0] waddr, raddr;

    always @(posedge clk) begin
        if (!rst_n) begin
            waddr <= 0;
            raddr <= 0;
        end else if (we) begin
            ram[waddr] <= din;
            waddr <= waddr + 1;
            raddr <= raddr + 1;
        end
    end

    assign dout_line0 = ram[raddr];
    assign dout_line1 = ram[raddr];  // 第二级延迟通过外部寄存器实现

endmodule

逐行说明

  • 第 1–3 行:模块声明,参数化图像宽度与数据位宽,便于复用。
  • 第 5–11 行:端口声明,clk/rst_n 为全局时钟复位;we 为写使能,仅在像素有效时写入;din 输入当前像素;dout_line0 输出上一行像素,dout_line1 输出上两行像素(实际需在顶层用寄存器延迟一拍)。
  • 第 13 行:声明 BRAM 数组 ram,深度 WIDTH,位宽 DATA_BITS。
  • 第 14 行:地址计数器,宽度由 $clog2 自动计算。
  • 第 16–23 行:写操作与地址更新。复位时地址清零;we 有效时写入并递增地址。
  • 第 25–26 行:读输出。注意 dout_line1 与 dout_line0 在同一地址读取,因为上两行像素需要在顶层用额外寄存器延迟实现。

3. Sobel 核心模块(sobel_core.v)

该模块接收 3×3 窗口的 9 个像素,计算 Gx 和 Gy 的绝对值之和,并与阈值比较输出二值化边缘。

module sobel_core #(
    parameter DATA_BITS = 8,
    parameter THRESHOLD = 64
) (
    input  wire                clk,
    input  wire                rst_n,
    input  wire [DATA_BITS-1:0] p00, p01, p02,  // 第 0 行(当前行)
    input  wire [DATA_BITS-1:0] p10, p11, p12,  // 第 1 行
    input  wire [DATA_BITS-1:0] p20, p21, p22,  // 第 2 行
    output reg  [DATA_BITS-1:0] sobel_out
);

    // 流水线寄存器
    reg signed [DATA_BITS+2:0] gx, gy;
    reg [DATA_BITS+1:0] sum;

    // 第 1 级:计算 Gx 和 Gy
    always @(posedge clk) begin
        if (!rst_n) begin
            gx <= 0;
            gy <= 0;
        end else begin
            // Gx = (p02 + 2*p12 + p22) - (p00 + 2*p10 + p20)
            gx <= (p02 + (p12 << 1) + p22) - (p00 + (p10 << 1) + p20);
            // Gy = (p20 + 2*p21 + p22) - (p00 + 2*p01 + p02)
            gy <= (p20 + (p21 << 1) + p22) - (p00 + (p01 << 1) + p02);
        end
    end

    // 第 2 级:计算幅值 |Gx| + |Gy|
    always @(posedge clk) begin
        if (!rst_n) begin
            sum <= 0;
        end else begin
            sum <= (gx[DATA_BITS+2] ? -gx : gx) + (gy[DATA_BITS+2] ? -gy : gy);
        end
    end

    // 第 3 级:阈值比较并输出
    always @(posedge clk) begin
        if (!rst_n) begin
            sobel_out <= 0;
        end else begin
            sobel_out <= (sum > THRESHOLD) ? 8'd255 : 8'd0;
        end
    end

endmodule

逐行说明

  • 第 1–2 行:参数化数据位宽与阈值,阈值默认为 64,可根据图像对比度调整。
  • 第 4–11 行:端口声明,p00–p22 为 3×3 窗口的 9 个像素(行优先,p00 为左上角)。
  • 第 14–15 行:内部寄存器 gx、gy 为有符号数,位宽 DATA_BITS+3 防止溢出(最大 ±1020)。sum 为无符号幅值。
  • 第 18–26 行:第 1 级流水线,计算 Gx 和 Gy。使用移位(<< 1)实现乘以 2,节省乘法器资源。注意减法可能产生负值,因此 gx/gy 声明为 signed。
  • 第 29–34 行:第 2 级流水线,计算绝对值之和。使用条件运算符判断符号位(最高位)实现绝对值,避免使用 DSP 或除法器。
  • 第 37–42 行:第 3 级流水线,与阈值比较,输出 0 或 255(二值化边缘)。

4. 顶层模块(sobel_top.v)

顶层例化两个行缓冲(line0 和 line1)以缓存两行像素,并例化 Sobel 核心。同时生成 3×3 窗口寄存器。

module sobel_top #(
    parameter WIDTH = 640,
    parameter DATA_BITS = 8,
    parameter THRESHOLD = 64
) (
    input  wire                clk,
    input  wire                rst_n,
    input  wire                de,          // 数据使能(像素有效)
    input  wire [DATA_BITS-1:0] pixel_in,
    output reg  [DATA_BITS-1:0] edge_out
);

    // 内部连线
    wire [DATA_BITS-1:0] line0_out, line1_out;
    reg  [DATA_BITS-1:0] shift_reg [0:8];  // 3×3 窗口寄存器
    wire we = de;

    // 例化行缓冲
    line_buffer #(.WIDTH(WIDTH), .DATA_BITS(DATA_BITS)) u_line0 (
        .clk(clk), .rst_n(rst_n), .we(we),
        .din(pixel_in),
        .dout_line0(line0_out), .dout_line1()
    );

    line_buffer #(.WIDTH(WIDTH), .DATA_BITS(DATA_BITS)) u_line1 (
        .clk(clk), .rst_n(rst_n), .we(we),
        .din(line0_out),
        .dout_line0(line1_out), .dout_line1()
    );

    // 生成 3×3 窗口
    always @(posedge clk) begin
        if (!rst_n) begin
            for (int i = 0; i &lt; 9; i++) shift_reg[i] &lt;= 0;
        end else if (we) begin
            // 第 0 行:pixel_in(当前行)
            shift_reg[0] &lt;= pixel_in;
            shift_reg[1] &lt;= shift_reg[0];
            shift_reg[2] &lt;= shift_reg[1];
            // 第 1 行:line0_out(上一行)
            shift_reg[3] &lt;= line0_out;
            shift_reg[4] &lt;= shift_reg[3];
            shift_reg[5] &lt;= shift_reg[4];
            // 第 2 行:line1_out(上两行)
            shift_reg[6] &lt;= line1_out;
            shift_reg[7] &lt;= shift_reg[6];
            shift_reg[8] &lt;= shift_reg[7];
        end
    end

    // 例化 Sobel 核心
    sobel_core #(.DATA_BITS(DATA_BITS), .THRESHOLD(THRESHOLD)) u_sobel (
        .clk(clk), .rst_n(rst_n),
        .p00(shift_reg[6]), .p01(shift_reg[7]), .p02(shift_reg[8]),
        .p10(shift_reg[3]), .p11(shift_reg[4]), .p12(shift_reg[5]),
        .p20(shift_reg[0]), .p21(shift_reg[1]), .p22(shift_reg[2]),
        .sobel_out(edge_out)
    );

endmodule

逐行说明

  • 第 1–4 行:顶层参数化,便于修改图像宽度与阈值。
  • 第 6–13 行:端口包括时钟、复位、数据使能 de(高电平表示像素有效)、像素输入与边缘输出。
  • 第 16–17 行:内部连线 line0_out、line1_out 来自两个行缓冲的输出。shift_reg 为 9 个寄存器,构成 3×3 窗口。
  • 第 20–25 行:例化第一个行缓冲 u_line0,输入 pixel_in,输出 line0_out(上一行)。第二个行缓冲 u_line1 输入 line0_out,输出 line1_out(上两行)。
  • 第 28–42 行:生成 3×3 窗口。每个时钟上升沿,当 we 有效时,shift_reg 更新:第 0 行(当前行)由 pixel_in 串入;第 1 行(上一行)由 line0_out 串入;第 2 行(上两行)由 line1_out 串入。注意 shift_reg 索引:shift_reg[0] 为当前行最新像素,shift_reg[2] 为当前行最旧像素,因此 Sobel 核心的 p00–p22 映射需要调整(见第 46 行)。
  • 第 44–49 行:例化 Sobel 核心,注意 p00–p22 的连线:p00 对应 shift_reg[6](上两行最左),p02 对应 shift_reg[8](上两行最右),p20 对应 shift_reg[0](当前行最左),p22 对应 shift_reg[2](当前行最右)。

5. 时序约束(sobel_top.xdc)

# 主时钟约束(25 MHz,周期 40 ns)
create_clock -period 40.000 -name clk [get_ports clk]

# 输入延迟约束(假设外部器件输出延迟 2 ns)
set_input_delay -clock clk -max 2.0 [get_ports pixel_in]
set_input_delay -clock clk -min 0.5 [get_ports pixel_in]

# 输出延迟约束(假设 VGA 建立时间 1 ns)
set_output_delay -clock clk -max 1.0 [get_ports edge_out]
set_output_delay -clock clk -min 0.2 [get_ports edge_out]

# 伪路径约束(复位信号异步)
set_false_path -to [get_ports rst_n]

逐行说明

  • 第 1 行:创建 25 MHz 主时钟,周期 40 ns,绑定到 clk 端口。
  • 第 4–5 行:输入延迟约束,假设外部摄像头或图像源输出延迟最大 2 ns、最小 0.5 ns。这确保 Vivado 在分析建立时间时考虑最坏情况。
  • 第 8–9 行:输出延迟约束,假设 VGA 接口要求数据在时钟上升沿前 1 ns 稳定。
  • 第 12 行:将复位信号设为伪路径,避免时序分析报告虚假违规(因为复位是异步的)。

6. 仿真验证

编写 testbench 读取 test_image.hex,逐像素输入,同时与 MATLAB 预计算的 golden 值比较。关键代码片段:

initial begin
    // 读取测试图像
    $readmemh("test_image.hex", img_mem);
    // 等待复位释放
    @(posedge rst_n);
    #100;
    // 逐像素输入
    for (int y = 0; y &lt; 480; y++) begin
        for (int x = 0; x &lt; 640; x++) begin
            @(posedge clk);
            de = 1;
            pixel_in = img_mem[y*640 + x];
            // 延迟 3 个时钟后检查输出
            if (y &gt;= 2 &amp;&amp; x &gt;= 2) begin
                // 比较 sobel_out 与 golden[y-2][x-2]
                if (sobel_out !== golden[y-2][x-2])
                    $error("Mismatch at (%d,%d): got %d, expected %d",
                           y, x, sobel_out, golden[y-2][x-2]);
            end
        end
        // 行消隐期间 de 拉低
        repeat(160) @(posedge clk) de = 0;
    end
    $finish;
end

逐行说明

  • 第 2 行:使用 $readmemh 将 hex 文件读入内存数组 img_mem。
  • 第 4–5 行:等待复位释放后延迟 100 ns 开始输入。
  • 第 7–8 行:双重循环遍历所有像素(480 行 × 640 列)。
  • 第 9–11 行:每个时钟上升沿,拉高 de,输入当前像素。
  • 第 12–16 行:由于流水线延迟,从第 3 行第 3 列开始检查输出(前两行和前两列没有有效输出)。与 golden 数组比较,golden 由 MATLAB 预计算。
  • 第 19 行:行消隐期间拉低 de 160 个时钟(模拟 VGA 行消隐)。

常见坑与排查

坑 2:3×3 窗口错位</strong
  • 坑 1:行缓冲地址溢出。如果图像宽度不是 2 的幂,地址计数器可能溢出。解决:使用 $clog2 计算地址宽度,并在计数器达到 WIDTH-1 后回绕。
  • 坑 2:3×3 窗口错位</strong
标签:
本文原创,作者:二牛学FPGA,其版权均为FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训所有。
如需转载,请注明出处:https://z.shaonianxue.cn/40871.html
二牛学FPGA

二牛学FPGA

初级工程师
这家伙真懒,几个字都不愿写!
91919.30W3.99W3.67W
分享:
成电国芯FPGA赛事课即将上线
基于FPGA的DDS信号发生器:相位累加器与查找表优化设计指南
基于FPGA的DDS信号发生器:相位累加器与查找表优化设计指南上一篇
FPGA中LUT与BRAM的资源分配策略:以CNN加速器设计为例下一篇
FPGA中LUT与BRAM的资源分配策略:以CNN加速器设计为例
相关文章
总数:944
FPGA项目实战:基于UART的通信模块设计与验证

FPGA项目实战:基于UART的通信模块设计与验证

QuickStart准备环境:安装Vivado2020.1及以上…
技术分享
7天前
0
0
20
0
从校园到职场:FPGA、嵌入式、单片机赛道对应的企业岗位与核心技能树

从校园到职场:FPGA、嵌入式、单片机赛道对应的企业岗位与核心技能树

本文旨在为电子、计算机、自动化等相关专业的在校生及初入职场者,提供一份关…
技术分享
14天前
0
0
26
0
基于ov5640的图像采集与UDP传输显示(学员项目答辩)

基于ov5640的图像采集与UDP传输显示(学员项目答辩)

机遇ov5640的图像采集与UDP传输显示(学员项目答辩现场)
技术分享
9个月前
0
0
405
0
评论表单游客 您好,欢迎参与讨论。
加载中…
评论列表
总数:0
FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训
没有相关内容