我们在用FPGA对视频进行处理时,常常会遇到:有时候图像中的某些文字显示模糊----这有可能是缩放导致;有时可能是AD/DA模块采用了不同厂家的芯片----导致转换后的效果不同;有可能在图像YUV422与YUV420互相转换----算法间接导致图像效果变差。林林总总,当然还有其它不同的图像处理而有可能降低画质的场景。在针对图像质量跟踪定位时,最直观的,莫过于直接抓取各图像处理功能模块的输入输出,取出前后图像直接对比,就能立竿见影的诊断出该模块是否异常。所以:如何在FPGA中抓取视频流中的一帧图像呢?本文将记录图像质量定位的实践中,如何对altera公司的fpga在高清视频流中,抓取图像。
在使用alera公司的fpga时,SignalTab是一个很方便的诊断工具。而用SignalTab进行图像抓取的时候,它的抓取深度最大为128K。而在实际的工程应用中,往往难以预留出128K的RAM,大多时候8K已是极限。更不用说,对于CycloneII/III系列,只能腾挪出2K也是常事。因此,想要一次性的将高清视频流的帧图像抓取出来,这点RAM是无法做到的。通过不断的逐次次抓取1到4行,然后将其拼接成一帧完整的图像成为一个可行的方法。
应用于还发现SignalTab存储的日志频率与深度有极大的相关性:深度为8K的时候,1秒只它只能记录1个日志;而深度为2K的时候,1秒它能记录4个日志。所以深度为2K的抓取效率(1次1行,1秒4行)与8K的抓取效率(1次3行,1秒3行)相近,并且从通信性考虑深度以2K为宜。从而做到让SignalTab在1秒内抓取4条日志,1条日志对应视频流帧图像中的1行有效数据。
对于1080p的高清视频,一行的有效数据是1920,如果现一行的有效数据就立即进行抓取,会因为SignalTab在抓取触发前截取1/8预留数据的这一特性,而无法得到1920的足量有效数据(此时只能抓取到1792个图像据)。基于SignalTab的这一特性,需要加一逻辑:在找到有效数据后,再偏移160个拍子(1792+160=1952大于1920),就能抓取成功啦。如下:基于YUV422视频流的抓取模块:
module vi_sync_gen_xy(
// input //
input sys_rst_n,
input vp_clk_in,
input [15:0] vp_data_in,
// output //
output reg [10:0] y,
output this2out
output [15:0] video_data,
);
输入:复位,时钟,YUV422数据;输出:列号y,有效数据位标志this2out,采集到的数据;
对于1080p/30的视频1秒30帧,SignalTab是1秒记录4个日志,所以可以近似的每8帧取1行视频数据,以便SignalTab平滑的记录每行的数据。以下是verilog实现模块代码:
1,解析EAV和SAV,感知有效行数据
//SAV和EAV为四个
reg [15 : 0] pdata_t1;
reg [15 : 0] pdata_t2;
reg [15 : 0] pdata_t3;
reg [15 : 0] pdata_t4;
//SAV和EAV的XY字节
wire xy_b7;
wire xy_f;
wire xy_v;
wire xy_h;
wire xy_p3;
wire xy_p2;
wire xy_p1;
wire xy_p0;
wire is_xy;
//------ Extract the synchronization from video stream in BT1120 format------//
assign xy_b7 = pdata_t1[7];
assign xy_f = pdata_t1[6];
assign xy_v = pdata_t1[5];
assign xy_h = pdata_t1[4];
assign xy_p3 = pdata_t1[3];
assign xy_p2 = pdata_t1[2];
assign xy_p1 = pdata_t1[1];
assign xy_p0 = pdata_t1[0];
//确定SAV和EAV的XY
assign is_xy = (xy_b7==1‘b1) & (xy_p3==xy_v^xy_h) & (xy_p2==xy_f^xy_h) & (xy_p1==xy_f^xy_v) & (xy_p0==xy_f^xy_v^xy_h);
always @(posedge vp_clk_in or negedge sys_rst_n)
begin
if(!sys_rst_n)
begin
pdata_t1 <= 8‘h00;
pdata_t2 <= 8‘h00;
pdata_t3 <= 8‘h00;
pdata_t4 <= 8‘h00;
end
else
begin
pdata_t1 <= vp_data_in;
pdata_t2 <= pdata_t1;
pdata_t3 <= pdata_t2;
pdata_t4 <= pdata_t3;
end
end
//produce sorts of EAV and SAV according to the protocol of SMPTE294
assign eav_sav_pulse = (&pdata_t4[7:0]) & (!(|pdata_t3[7:0])) & (!(pdata_t2[7:0])) & is_xy;
assign valid_sav = eav_sav_pulse & (~xy_f) & (~xy_v) & (~xy_h);//8080
assign valid_eav = eav_sav_pulse & (~xy_f) & (~xy_v) & (xy_h); //9D9D
assign blank_sav = eav_sav_pulse & (~xy_f) & (xy_v) & (~xy_h); //ABAB
assign blank_eav = eav_sav_pulse & (~xy_f) & (xy_v) & (xy_h); //B6B6
2,场同步和行同步
// produce hsync_flag,行同步,有效行
always @ ( posedge vp_clk_in or negedge sys_rst_n)
if ( !sys_rst_n )
hsync_flag <= 1‘b0;
else if ( valid_sav )
hsync_flag <= 1‘b1;
else if ( valid_eav | blank_eav )
hsync_flag <= 1‘b0;
else
hsync_flag <= hsync_flag;
//有效行的开始,和有效行结束的信号
always @ ( posedge vp_clk_in or negedge sys_rst_n)
if ( !sys_rst_n )
hsync_flag_buf <= 1‘b0;
else
hsync_flag_buf <= hsync_flag;
assign hsync_ad = hsync_flag_buf && (!hsync_flag); //有效行结束
assign hsync_ad_pos = hsync_flag && (!hsync_flag_buf); //有效行开始
// produce vsync_flag 场同步,一帧图像有效数据标志
always @ ( posedge vp_clk_in or negedge sys_rst_n)
if ( !sys_rst_n )
vsync_flag <= 1‘b0;
else if ( valid_sav | valid_eav )
vsync_flag <= 1‘b1;
else if ( blank_eav )
vsync_flag <= 1‘b0;
else
vsync_flag <= vsync_flag;
//有效数据开始信号
always @ ( posedge vp_clk_in or negedge sys_rst_n)
if ( !sys_rst_n )
begin
vsync_flag_buf <= 1‘b0;
end
else
begin
vsync_flag_buf <= vsync_flag;
end
assign vsync_ad = vsync_flag && (!vsync_flag_buf); //positive edge of vsynn_flag 有效数据开始信号
assign vsync_ad_neg = (!vsync_flag) && vsync_flag_buf; //negedge edge of vsync_flah 有效数据结束信号
3,有效数据抓取逻辑
//y,图像中行号逻辑
reg [10:0] max_y; //需要知道场扫描中,宽度
always @ ( posedge vp_clk_in or negedge sys_rst_n)
if ( !sys_rst_n )
begin
y <= 1‘b0;
max_y <= 1‘b0;
end
else
begin
if (vsync_ad)
y <= 1‘b0;
else begin
if (vsync_flag) //在读取有效数据时
if ( hsync_ad) //每读完一行 y自增,其它时间维持原值
y <= y + 1‘b1;
else
y <= y;
else if(vsync_ad_neg) begin
max_y <= y;
y <= 1‘b0;
end
else
y <= 1‘b0;
end
end
//x,图像中每行中x逻辑
reg [10:0] x;
reg state_x;
always @ ( posedge vp_clk_in or negedge sys_rst_n)
if ( !sys_rst_n )
begin
x <= 1‘b0;
state_x <= 1‘b0;
end
else
begin
if (vsync_ad)
x <= 1‘b0;
else begin
if (vsync_flag) //在读取有效数据时
if ( hsync_ad) begin //每读完一行有效数据后, x归0,直到出现下行一的有效数据时,此间一直维持为0
state_x <= 1‘b0;
x <= 1‘b0;
end
else if (hsync_ad_pos) begin //找到SAV后,即为有效数据的读取,x开始自增,直到离开有效数据时归0
state_x <= 1‘b1;
x <= 1‘b0;
end
else begin
if ( state_x) //读取有效数据,则自增
x <= x + 1‘b1;
else //非有效数据,持续为0
x <= 1‘b0;
end
else
x <= 1‘b0;
end
end
//开始抓取this2out的输出逻辑
reg [10:0] last_y;
reg [15:0] ticks;
reg [10:0] max_ticks;
parameter V1080P30_TICK = 11‘d8;
//隔一定的时间,取图像中一行
always @ ( posedge vp_clk_in or negedge sys_rst_n)
if ( !sys_rst_n )
begin
last_y <= 1‘b0;
ticks <= 1‘b0;
max_ticks <= V1080P30_TICK;
end
else begin
if (max_y>0)
max_ticks <= (16‘d8640/max_y)+1‘d1;
else
max_ticks <= max_ticks;
if ( ticks >= max_ticks)
begin
ticks <= 1‘b0;
if ( last_y >= max_y )
last_y <= 1‘b0;
else
last_y <= last_y + 3‘d1;
end
else
if ( vsync_ad)
ticks <= ticks + 1‘b1;
else
ticks <= ticks;
end
//定义thisout的逻辑
assign this2out = (last_y == y && vsync_flag && x > 11‘d160);
//定义video_data
assign video_data = pdata_t1;
因为抓取数据,需要SignalTab,都需要连接仿真器,所以对于选择的控制,可以添加一个ISSP的IP核,手动选取输出哪一路视频


之后,只需要将抓取模块添加到工程中,导入要监控的视频即可:
//qseng wire sv2st_rst,sv2st_clk; wire [15:0]sv2st_videodata; wire [5:0] sv2st_sel; isspv isspv_inst( .probe(), .source(sv2st_sel)); select_video_to_signaltab sv2st_inst( .sel(sv2st_sel), .rst1(reset_n),.clk1(VP1_FP1_TO_FP2_CLK),.vdata1(VP1_FP1_TO_FP2), //vp1 .rst2(reset_n),.clk2(VP2_FP1_TO_FP2_CLK),.vdata2(VP2_FP1_TO_FP2), //vp2 .rst(sv2st_rst), .clk(sv2st_clk), .videoData(sv2st_videodata)); vi_sync_gen_xy signaltab_catch_video( // input // .sys_rst_n(sv2st_rst), // reset .vp_clk_in(sv2st_clk), // clock .vp_data_in(sv2st_videodata) );
然后,添加stp文件,并创建需要监控的y,this2out和video_data

注意:this2out需要置1,只有抓取逻辑成立时才取相应数据。右边的时钟,与Sample depth如下:

在实际的工程应用中,抓取效果如下:

需要记下第一次抓取时的y值,如上为156h,直到下一次再次重复为156h时,表示一帧抓取结束。点击右下方的保存日志,此时有1000多条日志。这时不要日志进行导出(1000多条,一个一个导出肯定手也会酸吧),我们改为存储stp文件的方式,一次性的导出所有已存储的数据:

在出现的对话框中,新建一个名字,另存为一个新的stp文件

接下来,需要进行windows编程,解析此stp文件,并显示图片,并可以将该图片,存储为BMP文件,以便后续的分析。
可以直接到git的widnwos_release目录下,运行YUVSee.exe来解析刚才保存的stp文件。
关于stp的文件解析,请参看:YUVSee解析SignalTab保存的日志文件。
所有FPGA和YUVSee的源码,在github下:
https://github.com/qseng/zieee/tree/master/SignalTab_Capture_VideoFrame
原文:https://www.cnblogs.com/qseng/p/10347524.html