资讯动态

FPGA实时双路视频拼接融合:流水线架构与工程避坑

发布时间:2026/10/26 13:51:25 来源:尧图企业网站定制
简介面向FPGA开发与实时图像处理学习者的图像拼接融合工程包聚焦多视角图像对齐、特征匹配、透视校正与融合等关键环节适合具备一定硬件描述语言基础、希望借助并行架构提升图像处理吞吐与低延迟的开发者参考。压缩包共121个文件约31.59MB以Verilog源码、测试图像、仿真波形、PDF/Markdown说明文档、Python辅助脚本及Makefile构建文件为主兼顾工程实现、结果验证与流程复现。目前已有615人学习下载。工程内含完整源代码、配置与测试平台配合滤波、边缘检测等示例图像可帮助读者理解SIFT/SURF等特征算法的硬件加速思路、像素级映射模块设计以及加权平均和梯度融合的并行实现并借助仿真波形与文档排查时序与接口问题适合作为FPGA图像处理项目的实践参考。1. 两路摄像头拼一张图为什么最后落在了 fpga 开发上两路摄像头固定在同一个支架上视野重叠大约三分之一要把它们拼成一张无缝全景图推到屏幕上。最初跑在软件端一帧 1080p 的融合要吃掉几十毫秒帧率上不去CPU 占用还常年贴着天花板。改到 fpga 开发这条路上之后同样的活变成了一拍一个像素的流水线帧率钉在 60fps延迟只有几行扫描线。这份资源做的就是这样一件事把图像拼接融合拆成一组可综合的硬件模块——输入采集、行缓冲窗口、坐标重采样、重叠区加权融合、输出时序对齐。适合已经会写状态机和简单接口、但没做过完整视频通路的开发者也适合手上正好有个拼接需求、想先看看别人数据通路怎么搭的人。它不追求算法多新追求的是每一级都能跑满时钟、每一级都能被单独验证。2. 项目定位与技术栈这条通路上到底该摆哪些模块先把这个项目的边界说清楚它做的不是「从零开始算单应矩阵」那种全流程而是把已经标定好的几何关系固化到硬件里实时地把两路视频流拼成一路。配准参数来自上位机离线标定FPGA 只负责在线执行。这个分工决定了整个架构长什么样。2.1 为什么把拼接融合从软件搬到硬件流水线软件端做拼接本质上是「取一帧、算一帧、存一帧」天然带着帧级延迟。1080p60fps 的数据率是 1920×1080×60 ≈ 1.24 亿像素每秒每个像素哪怕只做一次乘加通用处理器也要在几百毫秒的预算里挤出这部分算力还得跟操作系统抢时间片。一旦要做双线性插值每个输出像素 4 次乘加再加融合再来 2 次乘加软件路径基本就告别实时了。硬件流水线的思路完全不同把运算摊到每个时钟周期上。像素时钟跑 148.5MHz一个时钟处理一个像素任何一级只要保证「一拍进、一拍出」、组合逻辑能在这个周期内收敛整条链路就是满速的。延迟不再是「一帧」而是流水线的级数乘上时钟周期——通常是几十纳秒到几微秒。这个特性对拼接特别友好因为两路图像的对齐误差必须控制在扫描线级别任何帧级缓存都会让「左边动一下、右边慢半拍」变得肉眼可见。但硬件不是白给的。代价是浮点运算要换成定点、除法要换成查表或移位、随机访问要换成流式访问。这个项目里几乎所有设计取舍都是在跟这三条较劲。2.2 位宽、带宽与资源预算先算清楚再写代码我在动手写第一行 RTL 之前习惯先把三张表列出来数据位宽表、带宽表、资源估算表。这个项目里它们大概长这样项目取值说明像素格式RGB888 / YUV422 可选融合需要三通道YUV422 要先插值出全采样单像素位宽24 bit三通道各 8 bit像素时钟148.5 MHz1080p60 标准单路数据率148.5M × 24bit ≈ 3.56 Gbps需要并行总线或高速串行接口行缓冲深度2 行 × 1920 × 24bit ≈ 92 Kb3×3 窗口至少要缓存前两行行缓冲实现片上 BRAM一块 36Kb BRAM 存不下整行要拼接多块坐标精度Q8.8 定点整数部分 8 位、小数 8 位插值权重精度足够融合权重Q8 定点0~255直接查表避免在线算除法带宽这笔账最容易翻车。如果采用「写 DDR 再读回来」的方案一帧 1080p 三通道大约 6.2 MB读写各一次就是 12.4 MB按 60fps 算需要 745 MB/s 的持续带宽。常见的 16bit DDR3 在 400MHz 下理论带宽大约 800MB/s看着够但实际有效带宽打个七折就只剩 560MB/s已经不够了。所以这个项目的通路里尽量不碰 DDR输入进来就进行缓冲重采样和融合都在片上完成DDR 只用来存几帧标定图或者做多帧平均。资源方面一块中等规模的器件几万 LUT、几百个 DSP、几百块 BRAM跑这条通路是有余量的。真正吃 DSP 的是双线性插值每个像素 4 次乘法三通道就是 12 次。如果把三通道并行展开一个像素时钟内就要 12 个乘法器同时工作DSP 数量会迅速堆上去。常见的省法是把三通道串行复用一个乘法器组代价是内部时钟要跑到像素时钟的 3 倍或者干脆用移位加法近似权重精度损失一点但省一半 DSP。2.3 目录结构从顶层到子模块的分层工程按「接口层 / 处理层 / 控制层 / 验证层」四块切目录大概是这样rtl/ top/ top_panorama.v # 顶层只做例化和连线 video_in_capture.v # 摄像头/HDMI 输入解析输出 AXI-Stream 风格握手 video_out_timing.v # 输出侧时序生成 proc/ line_buffer_3x3.v # 行缓冲 3x3 窗口生成 coord_gen.v # 逆向映射坐标递增器Q8.8 bilinear_resample.v # 双线性重采样 blend_alpha.v # 重叠区加权融合 csc_yuv2rgb.v # 可选色彩空间转换 cfg/ reg_bank.v # 配置寄存器组重叠区边界、单应参数、权重表地址 sim/ tb_top_panorama.v gen_pattern.py # 生成带特征点的测试图 constraints/ timing.sdc pinout.sdc分层的原则很简单top层不允许出现任何if/else之外的运算逻辑只做例化和连线proc层里每个模块都必须能在没有上下游的情况下靠激励文件单独跑通cfg层负责把所有「可能需要现场改」的参数抽成寄存器避免为了改一个重叠区边界就重新综合一版。提示把cfg单独抽一层是我踩过坑之后才养成的习惯。早期版本里重叠区宽度写成了localparam客户现场换个镜头就得重新烧一版比特流来回折腾好几次。3. 数据通路拆解一帧图像从进来到出去的完整链路这条通路的核心是「逆向映射」不是把左图搬到右图的位置上而是对输出图像的每一个像素反查它在左图和右图里分别对应哪个位置取回来再加权。这样做的好处是输出永远是规整的扫描线永远不会出现空洞或者重叠写入。3.1 顶层流水线与模块握手顶层把整条链路串成五级级与级之间用valid/ready握手避免任何一级因为上游抖动而丢像素// top_panorama.v 核心连线省略端口声明 video_in_capture u_cap_l ( .clk_pix (clk_pix), .rst_n (rst_n), .vin_data (cam_l_data), .vin_de (cam_l_de), .m_axis_tdata (s_l_tdata), .m_axis_tvalid(s_l_tvalid), .m_axis_tready(s_l_tready) ); coord_gen u_coord ( .clk_pix (clk_pix), .vsync (vsync), .cfg_homo (homo_param), // 上位机下发的单应参数Q8.8 .x_out (x_out), // 当前输出像素坐标 .src_x_l (src_x_l), // 反查左图坐标Q8.8 .src_y_l (src_y_l), .src_x_r (src_x_r) ); line_buffer_3x3 u_lb_l ( .clk_pix (clk_pix), .din (s_l_tdata), .din_valid (s_l_tvalid), .win_p00 (p00_l), .win_p01(p01_l), .win_p02(p02_l), .win_p10 (p10_l), .win_p11(p11_l), .win_p12(p12_l), .win_p20 (p20_l), .win_p21(p21_l), .win_p22(p22_l) ); bilinear_resample u_rs_l ( .clk_pix (clk_pix), .frac_x (src_x_l[7:0]), // Q8.8 的小数部分 .frac_y (src_y_l[7:0]), .p00(p00_l), .p01(p01_l), .p10(p10_l), .p11(p11_l), .pixel_out (px_l) ); blend_alpha u_blend ( .clk_pix (clk_pix), .px_l (px_l), .px_r (px_r), .weight (blend_w), // 来自 LUT0~255 .px_out (px_final) );这段代码里真正值得说的是握手方式。valid/ready看起来只是两根线但它决定了整条通路在输入短暂断流时会不会错位。行缓冲那一级在ready拉低时会暂停写入但坐标生成器不会停——因为坐标是跟着输出扫描线走的。所以coord_gen输出的坐标必须先打一拍再送进重采样跟行缓冲的出数节奏对齐。这个「打一拍」的位置如果放错现象就是图像整体右移一个像素而且是那种「看着对、拼起来错」的错。参数上homo_param是一组 9 个 Q8.8 定点数代表从输出坐标到输入坐标的仿射/单应变换矩阵。上位机离线标定完把矩阵归一化到h331其余八个数各乘 256 取整下发。FPGA 端每个像素做两次乘加得到src_x、src_y不需要除法——这是整个设计里省算力最关键的一步。3.2 行缓冲与 3x3 窗口生成双线性插值需要当前像素周围 2×2 的邻域而在流式输入下你要同时拿到「上一行同列」「上一行右一列」「当前行同列」「当前行右一列」这四个值就意味着必须缓存至少两整行。行缓冲干的就是这个事// line_buffer_3x3.v两行 BRAM 一级移位寄存器构成 3x3 窗口 // 行宽参数化按 BRAM 位宽做拼接 parameter H_ACTIVE 1920; parameter DW 24; reg [DW-1:0] line0 [0:H_ACTIVE-1]; // 倒数第二行 reg [DW-1:0] line1 [0:H_ACTIVE-1]; // 上一行 reg [10:0] wr_ptr; reg [10:0] rd_ptr; always (posedge clk_pix) begin if (din_valid) begin // 新像素进来时先把最老的一行挤掉再写入新的 line0[wr_ptr] line1[wr_ptr]; line1[wr_ptr] din; wr_ptr (wr_ptr H_ACTIVE-1) ? 11d0 : wr_ptr 1b1; end end // 3x3 窗口用三级移位寄存器在读出侧拼出来 reg [DW-1:0] r0_c0, r0_c1, r0_c2; reg [DW-1:0] r1_c0, r1_c1, r1_c2; always (posedge clk_pix) begin if (din_valid) begin // 第一行来自 line0 r0_c0 line0[rd_ptr]; r0_c1 r0_c0; r0_c2 r0_c1; // 第二行来自 line1 r1_c0 line1[rd_ptr]; r1_c1 r1_c0; r1_c2 r1_c1; // 第三行来自当前输入 r2_c0 din; r2_c1 r2_c0; r2_c2 r2_c1; end end这段代码有两个地方值得抠。第一line0[wr_ptr] line1[wr_ptr]这一句是「挤」而不是「搬」每次只写一个位置避免了一整行的批量拷贝如果把两行当成两个独立 buffer 做整体轮转综合出来会多出一倍 BRAM 和一堆不必要的使能逻辑。第二读写指针在这里是同一个wr_ptr/rd_ptr对因为在 3×3 窗口场景下读出永远比写入慢两行指针天然错开不需要额外的 FIFO 做隔离。资源上H_ACTIVE1920、DW24的单行是 46Kb两行 92Kb。主流器件的 36Kb BRAM 拼不出刚好一整行1920×24 46080 bit常见的做法是用两块 36Kb 拼成 72Kb 存一行剩下一半空间空着或者把位宽切成三通道分别存让每块 BRAM 的利用率更均衡。我一般选后者因为三通道在后续融合时是并行处理的分开存反而省了拆分逻辑。3.3 坐标解算与双线性重采样坐标生成器是这条通路里最容易被低估的模块。表面上看它只是「每个像素算一次矩阵乘法」但如果你真的每个像素都做一次 8 次乘法DSP 会瞬间见底。真正的做法是增量式// coord_gen.v逆向映射坐标的增量更新 // 输出扫描线从左到右x 每拍 1y 每行 1 // src H * [x, y, 1]^T展开后对 x 求偏导得到 dx对 y 求偏导得到 dy reg signed [31:0] src_x_q8, src_y_q8; reg signed [31:0] dx_q8, dy_q8; // 每拍 x 增量的 Q8.8 表示 always (posedge clk_pix) begin if (vsync) begin // 每帧起始用矩阵重新算一次起点避免误差累积 src_x_q8 h11 * x0 h12 * y0 h13; src_y_q8 h21 * x0 h22 * y0 h23; end else if (de_out) begin src_x_q8 src_x_q8 dx_q8; // 行内只做加法 if (line_end) begin src_x_q8 src_x_q8 - dx_q8 * H_ACTIVE dy_q8_x; src_y_q8 src_y_q8 dy_q8; end end end逻辑很直白单应矩阵对 x 的偏导在一行内是常数所以行内只需要累加每行结束时把 x 的累积量减回去、换成 y 方向的一步增量。这样每拍只有加法乘法只在帧起始做一次。代价是定点误差会沿着扫描线累积所以每帧必须用真值重算起点把误差清零——这一条如果漏了跑一分钟之后图像会整体漂移几个像素属于典型的「一开始好好的、跑久了就歪」类问题。重采样部分拿到 Q8.8 的src_x/src_y之后整数部分用来寻址实际上因为行缓冲已经在流式推进寻址是靠行列计数对齐实现的小数部分作为双线性插值的权重// bilinear_resample.v // fx, fy 为 Q8 小数权重0~256p00~p11 为四邻域 wire [8:0] fx frac_x; // 0~256 wire [8:0] fy frac_y; wire [8:0] ix 9d256 - fx; wire [8:0] iy 9d256 - fy; // 先水平插值再垂直插值也可反过来结果一致 wire [17:0] top p00 * ix p01 * fx; wire [17:0] bot p10 * ix p11 * fx; wire [17:0] sum top * iy bot * fy; // 归一化除以 65536用右移代替除法 wire [7:0] px sum[23:16];这里有几个参数必须对齐fx/fy是 9 位因为要表示 0 到 256含端点如果只用 8 位最大值 255插值在最右端会丢一个像素的权重。sum的位宽是 8999 35 位代码里截成 18 位是不够的真实工程里要么把中间结果位宽撑到 32 位以上要么在每一级之后先右移再相加。截位截错的现象是图像上出现条状的规律性色斑因为低位溢出在高频区域最明显。3.4 重叠区加权融合与输出对齐融合这一步在数学上最没意思但在工程上最容易出问题。两路图像在重叠区做线性加权// blend_alpha.v // weight 来自 LUT重叠区左边界为 0右边界为 255 wire [15:0] wl weight; wire [15:0] wr 16d255 - weight; // 三通道并行加权 assign px_out[23:16] (px_l[23:16] * wl px_r[23:16] * wr) 8; assign px_out[15:8] (px_l[15:8] * wl px_r[15:8] * wr) 8; assign px_out[7:0] (px_l[7:0] * wl px_r[7:0] * wr) 8;关键在于weight怎么来。常见两种做法一是在重叠区内按 x 坐标线性生成用行内计数器右移实现资源几乎为零二是把权重做成一张与列坐标一一对应的 LUT烧进 ROM适合重叠区不是规整矩形的场景比如镜头有畸变、重叠边界是斜的。这个项目里用的是后者因为标定完之后重叠边界确实是斜的用线性生成会在角落留下一条硬边。输出对齐是另一个坑。左图和右图经过各自的重采样之后同一行数据的到达时刻可能差一拍——因为两边的流水线深度不同或者矩阵参数导致某一路多做了一级对齐。解决办法是在融合前加一级可配置的延迟线用同步逻辑把两路的valid拉齐而不是靠「反正差得不多」硬扛。差一拍的后果是重叠区出现固定的垂直细缝看着像压缩伪影其实是时序问题。4. 工程搭建与上板从仿真波形到第一张拼接图写 RTL 只是三分之一的工作量剩下三分之二在约束、激励和上板联调上。这一章按「先让仿真跑对、再让时序收敛、最后让画面出现」的顺序走。4.1 时钟、复位与约束文件这套设计里至少有两个时钟域像素时钟输入侧和输出侧可能不同源和配置时钟上位机通过串口/以太网下发参数。跨域的地方只有cfg那一层用两级同步器加握手就够但约束文件里必须显式声明异步关系# timing.sdc create_clock -name clk_pix -period 6.734 [get_ports clk_pix] ;# 148.5MHz create_clock -name clk_cfg -period 20.000 [get_ports clk_cfg] ;# 50MHz # 两个时钟域之间只有配置寄存器通路声明为异步组 set_clock_groups -asynchronous \ -group [get_clocks clk_pix] \ -group [get_clocks clk_cfg] # 输入输出侧如果来自不同晶振也要各自成组 set_input_delay -clock clk_pix 2.0 [get_ports cam_*] set_output_delay -clock clk_pix 2.0 [get_ports lcd_*]这里最容易翻车的是set_input_delay的取值。摄像头送过来的数据相对于像素时钟的相位关系取决于走线取保守值比如 2.0ns能保证时序收敛但如果实际板子上走线偏长、裕量不够就会出现「仿真对、上板花屏」。血泪经验是先不写set_input_delay让工具按默认约束跑一版用在线逻辑分析仪抓一次实际到达时刻再回头把值填准。反过来做——先拍一个数、再上板验证——大概率要来回改好几版。复位这块整条通路用的是同步复位、低有效复位释放必须与clk_pix同步。异步复位直接接到行缓冲的 BRAM 使能上会在释放瞬间让读写指针错位现象是第一帧图像整体错位一格。稳妥做法是复位信号先过两级触发器再分发。4.2 用脚本造测试图做时序仿真纯靠随机激励是验证不了拼接的因为拼接的本质是「对应关系」随机数据没有对应关系。我一般用脚本生成一对带明显特征点的图——棋盘格加几个色块左图色块偏左右图色块偏右两张图的重叠区里有一组共同的标记# gen_pattern.py生成一对测试图输出 hex 供 $readmemh 使用 import random W, H 640, 480 OVERLAP 200 random.seed(7) def make_img(offset): img [] for y in range(H): for x in range(W): # 棋盘格底纹 v 200 if ((x // 32) (y // 32)) % 2 0 else 40 # 每隔 80 像素放一个色块带 1 像素宽的十字方便看对齐 if x % 80 4 or y % 80 4: v 255 img.append((v, v // 2, 255 - v)) return img left make_img(0) right make_img(W - OVERLAP) # 右图左移模拟重叠 with open(frame_left.hex, w) as f: for r, g, b in left: f.write(f{(r16)|(g8)|b:06x}\n) with open(frame_right.hex, w) as f: for r, g, b in right: f.write(f{(r16)|(g8)|b:06x}\n)测试平台里把这对图灌进行缓冲跑完一帧之后把输出写成 hex再用脚本还原成图片肉眼比对。这个流程比看波形高效得多因为拼接的错误大多是空间性的错位、错行、错色波形上看不出来图上一眼就能看见。注意测试图的尺寸不要直接用 1920×1080 跑仿真。行缓冲一帧 6MB 的写入仿真器要跑很久。用 640×480 甚至 128×96 先把控制逻辑验证对再换真实尺寸跑一次时序收敛确认。4.3 上板联调与在线观测上板之后第一件事不是看画面是确认时钟和复位。用在线逻辑分析仪抓clk_pix的实际频率、vsync的周期、以及行缓冲的读写指针是否在一个稳定的范围内来回摆。如果wr_ptr一直往上涨不回头说明行同步没对齐如果读写指针贴在一起说明读比写快会读到还没写进去的数据。第二件事是抓重叠区的权重值。把blend_w接到探针上触发条件设成「重叠区左边界」看它是不是从 0 平滑涨到 255。如果中间出现跳变说明坐标生成器和权重 LUT 的地址没对齐——这是我在这个项目里翻车最久的一次现象是重叠区有一条 8 像素宽的亮带查了三天才发现是 LUT 地址比像素晚了一拍。第三件事才是看画面。第一张拼接图出来的时候通常会有接缝先别急着调算法先用一张纯白图打进去看接缝处是不是有亮度差。有亮度差说明加权系数不对总和不是 256完全没接缝说明融合通了再去调几何对齐。5. 避坑记录五个让工程返工的现场问题5.1 图像撕裂与逐行错位现象输出画面每隔几十行出现一次水平撕裂撕裂位置不固定但同一帧内只出现一次。原因行缓冲的读指针和写指针共用了同一个计数器在行消隐期间没有正确复位导致某一行的读取起点比预期早或晚了一个像素后面的行全部跟着偏移。解决读写指针分开维护各自用hsync的上升沿复位到 0同时在行首加一级valid屏蔽消隐期间不产生读请求。改完之后撕裂消失但暴露出另一个问题——行首第一个像素的颜色偶发错误原因是 BRAM 读出有一拍延迟行首那一拍读到的是上一行的残留。补一个提前一拍的预读地址就解决了。5.2 融合带亮度断层现象重叠区中间出现一条明显的竖线两侧亮度不一致靠近边界处尤其明显。原因加权公式写成了(l * w r * (255 - w)) 8当w 255时255 - w 0看起来没问题但w的取值范围实际是 0 到 255除以 256 之后总和是 255/256整体亮度被压低了约 0.4%。单看一帧不明显但拼接图两侧是满亮度、中间暗一点肉眼对这条竖线极其敏感。解决把权重归一化改成w和256 - w范围 0 到 256右移 8 位后总和精确等于 256。权重 LUT 的深度相应改成 257 项。这个改动只涉及一个常数但定位花了很久因为一开始都以为是几何对齐的问题。5.3 时序不收敛与资源爆掉现象综合报告显示建立时间违例违例路径集中在重采样模块的乘法器输出到寄存器之间同时 DSP 使用率超过 90%布局布线时间从十几分钟涨到两个多小时。原因三通道并行展开每个通道 4 个乘法器再加上融合的 6 个一共 18 个乘法器挤在同一个时钟周期里。关键路径是「乘法 → 加法 → 移位 → 再乘法」两级乘法串在一起148.5MHz 下根本收不住。解决把重采样拆成两级流水水平插值的结果先打一拍再进垂直插值关键路径砍掉一半三通道改成时分复用用一个乘法器组在 3 倍时钟下轮流算DSP 数量降到原来的三分之一。代价是内部时钟要跑到 445MHz得单独约束一个衍生时钟。改完之后时序裕量回到正数但功耗上去了这是后话。5.4 上位机改参数后画面不刷新现象通过串口下发新的单应参数返回确认帧正常但画面纹丝不动必须重新上电才生效。原因参数寄存器写进去之后没有产生一个「生效」脉冲去触发坐标生成器重新加载。坐标生成器只在vsync时读取一次参数而参数寄存器是多字节分拍的如果最后一字节写入时刚好错过vsync就要再等一整帧——但代码里vsync的边沿检测写成了电平检测导致永远等不到。解决参数写入完成后置一个cfg_dirty标志vsync上升沿检测到该标志就重载参数并清零。另外把多字节写入改成先写影子寄存器、最后一字节触发提交避免中间态被读到。5.5 长时间运行后图像缓慢漂移现象上电前十分钟画面正常半小时后重叠区的接缝开始偏移一小时能偏出三四个像素。原因坐标解算用的是行内累加定点误差每行累积一次帧起始时虽然重算起点但重算用的是上一帧的终点做基准误差跨帧传递。解决每帧起始用单应矩阵和输出坐标的绝对真值重算不依赖上一帧任何中间量同时把 Q8.8 的精度提高到 Q12.12累加误差降低两个数量级。改完之后连续跑八小时接缝位置稳定在一个像素以内。6. 二次开发从两路到多路以及怎么验证改对了两路拼接跑通之后最常见的需求就是扩到三路、四路。这件事在软件上只是多写一个循环在硬件上则要重新算一遍数据通路。我的做法是把融合级做成参数化的树形结构而不是平铺// 参数化融合树N 路输入按二叉树逐级融合 // 每级融合的权重由各自的 overlap 区间决定 module blend_tree #( parameter N_CH 4, parameter DW 24 )( input wire [N_CH*DW-1:0] px_in, input wire [N_CH*8-1:0] w_in, output wire [DW-1:0] px_out ); // 第一级两两融合产生 N_CH/2 路 // 第二级继续两两融合直到剩一路 // 每级之间插一级寄存器保证时序 endmodule改成树形之后四路拼接的延迟只比两路多两级流水而不是线性叠加DSP 的消耗是 N-1 组融合单元也不会爆炸。真正麻烦的是权重表三路以上重叠区不再是两个区间而是一个区间集合LUT 的地址生成逻辑要跟着改。我一般把权重表放到片外上电时从 Flash 载入这样现场调边界不用重新综合。验证方法上我改完任何一版都会强制走一遍固定流程先用 128×96 的小图跑行为仿真确认控制逻辑没有死锁再用 640×480 的带特征点图跑时序仿真把输出还原成图片肉眼比对最后上板用真实摄像头看动态画面重点看快速平移时接缝会不会抖。三步都过才允许烧到现场设备上。这套流程救过我一次——某次改多路融合仿真全过上板之后快速平移时接缝狂抖原因是权重 LUT 的地址在跨行时没有正确复位静态图完全看不出来。从那以后我每次改数据通路哪怕只改一个位宽都强制把「小图仿真、特征图比对、上板动态测试」这三步走一遍一步都不跳。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑