FPGA verilog 临近插值任意比例视频缩放代码

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_46621272/article/details/126520389


FPGA verilog 临近插值任意比例视频缩放代码


文章目录

    • @[TOC](文章目录)
    • 前言
    • 简介
    • 两个版本的 verilog 视频缩放代码
    • 效果图片
    • V1 临近插值任意比例视频缩放代码 video_scale_near_v1.sv
    • V2 临近插值任意比例视频缩放代码 video_scale_near_v2.sv
    • 仿真测试 video_scale_near_testbench.sv
    • 用于验证的 C 语言编写的代码
    • 缩放模块中用到的 FIFO IP 截图
    • 本文中的一些没贴出的模块代码函数代码在连接中能找到
    • 下载链接

前言

  • 视频分割算法,视频拼接算法。
  • 图像分割算法,图像拼接算法。
  • 临近插值图像视频缩放模块。
  • 支持水平缩放、垂直缩放。支持任意比列的缩放算法。
  • 不到 300 行代码,占用FPGA资源极少。
  • 在 XILINX Artix-7 FPGA 上轻松实现 8 路 1080P60 视频分割。
  • 非常适合做动态视频监控中的多画面分割。
  • 由于临近算法的先天不足,不适用 PPT、地图、医学影像等静态视频图像的应用。
  • Syetem Verilog 源代码

简介

  • 临近缩放实现简介
  • 一行宽度 4 的图像放大到 5 [ AA BB CC DD ] --> [ AA AA BB CC DD ]
  • 一行宽度 4 的图像放大到 7 [ AA BB CC DD ] --> [ AA AA BB BB CC CC DD ]
  • 临近放大,就是将合适的像素复制重复。
  • 一行宽度 5 的图像缩小到 4 [ AA BB CC DD EE ] --> [ AA BB CC DD ]
  • 一行宽度 6 的图像缩小到 4 [ AA BB CC DD EE FF ]–> [ AA BB DD EE ]
  • 临近缩小,就是将合适的像素保留,不合适的像素舍弃。
  • 算法实现可以参见 “用 C 语言编写的临近缩放算法” https://blog.csdn.net/qq_46621272/article/details/126459136

两个版本的 verilog 视频缩放代码

  • 发布两个版本的 verilog 临近插值任意比例视频缩放代码,这两个代码采取的原始算法是一样的。在 verilog 代码实现上有所不同,也很难取舍。
  • 版本 V1 优点:水平放大和水平缩小采用相同的代码实现,代码简洁,代码可以延续到双线性缩放,双三次缩放。
  • 版本 V1 缺点:扫描部分代码需要优化。(在160MHz时钟,多个缩放模块例化会爆红)
  • 版本 V2 优点:没有 V1 的缺点。不爆红。
  • 版本 V2 缺点:水平放大和水平缩小采用不相同的代码实现,缩放两种扫描方式,代码不好理解。

效果图片

缩小算法 480x270 原图
FPGA verilog 临近插值任意比例视频缩放代码_第1张图片
缩小,479x269 图片
FPGA verilog 临近插值任意比例视频缩放代码_第2张图片
缩小,241x136 图片
FPGA verilog 临近插值任意比例视频缩放代码_第3张图片
缩小,159x89 图片
在这里插入图片描述
拉伸,95x539 图片
在这里插入图片描述
拉伸,961x55 图片
在这里插入图片描述

放大算法 160x120 原图
FPGA verilog 临近插值任意比例视频缩放代码_第4张图片
放大,161x121 图片
FPGA verilog 临近插值任意比例视频缩放代码_第5张图片
放大,321x241 图片
FPGA verilog 临近插值任意比例视频缩放代码_第6张图片
放大,480x360 图片
FPGA verilog 临近插值任意比例视频缩放代码_第7张图片
放大,801x601 图片

放大,1121x841 图片

V1 临近插值任意比例视频缩放代码 video_scale_near_v1.sv

  • System verilog
// video_scale_near_v1.sv
// 临近插值视频缩放模块。支持任意比列的缩放算法。代码非常少,占用FPGA资源也很少。
// 非常适合做动态视频监控中的多画面分割。由于临近算法的先天不足,不适用 PPT、地图、医学影像等静态视频图像的应用。
// 免责申明:本代码仅供学习、交流、参考。本人不保证代码的完整性正确性。由于使用本代码而产生的各种纠纷本人不负担任何责任。
// 708907433@qq.com
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

module video_scale_near_v1
(
	input				vout_clk,
	input				vin_clk,
	input				rst_n,
	input				frame_sync_n,		///< 输入视频帧同步复位,低有效
	input	[23:0]		vin_dat,			///< 输入视频数据
	input				vin_valid,			///< 输入视频数据有效
	output	reg	[23:0]	vout_dat,			///< 输出视频数据
	output	reg			vout_valid,			///< 输出视频数据有效

	input	[15:0]		vin_xres,			///< 输入视频水平分辨率
	input	[15:0]		vin_yres,			///< 输入视频垂直分辨率
	input	[15:0]		vout_xres,			///< 输出视频水平分辨率
	input	[15:0]		vout_yres,			///< 输出视频垂直分辨率
	output				vin_ready,			///< 输入准备好
	input				vout_ready			///< 输出准备好
);
	parameter	MAX_SCAN_INTERVAL	= 2;
	parameter	MAX_VIN_INTERVAL	= 2;
	
	logic	[31:0]		scaler_height	= 0;		///< 垂直缩放系数,[31:16]高16位是整数,低16位是小数
	logic	[31:0]		scaler_width	= 0;		///< 水平缩放系数,[31:16]高16位是整数,低16位是小数
	logic	[15:0]		scan_cnt_sx;				///< 水平扫描计数器,按像素位单位,步长 1 
	logic	[15:0]		scan_cnt_sy;				///< 垂直水平扫描计数器,按像素位单位,步长 1 
	logic				scan_cnt_state;				///< 水平扫描状态,1 正在扫描,0 结束扫描
	logic	[31:0]		scan_sy;					///< 垂直扫描计数器,定浮点,按比列因子计数,步长为 scaler_height
	logic	[15:0]		scan_sy_int;				///< 垂直扫描计数器,是 scan_sy 的整数部分 scan_sy_int = scan_sy[31:16]
	logic	[31:0]		scan_sx;					///< 水平扫描计数器,定浮点,按比列因子计数,步长为 scaler_width
	logic	[15:0]		scan_sx_int;				///< 水平扫描计数器,是 scan_sy 的整数部分 scan_sx_int = scan_sx[31:16]
	logic	[15:0]		scan_sx_int_dx;				///< scan_sx_int 延时对齐
	logic	[7:0][15:0]	scan_sx_int_dly;			///< scan_sx_int 延时对齐中间寄存器
	logic				scan_cnt_state_dx;			///< scan_cnt_state 延时对齐
	logic	[7:0]		scan_cnt_state_dly;			///< scan_cnt_state 延时对齐中间寄存器
	logic	[23:0]		fifo_rd_dat;				///< FIFO 读数据
	logic				fifo_rd_en;					///< FIFO 读使能
	logic				fifo_rd_empty;				///< FIFO 空状态
	logic				fifo_rd_valid;				///< FIFO 读数据有效
	logic				fifo_full;					///< FIFO 满
	logic				fifo_wr_en;					///< FIFO 写使能

	logic	[15:0]		line_buf_wr_addr;			///< LINER_BUF 写地址
	logic	[15:0]		line_buf_rd_addr;			///< LINER_BUF 读地址
	logic				line_buf_wen;				///< LINER_BUF 写使能
	logic	[23:0]		line_buf_wr_dat;			///< LINER_BUF 写数据
	logic	[23:0]		line_buf_rd_dat;			///< LINER_BUF 读数据

	logic	[7:0]		line_buf_rd_interval = 0;	///< LINER_BUF 读扫描间隙
	logic	[7:0]		line_buf_wr_interval = 0;	///< LINER_BUF 写扫描间隙
	logic	[15:0]		vin_sx = 0;					///< 视频输入水平计数
	logic	[15:0]		vin_sy = 0;					///< 视频输入垂直计数

	assign	vin_ready			= ~fifo_full;//fifo_prog_full;
	
	always@(posedge frame_sync_n)
	begin
		scaler_height	<= #1 ((vin_yres << 16 )/vout_yres) + 1;	///< 视频垂直缩放比例,2^16*输入高度/输出高度
		scaler_width	<= #1 ((vin_xres << 16 )/vout_xres) + 1;	///< 视频水平缩放比例,2^16*输入宽度/输出宽度
	end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///< 视频输入 FIFO IP 例化
///< 这个 FIFO 可以根据需要选用同步 FIFO 或异步 FIFO , 本代码采用的是异步 FIFO 方式
///< 这个 FIFO 很重要,在一些没有 DDR_SDRAM 的应用中的视频放大,这个 FIFO 就需要整大点最少能缓存 4-8 线的图形数据。
	assign	fifo_wr_en		= vin_valid & ~fifo_full;
	assign	fifo_rd_en		= (line_buf_wr_interval == 0) & (~fifo_rd_empty);	
	assign	fifo_rd_valid	= fifo_rd_en & (~fifo_rd_empty);
	AFIFO_24_FIRST vin_fifo_u1			// vivado IP//Native/Independent Clock Block RAM/First Word Fall Through
	(									// Write Width 24/Read Width 24/Depth 1024/ 其他设置缺省
		.wr_clk			(vin_clk),		// 根据需要,读写时钟可以不同,也可以相同
		.rd_clk			(vout_clk),
		.rst			(~frame_sync_n|~rst_n),
		.din			(vin_dat),
		.wr_en			(fifo_wr_en),
		.rd_en			(fifo_rd_en),
		.dout			(fifo_rd_dat),
		.full			(fifo_full),
		.empty			(fifo_rd_empty)
	);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///< 这部分是 LINER_BUF 读写间隙的逻辑控制
///< LINER_BUF SRAM 是双口 SRAM 需要不停的读扫描、写扫描(读写扫描是同时进行的)
///< 读写间隙需要互等、互判还不能互锁
///< 写扫描间隙 line_buf_wr_interval
	always_ff @( posedge vout_clk )
	begin
		if(frame_sync_n == 0 || rst_n == 0)
			line_buf_wr_interval	<= #1 0;
		else if( line_buf_wr_interval == 0 && fifo_rd_valid == 1 && vin_sx >= vin_xres - 1 )
			line_buf_wr_interval	<= #1 MAX_VIN_INTERVAL;
		else if ( line_buf_wr_interval != 0 && line_buf_rd_interval != 0 && vin_sy < scan_sy_int )
			line_buf_wr_interval	<= #1 line_buf_wr_interval -1;
		else if ( line_buf_wr_interval < MAX_VIN_INTERVAL && line_buf_wr_interval != 0 )
			line_buf_wr_interval	<= #1 line_buf_wr_interval -1;
	end

///< 读扫描间隙 line_buf_rd_interval
	always_ff @( posedge vout_clk )
	begin
		if(frame_sync_n == 0 || rst_n == 0)
			line_buf_rd_interval	<= #1 0;
		else if( vout_ready == 1 && line_buf_wr_interval != 0  && scan_cnt_sx >= vout_xres - 1 && scan_cnt_sy < vout_yres )
			line_buf_rd_interval	<= #1 MAX_SCAN_INTERVAL;
		else if (vout_ready == 1 && line_buf_rd_interval != 0 && vin_sy >= scan_sy_int )
			line_buf_rd_interval	<= #1 line_buf_rd_interval -1;
		else if (vout_ready == 1 && line_buf_rd_interval < MAX_SCAN_INTERVAL && line_buf_rd_interval != 0)
			line_buf_rd_interval	<= #1 line_buf_rd_interval -1;
	end

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///< LINER_BUF 写扫描地址计数
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	always_ff @( posedge vout_clk )
	begin
		if(frame_sync_n == 0 || rst_n == 0)
			vin_sx	<= #1 0;
		else if( fifo_rd_valid == 1 )
		begin
			if( vin_sx < vin_xres - 1 )
				vin_sx	<= #1 vin_sx + 1;
			else
				vin_sx	<= #1 0;
		end
	end

	always_ff @( posedge vout_clk )
	begin
		if(frame_sync_n == 0 || rst_n == 0)
			vin_sy	<= #1 0;
		else if( line_buf_wr_interval == 1 )
			vin_sy	<= #1 vin_sy + 1;
	end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///< 地址扫描部分,这部分是视频缩放的核心部分,由于水平放大和水平缩小这两个不同的工作方式,在一个模块中写出来,代码不太好理解。
///< 输入的原始视频先存入 LINER_BUF SRAM 中。输入的原始视频在不停的存,同时扫描部分在不停的扫描。扫描输出和输入存储互有快慢时
///< 需要互相等待。
///< 要注意的是:LINER_BUF 读扫描地址是 scan_sx 的整数部分 scan_sx_int,scan_sx_int累加步长小于等于 1
///< scan_cnt_sx;	///< 水平扫描计数器,按像素位单位,步长 1 
///< scan_cnt_sy;	///< 垂直水平扫描计数器,按像素位单位,步长 1 
///< scan_cnt_state;///< 水平扫描状态,1 正在扫描,0 结束扫描
///< scan_sy;		///< 垂直扫描计数器,定浮点,按比列因子计数,步长为 scaler_height
///< scan_sy_int;	///< 垂直扫描计数器,是 scan_sy 整数部分 scan_sy_int = scan_sy[31:16]
///< scan_sx;		///< 水平扫描计数器,定浮点,按比列因子计数,步长为 scaler_width
///< scan_sx_int;	///< 水平扫描计数器,是 scan_sx 整数部分 scan_sx_int = scan_sx[31:16]
	assign	scan_sy_int		= scan_sy[31:16];
	assign	scan_sx_int		= scan_sx[31:16];

	always_ff @( posedge	vout_clk )
	begin
		if(frame_sync_n == 0 || rst_n == 0)
		begin
			scan_cnt_state		<= #1 0;
			scan_cnt_sx			<= #1 0;
			scan_cnt_sy			<= #1 0;
			scan_sx				<= #1 0;
			scan_sy				<= #1 0;
		end
		else if (	vout_ready == 1 )
		begin
			if( line_buf_rd_interval == 0 && (scan_sx_int + scaler_width < vin_sx || line_buf_wr_interval != 0 ) &&
				scan_cnt_sy < vout_yres && ( scan_sy_int  <= vin_sy || vin_sy >= vin_yres-1  ))
			begin
				scan_cnt_state		<= #1 1;
				if( scan_cnt_sx < vout_xres - 1)	//读 LINE_BUF ,扫描 vout_xres
				begin
					scan_cnt_sx		<= #1 scan_cnt_sx + 1;
					scan_sx			<= #1 scan_sx + scaler_width;
				end
				else
				begin
					scan_cnt_sx		<= #1 0;
					scan_sx			<= #1 0;
					scan_cnt_sy		<= #1 scan_cnt_sy + 1;
					scan_sy			<= #1 scan_sy + scaler_height;
				end
			end
			else
				scan_cnt_state		<= #1 0;
		end
	end		//always_ff
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///< 扫描地址计数器延迟对齐部分。
///< 从扫描地址 scan_sx_int 的产生,到 LINER_BUF SRAM 的数据输出,需要几个时钟的延时
///< 在做缩放算法,视频数据保留、舍弃的判断时,需要 scan_sx_int 与 LINER_BUF SRAM 的数据保持同步
	localparam	SCAN_DLY	= 	0;
	assign	scan_cnt_state_dx		= scan_cnt_state_dly[SCAN_DLY+0];
	assign	scan_sx_int_dx			= scan_sx_int_dly[SCAN_DLY+1];

	always_ff @( posedge vout_clk )
	begin
		if(frame_sync_n == 0 || rst_n == 0)
		begin
			scan_sx_int_dly		<= #1 0;
			scan_cnt_state_dly	<= #1 0;
		end
		else if( vout_ready == 1 )
		begin
			scan_sx_int_dly[7:0]	<= #1 {scan_sx_int_dly[6:0],scan_sx_int};
			scan_cnt_state_dly[7:0] <= #1 {scan_cnt_state_dly[6:0],scan_cnt_state};
		end
	end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///< 输出
	always_ff @( posedge	vout_clk )
	begin
		if(frame_sync_n == 0 || rst_n == 0)
		begin
			vout_valid		<=	#1 0;
			vout_dat		<=	#1 0;
		end
		else if( vout_ready == 1 && scan_cnt_state_dx == 1 )
		begin
			vout_valid		<=	#1 1;
			vout_dat		<=	#1 line_buf_rd_dat;
		end
		else if( vout_ready == 1 )
			vout_valid		<=	#1 0;
	end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///< 在双线性缩放需要多组 LINER_BUF ,就可以将 LINER_BUF 做成一个模块多次例化
///< LINER_BUF 
	logic	[23:0]	line_buf_ram[2048];			///< 修改这个 line_buf 大小,可以修改本模块支持的最大水平分辨率,目前最大最大水平分辨率是 2048
	always_ff @( posedge	vout_clk )
	begin
		if( line_buf_wen ==1)		///< LINER_BUF  写
			line_buf_ram[line_buf_wr_addr]	<= line_buf_wr_dat;
		
		if( vout_ready == 1 )		///< LINER_BUF  读
			line_buf_rd_dat					<= line_buf_ram[line_buf_rd_addr];
	end

	always_ff @( posedge	vout_clk )
	begin
		line_buf_wen		<= #1 fifo_rd_valid;	
		line_buf_wr_addr	<= #1 vin_sx;
		line_buf_wr_dat		<= #1 fifo_rd_dat;
		if( vout_ready == 1 )
			line_buf_rd_addr<= #1 scan_sx_int;	///< scan_sx_int 是视频输出的水平计数器。
	end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
endmodule

V2 临近插值任意比例视频缩放代码 video_scale_near_v2.sv

  • System verilog
// video_scale_near_v2.sv
// 临近插值视频缩放模块。支持任意比列的缩放算法。代码非常少,占用FPGA资源也很少。
// 非常适合做动态视频监控中的多画面分割。由于临近算法的先天不足,不适用 PPT、地图、医学影像等静态视频图像的应用。
// 免责申明:本代码仅供学习、交流、参考。本人不保证代码的完整性正确性。由于使用本代码而产生的各种纠纷本人不负担任何责任。
// 708907433@qq.com
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

module video_scale_near_v2
(
	input				vout_clk,
	input				vin_clk,
	input				rst_n,
	input				frame_sync_n,		///< 输入视频帧同步复位,低有效
	input	[23:0]		vin_dat,			///< 输入视频数据
	input				vin_valid,			///< 输入视频数据有效
	output	reg	[23:0]	vout_dat,			///< 输出视频数据
	output	reg			vout_valid,			///< 输出视频数据有效

	input	[15:0]		vin_xres,			///< 输入视频水平分辨率
	input	[15:0]		vin_yres,			///< 输入视频垂直分辨率
	input	[15:0]		vout_xres,			///< 输出视频水平分辨率
	input	[15:0]		vout_yres,			///< 输出视频垂直分辨率
	output				vin_ready,			///< 输入准备好
	input				vout_ready			///< 输出准备好
);
	parameter	MAX_SCAN_INTERVAL	= 2;
	parameter	MAX_VIN_INTERVAL	= 2;
	
	logic	[31:0]		scaler_height	= 0;		///< 垂直缩放系数,[31:16]高16位是整数,低16位是小数
	logic	[31:0]		scaler_width	= 0;		///< 水平缩放系数,[31:16]高16位是整数,低16位是小数
	logic	[15:0]		scan_cnt_sx;				///< 水平扫描计数器,按像素位单位,步长 1 
	logic	[15:0]		scan_cnt_sy;				///< 垂直水平扫描计数器,按像素位单位,步长 1 
	logic				scan_cnt_state;				///< 水平扫描状态,1 正在扫描,0 结束扫描
	logic	[31:0]		scan_sy;					///< 垂直扫描计数器,定浮点,按比列因子计数,步长为 scaler_height
	logic	[15:0]		scan_sy_int;				///< 垂直扫描计数器,是 scan_sy 的整数部分 scan_sy_int = scan_sy[31:16]
	logic	[31:0]		scan_sx;					///< 水平扫描计数器,定浮点,按比列因子计数,步长为 scaler_width
	logic	[15:0]		scan_sx_int;				///< 水平扫描计数器,是 scan_sy 的整数部分 scan_sx_int = scan_sx[31:16]
	logic	[15:0]		scan_sx_int_dx;				///< scan_sx_int 延时对齐
	logic	[7:0][15:0]	scan_sx_int_dly;			///< scan_sx_int 延时对齐中间寄存器
	logic				scan_cnt_state_dx;			///< scan_cnt_state 延时对齐
	logic	[7:0]		scan_cnt_state_dly;			///< scan_cnt_state 延时对齐中间寄存器
	logic	[15:0]		scan_cnt_sx_dx;				///< scan_cnt_sx 延时对齐
	logic	[7:0][15:0]	scan_cnt_sx_dly;			///< scan_cnt_sx 延时对齐中间寄存器
	logic	[23:0]		fifo_rd_dat;				///< FIFO 读数据
	logic				fifo_rd_en;					///< FIFO 读使能
	logic				fifo_rd_empty;				///< FIFO 空状态
	logic				fifo_rd_valid;				///< FIFO 读数据有效
	logic				fifo_full;					///< FIFO 满
	logic				fifo_wr_en;					///< FIFO 写使能

	logic	[15:0]		line_buf_wr_addr;			///< LINER_BUF 写地址
	logic	[15:0]		line_buf_rd_addr;			///< LINER_BUF 读地址
	logic				line_buf_wen;				///< LINER_BUF 写使能
	logic	[23:0]		line_buf_wr_dat;			///< LINER_BUF 写数据
	logic	[23:0]		line_buf_rd_dat;			///< LINER_BUF 读数据

	logic	[7:0]		line_buf_rd_interval = 0;	///< LINER_BUF 读扫描间隙
	logic	[7:0]		line_buf_wr_interval = 0;	///< LINER_BUF 写扫描间隙
	logic	[15:0]		vin_sx = 0;					///< 视频输入水平计数
	logic	[15:0]		vin_sy = 0;					///< 视频输入垂直计数

	assign	vin_ready			= ~fifo_full;//fifo_prog_full;
	
	always@(posedge frame_sync_n)
	begin
		scaler_height	<= #1 ((vin_yres << 16 )/vout_yres) + 1;	///< 视频垂直缩放比例,2^16*输入高度/输出高度
		scaler_width	<= #1 ((vin_xres << 16 )/vout_xres) + 1;	///< 视频水平缩放比例,2^16*输入宽度/输出宽度
	end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///< 视频输入 FIFO IP 例化
///< 这个 FIFO 可以根据需要选用同步 FIFO 或异步 FIFO , 本代码采用的是异步 FIFO 方式
///< 这个 FIFO 很重要,在一些没有 DDR_SDRAM 的应用中的视频放大,这个 FIFO 就需要整大点最少能缓存 4-8 线的图形数据。
	assign	fifo_wr_en		= vin_valid & ~fifo_full;
	assign	fifo_rd_en		= (line_buf_wr_interval == 0) & (~fifo_rd_empty);	
	assign	fifo_rd_valid	= fifo_rd_en & (~fifo_rd_empty);
	AFIFO_24_FIRST vin_fifo_u1			// vivado IP//Native/Independent Clock Block RAM/First Word Fall Through
	(									// Write Width 24/Read Width 24/Depth 1024/ 其他设置缺省
		.wr_clk			(vin_clk),		// 根据需要,读写时钟可以不同,也可以相同
		.rd_clk			(vout_clk),
		.rst			(~frame_sync_n|~rst_n),
		.din			(vin_dat),
		.wr_en			(fifo_wr_en),
		.rd_en			(fifo_rd_en),
		.dout			(fifo_rd_dat),
		.full			(fifo_full),
		.empty			(fifo_rd_empty)
	);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///< 这部分是 LINER_BUF 读写间隙的逻辑控制
///< LINER_BUF SRAM 是双口 SRAM 需要不停的读扫描、写扫描(读写扫描是同时进行的)
///< 读写间隙需要互等、互判还不能互锁
///< 写扫描间隙 line_buf_wr_interval
	always_ff @( posedge vout_clk )
	begin
		if(frame_sync_n == 0 || rst_n == 0)
			line_buf_wr_interval	<= #1 0;
		else if( line_buf_wr_interval == 0 && fifo_rd_valid == 1 && vin_sx >= vin_xres - 1 )
			line_buf_wr_interval	<= #1 MAX_VIN_INTERVAL;
		else if ( line_buf_wr_interval != 0 && line_buf_rd_interval != 0 && vin_sy < scan_sy_int )
			line_buf_wr_interval	<= #1 line_buf_wr_interval -1;
		else if ( line_buf_wr_interval < MAX_VIN_INTERVAL && line_buf_wr_interval != 0 )
			line_buf_wr_interval	<= #1 line_buf_wr_interval -1;
	end

///< 读扫描间隙 line_buf_rd_interval
	always_ff @( posedge vout_clk )
	begin
		if(frame_sync_n == 0 || rst_n == 0)
			line_buf_rd_interval	<= #1 0;
		else if( vout_ready == 1 && line_buf_wr_interval != 0 && scan_cnt_sx >= vin_xres - 1 && scan_cnt_sx >= vout_xres - 1 && scan_cnt_sy < vout_yres )
			line_buf_rd_interval	<= #1 MAX_SCAN_INTERVAL;
		else if (vout_ready == 1 && line_buf_rd_interval != 0 && vin_sy >= scan_sy_int )
			line_buf_rd_interval	<= #1 line_buf_rd_interval -1;
		else if (vout_ready == 1 && line_buf_rd_interval < MAX_SCAN_INTERVAL && line_buf_rd_interval != 0)
			line_buf_rd_interval	<= #1 line_buf_rd_interval -1;
	end

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///< LINER_BUF 写扫描地址计数
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	always_ff @( posedge vout_clk )
	begin
		if(frame_sync_n == 0 || rst_n == 0)
			vin_sx	<= #1 0;
		else if( fifo_rd_valid == 1 )
		begin
			if( vin_sx < vin_xres - 1 )
				vin_sx	<= #1 vin_sx + 1;
			else
				vin_sx	<= #1 0;
		end
	end

	always_ff @( posedge vout_clk )
	begin
		if(frame_sync_n == 0 || rst_n == 0)
			vin_sy	<= #1 0;
		else if( line_buf_wr_interval == 1 )
			vin_sy	<= #1 vin_sy + 1;
	end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///< 地址扫描部分,这部分是视频缩放的核心部分,由于水平放大和水平缩小这两个不同的工作方式,在一个模块中写出来,代码不太好理解。
///< 输入的原始视频先存入 LINER_BUF SRAM 中。输入的原始视频在不停的存,同时扫描部分在不停的扫描。扫描输出和输入存储互有快慢时
///< 需要互相等待。
///< 要注意的是:水平放大时,LINER_BUF 读扫描地址是 scan_sx 的整数部分 scan_sx_int,scan_sx_int累加步长小于等于 1
///<             水平缩小时,LINER_BUF 读扫描地址是 scan_cnt_sx,scan_cnt_sx 累加步长大于 1
///< scan_cnt_sx;	///< 水平扫描计数器,按像素位单位,步长 1 
///< scan_cnt_sy;	///< 垂直水平扫描计数器,按像素位单位,步长 1 
///< scan_cnt_state;///< 水平扫描状态,1 正在扫描,0 结束扫描
///< scan_sy;		///< 垂直扫描计数器,定浮点,按比列因子计数,步长为 scaler_height
///< scan_sy_int;	///< 垂直扫描计数器,是 scan_sy 整数部分 scan_sy_int = scan_sy[31:16]
///< scan_sx;		///< 水平扫描计数器,定浮点,按比列因子计数,步长为 scaler_width
///< scan_sx_int;	///< 水平扫描计数器,是 scan_sx 整数部分 scan_sx_int = scan_sx[31:16]
	assign	scan_sy_int		= scan_sy[31:16];
	assign	scan_sx_int		= scan_sx[31:16];

	always_ff @( posedge	vout_clk )
	begin
		if(frame_sync_n == 0 || rst_n == 0)
		begin
			scan_cnt_state		<= #1 0;
			scan_cnt_sx			<= #1 0;
			scan_cnt_sy			<= #1 0;
			scan_sx				<= #1 0;
			scan_sy				<= #1 0;
		end
		else if (	vout_ready == 1 )
		begin
			if( line_buf_rd_interval == 0 && (scan_sx_int + 2 < vin_sx || line_buf_wr_interval != 0 ) &&
				scan_cnt_sy < vout_yres && ( scan_sy_int  <= vin_sy || vin_sy >= vin_yres-1  ))
			begin
				scan_cnt_state			<= #1 1;
				if( scan_cnt_sx < vout_xres - 1 || scan_cnt_sx < vin_xres - 1)	//读 LINE_BUF ,放大时  vout_xres > vin_xres 扫描 vout_xres
				begin															//读 LINE_BUF ,缩小时  vout_xres < vin_xres 扫描 vin_xres
					scan_cnt_sx			<= #1 scan_cnt_sx + 1;
					if( scan_sx_int <= scan_cnt_sx )	///<  水平缩小时,步长较大,需要等待 scan_cnt_sx。水平放大时该条件始终成立。
						scan_sx			<= #1 scan_sx + scaler_width;
				end
				else
				begin
					scan_cnt_sx			<= #1 0;
					scan_sx				<= #1 0;
					scan_cnt_sy			<= #1 scan_cnt_sy + 1;
					scan_sy				<= #1 scan_sy + scaler_height;
				end
			end
			else
				scan_cnt_state		<= #1 0;
		end
	end		//always_ff
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///< 扫描地址计数器延迟对齐部分。
///< 从扫描地址 scan_sx_int scan_cnt_sx 的产生,到 LINER_BUF SRAM 的数据输出,需要几个时钟的延时
///< 在做缩放算法,视频数据保留、舍弃的判断时,需要 scan_sx_int scan_cnt_sx 与 LINER_BUF SRAM 的数据保持同步
	localparam	SCAN_DLY	= 	0;
	assign	scan_cnt_state_dx		= scan_cnt_state_dly[SCAN_DLY+0];
	assign	scan_cnt_sx_dx			= scan_cnt_sx_dly[SCAN_DLY+1];
	assign	scan_sx_int_dx			= scan_sx_int_dly[SCAN_DLY+1];

	always_ff @( posedge vout_clk )
	begin
		if(frame_sync_n == 0 || rst_n == 0)
		begin
			scan_sx_int_dly		<= #1 0;
			scan_cnt_sx_dly		<= #1 0;
			scan_cnt_state_dly	<= #1 0;
		end
		else if( vout_ready == 1 )
		begin
			scan_sx_int_dly[7:0]	<= #1 {scan_sx_int_dly[6:0],scan_sx_int};
			scan_cnt_sx_dly[7:0]	<= #1 {scan_cnt_sx_dly[6:0],scan_cnt_sx};
			scan_cnt_state_dly[7:0] <= #1 {scan_cnt_state_dly[6:0],scan_cnt_state};
		end
	end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///< 输出
	always_ff @( posedge	vout_clk )
	begin
		if(frame_sync_n == 0 || rst_n == 0)
		begin
			vout_valid		<=	#1 0;
			vout_dat		<=	#1 0;
		end
		else if( vout_ready == 1 && scan_cnt_state_dx == 1 && vout_xres >= vin_xres )		///< 水平放大
		begin
			vout_valid		<=	#1 1;
			vout_dat		<=	#1 line_buf_rd_dat;
		end
		else if( vout_ready == 1 && scan_cnt_state_dx == 1 && vout_xres < vin_xres )		///< 水平缩小
		begin
			if(scan_sx_int_dx == scan_cnt_sx_dx)
			begin
				vout_valid	<=	#1 1;
				vout_dat	<=	#1 line_buf_rd_dat;
			end
			else
				vout_valid	<=	#1 0;
		end
		else if( vout_ready == 1 )
			vout_valid		<=	#1 0;
	end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///< 在双线性缩放需要多组 LINER_BUF ,就可以将 LINER_BUF 做成一个模块多次例化
///< LINER_BUF 
	logic	[23:0]	line_buf_ram[2048];			///< 修改这个 line_buf 大小,可以修改本模块支持的最大水平分辨率,目前最大最大水平分辨率是 2048
	always_ff @( posedge	vout_clk )
	begin
		if( line_buf_wen ==1)		///< LINER_BUF  写
			line_buf_ram[line_buf_wr_addr]	<= line_buf_wr_dat;
		
		if( vout_ready == 1 )		///< LINER_BUF  读
			line_buf_rd_dat					<= line_buf_ram[line_buf_rd_addr];
	end

	always_ff @( posedge	vout_clk )
	begin
		line_buf_wen		<= #1 fifo_rd_valid;	
		line_buf_wr_addr	<= #1 vin_sx;
		line_buf_wr_dat		<= #1 fifo_rd_dat;
		if( vout_ready == 1 )
			line_buf_rd_addr	<= #1 (vout_xres >= vin_xres) ? scan_sx_int:scan_cnt_sx;	///< 水平放大时 scan_sx_int 是视频放大输出的水平计数器。
	end																						///< 水平缩小时 scan_cnt_sx 是视频缩小输出的水平计数器。
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
endmodule

仿真测试 video_scale_near_testbench.sv

  • System verilog
  • 框图
  • FPGA verilog 临近插值任意比例视频缩放代码_第8张图片
//video_scale_near_testbench.sv
`timescale 1ns/100ps

module video_scale_near_testbench;

reg			rst_n;
reg			vclk_a;
reg			vclk_b;
reg			frame_sync_n;


parameter RESET_PERIOD			= 1000000.00;
parameter FRAME_H_PERIOD		= 16*1000*1000;			//16ms
parameter FRAME_L_PERIOD		= 60*1000;				//60us

parameter VIN_CLK_PERIOD_A		= 8;					//125MHz
parameter VIN_CLK_PERIOD_B		= 6;					//166MHz

initial	vclk_a = 0;
always	vclk_a = #(VIN_CLK_PERIOD_A/2.0) ~vclk_a;

initial	vclk_b = 0;
always	vclk_b = #(VIN_CLK_PERIOD_B/2.0) ~vclk_b;

initial	begin
	#0					frame_sync_n = 1;
	#RESET_PERIOD		frame_sync_n = 0;		// 16.7ms 帧脉冲
	while(1)
	begin
	#FRAME_L_PERIOD		frame_sync_n = 1;
	#FRAME_H_PERIOD		frame_sync_n = 0;
	end
end

initial	begin
	rst_n = 0;
	#RESET_PERIOD
	rst_n = 1;
end

logic	[23:0]	v01_dat;
logic			v01_valid;
logic			v01_ready;
logic	[15:0]	v01_xres;
logic	[15:0]	v01_yres;

logic	[23:0]	v02_dat;
logic			v02_valid;
logic			v02_ready;
logic	[15:0]	v02_xres;
logic	[15:0]	v02_yres;

logic	[23:0]	v1_dat;
logic			v1_valid;
logic			v1_ready;
logic	[15:0]	v1_xres;
logic	[15:0]	v1_yres;

logic	[23:0]	v2_dat;
logic			v2_valid;
logic			v2_ready;
logic	[15:0]	v2_xres;
logic	[15:0]	v2_yres;

parameter VIN_BMP_FILE1	= "gril.bmp";
parameter VIN_BMP_FILE2	= "160x120.bmp";
parameter VIN_BMP_PATH	= "../../../../../";
parameter VOUT_BMP_PATH	= {VIN_BMP_PATH,"vouBmpV/"};//"../../../../../vouBmpV/";


	bmp_to_videoStream	#
	(
		.iBMP_FILE_PATH		(VIN_BMP_PATH),
		.iBMP_FILE_NAME		(VIN_BMP_FILE1)
	)
	u01
	(
		.clk				(vclk_a),
		.rst_n				(rst_n),
		.vout_dat			(v01_dat),			//视频数据
		.vout_valid			(v01_valid),			//视频数据有效
		.vout_ready			(v01_ready),			//准备好
		.frame_sync_n		(frame_sync_n),		//视频帧同步复位,低有效
		.vout_xres			(v01_xres),			//视频水平分辨率
		.vout_yres			(v01_yres)			//视频垂直分辨率
	);

	bmp_to_videoStream	#
	(
		.iBMP_FILE_PATH		(VIN_BMP_PATH),
		.iBMP_FILE_NAME		(VIN_BMP_FILE2)
	)
	u001
	(
		.clk				(vclk_a),
		.rst_n				(rst_n),
		.vout_dat			(v02_dat),			//视频数据
		.vout_valid			(v02_valid),			//视频数据有效
		.vout_ready			(v02_ready),			//准备好
		.frame_sync_n		(frame_sync_n),		//视频帧同步复位,低有效
		.vout_xres			(v02_xres),			//视频水平分辨率
		.vout_yres			(v02_yres)			//视频垂直分辨率
	);

//	video_scale_near_v1	  u2
	video_scale_near_v2	  u2
//	video_scale_bilinear  u0002

	(
		.vin_clk			(vclk_a),
		.vout_clk			(vclk_b),
		.rst_n				(rst_n),
		.frame_sync_n		(frame_sync_n),		//输入视频帧同步复位,低有效
		.vin_dat			(v1_dat),			//输入视频数据
		.vin_valid			(v1_valid),			//输入视频数据有效
		.vin_ready			(v1_ready),			//输入准备好
		.vout_dat			(v2_dat),			//输出视频数据
		.vout_valid			(v2_valid),			//输出视频数据有效
		.vout_ready			(v2_ready),			//输出准备好
		.vin_xres			(v1_xres),			//输入视频水平分辨率
		.vin_yres			(v1_yres),			//输入视频垂直分辨率
		.vout_xres			(v2_xres),			//输出视频水平分辨率
		.vout_yres			(v2_yres)			//输出视频垂直分辨率
	);

	bmp_for_videoStream	#
	(
		.iREADY				(7),				//插入 0-10 级流控信号, 10 是满级全速无等待
		.iBMP_FILE_PATH		(VOUT_BMP_PATH)
	)
	u03
	(
		.clk				(vclk_b),
		.rst_n				(rst_n),
		.vin_dat			(v2_dat),			//视频数据
		.vin_valid			(v2_valid),			//视频数据有效
		.vin_ready			(v2_ready),			//准备好
		.frame_sync_n		(frame_sync_n),		//视频帧同步复位,低有效
		.vin_xres			(v2_xres),			//视频水平分辨率
		.vin_yres			(v2_yres)			//视频垂直分辨率
	);
//	assign	v2_xres = v1_xres-1;//*1.3;
//	assign	v2_yres = v1_yres-1;//*1.1;	//0.13,0.22,0.32,0.41,0.52,0.61,0.83,0.99

	logic	[15:0]	fn = 0;

	always_comb
	begin
		if(fn < 7)//(fn < 3)(fn < 15)
		begin
			v1_dat		<= v01_dat;
			v1_valid	<= v01_valid;
			v1_xres		<= v01_xres;
			v1_yres		<= v01_yres;
			v01_ready	<= v1_ready;
			v02_ready	<= 0;
		end
		else
		begin
			v1_dat		<= v02_dat;
			v1_valid	<= v02_valid;
			v1_xres		<= v02_xres;
			v1_yres		<= v02_yres;
			v01_ready	<= 0;
			v02_ready	<= v1_ready;
		end
	end
	
	always_ff@(negedge frame_sync_n)
	begin
		fn		<= #1 fn + 1;
		case(fn)
		0:		begin	v2_xres	<= #1 v01_xres*1 - 0;	v2_yres	<= #1 v01_yres*1 - 0;	end		//1:1 
		1:		begin	v2_xres	<= #1 v01_xres*1 - 1;	v2_yres	<= #1 v01_yres*1 - 1;	end		//1:0.99 
		2:		begin	v2_xres	<= #1 v01_xres/2 + 1;	v2_yres	<= #1 v01_yres/2 + 1;	end		//2:1
		3:		begin	v2_xres	<= #1 v01_xres/3 - 1;	v2_yres	<= #1 v01_yres/3 - 1;	end		//3:1
		4:		begin	v2_xres	<= #1 v01_xres/5 - 1;	v2_yres	<= #1 v01_yres*2 - 1;	end		//拉伸
		5:		begin	v2_xres	<= #1 v01_xres*2 + 1;	v2_yres	<= #1 v01_yres/5 + 1;	end		//拉伸
		
		6:		begin	v2_xres	<= #1 v02_xres*1 + 1;	v2_yres	<= #1 v02_yres*1 + 1;	end
		7:		begin	v2_xres	<= #1 v02_xres*2 + 1;	v2_yres	<= #1 v02_yres*2 + 1;	end
		8:		begin	v2_xres	<= #1 v02_xres*3 + 0;	v2_yres	<= #1 v02_yres*3 + 0;	end
		9:		begin	v2_xres	<= #1 v02_xres*5 + 1;	v2_yres	<= #1 v02_yres*5 + 1;	end
		10:		begin	v2_xres	<= #1 v02_xres*7 + 1;	v2_yres	<= #1 v02_yres*7 + 1;	end
		
		default	:	$stop;
		endcase
	end
endmodule

用于验证的 C 语言编写的代码

//scale_near.c
#include	
#include	
#include 	
#include 	"bmp.h"

void image_scale_near_x1(bmpSt *vin,bmpSt *vout);
void image_scale_near_x2(bmpSt *vin,bmpSt *vout);

void main( void )
{
	int		i;
	int		scale_width;
	int		scale_height;
	char	bmp_file_name[100];

	bmpSt	vin1,vin2,vout_x1;
	vin1.bmp_file_name	= "../gril.bmp";						///< 原始图片
	get_bmp_info(&vin1);										///< 获取BMP图片的宽高
	vin1.bmp_dat		= ( pixel_dat *) malloc( sizeof(pixel_dat) * vin1.width*vin1.height );	///< 申请内存
	read_bmp_file(&vin1);									///< 将BMP图片文件读入内存 bmp_dat

	vin2.bmp_file_name	= "../160x120.bmp";						///< 原始图片
	get_bmp_info(&vin2);										///< 获取BMP图片的宽高
	vin2.bmp_dat		= ( pixel_dat *) malloc( sizeof(pixel_dat) * vin2.width*vin2.height );	///< 申请内存
	read_bmp_file(&vin2);									///< 将BMP图片文件读入内存 bmp_dat

//	for(i=1;i<=14;i++)	//实现 14 帧图像的缩放比列
	for(i=1;i<=11;i++)	//实现 14 帧图像的缩放比列
	{
		sprintf(bmp_file_name,"../vouBmpC/vout_%03d.bmp",i);
		vout_x1.bmp_file_name	= bmp_file_name;
		printf("%s\n",vout_x1.bmp_file_name);
		switch(i)
		{
			case	1:		vout_x1.width	= vin1.width/1 - 0;	vout_x1.height	= vin1.height/1 - 0;	break;
			case	2:		vout_x1.width	= vin1.width/1 - 1;	vout_x1.height	= vin1.height/1 - 1;	break;
			case	3:		vout_x1.width	= vin1.width/2 + 1;	vout_x1.height	= vin1.height/2 + 1;	break;
			case	4:		vout_x1.width	= vin1.width/3 - 1;	vout_x1.height	= vin1.height/3 - 1;	break;
			case	5:		vout_x1.width	= vin1.width/5 - 1;	vout_x1.height	= vin1.height*2 - 1;	break; //拉伸
			case	6:		vout_x1.width	= vin1.width*2 + 1;	vout_x1.height	= vin1.height/5 + 1;	break; //拉伸

			case	7:		vout_x1.width	= vin2.width*1 + 1;	vout_x1.height	= vin2.height*1 + 1;	break;
			case	8:		vout_x1.width	= vin2.width*2 + 1;	vout_x1.height	= vin2.height*2 + 1;	break;
			case	9:		vout_x1.width	= vin2.width*3 + 0;	vout_x1.height	= vin2.height*3 + 0;	break;
			case	10:		vout_x1.width	= vin2.width*5 + 1;	vout_x1.height	= vin2.height*5 + 1;	break;
			case	11:		vout_x1.width	= vin2.width*7 + 1;	vout_x1.height	= vin2.height*7 + 1;	break;


			default:		vout_x1.width	= vin1.width/1 - 0;	vout_x1.height	= vin1.height/1 - 0;	break;
		}
		vout_x1.bmp_dat	= ( pixel_dat *) malloc( sizeof(pixel_dat) * vout_x1.width*vout_x1.height );	///< 申请内存
		if(i<7)
			image_scale_near_x2(&vin1,&vout_x1);		///< 临近缩放运算,将结果存入 vout_x1
		else
			image_scale_near_x2(&vin2,&vout_x1);		///< 临近缩放运算,将结果存入 vout_x1
		
		write_bmp_file(&vout_x1);					///< 创建并写入BMP图片文件
		free(vout_x1.bmp_dat);						///< 释放内存
	}	
	free(vin1.bmp_dat);								///< 释放内存
	free(vin2.bmp_dat);								///< 释放内存
}

缩放模块中用到的 FIFO IP 截图

  • AFIFO_24_FIRST IP 设置如下,其他是缺省值

FPGA verilog 临近插值任意比例视频缩放代码_第9张图片
FPGA verilog 临近插值任意比例视频缩放代码_第10张图片

本文中的一些没贴出的模块代码函数代码在连接中能找到

  • System Verilog 视频缩放图像缩放 vivado 仿真 https://blog.csdn.net/qq_46621272/article/details/126439519

下载链接

  • 本仿真工程文件下载,采用 Xilinx vivado 2017.4 版本
  • system verilog vivado 图像视频缩放代码,仿真工程

你可能感兴趣的:(视频处理,fpga开发,Verilog,视频缩放,图像缩放,图像处理)