尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32嵌入式视觉:FLIR Lepton热像模板匹配实现无人机精准降落

STM32嵌入式视觉:FLIR Lepton热像模板匹配实现无人机精准降落 简介面向嵌入式开发者的FLIR Lepton热成像摄像机与STM32模板匹配项目以辅助无人机降落为核心场景覆盖毕业设计、课程设计、大作业与工程实训等需求。项目经过实测可正常运行完整源码与工程文件可直接烧录复现也可在模板匹配、视觉定位等方向继续扩展。资源包共578个文件约13.37MB以337个C源文件与92个头文件为主辅以uvprojx/uvoptx工程配置、hex烧录文件、ioc引脚配置、map/lst编译映射以及txt/md说明文档模块划分清楚能快速找到算法与驱动代码。内容包含ARM DSP库和STM32 HAL驱动适合学习热成像数据处理、单片机图像匹配与无人机辅助降落逻辑。目前已有234人学习下载可作为项目开发、学科竞赛和初期立项的重要参考。1. 这个实习项目到底在解决什么问题无人机降落时可见光相机最怕的是逆光、扬尘和夜间而 FLIR Lepton 热成像模组不依赖环境光照它输出 160×120、14 bit 的热辐射数组画面分辨率不高但信息足够。真正麻烦的是后半段要在 STM32 上完成模板匹配辅助无人机识别降落标记的位置。MCU 上跑不了 OpenCV模板匹配必须自己做算法裁剪还要把 SPI 采集、内存布局、二值化阈值、串口输出全部纳入 9 Hz 的帧周期里统筹。这篇按嵌入式视觉落地的顺序来写从 Lepton 的 SPI 数据链路读出开始到匹配算法选型和定点化再到串口协议与飞控联动最后给出 FFC 突变处理与回放验证这类工程化技巧。2. 先把 FLIR Lepton 的 160×120 热图搬进内存SPI 读出与预处理2.1 Lepton 的 SPI 数据链路包结构、帧同步与 DMA 双缓冲FLIR Lepton 是 SPI 从机MCU 要扮演主机主动读它。常见的 Lepton 3/3.5 分辨率是 160×120每像素 16 bit其中包含 14 bit 有效热辐射数据值域约 016383。SPI 时钟一般取 20 MHz 以下调试时如果出现错位或乱码先把时钟降到 10 MHz再检查极性相位。多数例程用 CPOLLow、CPHA1EdgeMode 0如果读出来全是半帧花屏试一次 Mode 3 对比。Lepton 的数据不是一次一帧丢过来而是按 164 字节的小包连续输出包里有 4 字节包头和 160 字节有效负载一帧约 240 包。包头里包含包序号和帧状态字段可以用它来判断帧从哪里开始。工程上更简洁的做法是MCU 用 DMA 循环接收一整帧长度的数据接收完成后由主循环从缓冲区取帧处理再配合双缓冲避免 DMA 和 CPU 互相踩内存。#define LEPTON_IMG_W 160 #define LEPTON_IMG_H 120 #define LEPTON_FRAME_BYTES (160UL * 120UL * 2UL) // 两个 DMA 缓冲轮换使用防止主循环读到一半被覆盖 uint8_t lep_buf[2][LEPTON_FRAME_BYTES] __attribute__((aligned(4))); volatile uint8_t lep_buf_idx 0; volatile uint8_t lep_frame_ready 0; void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi-Instance LEPTON_SPI) { lep_buf_idx ^ 1U; lep_frame_ready 1; // 只做标记预处理放到主循环 } }这段代码的思路是把 DMA 当作“搬运工”CPU 不在中断里做图像处理因为 9 Hz 帧率下主循环有大约 111 ms 可以挥霍预处理和匹配都放主循环能显著降低中断抖动带来的风险。参数层面的两个关键点是DMA 缓冲区必须按 4 字节对齐否则部分总线繁忙时效率会明显下降缓冲区大小要按 160×120×2 字节算如果你换成带辐射测量的 Lepton 3.5数据宽度不变但切帧逻辑要重新验证。拿到一帧原始数据后要先把 16 bit 容器里的 14 bit 灰度取出来。不同批次 Lepton 的字节序会有差异如果画面出现棋盘格状噪声大概率是高低字节顺序反了交换一下读取顺序即可。这一步不用做太多判断读取顺序稳定之后把它固化成一个lepton_unpack_frame()函数。2.2 14-bit 转 8-bit 灰度用整帧 min-max 拉伸而不是固定阈值14 bit 灰度直接参与模板匹配是不现实的。一是动态范围宽背景和标记在数值上可能相差几千甚至上万二是固定阈值遇到环境温度变化就会失效。常见做法是先做一次整帧的 min-max 拉伸把 14 bit 压到 8 bit之后再进二值化。这个过程的计算量很小19,200 个像素遍历一遍平均每个像素一次比较、一次乘法在 168 MHz 的 STM32F4 上只需要几毫秒。void lepton_14bit_to_8bit(const uint16_t *raw, uint8_t *gray, int n) { uint16_t mn 0xFFFF, mx 0; for (int i 0; i n; i) { if (raw[i] mn) mn raw[i]; if (raw[i] mx) mx raw[i]; } // 保护如果整帧几乎无温差直接置为中灰 if (mx - mn 16) { memset(gray, 128, n); return; } for (int i 0; i n; i) { gray[i] (uint8_t)(((uint32_t)(raw[i] - mn) * 255U) / (uint32_t)(mx - mn)); } }这段代码有两个值得注意的工程点。第一个是mx - mn 16的保护分支Lepton 在刚开机或 FFC 快门校准瞬间可能输出对比度极低的帧此时强行拉伸会把噪声放大成全屏雪花。第二个是计算时把分子提升到 32 bit避免uint8_t中间量截断导致灰阶跳变。min-max 拉伸是线性映射它不会改变目标的相对对比度但会受个别坏像素影响所以在统计 min/max 时可以跳过值域两端的极端亮度像素更稳妥。2.3 二值化自适应阈值为什么更适合降落场景模板匹配如果跑在 8 bit 灰度上计算量会随搜索位置数量线性膨胀。一个更符合 MCU 习惯的做法是先把图像二值化把 0255 的灰度变成 0/1再用位运算做匹配。二值化阈值有两个选择固定阈值和自适应阈值。固定阈值实现简单但无人机从几十米高度掉到几米地面标记在画面里的温度和占比都会变固定阈值会漏检。自适应阈值里最简单可靠的是 Otsu 大津法它通过最大化类间方差选出阈值不依赖人工标定。uint8_t otsu_threshold(const uint8_t *gray, int n) { uint32_t hist[256] {0}; for (int i 0; i n; i) hist[gray[i]]; uint32_t sum 0; for (int i 0; i 256; i) sum (uint32_t)i * hist[i]; uint32_t w_b 0, sum_b 0; double max_var 0.0; uint8_t thr 128; for (int t 0; t 256; t) { w_b hist[t]; if (w_b 0) continue; uint32_t w_f (uint32_t)n - w_b; if (w_f 0) break; sum_b (uint32_t)t * hist[t]; double mb (double)sum_b / w_b; double mf (double)(sum - sum_b) / w_f; double var (double)w_b * (double)w_f * (mb - mf) * (mb - mf); if (var max_var) { max_var var; thr (uint8_t)t; } } return thr; }Otsu 的循环 256 次内部只有整数累加和少量浮点运算整个函数跑下来不到 1 ms完全可以每帧都做一次。需要注意的是如果画面里目标区域占比特别小Otsu 会把阈值推近背景峰值附近导致标记二值化后变窄。补救办法是先统计二值化后标记像素占比如果低于 2% 或高于 90%就退回上一个已知合理阈值。这个细节对后续模板匹配的鲁棒性影响很大因为模板匹配对“形状残缺”的容忍度远低于“对比度变化”。3. 模板匹配算法怎么选从 SAD/SSD/NCC 到 STM32 可跑的位图匹配3.1 SAD、SSD 和 NCC 的代价函数对比模板匹配的定义是在大图里滑动一个小图计算每个位置的相似度。在 PC 上OpenCV 的matchTemplate提供了一整套接口但把它直接搬到 STM32 是行不通的。先看三个最常见代价函数的公式算法公式单像素计算量对灰度偏移的鲁棒性MCU 友好度SADΣ|I-T|1次减法1次绝对值1次加法差很高SSDΣ(I-T)²1次减法和1次乘加差中NCCcov(I,T) / (σI·σT)多次乘加且需要平方根好低SAD 计算量最小但无人机降落时高度变化会导致整个画面的温度分布和增益变化SAD 对加性偏移很敏感背景从 100 变到 105所有位置的 SAD 都同时增大容易误判。NCC 理论上抗灰度线性变化但每个搜索位置都要做归一化计算128×96 的搜索范围下根本跑不完。实际上做 Lepton 降落匹配更常用的思路是先二值化再用位图匹配相当于把“灰度模板匹配”降维成“形状匹配”这样既绕开了灰度漂移问题又能利用 ARM 的 32 位位运算加速。3.2 位图模板匹配一次异或比较 32 个像素二值化之后图像里每个像素只有 0 和 1。把图像按行打包每 32 个像素放进一个uint32_t模板也按同样的方式打包。匹配时对候选位置和模板对应的uint32_t做异或结果中 0 表示该位匹配1 表示不匹配。统计异或结果里 1 的个数就能得到该位置的不匹配程度。这个操作在 ARM Cortex-M 上一条XOR指令就完成 32 个像素的差异计算。static const uint8_t popcnt8[256] { 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4, /* 生成后可直接固化 */ }; uint16_t bit_template_match(const uint32_t *img_words, int img_w, const uint32_t *tmpl_words, const uint32_t *mask_words, int tmpl_h, int col, int row) { int words_per_row (img_w 31) / 32; int bit_shift col 31; int word_off col 5; uint32_t mismatch 0; for (int i 0; i tmpl_h; i) { uint32_t w0 img_words[(row i) * words_per_row word_off]; uint32_t im; if (bit_shift 0) { im w0; } else { uint32_t w1 img_words[(row i) * words_per_row word_off 1]; im (w0 bit_shift) | (w1 (32 - bit_shift)); } uint32_t diff (im ^ tmpl_words[i]) mask_words[i]; mismatch popcnt8[diff 0xFF] popcnt8[(diff 8) 0xFF] popcnt8[(diff 16) 0xFF] popcnt8[(diff 24) 0xFF]; } return (uint16_t)(tmpl_h * 32 - mismatch); }这里模板宽度必须小于等于 32 像素一行占一个uint32_t。mask_words是有效位掩码因为模板宽度可能不是 32 的倍数右侧空位必须用掩码清零否则异或时会把图像的空位也算成不匹配。调用时col和row是候选点左上角坐标函数返回值是匹配上的像素数越接近tmpl_h * 32越好。位图匹配的加速效果非常明显模板高度 24 时一个候选位置只需要 24 次异或和 24×4 次查表全图搜索约 13,000 个候选位置总循环只有三十多万次在 F4 上可以在几毫秒内完成。3.3 金字塔粗定位加局部精搜索把搜索次数进一步压缩全图位图匹配虽然已经很快但为了给姿态变化和后续输出留余量更稳的做法是加上一层降采样金字塔。先对二值图做 2×2 降采样在 80×60 的小图上跑一遍匹配找出最高分的 3 个候选点再映射回原图坐标在每个候选点附近 ±4 像素范围内做完整匹配。这样能把搜索位置从 13,000 降到 3×81 个耗时再降一个数量级同时降低了误匹配概率。void downsample_binary2x(const uint8_t *src, int sw, int sh, uint8_t *dst) { int dw sw 1, dh sh 1; for (int y 0; y dh; y) { for (int x 0; x dw; x) { uint8_t v src[(2*y)*sw 2*x] | src[(2*y)*sw 2*x 1] | src[(2*y1)*sw 2*x] | src[(2*y1)*sw 2*x 1]; dst[y*dw x] v; } } }降采样用逻辑或而不是平均值是因为二值化后 1 代表标记区域逻辑或可以保证标记细线在降采样后不断裂。如果换成平均值再阈值可能会把边界上的孤立 1 磨掉。但这也带来一个副作用噪声点更容易被保留。所以降采样层数建议最多一层两层会造成标记边缘膨胀影响最后的精匹配定位精度。3.4 别把 PC 上的 float NCC 直接搬到 STM32常见误用写法是把 OpenCV 代码逐行翻译成 C再用 double 全程计算 NCC。到了候选位置 13,000 这个规模时FPU 也扛不住而且归一化涉及的平方根和除法会引入不可控的耗时波动甚至超过帧周期。嵌入式上模板匹配的思路不是“更精确”而是“够用且可预测”。位图匹配 金字塔方案牺牲了对灰度细节的刻画但对热像降落标记这种高对比目标形状本身已经足够区分换来的是稳定的几十微秒级单次匹配耗时。4. 在嵌入式串口与飞控链路中落地模板匹配4.1 内存规划与任务划分F407 级别芯片是起步线Lepton 一帧原始数据是 38,400 字节双缓冲就是约 75 KB再加上灰度缓冲 19.2 KB、二值位图 2.4 KB、模板库若干 KB总内存需求已经超过 100 KB。如果用 STM32F103 这类 64 KB RAM 的芯片必须使用外部 SDRAM 或去掉一帧缓冲开发复杂度会大幅上升。常见的可靠选型是 STM32F407 及以上或者 H7 系列。资源大小用途DMA 双缓冲38.4 KB × 2接收 Lepton 原始帧灰度图19.2 KB14-bit 转 8-bit 后的中间结果二值位图2.4 KB按位打包后的匹配输入模板库18 KB多角度降落标记模板任务划分上主循环按“取帧 → 预处理 → 匹配 → 串口发送”的顺序执行9 Hz 帧率下每一帧有约 111 ms 预算实际预处理加匹配通常只需要 2040 ms余量充足。不建议开 RTOS 做多线程因为 Lepton 是连续数据流任务间同步反而引入额外延迟裸机状态机配合 DMA 中断已经足够。4.2 降落标记与模板库设计T 字形和 4 方向模板降落标记常用 T 字形或 H 字形热像上通常把标记做成加热膜或高发射率涂料的金属板。T 字的优点在于它天然带有方向信息飞控可以据此修正偏航角。但无人机姿态变化时标记可能在画面里旋转模板匹配对旋转非常敏感所以模板库要覆盖多个角度。常见做法是做 0°、90°、180°、270° 四个方向的模板降落阶段飞控会把机头对准标记中间角度的偏差交给飞控的姿态环去修正模板不需要太密。模板的尺寸也要跟着飞行高度变。高度高时标记在画面里小用 16×16 模板高度低时标记大用 32×32 模板。模板库可以同时存几组不同尺寸匹配时按当前估计高度选择对应的那一组。模板数据是离线生成的把二值化后的模板按行打包成uint32_t数组写进头文件固化成const变量运行时从 Flash 读取不占用 RAM。4.3 嵌入式串口配置与飞控数据帧格式模板匹配的结果只有三个量目标在图像中的坐标、匹配分数、锁定状态。坐标可以用整型像素值分数用 0255 表示这些数据通过 UART 发给飞控。串口波特率 115200 就够数据量每帧 8 字节9 Hz 下只有 720 bps。嵌入式串口配置里要注意的是飞控端如果是 3.3 V TTLSTM32 也输出 3.3 V可以直接连如果飞控是 5 V 电平中间必须加电平转换。typedef struct __attribute__((packed)) { uint8_t head[2]; // 0x5A 0xA5 uint8_t type; // 0x10 目标位置 uint16_t x; // 目标中心 x单位像素 uint16_t y; // 目标中心 y单位像素 uint8_t score; // 匹配分数 0~255 uint8_t state; // 0 未锁定 / 1 已锁定 / 2 丢失 uint8_t crc; // CRC8 校验 } landing_target_msg_t;CRC 多项式可以选 0x31对整包数据做一次 CRC8 计算。注意结构体用了__attribute__((packed))否则编译器会填充对齐字节导致飞控端解析错位。发送函数用阻塞式 UART 发送即可因为每帧只有 9 字节在 115200 波特率下耗时不到 1 ms不会挤压匹配计算的时间。发送时机放在匹配完成之后、等待下一帧 DMA 中断之前。4.4 需要现场调的四个参数参数建议初始值调节方向二值化阈值Otsu 自动标记边缘大量断裂时适当下调或加中值滤波模板尺寸24×24目标在画面中占比过小时缩小模板匹配阈值0.70误检多就上调漏检多就下调搜索 ROI全图已知降落地点在画面下侧时裁剪 ROI 减半搜索范围调试匹配阈值最靠谱的办法不是看实时画面感觉而是录制一段带标注的 Lepton 原始数据回放统计正确匹配和错误匹配的分数分布再取两类分布的中间值作为阈值。这个工作可以在 PC 上做不必占用真机飞行时间。5. 让模板匹配在真实降落场景更稳的两个技巧与验证方法5.1 用帧间 EMA 平滑坐标不把单帧抖动直接交给飞控Lepton 的 9 Hz 帧率本身不高匹配结果每一帧都可能有 12 像素的抖动。如果直接发给飞控会引起降落修正的震荡。常见做法是对坐标做一阶低通滤波x_smooth 0.7f * x_smooth 0.3f * x_rawy 轴同理。系数 0.3 表示新观测的权重帧率越低权重可以越小。这个平滑不会引入明显延迟因为降落阶段目标运动速度相对帧率较慢。FFC 快门校准时Lepton 的灰度输出会瞬间跳变二值化结果可能出现一整帧失效。处理方式是记录上一帧的匹配分数如果当前帧分数突然跌到阈值以下但匹配位置还在合理范围就判定为 FFC 事件直接沿用上一帧坐标并计数器加一连续两三帧低分才真正切换状态。这个逻辑看起来简单但能避免飞控收到一次来自热像仪内部校准的“假丢失”。5.2 用 RAW 回放对比验证不要拿真机反复试错实习项目里最容易踩的坑是每改一个参数就飞一次真机成本高且复现性差。更有效的方法是把 Lepton 输出的 16 bit 原始帧按时间戳保存成文件离线在 PC 上复现同一套二值化和位图匹配逻辑把 STM32 日志输出与 PC 参考输出逐帧对比。对比规则可以这样定坐标偏差超过 2 像素标记为异常帧连续 4 帧异常认为该段匹配失效检测率低于 95% 时先检查二值化质量而不是强行放宽匹配阈值。最后把两路坐标画在同一张图里异常帧标红落差点集中在哪个高度区间就优先处理哪个区间的模板尺寸。这套回放验证体系搭建完之后后面所有参数调整都能在几分钟内得出结论真机飞行只做最终确认。关于二值化的极性还有一个小技巧热像里标记可能比背景热也可能比背景冷同一套位图模板匹配逻辑不区分正负形状。离线生成模板时把二值图同时取反生成一份负模板匹配时正负各跑一次取分数高者。这样即使标记是冷板而不是加热板匹配逻辑无需改动只是多一次异或计算。本文还有配套的精品资源点击获取
返回列表