引言
Descriptor 描述子运算的整个代码量比较少,因此本章节就是描述子运算 HLS解析 的最后一章节
该章节依旧是以代码注释的方式呈现。
需要额外关注的是,整个代码从输入到输出他的数据流向是怎么样?数据经过了哪些函数处理,进行了什么操作?
最后打包成AXI-Stream 协议格式输出。
windowSliderProc函数
windowSliderProc是滑动窗口处理的行方向控制核心。
// 模板参数说明:// ROWS/COLS:图像的最大行数/列数(配置参数,用于适配不同尺寸图像)// DEPTH:像素数据深度(如8位、16位,由图像类型决定)// NPC:每周期处理的像素数(Xilinx专用宏,如XF_NPPC1=1像素/周期,XF_NPPC8=8像素/周期)// WORDWIDTH:数据总线宽度(由DEPTH和NPC计算,如8位×8像素=64位)// TC:列循环迭代次数提示(用于HLS综合时的性能优化参考)// WIN_SZ:滑动窗口的尺寸(如31表示31×31的窗口)// WIN_SZ_SQ:滑动窗口尺寸的平方(如31×31=961,用于缓冲区大小计算)template<int ROWS, int COLS, int DEPTH, int NPC, int WORDWIDTH, int TC, int WIN_SZ, int WIN_SZ_SQ>void windowSliderProc(
hls::stream<ap_uint<8>> &img, // 输入图像数据流(8位像素数据)
hls::stream<ap_uint<88>> &dataPackStreamIn, // 输入辅助数据打包流(88位,用于特征计算等)
hls::stream<ap_uint<336>> &dataPackStreamOut, // 输出打包数据流(窗口数据+辅助数据,共336位)
ap_uint<8> win_size, // 滑动窗口的实际尺寸(与WIN_SZ一致,此处为31)
uint16_t img_height, uint16_t img_width // 图像的实际高度和宽度(像素级)) {
// 窗口行索引数组:存储当前滑动窗口所覆盖的图像行的索引(如31×31窗口的行索引可能为[2,3,...,32])
ap_uint<13> row_ind[WIN_SZ];#pragma HLS ARRAY_PARTITION variable=row_ind complete dim=1 // 数组完全分区:将row_ind的所有元素拆分到独立硬件资源,支持并行访问(无冲突)
uint16_t shift_x = 0; // 列方向偏移量(预留变量,当前未使用)
ap_uint<13> row, col; // 行、列循环变量(ap_uint<13>适配图像尺寸最大为8192,满足多数场景)
// 窗口像素缓存:用于暂存滑动窗口内的像素数据,支持列方向滑动
// 维度为[WIN_SZ行][每周期像素数 + 窗口列数-1],确保窗口滑动时能缓存足够的历史数据
XF_PTNAME(DEPTH) src_buf[WIN_SZ][XF_NPIXPERCYCLE(NPC) + (WIN_SZ - 1)];#pragma HLS ARRAY_PARTITION variable=src_buf complete dim=1 // 行完全分区:窗口的每一行可并行访问#pragma HLS ARRAY_PARTITION variable=src_buf complete dim=2 // 列完全分区:每行的像素可并行访问,加速窗口数据提取
// 行缓冲区:存储滑动窗口覆盖的多行图像数据(按"周期"粒度存储,非像素粒度)
// 维度为[WIN_SZ行][列数/NPC](因NPC个像素打包成1个周期数据)
XF_SNAME(WORDWIDTH) buf[WIN_SZ][(COLS >> XF_BITSHIFT(NPC))];#pragma HLS ARRAY_PARTITION variable=buf complete dim=1 // 行完全分区:并行访问不同行的缓冲区数据#pragma HLS RESOURCE variable=buf core=RAM_S2P_BRAM // 资源约束:指定使用双端口BRAM(块RAM)存储,适合大容量数据(避免占用寄存器资源)
// 初始化窗口行索引:初始窗口覆盖从0开始的连续WIN_SZ行(如31行窗口初始索引为0~30)
for (int init_row_ind = 0; init_row_ind < win_size; init_row_ind++) {#pragma HLS LOOP_TRIPCOUNT min=WIN_SZ max=WIN_SZ // 告知HLS循环迭代次数范围(WIN_SZ次),辅助综合工具优化硬件结构
row_ind[init_row_ind] = init_row_ind; // 索引初始化为0,1,2,...,win_size-1
}
// 预读初始行数据到行缓冲区buf:为窗口滑动提供初始数据
// 读取范围:从窗口中心行(win_size>>1,即win_size/2)到窗口最后一行(row_ind[win_size-1])
// 目的:避免窗口首次滑动时缓冲区为空,确保初始窗口数据完整
read_lines:
for (int init_buf = row_ind[win_size >> 1]; init_buf < row_ind[win_size - 1]; init_buf++) {#pragma HLS LOOP_TRIPCOUNT min=WIN_SZ max=WIN_SZ // 迭代次数约为WIN_SZ/2(窗口半高)
// 按"周期"粒度读取列数据(每周期处理NPC个像素,因此列数需除以NPC)
for (col = 0; col < img_width >> XF_BITSHIFT(NPC); col++) {#pragma HLS LOOP_TRIPCOUNT min=TC max=TC // 列循环次数提示(与TC参数一致)#pragma HLS pipeline // 流水线优化:每个时钟周期处理一个列,提升数据读取吞吐量(目标II=1)#pragma HLS LOOP_FLATTEN OFF // 禁止循环合并:避免与外层循环合并,保证流水线稳定性
buf[init_buf][col] = img.read(); // 从输入图像流读取数据,存入行缓冲区对应位置
}
}
// 处理顶部边界:当窗口顶部超出图像范围时,填充0(边界填充策略)
// 覆盖窗口上半部分(前WIN_SZ>>1行),避免访问图像外的无效数据
for (col = 0; col < img_width >> XF_BITSHIFT(NPC); col++) {#pragma HLS LOOP_TRIPCOUNT min=TC max=TC // 列循环次数提示
for (int init_buf = 0; init_buf < WIN_SZ >> 1; init_buf++) {#pragma HLS LOOP_TRIPCOUNT min=WIN_SZ max=WIN_SZ // 行循环次数为窗口半高(WIN_SZ/2)#pragma HLS UNROLL // 循环展开:并行填充边界数据,无延迟
buf[init_buf][col] = 0; // 顶部边界填充0(可根据需求修改为镜像/复制等其他策略)
// 注:注释掉的"buf[row_ind[win_size>>1]][col]"表示另一种策略(复制中心行数据)
}
}
// 行方向滑动主循环:窗口从图像中心行开始,向下滑动至覆盖底部边界
// 循环范围:[窗口半高, 图像高度+窗口半高],确保窗口完全覆盖图像及边缘(包括超出图像底部的部分)
Row_Loop:
for (row = (win_size >> 1); row < img_height + (win_size >> 1); row++) {#pragma HLS LOOP_TRIPCOUNT min=ROWS max=ROWS // 告知HLS循环迭代次数范围(约为图像行数ROWS)
// 调用lineProcess处理当前行的列方向滑动:
// 功能:提取当前行中窗口覆盖的列数据、处理列方向边界、打包窗口数据与辅助数据
lineProcess<ROWS, COLS, DEPTH, NPC, WORDWIDTH, TC, WIN_SZ, WIN_SZ_SQ>
(img, dataPackStreamIn, dataPackStreamOut, buf, src_buf, img_width, img_height, row_ind, row, win_size);
// 更新窗口行索引:实现窗口向下滑动(类似"队列左移",旧行索引循环复用)
ap_uint<13> zero_ind = row_ind[0]; // 保存窗口最顶部的行索引(即将被移出窗口)
for (int init_row_ind = 0; init_row_ind < WIN_SZ - 1; init_row_ind++) {#pragma HLS LOOP_TRIPCOUNT min=WIN_SZ max=WIN_SZ // 迭代次数为窗口尺寸-1#pragma HLS UNROLL // 并行更新索引,无延迟
row_ind[init_row_ind] = row_ind[init_row_ind + 1]; // 索引左移(每行上移一个位置)
}
row_ind[win_size - 1] = zero_ind; // 最旧的索引移至窗口底部(循环利用,实现窗口整体下移)
} // Row_Loop结束}
windowSlider函数
// 模板参数说明:
// NMS:非极大值抑制配置开关(1启用/0禁用,用于后续特征筛选)
// SRC_T:输入图像数据类型(此处固定为XF_8UC1,8位单通道灰度图)
// ROWS/COLS:图像最大尺寸(HEIGHT/WIDTH为全局宏定义,适配多尺度图像)
// NPC:每周期处理像素数(默认1,即1像素/时钟周期)
template<int NMS, int SRC_T, int ROWS, int COLS, int NPC=1>
void windowSlider(
hls::stream<ap_uint<8>> &src, // 输入原始图像数据流(8位像素数据)
hls::stream<ap_uint<88>> &dataPackStreamIn, // 输入辅助数据打包流(88位,如预处理参数/坐标信息)
hls::stream<ap_axiu<32,1,1,1>> &descriptor_out, // 输出特征描述符流(AXI-Stream协议,32位数据位宽)
unsigned short imageheight, unsigned short imagewidth, // 当前尺度图像实际宽高
ap_uint<8> img_level // 图像金字塔层级(0-3,对应不同缩放尺度)
) {
#pragma HLS inline off // 禁止函数内联:保持函数独立性,避免综合时逻辑混乱,便于优化与调试
#pragma HLS DATAFLOW // 数据流优化:子函数并行执行(windowSliderProc与Descriptors同时运行),通过中间流传递数据,最大化硬件吞吐量
// 中间数据流:连接windowSliderProc输出与Descriptors输入,传递336位打包数据(窗口像素+辅助信息)
hls::stream<ap_uint<336>> dataPackStream;
// 调用滑动窗口核心处理函数windowSliderProc:
// 模板参数解析:
// ROWS/COLS:图像最大尺寸;XF_DEPTH(SRC_T,NPC):由输入类型计算像素深度(8位);
// NPC:每周期像素数(1);XF_WORDWIDTH(SRC_T,NPC):计算总线宽度(8位×1=8位);
// (COLS>>XF_BITSHIFT(NPC))+(31>>1):列循环次数提示(图像列数+窗口半宽,适配边缘处理);
// 31:滑动窗口尺寸(31×31);31*31:窗口尺寸平方(用于缓冲区配置)
windowSliderProc<ROWS, COLS, XF_DEPTH(SRC_T, NPC), NPC,
XF_WORDWIDTH(SRC_T, NPC), (COLS >> XF_BITSHIFT(NPC)) + (31 >> 1), 31, 31 * 31>
(src, dataPackStreamIn, dataPackStream, 31, imageheight, imagewidth);
// 调用描述符生成函数:将打包的窗口数据转换为AXI-Stream格式的特征描述符输出
// 输入:中间数据流dataPackStream;输出:descriptor_out;参数包含图像宽高与金字塔层级
Descriptors(dataPackStream, descriptor_out, imagewidth, imageheight, img_level);
}
comDescriptor_accel函数
// 函数功能:特征描述符计算加速器顶层入口(FPGA/ASIC硬件加速入口函数)
// 负责接收输入数据流、解析图像尺度、调用滑动窗口处理流水线,输出特征描述符
void comDescriptor_accel(
hls::stream<ap_uint<8>> &srcStream, // 输入原始图像数据流(8位像素,首数据为图像层级)
hls::stream<ap_uint<88>> &dataStreamIn, // 输入辅助数据数据流(88位,首数据未实际使用)
hls::stream<ap_axiu<32,1,1,1>> &descriptor_out // 输出特征描述符流(AXI-Stream协议,供下游模块使用)
) {
// 硬件接口约束:
#pragma HLS INTERFACE ap_ctrl_none port=return // 无控制接口(纯数据流驱动,无start/stop信号),适配流水线硬件设计
#pragma HLS INTERFACE axis register both port=srcStream // srcStream接口:AXI-Stream协议,输入输出均寄存器缓存
#pragma HLS INTERFACE axis register both port=dataStreamIn // dataStreamIn接口:同上,AXI-Stream带寄存器缓存
#pragma HLS INTERFACE axis register both port=descriptor_out // descriptor_out接口:同上,AXI-Stream带寄存器缓存
// 读取图像金字塔层级(srcStream首字节存储,0-3对应4个缩放尺度)
ap_uint<8> img_level = srcStream.read();
// 读取辅助数据首包(未实际使用,仅占位接收,适配数据流同步)
ap_uint<88> img_level2 = dataStreamIn.read();
// 多尺度图像尺寸数组:对应4个金字塔层级(0-3)的图像宽高(像素级)
// 层级0:480行×640列;层级1:320×427;层级2:213×284;层级3:142×190(按比例缩放)
unsigned short imageheight[4] = {480, 320, 213, 142};
unsigned short imagewidth[4] = {640, 427, 284, 190};
// 调用滑动窗口处理函数windowSlider,启动特征提取流水线:
// 模板参数:NMS=1(启用非极大值抑制)、SRC_T=XF_8UC1(8位单通道图像)、
// ROWS=HEIGHT(全局宏定义最大行数)、COLS=WIDTH(全局宏定义最大列数)、NPC=1(1像素/周期)
// 实参:输入流、输出流、当前尺度图像宽高(由img_level索引获取)、图像金字塔层级
windowSlider<1, XF_8UC1, HEIGHT, WIDTH, 1>(
srcStream, dataStreamIn, descriptor_out,
imageheight[img_level], imagewidth[img_level], img_level
);
}
整体总结
各个模块的功能和核心联系
从输入到输出的数据流向:
srcStream(8 位)→ comDescriptor_acceldataStreamIn(88 位)→ comDescriptor_accel
comDescriptor_accel → windowSlider(转发 srcStream 和 dataStreamIn)
windowSlider 中:srcStream → windowSliderProcdataStreamIn → windowSliderProcwindowSliderProc → dataPackStream(336 位)
dataPackStream(336 位)→ Descriptors
Descriptors → descriptor_out(32 位 AXI-Stream)
ORB-SLAM3硬件加速项目系列

点击图片中的 #slam移植系列 获取全部文章。
至此,本系列已经完成了两个模块的详解。后续会讲解一些额外的辅助模块。