版权声明:本文为博主原创文章,遵循 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 截图
- 本文中的一些没贴出的模块代码函数代码在连接中能找到
- 下载链接
文章目录
- @[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 原图
缩小,479x269 图片
缩小,241x136 图片
缩小,159x89 图片
拉伸,95x539 图片
拉伸,961x55 图片
放大算法 160x120 原图
放大,161x121 图片
放大,321x241 图片
放大,480x360 图片
放大,801x601 图片
放大,1121x841 图片
文章来源:https://uudwc.com/A/noPE0
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 设置如下,其他是缺省值
文章来源地址https://uudwc.com/A/noPE0
本文中的一些没贴出的模块代码函数代码在连接中能找到
- System Verilog 视频缩放图像缩放 vivado 仿真 https://blog.csdn.net/qq_46621272/article/details/126439519
下载链接
- 本仿真工程文件下载,采用 Xilinx vivado 2017.4 版本
- system verilog vivado 图像视频缩放代码,仿真工程
- 感谢 zc3539591 的测试,发现 V1 版本的一个 bug ,现已经更新。下载链接的代码没更新,请下载代码的朋友参考这里的 V2 代码 2022-11-24