Verilog实现图像处理的行缓存Line Buffer

在图像处理中,难免会遇到对图像进行卷积或者模板的局部处理,例如ISP中的一些算法,很大部分都需要一个窗口,在实时视频处理中,可以利用行缓存Line buffer可以暂存几行数据,然后同时输出每行中的对应列的像素。

下面以一个三行缓存进行解释

DVP像素流从Shif_in输入,像素不流不断在行缓存中移动,行缓存的最后一个像素使用line_out来保存,通过这种方式,lin_out输出部分就是每行对应列中的像素,在算法模块中,对line_out寄存三拍,就可以得到3x3的窗口大小。

暂存区的设计可以使用一个双口同步RAM来实现:

cpp 复制代码
module simple_dp_ram
#(
	parameter DW = 8,
	parameter AW = 4,
	parameter SZ = 2**AW
)
(
	input           clk,
	input           wen,
	input  [AW-1:0] waddr,
	input  [DW-1:0] wdata,
	input           ren,
	input  [AW-1:0] raddr,
	output [DW-1:0] rdata
);
	reg [DW-1:0] mem [SZ-1:0];
	always @ (posedge clk) begin
		if (wen) begin
			mem[waddr] <= wdata;
		end
	end
	reg [DW-1:0] q;
	always @ (posedge clk) begin
		if (ren) begin
			q <= mem[raddr];
		end
	end
    
	// always @(posedge clk) begin
	// 	// 先执行读操作
	// 	if (ren) begin
	// 		q <= mem[raddr];
	// 	end

	// 	// 后执行写操作
	// 	if (wen) begin
	// 		mem[waddr] <= wdata;
	// 	end
	// end

	assign rdata = q;
endmodule

这里涉及到一个问题,当读地址和写地址相同时,会导致冲突吗?

我这里找到的解释是,具体行为会依赖于底层的实现,在同一个时钟周期同时读写同一个地址在FPGA中通常是读优先的,即会先读取该地址的旧值,然后向该地址写如新值。(有大佬了解的可以帮忙解释以下:))。

行缓存LineBuffer源码如下所示:

cpp 复制代码
///基于卷积或者模板的算法需要使用该模块
module shift_register
#(
	parameter BITS = 8,         
	parameter WIDTH = 480,    //行缓存宽度
	parameter LINES = 3       //行缓存行数
)
(
	input                clock,    
	input                clken, 
	input  [BITS-1:0]    shiftin,        //输入数据流
	output [BITS-1:0]    shiftout,       //当前行最后一个像素的数据输出。
	output [BITS*LINES-1:0] tapsx        //行缓存的输出:包含每行最后一个像素的输出,拼接成一个长向量
);

	localparam RAM_SZ = WIDTH - 1;   //每行的实际大小
	localparam RAM_AW = clogb2(RAM_SZ);  //计算地址宽度


    ///地址指针逻辑
	reg [RAM_AW-1:0] pos_r;   //pos_r:记录当前写入的位置。
    ///(RAM_SZ[RAM_AW-1:0] - 1'b1)实际上就是WIDTH - 2
    //行缓存的最后一个像素使用line_out来保存,RAM实际上只保存了WDITH-1个像素(pos在0~WIDTH-2之间)
	wire [RAM_AW-1:0] pos = pos_r < RAM_SZ ? pos_r : (RAM_SZ[RAM_AW-1:0] - 1'b1);  ///当 pos_r 超过范围时限制其值,用于确保地址有效
	always @ (posedge clock) begin
		if (clken) begin     
			if (pos_r < RAM_SZ - 1)  
				pos_r <= pos_r + 1'b1;   //地址指针 pos_r 循环递增,直到到达最大地址后重置为 0
			else
				pos_r <= 0;
		end
	end

	reg [BITS-1:0] in_r;  
	always @ (posedge clock) begin
		if (clken) begin
			in_r <= shiftin;      //寄存输入数据 shiftin,用于提供给第 0 行的 RAM 写入。
		end
	end

	wire [BITS-1:0] line_out[LINES-1:0];    //记录每行的最后一个像素

    ///生成菊花链行缓存结构,在第二行开始才将上一行末尾连接到下一行的输入中,第一行输入in_r,每一行的最后一个像素寄存在line_out中
	generate          
		genvar i;
		for (i = 0; i < LINES; i = i + 1) begin : gen_ram_inst
            //在当前周期,line_out[i] 先从 pos 地址中读取之前存储的数据。随后将 (i > 0 ? line_out[i-1] : in_r) 写入 pos 地址。
			simple_dp_ram #(BITS, RAM_AW, RAM_SZ) u_ram(clock, clken, pos, (i > 0 ? line_out[i-1] : in_r), clken, pos, line_out[i]);
		end
	endgenerate


	assign shiftout = line_out[LINES-1];     //移位输出
	generate
		genvar j;
		for (j = 0; j < LINES; j = j + 1) begin : gen_taps_assign
			assign tapsx[(BITS*j)+:BITS] = line_out[j];
		end
	endgenerate


    //计算需要位宽
	function integer clogb2;    
	input integer depth;
	begin
		for (clogb2 = 0; depth > 0; clogb2 = clogb2 + 1)
			depth = depth >> 1;
	end
	endfunction
endmodule

这里使用了一个clogb2函数来获取配置的WIDTH需要的位宽。在代码中涉及了同一时钟周期对双口RAM中同一地址的读写问题,既然项目最后能跑起来,应该就是读优先的。

相关推荐
bnsarocket1 天前
Verilog和FPGA的自学笔记1——FPGA
笔记·fpga开发·verilog·自学
七芒星20231 天前
多目标识别YOLO :YOLOV3 原理
图像处理·人工智能·yolo·计算机视觉·目标跟踪·分类·聚类
helesheng2 天前
用低成本FPGA实现FSMC接口的多串口(UART)控制器
stm32·fsmc·fpga·uart控制器
2401_841495643 天前
【计算机视觉】分水岭实现医学诊断
图像处理·人工智能·python·算法·计算机视觉·分水岭算法·医学ct图像分割
格林威3 天前
常规可见光相机在工业视觉检测中的应用
图像处理·人工智能·数码相机·计算机视觉·视觉检测
扶尔魔ocy4 天前
【QT常用技术讲解】multimedia实现指定分辨率打开摄像头
图像处理·qt
格林威4 天前
工业视觉检测里的 “柔性” 是什么?
图像处理·人工智能·深度学习·yolo·计算机视觉·视觉检测
格林威4 天前
不同光谱的工业相机有哪些?能做什么?
图像处理·人工智能·深度学习·数码相机·计算机视觉·视觉检测
CiLerLinux4 天前
第三十八章 ESP32S3 SPIFFS 实验
图像处理·人工智能·单片机·嵌入式硬件
要做朋鱼燕4 天前
【OpenCV】图像处理入门:从基础到实战技巧
图像处理·人工智能·opencv