在学习这个视频系统相关内容的时候,我想起来昨天看到b站一位叫硬件茶谈的up主的视频,非常厉害,【硬件科普】全网最详细易懂的G-sync Freesync 垂直同步工作原理科普
本文参考 Parallel Programming for FPGAs(the HLS Book)
数字视频
以一般的高清视频格式为例
全高清视频格式(1080p@60HZ)需要的处理速率是 1920(像素/⾏)x 1080(⾏/帧)x 60(帧/秒) = 124,416,000(像素/
秒)。
当对数字视频流(1080p@60HZ)进行编码时(1080p视频帧实际传输时像素大小为2200x1125,其中行2200像素 = 行同步+行消隐前肩+有效像素(1920像素)+行消隐后肩,场1125 行=场同步+场消隐前肩+有效行(1080行)+ 场消隐后肩 ),其中消隐像素也会伴随有效像素以148.5MHz(1080P@60HZ)的频率在FPGA流水线电路中进行处理。
像素表示
-
眼球中的视锥细胞对红、绿和蓝光敏感。其他颜色都可以视为红、绿和蓝色的融合。因此,摄像机和显示器模仿人类视觉系统的功能,对红、绿、蓝光采集或者显示红、绿和蓝像素,并用红色、绿色和蓝色分量的组合来表示颜色空间。
-
在一个视频处理系统内,通常会将RGB颜色
空间转换到到YUV颜色空间,它将像素描述为亮度(Y)和色度(U和V)的分量组合,其中色度分量U和V是独立于亮度分量Y的。例如常见视频格式YUV422,就是对于4个像素点,采样4个Y的值,两个U的值,两个V的值。这种格式应用于视频压缩时的采样叫色度子采样。另一种常见的视频格式YUV420表示采样4个像素值由4个Y值,一个U值和一个V值组成,进一步减少了表示图像所需像素数据。视频压缩通常在YUV色彩空间中进行。 -
是由于眼睛中的眼杆和感光部分对绿光比对红光更加敏感,并且大脑主要从绿光中获取亮度信息。因此,图像传感器和显示器采用马赛克模式,比如采样2个绿色像素和1个红像素与1个蓝像素比例的Bayer模式。于是在相同数量焦平面采集单元或显示器显示单元条件下系统可以采集或显示更高分辨率的图像,从而降低图像采集传感器或显示器的制造成本。
视频格式
行前肩和行后肩: 相邻行像素之间(从左到右顺序扫描)由水平同步信号分隔开,且相邻行像素之间的水平同步信号由若干时钟周期的脉冲信号组成。另外,在水平同步信号和有效像素信号中间还有若干时钟周期的无效信号,这些信号称为行前肩和行后肩。
相邻帧信号之间(从上到下顺序扫描)由垂直同步脉冲分隔。每个视频帧之间有若干行有效的帧同步信号。
相邻帧之间的垂直同步信号仅在视频帧的开始出现。同样,帧视频信号中除了同步信号和视频有效信号外,还有无效视频行组成的场前肩和场后肩信号。
所有无效的视频像素一起组
成水平消隐间隔和垂直消隐间隔。
典型1080P(60Hz)视频帧共包含2200 1125个数据采样像素点。在每秒60帧的情况下,
这相当于每秒总共148.5百万的采样像素点。这比1080P(60Hz)帧中的有效视频像素的平均速率(1920 * 1080 * 60 =每秒124.4百万像素)高很多。
如果FPGA能够满足一个时钟周期一次采样固然好,但是如果不够,需要一个周期采样更多像素点,就需要增加数据的吞吐量了。
简单视频滤波器
#include ”video_common.h”
unsigned char rescale(unsigned char val, unsigned char offset, unsigned char scale) {
return ((val − offset) ∗ scale) >> 4;
}
rgb_pixel rescale_pixel(rgb_pixel p, unsigned char offset, unsigned char scale) {
#pragma HLS pipeline
p.R = rescale(p.R, offset, scale);
p.G = rescale(p.G, offset, scale);
p.B = rescale(p.B, offset, scale);
return p;
}
void video_filter_rescale(rgb_pixel pixel_in[MAX_HEIGHT][MAX_WIDTH],
rgb_pixel pixel_out[MAX_HEIGHT][MAX_WIDTH],
unsigned char min, unsigned char max) {
#pragma HLS interface ap_hs port = pixel_out
#pragma HLS interface ap_hs port = pixel_in
row_loop:
for (int row = 0; row < MAX_WIDTH; row++) {
col_loop:
for (int col = 0; col < MAX_HEIGHT; col++) {
#pragma HLS pipeline
rgb_pixel p = pixel_in[row][col];
p = rescale_pixel(p,min,max);
pixel_out[row][col] = p;
}
}
}
程序中执行每次循环的II = 1(Initiation interval),也就是一个时钟周期处理一次采样数据。代码中采用嵌套循环的方式是按照图9.1中所示的扫描线顺序处理图像中的像素。II = 3的设计方式可以通过共享计算组件的方式来减少资源使用量。通过设置内层循环展开因子为2和输入输出数组拆分因子为2就可以满足一个时钟处理两个像素。这种例子相对简单,因为每个组件和每个像素的处理是相对独立的。
视频处理架构
大部分视频系统的选择是将视频帧存储在外部存储器中,比方说DDR。
在FPGA内部,外部存储器的控制器(MIG)集成了ARM AXI4标准化从接口与FPGA内的其他模块进行通信。
FPGA内的其它模块使用AXI4主接口或者是通过专门的AXI4内部互
联模块与外部存储器的AXI4从接口相连。AXI内部互联模块允许多个主设备模块访问多个从设备模块。
视频处理系统
行缓冲和列缓冲
没有缓冲
通常计算一个输出像素的值时需要输入像素及其周围的像素的值做参考,我们把储存计算所需要的输入像素值的区域叫窗口。
rgb_pixel filter(rgb_pixel window[3][3]) {
const char h[3][3] = {{1, 2, 1}, {2, 4, 2}, {1, 2, 1}};
int r = 0, b = 0, g = 0;
i_loop: for (int i = 0; i < 3; i++) {
j_loop: for (int j = 0; j < 3; j++) {
r += window[i][j].R ∗ h[i][j];
g += window[i][j].G ∗ h[i][j];
b += window[i][j].B ∗ h[i][j];
}
}
rgb_pixel output;
output.R = r / 16;
output.G = g / 16;
output.B = b / 16;
return output;
}
void video_2dfilter(rgb_pixel pixel_in[MAX_HEIGHT][MAX_WIDTH],
rgb_pixel pixel_out[MAX_HEIGHT][MAX_WIDTH]) {
rgb_pixel window[3][3];
row_loop: for (int row = 0; row < MAX_HEIGHT; row++) {
col_loop: for (int col = 0; col < MAX_WIDTH; col++) {
#pragma HLS pipeline
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
int wi = row + i − 1;
int wj = col + j − 1;
if (wi < 0 || wi >= MAX_HEIGHT || wj < 0 || wj >= MAX_WIDTH) {
window[i][j].R = 0;
window[i][j].G = 0;
window[i][j].B = 0;
} else
window[i][j] = pixel_in[wi][wj];
}
}
if (row == 0 || col == 0 || row == (MAX_HEIGHT − 1) || col == (MAX_WIDTH − 1)) {
pixel_out[row][col].R = 0;
pixel_out[row][col].G = 0;
pixel_out[row][col].B = 0;
} else
pixel_out[row][col] = filter(window);
}
}
}
是针对视频帧进行二维滤波。在计算每一个输出像素结果前需要从输入视频帧中读取相应的窗口像素数据(存储在数组中)。
有缓冲
相邻的窗口缓冲区中缓冲的数据高度重叠叠,这意味着相邻窗口缓冲区之间数据更高的依存性。这也意味来自输入图像的像素可以被本地缓存或者高速缓存存储,以备数据被多次访问使用。
在视频系统中,由于本地缓冲区存储窗口周围多行视频像素,所以本地缓冲区也称为线性缓冲区。线性缓冲区通常使用Block RAM(BRAM)资源实现,而窗口缓冲区则使用触发器(FF)资源实现。
对于N×N图像滤波器,只需要N-1行存储在线性缓冲区中即可。
void video_2dfilter_linebuffer(rgb_pixel pixel_in[MAX_HEIGHT][MAX_WIDTH],
rgb_pixel pixel_out[MAX_HEIGHT][MAX_WIDTH]) {
#pragma HLS interface ap_hs port=pixel_out
#pragma HLS interface ap_hs port=pixel_in
rgb_pixel window[3][3];
rgb_pixel line_buffer[2][MAX WIDTH];
#pragma HLS array_partition variable=line_buffer complete dim=1
row_loop: for (int row = 0; row < MAX_HEIGHT; row++) {
col_loop: for (int col = 0; col < MAX_WIDTH; col++) {
#pragma HLS pipeline
for(int i = 0; i < 3; i++) {
window[i][0] = window[i][1];
window[i][1] = window[i][2];
}
window[0][2] = (line_buffer[0][col]);
window[1][2] = (line_buffer[0][col] = line_buffer[1][col]);
window[2][2] = (line_buffer[1][col] = pixel_in[row][col]);
if (row == 0 || col == 0 ||
row == (MAX_HEIGHT − 1) ||
col == (MAX_WIDTH − 1)) {
pixel_out[row][col].R = 0;
pixel_out[row][col].G = 0;
pixel_out[row][col].B = 0;
} else {
pixel_out[row][col] = filter(window);
}
}
}
}
边界条件
计算缓冲区窗口只是输入图像的一个区域。然而,在输入图像的边界区域,滤波器进行计算的范围将超过输入图像的边界。根据不同应用的要求,会有很多不同的方法来解决图像边界处的计算问题。
一些补充方式:
- 缺少的输入值可以用常量来填充;
- 可以从输入图像的边界像素处填充缺少的输入值;
- 缺失的输入值可以在输入图片内部像素中进行重构。