资讯动态

毫米波雷达人员计数:点云聚类、目标跟踪与状态机调参

发布时间:2026/10/26 5:16:51 来源:尧图企业网站定制
简介面向毫米波感知与嵌入式应用学习者的 lab0011-pplcount 实践包围绕 TI 毫米波传感器的人数检测展开适合希望从硬件配置、数据采集到上位机交互完整走通一遍的初学者与工程技术人员。压缩包共 309 个文件约 14.67MB包含 50 个 c 源文件、45 个 h 头文件、40 张 png 截图及若干工程配置、算法参数与说明文档覆盖底层驱动、信号处理、GUI 源码、快速上手指引和项目工程等模块。已有 361 人学习说明该实验在毫米波入门与课程实践中具备一定参考价值。读者可据此了解传感器参数配置、目标检测与人数统计的基本流程观察 GUI 与数据处理代码的组织方式并借助文档和工程文件搭建自己的实验环境对智能交通、安防监控、智能家居等场景中的毫米波应用开发也有直接借鉴意义。1. 从一帧点云到人数lab0011-pplcount 资源包到底解决什么问题门口装一只毫米波雷达做人数统计不靠摄像头听起来像实验室里的演示但 lab0011-pplcount 这个 mmWave 人员计数实验包把它拆成了能照着复现的链路射频配置、点云聚类、目标跟踪、进出计数。它适合已经拿到毫米波雷达板、想把点云跑成人数事件的开发者也适合做会议室占用、走廊客流统计的方案验证。资源包里最值钱的地方不是某个单独算法而是把 chirp 参数、聚类半径、计数状态机放在同一个工程里改一个参数就能看到最终人数怎么变。先说结论这份资源能解决“有板子但不知道从哪调参”的问题不适合只想调现成 API 的纯软件集成。下面按我实际拆包的顺序从链路、回放、标定到避坑讲清楚。2. 毫米波人员计数链路拆解chirp 参数、点云聚类与计数状态机2.1 射频参数与 chirp 配置距离分辨率、速度量程怎么定毫米波人员计数不是雷达直接吐一个人数而是先出点云再把点云聚成目标最后用跟踪和状态机决定“进”还是“出”。FMCW 雷达发射一串 chirp混频后得到中频信号距离维做 FFT 得到距离 binchirp 维做 FFT 得到多普勒CFAR 检测后留下点云。参数定得不对后面聚类怎么调都像玄学。常见做法是先按场景定距离分辨率和最大速度再回头选带宽、采样点数和 chirp 周期。我在 lab0011-pplcount 里最先看的是配置目录下的 profile 文件核心参数可以用一张表拆开参数符号典型取值影响起始频率f060 GHz波长约 5 mm带宽B3.5 GHz距离分辨率约 4.3 cm每 chirp 采样点数Ns128距离 bin 数chirp 重复周期Tc50 us最大不模糊速度每帧 chirp 数Nc64速度分辨率帧周期Tf50 ms人数刷新率这些参数不是拍脑袋填的距离分辨率、最大速度、速度分辨率都能直接算。下面这段脚本我放在 scripts/param_check.py 里改配置前先跑一遍省得配完才发现速度量程不够import numpy as np C 3e8 def radar_params(bandwidth, num_samples, chirp_period, num_chirps, f0): 根据 chirp 配置估算距离/速度指标单位统一为米、秒 lam C / f0 range_res C / (2 * bandwidth) max_range num_samples * range_res v_max lam / (4 * chirp_period) v_res lam / (2 * num_chirps * chirp_period) return { wavelength_m: lam, range_res_m: range_res, max_range_m: max_range, v_max_mps: v_max, v_res_mps: v_res, } if __name__ __main__: cfg radar_params( bandwidth3.5e9, num_samples128, chirp_period50e-6, num_chirps64, f060e9, ) for k, v in cfg.items(): print(f{k}: {v:.4f})这段代码只做一件事把配置里的物理量换算成距离分辨率和速度指标。bandwidth是扫频带宽不是采样率chirp_period是一个 chirp 的重复周期不是扫频时间。算出来距离分辨率约 4.3 cm最大速度约 25 m/s速度分辨率约 0.78 m/s。人体步行速度 1.5 m/s 左右速度量程足够但速度分辨率偏粗慢走和静止的人容易落到零多普勒附近。门口计数场景里我一般把num_chirps加到 128速度分辨率降到 0.39 m/s代价是帧时间变长。走廊场景人多时帧周期 50 ms 够用会议室静态多可以把帧周期放到 100 ms换取更稳的多普勒。这里有个容易翻车的点只看最大距离不看距离分辨率。最大距离由采样点数决定但两个人并排能不能分开靠的是带宽。3.5 GHz 带宽在 3 m 处两个反射点差 4 cm 以上才可能分开实际人体肩宽 45 cm理论够但点云稀疏时仍会粘在一起。所以参数表里带宽和采样点数要一起看不能只把Ns调大。2.2 点云预处理静态杂波滤除与 DBSCAN 聚类的参数边界拿到一帧点云后先别急着聚类。静态杂波不滤门框、墙角和桌腿会一直出现在同一个位置DBSCAN 会把它们聚成“永远不动的人”。lab0011-pplcount 里常见的做法是沿帧维度做背景均值相减或者直接剔除零多普勒 bin。两种做法各有用处背景相减适合固定安装、背景变化不大的门口零多普勒剔除适合走廊但人站着不动时会丢。我的习惯是先用背景相减再保留零多普勒附近一个窄带避免完全把静止目标抹掉。聚类用 DBSCAN 比 KMeans 合适因为人数未知KMeans 要先指定簇数。DBSCAN 的关键参数只有两个eps和min_samples。eps是邻域半径单位跟点云坐标一致min_samples是一个簇最少要几个点。人体在 2 m 处的点云直径大概 0.3 到 0.5 meps取 0.30 到 0.40 m 比较稳min_samples取 4 到 5低于 3 容易把噪声当成目标。下面这段聚类函数可以直接套import numpy as np from sklearn.cluster import DBSCAN def cluster_frame(points, eps0.35, min_samples4): points: N x 3 数组列顺序为 x, y, z单位米 返回簇中心列表和每个点的标签 if points.size 0: return np.empty((0, 3)), np.array([]) xy points[:, :2] labels DBSCAN(epseps, min_samplesmin_samples).fit_predict(xy) centers [] for lab in set(labels): if lab -1: continue cluster_points points[labels lab] centers.append(cluster_points.mean(axis0)) return np.array(centers), labels # 示例一帧点云先转成米再聚类 raw_points_mm np.array([[1000, 200, 0], [1050, 220, 0], [3000, -500, 0]]) points_m raw_points_mm / 1000.0 centers, labels cluster_frame(points_m, eps0.35, min_samples2) print(centers)代码里先把毫米转成米这一步不能省否则eps0.35会被当成 0.35 毫米所有点都聚不到一起。xy只取水平面是因为人员计数主要看地面投影z 轴受安装高度影响大早期调参容易把高度噪声带进来。labels -1是噪声点不参与簇中心计算。centers是每个簇的均值后面跟踪拿它当观测值。eps的边界很具体两个人并排站肩宽 0.45 m点云边缘间距可能只有 0.2 m。eps大于 0.4 m 时两个人容易被连成一个簇eps小于 0.2 m 时一个人会被拆成胳膊、腿几个簇。我的做法是按距离分段2 m 内eps0.282 到 4 m 用 0.354 m 以外用 0.45。远距离点稀疏半径不放大的话人还没走到计数区就丢了。min_samples也不要一刀切远距离可以降到 3但低于 3 时噪声簇会变多需要靠后面的跟踪窗口过滤。2.3 目标跟踪与计数状态机如何避免一人多计和漏计聚类只给“这一帧有几个簇”不给“谁是谁”。没有跟踪同一个人走进走出会被当成多个目标人数直接翻倍。lab0011-pplcount 里的跟踪不复杂最近邻匹配加一个短生命周期轨迹就够用。每一帧把当前簇中心和上一帧轨迹预测位置做距离匹配匹配门限取 0.5 m 左右匹配不上就新建轨迹连续多帧匹配不上就删除。计数状态机挂在轨迹上不是挂在簇上这样一人多计会少很多。下面是一个简化版状态机重点看confirm_frames和lost_frames两个参数class TrackCounter: def __init__(self, match_dist0.5, confirm_frames5, lost_frames8): self.match_dist match_dist self.confirm_frames confirm_frames self.lost_frames lost_frames self.next_id 0 self.tracks {} # id - {pos: np.array, hits: int, misses: int, inside: bool} self.count 0 def update(self, detections, inside_zone): # detections: list of (x, y) unmatched list(range(len(detections))) for tid, tr in list(self.tracks.items()): best_idx, best_dist None, self.match_dist for idx in unmatched: d np.linalg.norm(tr[pos] - np.array(detections[idx])) if d best_dist: best_idx, best_dist idx, d if best_idx is not None: tr[pos] np.array(detections[best_idx]) tr[hits] 1 tr[misses] 0 unmatched.remove(best_idx) else: tr[misses] 1 # 进入计数区且连续命中足够帧才计一次进入 if tr[hits] self.confirm_frames and not tr[inside]: if inside_zone(tr[pos]): tr[inside] True self.count 1 if tr[misses] self.lost_frames: del self.tracks[tid] for idx in unmatched: self.tracks[self.next_id] { pos: np.array(detections[idx]), hits: 1, misses: 0, inside: False, } self.next_id 1 return self.countmatch_dist是轨迹和检测的关联门限太大容易把两个人换 ID太小同一个人走快一点就丢。confirm_frames5表示连续 5 帧命中才承认目标按 50 ms 帧周期算 250 ms能压掉大部分一闪而过的噪声簇。lost_frames8表示目标消失 8 帧才删除轨迹400 ms 的容忍时间人短暂被遮挡不会立刻断轨。inside_zone是一个判断点是否在计数区的函数门口场景可以写成矩形区域走廊可以写成一条线两侧的滞回区。一人多计的根因通常不是状态机而是轨迹 ID 切换两个人交叉走最近邻匹配把 ID 换了旧轨迹消失、新轨迹进入计数加两次。解决方法是把match_dist降到 0.35 m同时用上一帧速度做预测不要只拿上一帧位置匹配。漏计则多是聚类把两个人合成一个簇轨迹只有一个计数自然少一个。这时候先调eps再考虑提高距离分辨率不要直接改confirm_frames否则延迟会变大。3. 把 lab0011-pplcount 跑起来环境、数据回放与首帧验证3.1 环境准备与目录结构不装整包也能先跑回放拿到实验包后不要急着上板。我见过太多人第一步就插串口、开配置工具结果点云全零回头查一下午。lab0011-pplcount 的目录一般分 config、data、scripts、output 四块先跑离线回放确认算法链路通再碰硬件。目录结构大致如下lab0011-pplcount/ ├── config/ │ └── profile_60g.json ├── data/ │ ├── raw/ │ │ └── sample_adc.bin │ └── pointcloud/ │ └── sample_points.csv ├── scripts/ │ ├── replay.py │ ├── range_doppler.py │ └── check_first_frame.py └── output/ └── count_events.csv环境不用装厂商整套工具先建一个轻量 Python 环境就够跑回放python -m venv .venv source .venv/bin/activate pip install numpy scipy scikit-learn pandas matplotlibnumpy和scipy做 FFT 和信号处理scikit-learn只用来跑 DBSCANpandas读 CSV 和写事件日志matplotlib看距离剖面。不要一上来装一堆深度学习框架人员计数这点点云用不上。虚拟环境建好后再看data/pointcloud/sample_points.csv的列名常见是frame,x,y,z,snr,doppler如果坐标是毫米先统一除以 1000不然后面eps全错。回放脚本的核心不是把 CSV 读进来而是按帧分组、逐帧调用聚类和跟踪。下面这段是骨架import pandas as pd def load_frames(csv_path): df pd.read_csv(csv_path) # 坐标统一转米避免后续 eps 单位混乱 for col in [x, y, z]: if df[col].abs().max() 100: df[col] df[col] / 1000.0 return [g[[x, y, z]].values for _, g in df.groupby(frame)] frames load_frames(data/pointcloud/sample_points.csv) print(frames:, len(frames), first frame points:, len(frames[0]))groupby(frame)保证每帧独立处理abs().max() 100是一个粗糙但好用的单位判断毫米坐标通常上千米坐标不会超过几十。这个判断不严谨但能拦住大部分“点云全是几千”的配置错误。回放时把frames逐帧喂给cluster_frame和TrackCounter输出output/count_events.csv字段至少要有timestamp, track_id, event, count后面对齐视频和排查都靠它。3.2 采集数据回放从 ADC 数据到距离-多普勒图点云回放跑通后再看原始 ADC 回放。lab0011-pplcount 里如果有data/raw/sample_adc.bin说明作者留了从原始数据到点云的入口。原始 ADC 一般是 int16 交错排列维度是frames × chirps × rx × samples。读的时候先把二进制 reshape 成这个四维数组再做距离 FFT 和多普勒 FFT。下面这段代码展示最小闭环import numpy as np def load_adc(path, frames, chirps, rx, samples): raw np.fromfile(path, dtypenp.int16) expected frames * chirps * rx * samples if raw.size ! expected: raise ValueError(fADC 长度不符: {raw.size} ! {expected}) return raw.reshape(frames, chirps, rx, samples).astype(np.float32) def range_doppler(cube): # cube: frames, chirps, rx, samples cube cube - cube.mean(axis-1, keepdimsTrue) # 去直流 window np.hanning(cube.shape[-1]) cube cube * window range_fft np.fft.fft(cube, axis-1) doppler_fft np.fft.fftshift(np.fft.fft(range_fft, axis1), axes1) return np.abs(doppler_fft).sum(axis2) # 多接收天线非相干累加 cube load_adc(data/raw/sample_adc.bin, frames200, chirps64, rx4, samples128) rd range_doppler(cube) print(rd.shape) # (200, 64, 128)load_adc里的长度校验很重要二进制文件少一个字节reshape 就会错位后面距离维全是乱的。cube.mean(axis-1)去的是每个 chirp 内的直流不是全局均值不去直流的话零距离 bin 会特别亮CFAR 会一直报一个假目标。加汉宁窗是为了压距离旁瓣代价是主瓣变宽距离分辨率会略降人员计数场景可以接受。fftshift把零多普勒移到中间不移的话正负速度是折叠的跟踪时速度方向会反。跑完rd后不要只看形状抽一帧画距离-多普勒图。人体目标在图上是一个斜着的亮斑静止杂波在零多普勒线上是一条横线。如果整张图只有零多普勒线亮说明人没动或者背景太强如果整张图都是噪声检查rx和samples顺序是不是反了。不同板子的 ADC 排列可能是chirps × rx × samples也可能是rx × chirps × samplesreshape 错一次点云就永远出不来。3.3 首帧验证用距离剖面和噪声底判断链路是否通首帧验证不要看人数先看距离剖面。把距离-多普勒图沿速度维平均得到一条距离剖面找峰值和噪声底。峰值信噪比大于 10 dB说明链路基本通峰值位置和卷尺量的距离对得上说明距离换算没大错。下面这段判断逻辑我每次上板都会跑import numpy as np def check_first_frame(rd_map, range_res_m0.043): profile rd_map.mean(axis0) # 沿多普勒维平均 noise np.percentile(profile, 30) peak_idx int(np.argmax(profile)) peak profile[peak_idx] snr_db 20 * np.log10(peak / max(noise, 1e-6)) distance_m peak_idx * range_res_m return { peak_bin: peak_idx, distance_m: distance_m, noise_floor: noise, snr_db: snr_db, ok: snr_db 10 and 0.3 distance_m 6.0, } result check_first_frame(rd[0]) print(result)np.percentile(profile, 30)取 30% 分位当噪声底比直接取均值稳因为目标峰会把均值拉高。snr_db 10是经验门限低于 10 dB 时点云会稀稀拉拉聚类参数怎么调都不稳。0.3 distance_m 6.0是门口场景的合理距离窗太近可能是天线耦合太远可能是多径。range_res_m要跟配置里的带宽对应不要拿默认值硬套带宽改了这个值也要改。如果首帧ok为 False按顺序查三件事配置有没有真正下发、ADC 维度顺序对不对、天线有没有接好。先回读配置确认 chirp 参数和脚本里一致再用一个已知距离的静止目标比如墙角看峰值 bin 是否稳定最后才怀疑算法。这个顺序能省掉大量“改代码改到怀疑人生”的时间。4. 参数标定与场景适配门口、走廊、会议室的三组配置4.1 安装高度与俯仰角用静止目标标定雷达坐标系毫米波雷达点云的 x、y、z 是相对雷达的不是相对地面的。安装高度和俯仰角没标定计数区画在地上点云却飘在半空人明明进了门状态机认为没进。标定不需要复杂设备一卷尺加一个静止目标就够。人站在 1 m、2 m、3 m 处记录点云距离均值和卷尺真值做线性拟合得到比例系数和偏移。下面这段拟合代码可以判断距离换算有没有系统偏差import numpy as np def fit_range_offset(measured, true): # measured: 点云距离均值列表true: 卷尺距离列表 A np.vstack([measured, np.ones_like(measured)]).T k, b np.linalg.lstsq(A, np.array(true), rcondNone)[0] return k, b measured [0.98, 1.95, 2.92] true [1.0, 2.0, 3.0] k, b fit_range_offset(measured, true) print(fscale{k:.4f}, offset{b:.4f})k应该接近 1b接近 0。k偏离 1 超过 5%说明距离 bin 换算用错了常见原因是带宽填错或者 FFT 点数没对应采样点数。b偏大说明有固定延迟可能是天线到参考面的机械偏移。这个拟合不用每天做换安装位置或换板子时做一次。俯仰角标定更简单雷达装高 h波束中心打在地面的距离 d俯仰角 θ 满足d h / tan(θ)。比如装高 2.2 m想覆盖门口 3 m 处θ 约 36°。实际安装时不可能这么准所以要用静止目标看 z 轴偏移人站在已知距离点云 z 均值应该接近 0地面附近如果 z 一直偏正说明俯仰角偏下计数区要跟着调。我的习惯是先把 z 轴关掉只用 x、y 做聚类和计数等距离标定稳了再开 z 过滤能少很多“人飞在天上”的假目标。4.2 门限与聚类半径按人体尺寸和距离分段设置门限分两层检测门限和聚类门限。检测门限用 CFAR阈值太高点云少人走快了会断太低噪声多DBSCAN 会聚出一堆假目标。人员计数场景我一般把 CFAR 阈值放在 12 dB 左右SNR 门限 8 dB先保证点云连续再靠聚类和跟踪去噪。聚类门限就是eps和min_samples要按场景和距离分段不能一套参数打天下。三组典型配置如下数值是起点不是标准答案场景安装高度俯仰角距离门限epsmin_samplesconfirmlost门口2.2 m30°0.5–4 m0.30 m458走廊2.8 m20°1–6 m0.40 m5610会议室3.0 m15°0.8–5 m0.35 m4812门口场景距离短、人流量大eps取小一点避免两个人并排粘在一起confirm取 5按 50 ms 帧周期是 250 ms响应快。走廊距离远、点云稀疏eps放大到 0.40 mmin_samples提到 5压噪声。会议室人坐得多、动得少零多普勒目标多confirm放到 8减少起身倒水被计成进出。距离门限不是随便写的要跟安装高度和俯仰角匹配门口 0.5 m 以内可能是门框反射直接裁掉6 m 以外点云太稀计数不可信。如果两个人并排还是只出一个簇先把eps降到 0.25 m 试一帧看是不是聚类半径的问题如果降了还是粘着说明距离分辨率不够调带宽比调eps有用。反过来一个人被拆成两个簇先把min_samples降到 3再看eps是不是太小。每次只改一个参数改完跑同一段回放记录人数误差不然根本不知道是哪个参数起了作用。4.3 计数去抖进入/离开事件的滞回窗口人数跳变不一定是算法错很多时候是状态机在边界抖动。人站在计数区边缘点云一帧在内一帧在外计数就会加一次减一次。滞回窗口解决这个问题进入要连续confirm帧在区内离开要连续lost帧在区外中间状态不变。下面这段滞回计数器可以直接替换前面的简化状态机class HysteresisCounter: def __init__(self, confirm5, lost8): self.confirm confirm self.lost lost self.enter_cnt 0 self.exit_cnt 0 self.inside False self.count 0 def update(self, is_inside): if is_inside: self.enter_cnt 1 self.exit_cnt 0 if self.enter_cnt self.confirm and not self.inside: self.inside True self.count 1 else: self.exit_cnt 1 self.enter_cnt 0 if self.exit_cnt self.lost and self.inside: self.inside False return self.countconfirm5和lost8是按帧数算的帧周期变了要重新换算。更稳的做法是用时间戳记录第一次进入的时间超过 250 ms 才确认记录第一次离开的时间超过 400 ms 才取消。这样帧率从 20 fps 降到 10 fps 时参数不用重调。inside状态只翻转一次计数只在False - True时加一离开不减人这样“当前人数”和“累计进入”是两个概念事件日志里要分开记不然后面业务系统对不上。滞回窗口不是越大越好。confirm太大人已经走过去才计数业务上就是延迟lost太大人离开了还占着名额会议室占用会一直不释放。门口场景我一般把总延迟压在 500 ms 以内走廊可以放到 800 ms会议室 1 s 也能接受。调参时拿秒表掐一下比看代码猜有用。5. 避坑与排查lab0011-pplcount 落地时最容易翻车的五件事5.1 硬件与链路类问题点云全零和实采丢帧坑一串口有数据点云全零。现象是采集脚本能读到字节但每帧点云数量为 0距离剖面只有噪声底。原因通常不是算法而是 chirp 配置没真正下发或者配置回读失败。厂商配置工具里点了“发送”脚本又用自己的配置覆盖两边参数不一致雷达输出的帧格式和解析代码对不上。解决顺序是先回读配置确认起始频率、带宽、chirp 周期和脚本里一致再检查帧头看每帧长度是不是等于chirps × rx × samples × 2最后用厂商工具单独发一次配置不跑脚本看原始数据有没有变化。不要一上来改聚类参数点云全零时改eps没有任何意义。坑二回放正常实采丢帧。现象是离线 CSV 回放人数平滑接上实采后人数卡顿、事件延迟日志里帧号不连续。原因是串口吞吐不够20 fps、4 收天线、128 采样点的数据量不小缓冲区又小采集线程读得慢就丢帧。解决方法是先降帧率到 10 fps看丢帧是否消失再把波特率提高一档确认线材和串口芯片支持然后在采集脚本里加环形缓冲区丢帧时记录帧号不要用sleep控制采集节奏。丢帧不记录后面排查就是黑匣子根本不知道丢在哪一段。5.2 算法与参数类问题人数跳变、并行漏计和延迟坑三人数频繁跳变1 变成 3 又变回 0。现象是门口没人时计数偶尔加一有人经过时一次进两个人。原因多半是金属门框和玻璃多径静态杂波没滤干净DBSCAN 把门框反射聚成目标。解决分三步先做背景帧均值相减把固定反射压掉再剔除零多普勒窄带门框静止反射大多落在零速附近最后在计数区里画 ROI 屏蔽门框位置不让门框点云进入聚类。只调eps会把真人点云也滤掉治标不治本。背景帧不要只采一次门开关状态变了要重采。坑四两个人并排走只计一个人。现象是视频里明明两个人事件日志只有一个 track。原因是eps太大两个簇被连成一个或者距离分辨率不够两个人在雷达径向差太小点云本来就分不开。先把eps从 0.35 降到 0.25min_samples提到 5跑同一段回放看簇数如果簇数还是 1检查带宽和距离分辨率3.5 GHz 带宽在 3 m 处理论能分 4 cm但点云稀疏时实际要差 20 cm 以上才稳。还可以按距离分段eps近处小、远处大不要一套参数跑全距离。用 KMeans 强行指定 K2 更不可取没人时它会硬分两个簇。坑五计数延迟高人过去两三秒才加一。现象是事件日志时间戳比视频晚很多或者人已经离开计数区inside还是 True。原因是confirm_frames和lost_frames设得太大跟踪窗口拉太长。解决方法是把confirm从 10 降到 5lost从 15 降到 8用时间戳代替帧计数帧率变化时不用重调。同时检查跟踪匹配门限match_dist太大时轨迹会粘在旧目标上新目标要等很久才新建。延迟和稳定是跷跷板门口场景优先响应会议室可以牺牲一点响应换稳定但不要超过 1 s。6. 进阶验证把计数事件接进业务系统前先做三件事6.1 离线回放回归测试固定数据集和指标调参最怕“这组参数今天好用明天换段数据就崩”。我一般固定三段回放单人进出、两人并行、多人停留。每段数据人工标注真实人数和事件时间跑完脚本算两个指标计数误差abs(预测人数 - 真实人数)和事件延迟预测时间 - 真实时间。每次改参数只跑这三段误差变大就回滚。下面这个命令把回放结果和标注对比输出到 CSV方便后面画趋势python scripts/replay.py --input data/pointcloud/regression --output output/regression.csv python scripts/eval_count.py --pred output/regression.csv --gt data/labels/regression.csv回归集不要用调参时看过的那一段留一段“没见过”的数据做验证不然参数会过拟合到某一天的场景。计数误差能压到 1 人以内事件延迟压到 500 ms 以内再考虑接业务系统。6.2 事件日志对齐视频定位漏计和多计事件日志至少要有timestamp, track_id, event, count四个字段。拿手机拍一段带时间戳的视频把日志和视频按秒对齐逐个事件看漏计通常是聚类把两个人合成一个簇或者跟踪 ID 提前丢失多计通常是 ID 切换同一个人被新建了两次轨迹。对齐时不要只看总人数要看每个track_id的生命周期轨迹太短少于 5 帧多半是噪声轨迹突然断掉再新建多半是匹配门限太小。这个笨办法比看代码快血泪经验是日志字段少一个排查时间翻倍。6.3 点云质量看门狗让坏帧自己暴露最后加一个点云质量看门狗每 100 帧统计点数中位数、SNR 中位数、零多普勒比例。点数突然掉到 0说明配置或链路断了SNR 中位数掉 6 dB 以上说明天线脏了或者供电不稳零多普勒比例突然升高说明场景里多了静止强反射。看门狗只告警不自动改参数避免把问题藏起来。下面这段逻辑放在主循环里开销很小def quality_gate(frame_points, snr_values, zero_doppler_ratio): if len(frame_points) 0: return EMPTY if snr_values and sum(snr_values) / len(snr_values) 8: return LOW_SNR if zero_doppler_ratio 0.8: return STATIC_DOMINANT return OK阈值按现场噪声底调不要照搬。看门狗告警后先查硬件和配置再查参数。从那以后我每次拿到新的毫米波计数包都先跑固定回放再看实采首帧最后才调业务参数顺序反了就要用更多时间填坑。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑