草庐IT

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

老皮芽子 2023-05-07 原文

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


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


文章目录

前言

  • 视频分割算法,视频拼接算法。
  • 图像分割算法,图像拼接算法。
  • 临近插值图像视频缩放模块。
  • 支持水平缩放、垂直缩放。支持任意比列的缩放算法。
  • 不到 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 原图

缩小,479x269 图片

缩小,241x136 图片

缩小,159x89 图片

拉伸,95x539 图片

拉伸,961x55 图片

放大算法 160x120 原图

放大,161x121 图片

放大,321x241 图片

放大,480x360 图片

放大,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[31:15] + 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)	//读 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
  • 框图
//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	<stdlib.h>
#include	<string.h>
#include 	<stdio.h>
#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 临近插值任意比例视频缩放代码的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

    如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

  3. ruby-on-rails - Rails 源代码 : initialize hash in a weird way? - 2

    在rails源中:https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb可以看到以下内容@load_hooks=Hash.new{|h,k|h[k]=[]}在IRB中,它只是初始化一个空哈希。和做有什么区别@load_hooks=Hash.new 最佳答案 查看rubydocumentationforHashnew→new_hashclicktotogglesourcenew(obj)→new_has

  4. ruby-on-rails - 浏览 Ruby 源代码 - 2

    我的主要目标是能够完全理解我正在使用的库/gem。我尝试在Github上从头到尾阅读源代码,但这真的很难。我认为更有趣、更温和的踏脚石就是在使用时阅读每个库/gem方法的源代码。例如,我想知道RubyonRails中的redirect_to方法是如何工作的:如何查找redirect_to方法的源代码?我知道在pry中我可以执行类似show-methodmethod的操作,但我如何才能对Rails框架中的方法执行此操作?您对我如何更好地理解Gem及其API有什么建议吗?仅仅阅读源代码似乎真的很难,尤其是对于框架。谢谢! 最佳答案 Ru

  5. ruby - 模块嵌套代码风格偏好 - 2

    我的假设是moduleAmoduleBendend和moduleA::Bend是一样的。我能够从thisblog找到解决方案,thisSOthread和andthisSOthread.为什么以及什么时候应该更喜欢紧凑语法A::B而不是另一个,因为它显然有一个缺点?我有一种直觉,它可能与性能有关,因为在更多命名空间中查找常量需要更多计算。但是我无法通过对普通类进行基准测试来验证这一点。 最佳答案 这两种写作方法经常被混淆。首先要说的是,据我所知,没有可衡量的性能差异。(在下面的书面示例中不断查找)最明显的区别,可能也是最著名的,是你的

  6. ruby - 寻找通过阅读代码确定编程语言的ruby gem? - 2

    几个月前,我读了一篇关于ruby​​gem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:

  7. ruby - Net::HTTP 获取源代码和状态 - 2

    我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur

  8. ruby-on-rails - Ruby on Rails I18n 插值 - 2

    大家好!我对我的:username字段进行了一个小的验证,它应该是4到30个字符。我写了一个验证::length=>{:within=>4..30,:message=>I18n.t('activerecord.errors.range')-我想显示一个错误各种错误的消息(不像,太长或太短),但这里有一个问题-我可以将最小值和最大值都传递给翻译,以便有类似的东西:用户名应该在4到30个字符之间。目前我有:range:"shouldbebetween%{count}and%{count}characters",这显然不起作用(只是为了检查)。是否可以从范围中获取这些值?谢谢大家的指教!

  9. 程序员如何提高代码能力? - 2

    前言作为一名程序员,自己的本质工作就是做程序开发,那么程序开发的时候最直接的体现就是代码,检验一个程序员技术水平的一个核心环节就是开发时候的代码能力。众所周知,程序开发的水平提升是一个循序渐进的过程,每一位程序员都是从“菜鸟”变成“大神”的,所以程序员在程序开发过程中的代码能力也是根据平时开发中的业务实践来积累和提升的。提高代码能力核心要素程序员要想提高自身代码能力,尤其是新晋程序员的代码能力有很大的提升空间的时候,需要针对性的去提高自己的代码能力。提高代码能力其实有几个比较关键的点,只要把握住这些方面,就能很好的、快速的提高自己的一部分代码能力。1、多去阅读开源项目,如有机会可以亲自参与开源

  10. 7个大一C语言必学的程序 / C语言经典代码大全 - 2

    嗨~大家好,这里是可莉!今天给大家带来的是7个C语言的经典基础代码~那一起往下看下去把【程序一】打印100到200之间的素数#includeintmain(){ inti; for(i=100;i 【程序二】输出乘法口诀表#includeintmain(){inti;for(i=1;i 【程序三】判断1000年---2000年之间的闰年#includeintmain(){intyear;for(year=1000;year 【程序四】给定两个整形变量的值,将两个值的内容进行交换。这里提供两种方法来进行交换,第一种为创建临时变量来进行交换,第二种是不创建临时变量而直接进行交换。1.创建临时变量来

随机推荐