ProcessFast函数
该代码在 FAST 角点检测中负责图像数据的流式缓存与滑动窗口管理、边界条件处理、调用核心检测函数执行角点判断,将结果与原始像素信息打包输出,并通过 HLS 优化指令实现硬件高效运行。
template<int ROWS, int COLS, int DEPTH, int NPC, int WORDWIDTH, int TC, int WIN_SZ, int WIN_SZ_SQ>
void ProcessFast(hls::stream<ap_uint<8> > & srcmat,
hls::stream< ap_uint<16> > & outmat,
unsigned char buf[WIN_SZ][(COLS >> XF_BITSHIFT(NPC))],
unsigned char src_buf[WIN_SZ][XF_NPIXPERCYCLE(NPC)+(WIN_SZ-1)],
unsigned char OutputValues[XF_NPIXPERCYCLE(NPC)],
XF_SNAME(WORDWIDTH) &P0, uint16_t img_width, uint16_t img_height, uint16_t &shift_x, ap_uint<13> row_ind[WIN_SZ], ap_uint<13> row, ap_uint<8> win_size,uchar_t threshold,XFPTNAME(DEPTH) &pack_corners)
{
#pragma HLS INLINE
unsigned char buf_cop[WIN_SZ];
#pragma HLS ARRAY_PARTITION variable=buf_cop complete dim=1
uint16_t npc = XF_NPIXPERCYCLE(NPC);
uint16_t col_loop_var = 0;
if(npc == 1)
{
col_loop_var = (WIN_SZ>>1);
}
else
{
col_loop_var = 1;
}
for(int extract_px=0;extract_px<WIN_SZ;extract_px++)
{
#pragma HLS LOOP_TRIPCOUNT min=WIN_SZ max=WIN_SZ
#pragma HLS unroll
for(int ext_copy=0; ext_copy<npc + WIN_SZ - 1; ext_copy++)
{
#pragma HLS unroll
src_buf[extract_px][ext_copy] = 0;
}
}
Col_Loop:
for(ap_uint<13> col = 0; col < ((img_width)>>XF_BITSHIFT(NPC)) + col_loop_var; col++)
{
#pragma HLS LOOP_TRIPCOUNT min=TC max=TC
#pragma HLS pipeline
#pragma HLS LOOP_FLATTEN OFF
if(row < img_height && col < (img_width>>XF_BITSHIFT(NPC)))
buf[row_ind[win_size-1]][col] = srcmat.read(); // Read data
if(NPC == XF_NPPC8)
{
}
else
{
for(int copy_buf_var=0;copy_buf_var<WIN_SZ;copy_buf_var++)
{
#pragma HLS LOOP_TRIPCOUNT min=WIN_SZ max=WIN_SZ
#pragma HLS UNROLL
if( (row >(img_height-1)) && (copy_buf_var>(win_size-1-(row-(img_height-1)))))
{
buf_cop[copy_buf_var] = buf[(row_ind[win_size-1-(row-(img_height-1))])][col];
}
else
{
buf_cop[copy_buf_var] = buf[(row_ind[copy_buf_var])][col];
}
}
for(int extract_px=0;extract_px<WIN_SZ;extract_px++)
{
#pragma HLS LOOP_TRIPCOUNT min=WIN_SZ max=WIN_SZ
#pragma HLS UNROLL
if(col<img_width)
{
src_buf[extract_px][win_size-1] = buf_cop[extract_px];
}
else
{
src_buf[extract_px][win_size-1] = src_buf[extract_px][win_size-2];
}
}
if((col<(img_width) && row<(img_height)) && col>=6 && row>=6)
{
xFfastProc<NPC, WORDWIDTH,DEPTH, WIN_SZ, WIN_SZ_SQ>(OutputValues,src_buf, win_size,_threshold,pack_corners);
}
if(row>=img_height || col>=img_width)
{
OutputValues[0]=0;
}
if(col >= (WIN_SZ>>1))
{
ap_uint<16> temp;
temp.range(15,8) = OutputValues[0];
temp.range(7,0) = src_buf[3][3];
outmat.write(temp);
}
for(int wrap_buf=0;wrap_buf<WIN_SZ;wrap_buf++)
{
#pragma HLS UNROLL
#pragma HLS LOOP_TRIPCOUNT min=WIN_SZ max=WIN_SZ
for(int col_warp=0; col_warp<WIN_SZ-1;col_warp++)
{
#pragma HLS UNROLL
#pragma HLS LOOP_TRIPCOUNT min=WIN_SZ max=WIN_SZ
if(col==0)
{
src_buf[wrap_buf][col_warp] = src_buf[wrap_buf][win_size-1];
}
else
{
src_buf[wrap_buf][col_warp] = src_buf[wrap_buf][col_warp+1];
}
}
}
}
} // Col_Loop
}
函数定义与模板参数解析
ROWS/COLS:图像最大行数 / 列数;DEPTH:每个像素的位数(比如 8 位灰度);
NPC:硬件一次能处理的像素数量(并行度,比如一次处理 1 个或 8 个);
WIN_SZ:检测窗口大小(比如 9,对应 9x9 的窗口,FAST 算法需要这个窗口里的像素);
其他参数是硬件优化相关的(比如WORDWIDTH是数据总线宽度)
输入输出参数
srcmat:输入的图像数据流(每个像素 8 位,像水管一样一行一行传数据);
outmat:输出的结果流(16 位,一半存检测结果,一半存原始像素);
buf:临时缓存图像行数据的缓冲区(存最近几行的像素,方便取窗口数据);
src_buf:滑动窗口缓冲区(存当前要检测的 9x9 窗口内的像素);
OutputValues:存 FAST 检测的结果(比如这个像素是不是角点);
其他参数:图像实际宽高(img_width/img_height)、当前处理的行(row)、检测阈值(_threshold)等。
硬件内联指令不重复讲了,已经提到好多次了,可以参考我先前的文章。
unsigned char buf_cop[WIN_SZ];
#pragma HLS ARRAY_PARTITION variable=buf_cop complete dim=1
buf_cop:临时复制buf中的数据(方便处理边界);
下面的指令:把buf_cop这个数组拆成独立的寄存器(比如 WIN_SZ=9 就拆成 9 个),硬件可以同时访问,不用排队(并行加速)
uint16_t col_loop_var = 0;
if(npc == 1)
{
col_loop_var = (WIN_SZ>>1);
}
else
{
col_loop_var = 1;
}
当 npc == 1(一次处理 1 个像素)时:col_loop_var = (WIN_SZ >> 1),其中 WIN_SZ 是滑动窗口大小(比如 9),>>1 是右移 1 位(相当于除以 2 取整)。
例:若窗口大小为 9,9>>1=4,则 col_loop_var=4—— 需要多循环 4 次,确保窗口能覆盖最后几列边缘像素。
当 npc != 1(一次处理多个像素,比如 2 个、4 个)时:因为并行处理减少了单像素的循环次数,所以 col_loop_var=1—— 只需多循环 1 次就能覆盖边缘。
简单说:这行代码是在 “根据硬件并行能力,动态调整列循环的额外次数”,确保所有像素(包括边缘)都能被滑动窗口完整检测。
for(int extract_px=0;extract_px<WIN_SZ;extract_px++)
{
#pragma HLS LOOP_TRIPCOUNT min=WIN_SZ max=WIN_SZ
#pragma HLS unroll
for(int ext_copy=0; ext_copy<npc + WIN_SZ - 1; ext_copy++)
{
#pragma HLS unroll
src_buf[extract_px][ext_copy] = 0;
}
}
这段代码的核心作用是初始化滑动窗口缓冲区src_buf,将其所有元素清零,为后续存储图像的局部窗口像素数据做准备。
负责从左到右逐列处理图像,完成 “读取像素→更新缓冲区→准备滑动窗口数据” 的核心流程,同时通过硬件优化指令提升处理效率。
Col_Loop:
for(ap_uint<13> col = 0; col < ((img_width)>>XF_BITSHIFT(NPC)) + col_loop_var; col++)
读取图像数据, ((img_width)>>XF_BITSHIFT(NPC)) 的含义是:图像位宽除去每次处理的像素点数,可以做多像素点处理匹配。
#pragma HLS LOOP_TRIPCOUNT min=TC max=TC
指定循环的迭代次数。这里min=TC和max=TC表示该循环的迭代次数是固定值
如果不指定,工具可能默认最坏情况(如迭代次数极大),导致过度保守的优化;指定后工具可更精准地调整策略。
#pragma HLS pipeline
控制是否 “展平嵌套循环”(将多层嵌套循环合并为单层循环)LOOP_FLATTEN ON(默认可能开启)会将嵌套循环展平,增加单次循环的迭代次数,可能利于流水线或并行优化。
#pragma HLS LOOP_FLATTEN OFF
则禁止展平,保持原有的嵌套结构。这在以下场景有用:
嵌套结构更适合资源分配(例如内层循环操作简单,外层循环控制整体流程);
避免展平后迭代次数过大导致工具优化困难;
需保留嵌套逻辑以满足特定时序或依赖约束。
if(row < img_height && col < (img_width>>XF_BITSHIFT(NPC)))
buf[row_ind[win_size-1]][col] = srcmat.read(); // Read data
if(NPC == XF_NPPC8)
{
}
如果满足XF_NPPC8(应该是八通道并行处理的宏定义)就执行空指令。
不满足就执行滑动窗口的操作。
for (int copy_buf_var=0; copy_buf_var<WIN_SZ; copy_buf_var++)
WIN_SZ 是一个宏定义(或变量),表示滑动窗口的大小(例如 3x3 窗口的 WIN_SZ=3,5x5 窗口的 WIN_SZ=5)。
条件的意思是:只要 copy_buf_var 的值小于 WIN_SZ,循环就继续执行;当 copy_buf_var 大于或等于 WIN_SZ 时,循环终止。
每次循环结束后,copy_buf_var 的值自动加 1(从 0→1→2→...→WIN_SZ-1),实现对窗口内所有位置的遍历。
if( (row >(img_height-1)) && (copy_buf_var>(win_size-1-(row-(img_height-1)))))
{
buf_cop[copy_buf_var] = buf[(row_ind[win_size-1-(row-(img_height-1))])][col];
}
else
{
buf_cop[copy_buf_var] = buf[(row_ind[copy_buf_var])][col];
}
当当前处理的行(row)超出图像有效高度范围(即 row > img_height-1,img_height-1 为图像最后一行索引),且窗口内当前位置索引(copy_buf_var)大于 “窗口大小减 1 减去超出行数”(超出行数为 row - (img_height-1))时,说明窗口该位置已超出图像底部边界;此时会用图像最后一行对应位置的像素(通过 row_ind [win_size-1 - 超出行数] 获取)填充 buf_cop [copy_buf_var];否则,直接用窗口当前位置(copy_buf_var)对应的原始图像像素(row_ind [copy_buf_var])填充 buf_cop,以此实现滑动窗口底部边界的像素填充,避免越界访问。
for(int extract_px=0;extract_px<WIN_SZ;extract_px++)
{
#pragma HLS LOOP_TRIPCOUNT min=WIN_SZ max=WIN_SZ
#pragma HLS UNROLL
if(col<img_width)
{
src_buf[extract_px][win_size-1] = buf_cop[extract_px];
}
else
{
src_buf[extract_px][win_size-1] = src_buf[extract_px][win_size-2];
}
}
这段代码是滑动窗口在列方向的边界处理逻辑,用于将窗口数据存入缓冲区src_buf,同时处理列索引超出图像右边界的情况,结合 HLS 指令优化硬件执行效率,具体逻辑如下:
循环遍历滑动窗口的每个行索引(extract_px范围0~WIN_SZ-1,WIN_SZ为窗口大小),对src_buf的最后一列(win_size-1,win_size与WIN_SZ一致)赋值:
若当前列col在图像有效宽度内(col < img_width),直接将窗口临时缓冲区buf_cop中对应行的数据(buf_cop[extract_px])存入src_buf[extract_px][win_size-1],即正常写入当前列的窗口数据;
若col超出图像右边界(col >= img_width),则用src_buf中当前行的前一列数据(src_buf[extract_px][win_size-2])填充最后一列,通过 “复制前一列有效数据” 的方式处理右边界,避免越界访问。
同时,#pragma HLS指令指定循环次数固定为WIN_SZ并完全展开循环,以提升硬件并行处理效率(适合 FPGA 等加速场景)。
上面的行和列的缓冲代码,实现窗口滑动逻辑,接下来则需要对窗口内的图像数据进行判断。
col < img_width && row < img_height:确保当前行列在图像有效范围内(未超出图像边界);
col >=6 && row >=6:确保当前位置距离图像左上角边界至少 6 个像素(可能是因为核心算法需要窗口周围有足够的有效像素,避免过近边界导致窗口数据不完整)。
当以上条件满足后就执行调用之前讲过的函数:
xFfastProc<NPC, WORDWIDTH,DEPTH, WIN_SZ, WIN_SZ_SQ>(OutputValues,src_buf, win_size,_threshold,pack_corners);
当当前行超出图像高度(row >= img_height)或当前列超出图像宽度(col >= img_width)时(即处理图像外部区域)。
执行操作:将 OutputValues[0] 置为 0。这是因为图像外部没有有效像素,核心处理无法执行,因此用 0 表示 “无有效特征”。
最后一段代码的意思就是
当列索引大于等于窗口大小的一半(WIN_SZ>>1)时,会将特征计算结果(OutputValues [0])作为高 8 位、窗口中心像素(src_buf [3][3])作为低 8 位,组合成 16 位数据写入输出矩阵;之后通过两层完全展开的循环(硬件并行处理)更新窗口缓冲区 src_buf 的列数据:若当前是第 0 列,就用窗口最后一列数据填充各列,否则将各列数据左移一位(用下一列数据覆盖当前列),为下一列的窗口处理做准备。
ORB-SLAM3硬件加速项目系列

点击文章下方标签获取整个系列更新文章!