简介TI毫米波传感器人数计数实验lab0011-pplcount的完整工程资料包面向嵌入式、物联网与毫米波雷达应用开发者可用于学习人数统计项目或作为二次开发起点。包内共309个文件压缩包约14.67MB主要包含C/H源码、GUI工程、快速启动指南、docs文档、cfg配置、PDF及PNG截图等能对应到硬件连接、参数配置、信号处理、界面展示的完整脉络。已有360人浏览学习内容组织适合逐步深入。借助配置JSON与工程源码可掌握传感器频率、增益、采样率等参数的调整方法结合docs中的说明可理解目标检测阈值、跟踪与滤波等算法细节再通过GUI代码快速改造出自己的应用界面。1. 毫米波雷达做人员计数lab0011-pplcount 能数多少、误差在哪毫米波雷达做人员计数听起来有点像玄学不靠摄像头不碰隐私仅凭一簇稀疏点云竟然能算出房间里现在站着几个人、坐在哪里。lab0011-pplcount 这套实验资源要解决的就是这件事——它把某主流芯片厂商的毫米波雷达套件拉到 60GHz 频段用 FMCW 信号链输出人员点云再在 PC 端把散点聚成“人”实时输出计数结果。实测下来单雷达在 8m 范围内、3~5 人场景里能稳定跑通误差主要在静止目标漏检和两人并排被合并这两类。适合正在做室内客流统计、智能照明联动、电梯无人值守或者实验室人数监测的开发者拿来起步。2. 点云从哪里来FMCW 信号链拆解与一份能跑的起始配置2.1 信号链四步从一段调频波到一簇点云毫米波雷达发射的是线性调频连续波也就是 FMCW。每一帧里发射天线发出一个频率随时间线性上升的 chirp 信号这个信号打到人身上反射回来接收天线收到后会与本地发射信号做混频得到一个差频信号也就是 IF 信号。这个 IF 信号的频率直接对应目标的距离相位对应目标的微小位移。接下来是三步处理。距离维 FFT 把 IF 信号从时域变换到距离域算清楚每个距离 bin 上有没有反射能量多普勒维 FFT 再对连续多个 chirp 做二次变换提取目标的径向速度顺便把静止杂波和运动目标区分开最后是 CFAR 检测用一个自适应门限在距离-多普勒谱上找峰值只有超过门限的 bin 才被认为可能是有效目标。经过这三步你会得到一个包含距离、角度、速度信息的目标点列表每个点再通过天线阵列的角度估计换算成三维坐标最终输出形式就是x, y, z, doppler, snr, noise这样的点云数据。lab0011-pplcount 用的正是这条链路它的核心优势在于点云天然就是稀疏的不需要像摄像头那样做密集的像素语义分割处理量小很多而且雷达不记录图像信息部署在办公室、教室、卫生间这类场景时没有隐私争议。2.2 起始配置一次调参要动的全在四个字段里拿到一套 60GHz 毫米波开发板第一步就是配置射频参数。常规做法是直接在 SDK 的cfg文件里改字段或者通过串口把配置逐行发给设备。我一般先把下面这份作为起步配置跑通再根据场景微调。sensorStop profileCfg 0 60.0 30 15.25 73.2 0 0 83.5 1 100 512 0 0 248 frameCfg 0 0 11 0 255 1 0 53第一行的sensorStop是停止当前配置防止在改参时设备仍按旧参数收发。第二行profileCfg是射频核心配置各字段含义如下表字段位置含义示例值对我调参的影响第 2 个起始频率 (GHz)60.0改这个等于换频段一般不动第 3 个idle time (us)30影响 chirp 之间的间隔间接影响最大不模糊速度第 4 个ADC 起始时间 (us)15.25影响盲区范围调大了近处目标会丢第 5 个调频斜率 (MHz/us)73.2斜率越低同样采样下探测距离越远第 6-7 个发射/接收天线起始配置0 0天线选择复用默认即可第 8 个采样率 (Msps)83.5影响距离分辨率调高数字越大分辨率越细第 10 个每 chirp 采样点数100越多距离维 FFT 点数越高第 11 个chirp 数量512越多多普勒分辨率越高但帧率会掉第三行frameCfg里frame period 53ms意味着帧率约 18.8fps对人员计数足够。需要注意的是不同 SDK 版本的profileCfg字段顺序可能略有差异以你的设备头文件里的结构体定义为准。这组参数里最值得反复调的是采样率和 chirp 数量采样率决定距离分辨率chirp 数量决定速度分辨率人员计数场景里我一般把采样率控制在 100Msps 附近。2.3 为什么选毫米波而不是摄像头或热释电这个选择在部署前就该想清楚。摄像头方案成熟且便宜但在光线暗、有遮挡、或者卫生间这类场所基本没法用光一个隐私合规就把很多项目卡死了。热释电传感器只能感知“有没有人在动”完全无法区分“一个人走过”和“两个人在聊天”更给不出位置。毫米波雷达的输出是三维坐标点不受光照影响且天然脱敏。代价是点云噪声明显、静止目标容易被 CFAR 滤掉这两点正是后面要避的坑。我实际测过一套 60GHz 开发板放在 2.8m 高度的效果正下方 1.2m 以内是盲区2.5m 到 6m 范围内点云最干净超过 6m 后点云开始变稀尤其是一个人站着不动时多普勒速度约等于 0CFAR 很容易把它当杂波滤掉。因此如果你要覆盖 10m 以上的走廊需要把调频斜率调低或者把检测区域单独配置成“静态目标保留”模式。3. 拿到点云串口帧格式、Python 解析与实时打点3.1 串口输出你从设备端读到的原始字节是什么配置完成后开发板会通过 UART 持续往外发点云帧。不同演示程序的帧格式略有差异但 lab0011-pplcount 这类人员计数实验普遍遵循一种通用结构帧头 点云数据。帧头部分通常以 8 字节魔数开头例如02 01 04 03 06 05 08 07用于让你在字节流里快速定位一帧的起点然后是版本号、帧号、点云点数等元数据。点云数据部分则是每个点的三维坐标和速度。这里最容易踩的坑是字节对齐。有的 SDK 在点云数据前加了 12 字节的平台信息有的点里还带 SNR/noise 扩展字段。如果用固定偏移去解析换一个 SDK 版本就全乱。所以我一般先把一帧完整 dump 成 hex 文件人工看一遍头结构再写解析代码。3.2 Python 解析一段能直接跑的串口读取脚本我用 Python pyserial 做了一个点云读取脚本逻辑是循环读串口字节流在缓冲区里搜索魔数一旦定位到帧头就按帧结构取出点云交给后续聚类模块处理。import serial import struct import numpy as np MAGIC b\x02\x01\x04\x03\x06\x05\x08\x07 MAGIC_LEN len(MAGIC) # 每点 4 个 floatx, y, z, doppler共 16 字节 # 如果你的 SDK 还输出 snr/noise每个点会附加 4 字节把下面这行改成 20 即可 POINT_BYTE_SIZE 16 ser serial.Serial(/dev/ttyUSB0, 921600, timeout2) buf b def parse_frame(payload: bytes): num_points struct.unpack(H, payload[10:12])[0] points [] offset 12 for i in range(num_points): x, y, z, doppler struct.unpack( 4f, payload[offset: offset 16] ) points.append((x, y, z, doppler)) offset POINT_BYTE_SIZE return np.array(points) while True: # 先补齐缓冲区到至少一个帧头长度 if len(buf) MAGIC_LEN 12: buf ser.read(1024) continue idx buf.find(MAGIC) if idx -1: buf ser.read(1024) continue if idx 0: buf buf[idx:] # 丢掉魔数之前的垃圾字节 num_points struct.unpack(H, buf[MAGIC_LEN 10:MAGIC_LEN 12])[0] total_len MAGIC_LEN 12 num_points * POINT_BYTE_SIZE if len(buf) total_len: buf ser.read(1024) continue frame buf[:total_len] buf buf[total_len:] pts parse_frame(frame[MAGIC_LEN:]) # 这里先打印点数后续接聚类 print(fframe points: {len(pts)})这段代码我给你解释三个关键点。第一buf.find(MAGIC)是定位帧头的最省事写法但注意如果一个帧头恰好跨在两次串口读取的边界上第一次可能找不到所以find之后要继续补齐缓冲区再判断。第二num_points的偏移来自帧头里“点数”字段的位置我用的偏移是MAGIC_LEN 10如果你的 SDK 版本帧头结构不同这个偏移要跟着改最稳妥的办法是先打印一帧 hex 对照看。第三POINT_BYTE_SIZE 16是常见点云格式SNR/noise 在很多演示里是被单独放在另一个 section 输出的并不会跟着坐标点走。3.3 实时可视化把点云画出来比看数字直观一百倍解析出点云后我建议立即加一个 matplotlib 散点图来做实时预览。这一步不是花架子它能让你在调参时直接看到“这个阈值砍掉了哪些点”“这个高度为什么数出 5 个人”。可视化核心代码import matplotlib.pyplot as plt plt.ion() fig, ax plt.subplots(figsize(8, 6)) # 假设 pts 是上一节 parse_frame 返回的 Nx4 数组 def draw_points(pts, ax): ax.clear() if len(pts) 0: # 只画 x-y 平面颜色按多普勒速度映射 sc ax.scatter(pts[:, 0], pts[:, 1], cpts[:, 3], cmapjet, s20) plt.colorbar(sc, axax) ax.set_xlim(-5, 5) ax.set_ylim(0, 8) ax.set_xlabel(x (m)) ax.set_ylabel(y (m)) plt.pause(0.01)可视化时一个实用习惯是固定坐标轴范围不要让散点自动缩放否则你很难察觉“远处突然冒出来的几个虚警点”。我一般会把动态点云叠加在上电后前几百帧的静态背景图上杂波位置一目了然。4. 数出人头聚类、帧间匹配与计数阈值的取舍4.1 为什么不做多目标跟踪而先用距离聚类人员计数的朴素做法是把每个点当目标直接数点数。这在点云非常稀的时候可能跑通但只要一个人身上反射出 10 个点计数就会膨胀。反过来如果直接上卡尔曼多目标跟踪器又有点杀鸡用牛刀——室内人员计数通常只有 3~10 个目标点云本身已经做了 CFAR 过滤剩下的点大多集中在人身上用基于欧氏距离的聚类就能把属于同一个人的点圈在一起。这个选型背后有实测依据60GHz 雷达在 4m 处一个成年人身上大约能稳定产生 5~15 个点分布在一个直径约 0.4~0.7m 的椭圆区域内。两个人并排站时两个聚类中心间距通常大于 0.8m。所以只要聚类半径取 0.5~0.6m大部分场景能正确切分开。4.2 聚类实现不依赖第三方库的邻域生长我习惯写一个极简的邻域生长聚类不引 sklearn方便后续移植到嵌入式设备。核心思路是任取一个未访问点把离它小于eps的所有点划进同一个簇再对簇内新点重复这个过程。import numpy as np def simple_cluster(points: np.ndarray, eps: float 0.6): 基于欧氏距离的邻域生长聚类 points: Nx3 ndarray (x, y, z) eps: 聚类的最大半径单位米 n len(points) if n 0: return [] visited np.zeros(n, dtypebool) clusters [] for i in range(n): if visited[i]: continue # 用栈做深度优先邻域搜索 stack [i] visited[i] True member_idx [i] while stack: j stack.pop() # 计算当前点与所有未访问点的欧氏距离 diff points - points[j] dist np.sqrt((diff[:, 0] ** 2 diff[:, 1] ** 2 diff[:, 2] ** 2)) neighbor_idx np.where((dist eps) (~visited))[0] for k in neighbor_idx: visited[k] True stack.append(k) member_idx.append(k) clusters.append(points[member_idx]) return clusters这里eps是最敏感的参数。取太小一个人会被拆成两三个人取太大两个人靠得近会被合并成一个人。我的经验值是雷达安装高度 2.8m 时eps取 0.5m 比较稳如果雷达水平安装、距离目标 3~6m 时目标点云在距离维上会被压缩这时候eps可以适当放大到 0.65m。调eps时不要只看一帧要连续观察十几帧因为人体摆动会导致点云密度周期性变化。4.3 计数规则点数、SNR 和帧间确认聚类完成后“数人头”还需要两条过滤规则。第一过滤噪点簇——一个有效人员至少应该有点点数下限和一定的反射能量第二帧间确认——一个簇要连续出现若干帧才认为确实有人进入检测区域避免偶发杂波被误计。def cluster_to_people(clusters, min_points5, min_height0.3, max_height2.2): 从聚类结果中筛出有效人员 min_points: 一个簇最少点云数 min_height / max_height: 人员点云高度范围用来排除地面杂波 people [] for cl in clusters: if len(cl) min_points: continue center cl.mean(axis0) # 簇中心当作人员位置 z_mean center[2] if z_mean min_height or z_mean max_height: continue people.append(center) return peoplemin_points这个值在不同距离下要区别对待。近距离 2m 处人身上点可能超过 10 个远距离 6m 处可能只有 4~5 个。如果你把min_points全局设为 8远处的人会被直接漏掉设成 3近处又会把桌腿之类的反射物当成人。稳妥的做法是把检测区域分成近、中、远三档分别设min_points这个我在第 6 章会展开。帧间匹配我通常用一个简单机制维护一个人员轨迹列表每条轨迹有位置和“有效帧计数”。新一帧聚类出的每个中心跟上一帧所有轨迹中心做最近邻匹配距离小于 1m 就连到同一条轨迹上连续 3 帧都匹配上的轨迹才正式进入输出计数退出检测区域或连续 2 帧消失的轨迹则从列表移除。5. 实战避坑人员计数最容易翻车的五个问题5.1 点云稀疏到没法聚类CFAR 门限设得太严现象雷达对着一个人但输出点云只有两三个点聚类min_points5直接把人滤没了计数结果偶发变成 0。原因CFAR 检测门限在高信噪比下会自动抬高但人体边缘反射能量低尤其是手臂、腿部这些非正反射区域点很容易被门限吃掉。解决在profileCfg或检测配置里调低多普勒维 CFAR 的阈值系数并对“每帧最少点数”做告警监控。我一般把 CFAR 的 false alarm rate 从默认的 1e-4 放宽到 1e-3点数立刻从 5 个涨到 12 个。代价是地面、墙面的静止杂波也会变多所以同时要开启静态杂波扣除或者靠 5.3 的 ROI 过滤兜底。5.2 人站着不动点云整体消失现象人员走动时计数正常一旦他站定在某个位置 2 秒以上点数逐渐变少甚至完全消失再一走动又恢复。原因FMCW 雷达的 CFAR 检测如果在多普勒维做静止目标的多普勒频率为 0会被当成“零速杂波”直接滤除。这是毫米波人员计数最经典的坑。解决在检测配置里开启静态目标保留也就是把 CFAR 的“多普勒方向”配置从双向改成仅距离维检测或者在 demo 里开启“零速补偿”选项。另一个软件做法是做多帧点云累积把最近 5~10 帧的点云做坐标叠加后再聚类。人静止时身体仍有微动累积后点云密度会显著提升。我目前部署的方案就是 CFAR 宽松 5 帧累积静止人员漏检率基本降到 5% 以内。5.3 地面、墙角、金属门框带来的满屏虚警现象环境里没人但点云里不断出现固定位置的点簇位置通常在墙角、地面和金属门框附近导致人数在 0 和 2 之间来回跳。原因这些位置有强烈的镜面反射或多次反射回波CFAR 会把它们当作有效目标。金属门框尤其离谱斜对雷达时反射强度甚至比人还高。解决最简单有效的是给检测区域加三维 ROI把 x、y、z 限制在人员活动空间内。地面杂波主要集中在 z 很低的位置墙角杂波集中在特定 x-y 区域。你可以在上电后先采集 200 帧空场景做一个背景杂波图每帧点云进来先把背景图里的热点位置减掉再进聚类。我在 5.2 提到的 5 帧累积在这里也要配合用先累积再做背景扣除否则累积反而把杂波也加强了。5.4 两人并排走被算成一个人现象两人肩并肩走过雷达正前方时输出人数从 2 掉到 1走开后恢复。原因两人距离小于聚类半径eps且运动速度几乎一致聚类算法因为距离太近把两个簇合并了多普勒维也没有速度差可供拆分。解决第一步把eps从 0.6 降到 0.4观察是否能把两人分开。如果还不行就需要利用聚类内的点云分布特征两个人的点云合在一起时沿 x 或 y 方向会有明显的双峰分布可以把每个簇的投影做一次一维峰值检查峰数为 2 就强制分成两个人员。注意这会带来“一个人站在镜子旁被劈成两个”的新问题所以拆分逻辑要附加条件两个峰的间距大于 0.3m 且每个峰的点数都超过min_points的 60%。5.5 串口丢帧导致计数卡顿和重复计数现象算法代码没变但计数结果时好时坏间隔 1~2 秒不更新查看打印发现帧率经常掉到 5fps 以下偶尔一帧的点数是 0。原因UART 缓冲区溢出导致丢帧。921600 波特率下如果单帧点云上百个点每帧的字节数超过缓冲区大小上位机来不及读设备端就把旧帧丢弃了。解决把波特率降到 460800 或 230400同时把读取程序改成独立线程用队列把串口读取和聚类计算解耦不要让聚类耗时阻塞读串口。更稳妥的做法是改用 USB 转接模块的硬件流控。我从那以后做雷达开发串口参数第一件事就是确认流控线接好而不是先去调聚类参数。6. 一个调参技巧用 ROI 分段与多帧确认把结果压稳第 5 章的五个坑解决之后lab0011-pplcount 本身的计数精度已经能到 95% 左右但要在真实环境里长期稳定跑还得再压最后一道分段 ROI 和动态确认窗口。我做的第一件事是把检测区域按距离切三段。近距离 0~3m 段人身上点密集但地面杂波也最强这里的min_points设 6同时把高度下限提到 0.5m中距离 3~6m 段点云数量下降min_points降为 4高度下限降到 0.2m远距离 6~8m 段只保留运动目标因为它最容易把墙面反射误报成人我通常把min_points设 3并强制要求该段目标必须有非零多普勒速度。这个分段过滤代码就是把上一章的cluster_to_people按距离分支处理逻辑不复杂但效果立竿见影。第二件事是多帧确认窗口从固定 3 帧改成自适应。人快速通过雷达区域时全程可能只有 6~7 帧如果确认窗口太长人都走出去了还没计上数。我一般把确认窗口设成 3 帧同时加一个“快速通过”旁路某个位置的点云连续 2 帧就消失且轨迹速度大于 1.5m/s直接按“一次通过”计数不计入常驻人数。这样既能避免把路过的人当成常驻又能保留客流计数的准确率。第三件事是输出平滑。最终计数输出不要直接显示每帧人数而是用一个 5 帧滑窗取中位数滤掉单帧抖动。这步看起来简单但对展示端体验提升非常大尤其避免了因为两个人短暂交叠导致人数从 2 跳到 1 再跳回 2 的尴尬。从那以后我每次做毫米波人员计数部署都会强制走一遍“CFAR 门限 → 静态目标处理 → ROI 分段 → 聚类半径 → 确认窗口”五步检查雷打不动。这套流程就是从 lab0011-pplcount 这个实验里一点点磕出来的希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?