(一)FPGA-RGB转灰度
前言
在实际工程中,图像预处理往往是整个视觉系统中最基础、也是最关键的一环。从彩色图像到灰度图像的转换,看似简单,却广泛应用于边缘检测、目标识别、图像增强等后续算法中,同时也是 FPGA 图像处理入门中最典型、最具代表性的算子之一。
本文将以 最基础的RGB 转灰度为切入点,在python框架下结合cocotb构建仿真验证环境,并对比 Zynq PS 端 C 语言实现,最后通过上板测试展示实际图像效果与完整调试过程,帮助读者从算法到验证再到实现建立完整的工程认知。
cocotb 非常适合图像类仿真,在FPGA图像处理验证场景里比传统Verilog testbench更有优势,天然适合“大规模数据流”,其次可以直接复用算法作为reference进行自动对比。python支持复杂数据结构,可用于多通道/窗口/行缓存等情况,调试起来非常方便(图像可视化)。
(一)准备工作
在开始本文实践之前,需要准备基本的仿真与验证环境,包括 ModelSim、Python 以及相关第三方库。推荐使用 ModelSim 2024.1,Python 3.10,并在 Python 环境中安装 cocotb(1.9.2)及 numpy 库。上述版本组合已在实际工程中验证,兼容性良好且运行稳定,可直接用于 FPGA 设计的功能仿真与视频数据处理验证。
(二)文件目录
因为笔者是在windows下使用cocotb进行仿真,官方推荐的是使用 cocotb.runner,不需要 Makefile,会少很多路径造成的问题,直接用python脚本完成编译和仿真。文件目录如下所示。

图中run_sim.py主要功能是启动仿真,tb_rgb_2_gray.py起testbench的作用,给rtl激励,包括输入图像数据以及收取处理后的图像数据,rgb_2_gray.v就是实现RGB转灰度的rtl文件,sim_build文件夹存放仿真的生成文件,wave.do文件用来添加波形。
#run_sim.py
def main():
# ========== 设置路径 ==========
PROJ_PATH = Path(r"./")
RTL_FILES = [PROJ_PATH / "rtl" /"rgb_2_gray.v"]
TOP_NAME = "rgb_2_gray"
TEST_MODULE = "tb_rgb_2_gray"
SIMULATOR = "questa"
# ==============================
runner = get_runner(SIMULATOR)
# ========== 建立仿真工程 ==========
runner.build(
sources=RTL_FILES,
hdl_toplevel=TOP_NAME,
defines={
"COCOTB_SIM": None
},
timescale=("1ns", "1ps"),
build_dir=PROJ_PATH / "sim_build",
build_args=[
"-sv",
"-mfcu",
"+acc"
]
)
# ========== 运行仿真 ==========
runner.test(
hdl_toplevel=TOP_NAME,
test_module=TEST_MODULE,
build_dir=PROJ_PATH / "sim_build",
gui = OPEN_GUI,
waves= False,
test_args=[
"-voptargs=+acc",
"-do",
"../wave.do"
]
)
tb_rgb_2_gray.py主要是读取图片然后灌给rtl,并将rtl的输出结果与python执行的结果作对比。
#tb_rgb_2_gray.py部分
@cocotb.test()
async def tb_rgb_2_gray(dut):
img_path = Path(__file__).parent / "a.jpeg"
img = cv2.imread(str(img_path))
#img_64 = cv2.resize(img,dsize=(64,64),interpolation=cv2.INTER_AREA)
gray_ref = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
h, w, c = img.shape
out_gray_buf = np.zeros((h, w), dtype=np.uint8)
#sim begin
await init_sys(dut,10)
await Timer(200,units="ns")
#启动接收图像
recv_task = cocotb.start_soon(recv_gray_task(dut, h, w, out_gray_buf))
await load_img(dut,img)
await recv_task
await Timer(20000,units="ns")
compare_result(gray_ref,out_gray_buf)
(三)rgb转灰度原理
RGB 转灰度图像的核心原理是依据人眼对红、绿、蓝三色光的感知权重进行亮度加权求和,由于人眼对绿光敏感度最高、红光次之、蓝光最低,行业通用转换公式为 Gray = 0.299R + 0.587G + 0.114B,通过该公式对像素点的三组色彩通道数值分别加权运算后合并为单一亮度值,舍弃色彩信息仅保留画面明暗层次,最终得到单通道灰度图像。
//Gray=(306×R+601×G+117×B)>>10
//rgb_2_gray.v 部分代码
reg [17:0] R_w,G_w,B_w;
reg [17:0] Gray_w;
reg fsync_d1,fsync_d2;
reg data_en_d1,data_en_d2;
always @(posedge i_clk or negedge i_rst_n) begin
if(~i_rst_n)begin
R_w <= 0;
G_w <= 0;
B_w <= 0;
end else begin
`ifdef RGB
R_w <= i_data[23:16] * 306;
G_w <= i_data[15:8] * 601;
B_w <= i_data[7:0] * 117;
`else
R_w <= i_data[7:0] * 306;
G_w <= i_data[15:8] * 601;
B_w <= i_data[23:16] * 117;
`endif
end
end
always @(posedge i_clk or negedge i_rst_n) begin
if(~i_rst_n)begin
Gray_w <= 0;
end else begin
Gray_w <= (R_w + G_w + B_w);
end
end
always @(posedge i_clk or negedge i_rst_n) begin
if(~i_rst_n)begin
fsync_d1 <= 0;
fsync_d2 <= 0;
data_en_d1 <= 0;
data_en_d2 <= 0;
end else begin
fsync_d1 <= i_fsync;
fsync_d2 <= fsync_d1;
data_en_d1 <= i_data_en;
data_en_d2 <= data_en_d1;
end
end
assign o_data = (Gray_w>>10);
rtl代码先将小数拓展成整数,乘以1024倍,相加后的最终结果再除以1024。
(四)结果对比

原图笔者选择的是没有版权问题的新时代lena图,rtl和python的处理结果如下:

从人眼可能分辨不出来差别,所以两者之差的绝对值的热力图如下:
因为数据类型是uint8类型,所以最大差值的绝对值是在1以内,证明rtl的处理结果完全正确。
(五)实际效果
实际过程中,我是用的AXI-streaming接口来实现图像的处理,因为图像输入是通过Mipi,经CSI-2 rx subsystem输出的接口就是AXI-streaming接口,经过rgb转灰度处理后通过VDMA写入DDR,然后继续通过VDMA读出后由hdmi显示器显示。整个工程架构如下图所示。

因此,rtl代码也需要做相应的更改,因为AXI-S接口的关系,使用了两级寄存器需要对应两级反压,另外乘法的实现也从*号变成了移位实现(*号综合后可能会使用更多的资源),具体代码如下。
//axis_rgb_2_gray
// `define RGB
module axis_rgb_2_gray(
input clk,
input aresetn,
//S_AXIS
input [23:0] s_axis_tdata,
input s_axis_tvalid,
output s_axis_tready,
input s_axis_tlast,
input s_axis_tuser,
//M_AXIS
output [23:0] m_axis_tdata,
output m_axis_tvalid,
input m_axis_tready,
output m_axis_tlast,
output m_axis_tuser
);
wire slot1_ready,slot2_ready;
reg valid_1,valid_2;
reg stg1_last,stg2_last;
reg stg1_user,stg2_user;
reg [17:0] R_w,G_w,B_w;
reg [17:0] gray_sum_r;
wire [7:0] gray_8b;
assign gray_8b = gray_sum_r[17:10];
assign s_axis_tready = slot1_ready;
assign slot1_ready = (~valid_1) || slot2_ready;
assign slot2_ready = (~valid_2) || m_axis_tready;
always @(posedge clk or negedge aresetn) begin
if(~aresetn)begin
R_w <= 0;
G_w <= 0;
B_w <= 0;
valid_1 <= 0;
stg1_user <= 0;
stg1_last <= 0;
end else if(slot1_ready)begin
valid_1 <= s_axis_tvalid;
if(s_axis_tvalid)begin
`ifdef RGB
R_w <= (s_axis_tdata[23:16]<<8) + (s_axis_tdata[23:16]<<5) + (s_axis_tdata[23:16]<<4) + (s_axis_tdata[23:16]<<1); //306 = 256 + 32 + 16 + 2
G_w <= (s_axis_tdata[15:8]<<9) + (s_axis_tdata[15:8]<<6) + (s_axis_tdata[15:8]<<4) + (s_axis_tdata[15:8]<<3) + (s_axis_tdata[15:8]);//601 = 512 + 64 + 16 + 8 + 1
B_w <= (s_axis_tdata[7:0]<<6) + (s_axis_tdata[7:0]<<5) + (s_axis_tdata[7:0]<<4) + (s_axis_tdata[7:0]<<2) + (s_axis_tdata[7:0]); //64 + 32 + 16 + 4 + 1
`else
R_w <= (s_axis_tdata[7:0]<<8) + (s_axis_tdata[7:0]<<5) + (s_axis_tdata[7:0]<<4) + (s_axis_tdata[7:0]<<1); //306 = 256 + 32 + 16 + 2
G_w <= (s_axis_tdata[15:8]<<9) + (s_axis_tdata[15:8]<<6) + (s_axis_tdata[15:8]<<4) + (s_axis_tdata[15:8]<<3) + (s_axis_tdata[15:8]);//601 = 512 + 64 + 16 + 8 + 1
B_w <= (s_axis_tdata[23:16]<<6) + (s_axis_tdata[23:16]<<5) + (s_axis_tdata[23:16]<<4) + (s_axis_tdata[23:16]<<2) + (s_axis_tdata[23:16]); //64 + 32 + 16 + 4 + 1
`endif
stg1_last <= s_axis_tlast;
stg1_user <= s_axis_tuser;
end else begin
R_w <= 0;
G_w <= 0;
B_w <= 0;
stg1_last <= 0;
stg1_user <= 0;
end
end
end
always @(posedge clk or negedge aresetn) begin
if(~aresetn)begin
gray_sum_r <= 0;
valid_2 <= 0;
stg2_user <= 0;
stg2_last <= 0;
end else if(slot2_ready)begin
valid_2 <= valid_1;
if(valid_1)begin
gray_sum_r <= (R_w + G_w + B_w);
stg2_user <= stg1_user;
stg2_last <= stg1_last;
end else begin
gray_sum_r <= 0;
stg2_user <= 0;
stg2_last <= 0;
end
end
end
assign m_axis_tdata = {gray_8b,gray_8b,gray_8b};
assign m_axis_tuser = stg2_user;
assign m_axis_tvalid = valid_2;
assign m_axis_tlast = stg2_last;
endmodule
实际上板调试效果如下所示:

想要仿真工程的宝子可以关注并私信博主,免费获取系列章节的任一仿真工程~
更多推荐

所有评论(0)