多数跟踪器止步于“在哪里”。 这一个继续往下走。
真实世界里的跟踪从来不是连续的。一辆车开到楼后面,一个人走出摄像头的范围。大多数系统把这当成航迹的终点。TRACE 不这样:它把一条航迹保存成那个东西仍然在场的概率,而不是一个是或否,所以航迹是在缺口里慢慢变淡,而不是在缺口处结束,还能在另一头重新接上。
核心里没有任何东西知道自己在跟踪什么。摄像头、船舶、托盘、动物和球员,是同一个问题换了一套参数。下面的 MOT17 和 MOT20 数字是多目标跟踪准确度(MOTA),也就是这个领域用来表示一个跟踪器干得怎么样的那一个数。
一分钟说清这是什么
传感器会漏。一个摄像头不会拍到每一个走过它的人,一艘船可以关掉应答器,一条走廊可能根本没有摄像头。大多数跟踪器把“没看见”读成“不在那里”,于是那个东西再出现时,就被记成了一个新的。它去过哪里、见过谁、这对它来说正不正常 — 这些都再也答不上来了。
TRACE 给每一条航迹记着一个数字:那个东西仍然在场的可能性有多大。一次漏检会把这个数字压低,并把它可能所在的范围放大;它不删除任何东西。航迹靠预测惰行下去,不确定度老老实实地增长,等到有东西再次出现,引擎再判断它是不是同一个。这里两个演示都让你把这件事一直推到它判断错为止。
任何要跨不止一个传感器、而传感器之间还留着缺口去跟踪东西的人:一个摄像头覆盖不到每条走廊的场区,或者一艘船可以在航途中关掉应答器的海上监控。核心并不知道自己在跟踪什么 — 摄像头、船舶、托盘、动物和球员,是同一个问题换了一套参数。它接收检测结果;把检测结果做出来是别人的活。
一漏检就被删掉的航迹,从来就不算航迹
传感器会漏掉东西。摄像头有的是检测概率,不是保证;船可以关掉自己的应答器;走廊里可能根本没有摄像头。覆盖一旦出现缺口,把“没看见”等同于“不存在”的跟踪器就会丢掉身份,等该实体重新出现时又给它一个新的。所有下游的问题 — 它去过哪里,和谁碰过面,这对它来说正常吗 — 现在都答不上来了。
TRACE 把多数跟踪器混为一谈的两件事分开。每条航迹都带有 r,即它究竟存不存在的概率,与它在哪里分开保存。一次漏掉的扫描会降低 r 并让位置估计变宽;它不会删除任何东西。航迹转入惰行,而在惰行的同时,它的不确定度会诚实地增长。
下面两个演示给你看的正是这件事。那个虚线圆圈就是引擎在说 它就在这一圈里的某个地方,而我每一秒都更不确定 — 这是一句有用的话,也正是一条被删掉的航迹没法告诉你的话。
Bernoulli,不是布尔值
一个跑在 320 粒子滤波器上的泊松多伯努利混合(PMBM)跟踪器。存在性是一个概率,会因漏检而衰减,也会因证据而恢复,所以一次遮挡损失的是置信度,不是身份。
对证据的第二种意见
在概率之外,还并行跑着一个可能性意义上的存在度,它跟踪的是证据的 质量 而不是数量。当两者出现分歧时,就说明大量微弱的检测被洗成了虚假的确定性。
一片摄像头布设,其中有没人盯着的走廊
迷宫是真实摄像头布设的一个廉价替身,而它把让多摄像头跟踪变难的每一种性质都给齐了:墙迫使路线变得非线性,每一块彩色面板就是一台摄像头,所以越过边界就是一次真正的交接,而带斜纹的面板则处于 关闭 状态 — 那是盲区走廊,引擎必须在完全没有任何信息的情况下保住身份,并在另一端重新接上。
trace::Engine — WebAssembly
加载中…对照真值评分
本次扫描
已触发事件 —
预测的下一次会面 —
占摄像头所给的比例 用的才是诚实的分母:没有哪个跟踪器能报出任何传感器都没检测到的实体,所以原始检测率会把跟踪器自己的失败和这片布设的失败混在一起。超过 100% 说明引擎在没有任何东西看见它们的扫描里也保住了实体。把摄像头关掉,看着它往上爬。
trace::Engine 取自 libtrace_core.a,即原生工具所链接的同一个库 — —。整次运行是一次性算完再回放的,所以动画速度不是引擎的速度;旁边那个扫描时间中位数才是。
五个阶段,一份报告
跟踪与关联
一个带 Bernoulli 存在性的 PMBM 跟踪器,跑在 320 粒子的混合 Ornstein–Uhlenbeck 滤波器上 — 速度服从一个 OU 过程,而它处在哪种运动模式(步行、乘车、静止)本身又是一条马尔可夫链,“混合”指的就是这件事。用 Gibbs 采样做一对一指派,扫过 14 轮,然后合并重复项。关联是 按传感器分别进行的,因为排他性是关于某个传感器的事实,而不是关于世界的事实 — 两台视野重叠的摄像头同时报出一个人,那是相互印证,不是两个人。在全局强制排他性的代价是每次扫描 2.08 条幽灵航迹;按传感器来则是 0.08。
靠生活规律做重识别
休眠的航迹靠它的生活模式重新捕获 — 对每个实体在小时、x 和 y 上建一个高斯混合。外观描述子是支持的,但在公开基准上刻意处于 关闭 状态,因为在那里即使换上完美的先知描述子,分数也纹丝不动:88% 的失分是漏检,而外观对漏检无能为力。
三个汇合预测器,叠在一起
几何拦截法对两个相向而行的实体是精确的,只要有一方机动就完全没用。接近率外推能处理弯曲的路线。生活模式交叉预测是唯一能在双方都还静止不动时就给出结果的。它们各自在不同情况下失效,所以三个都跑,最有把握的那个胜出。
八种行为,可热插拔
BRUSH_PASS, SDR_PATTERN, DEAD_DROP,
PARALLEL_ROUTE, MODE_TRANSITION, LOITER,
COVER_STOP, CHOKEPOINT。威胁评分通过 Beta–Monte-Carlo 融合八个证据维度,报告里给出的是各项分解,而不是一个数字。
一艘船关掉了自己的应答器
一个大洋海盆上的卫星自动识别系统(AIS)覆盖。五艘船在航行中;其中一艘在航途中关掉应答器,后来又开了回来。这中间没有任何东西报告它 — 没有传感器,没有检测,没有任何形式的证据。
看看引擎拿这种情况怎么办。在第一次漏掉的扫描上,位置估计就从几百米跳到约 12 公里,航迹只靠预测惰行。再过几次扫描,它会被退入休眠池而不是被删除;等这艘船重新出现,引擎必须判断这是个新东西,还是它已经认识的东西。
把静默期拖得更长,它这个判断就会出错。 五次扫描的静默,身份还能保住;超过大约十次就保不住了,这艘船会以一条新航迹的身份回来。这就是诚实的结果,它比一个调到永远获胜的演示更有价值 — 原因就在控件下面。
dark-vessel — WebAssembly
加载中…目标船,此刻
整段航程
它为什么会失效。 这里的重识别靠的是生活模式 — 对每个实体在小时和位置上建的混合分布,问的是这次新的目击是否符合引擎已经掌握其规律的某个东西。一艘只走一条直线航程的船没有规律。它只被看见过一次,朝一个方向走。所以过了几次扫描之后,剩下的就只有运动预测,而在 12 节的航速、每小时扫描一次的条件下,那个不确定度在一天之内就会涨到没有任何用处。
在确实有规律可学的场合,同一套机制 就是 决定性的一环,上面那座迷宫展示的正是这一点。一条说明了成因的局限,比一条什么都没说明的能力更有价值,而这一条就写在仓库自己的清单里。
dark-vessel 场景由 trace_sim 在本地原生运行,剖面配置、传感器和洋区都相同。
在真实检测数据上回放,而不只是在它自己的模拟器上
MOTChallenge 序列 — 真实视频上真实检测器给出的真实检测 — 是这里唯一不是由 TRACE 自己的模拟器产出的数字。
| 基准测试 | 检测框数 | MOTA | 对检测器上限的还原度 |
|---|---|---|---|
| MOT17 训练集,21 个序列 | 336,891 | 53.0% | 108.2% |
| MOT20 训练集,4 个序列,每帧 62–226 人 | 1,134,614 | 62.5% | 114.7% |
对人群规模实际上是线性的
| 航迹数 | 每次扫描 ms 中位数 | 每条航迹 µs |
|---|---|---|
| 10 | 1.7 | 135 |
| 120 | 26.6 | 152 |
| 270 | 72.1 | 159 |
| 400 | 127.5 | 159 |
单个处理器核心,Release 构建,高级向量扩展(AVX-512)。开销大致按 n1.17 增长,而单看跟踪本身,从十条航迹到四百条,都平稳保持在每条航迹 135–159 µs — 可以读作“要紧的是常数,而不是指数”。
它原本是 n1.82 这么高,直到汇合检测器不再对每一对组合都重建一遍每条航迹的生活模式预报。那换来了常数上二十倍的改善,而 并不是 更好的指数 — 随之加上的空间索引门半径比整个场景还大,所以它返回了每一对组合,什么也没做。改成用每一对各自的两个速度来界定范围,把这个检测器从占引擎的 56% 降到 44%,指数也降到接近线性。
在 400 条并发航迹下,单个处理器核心大约是每秒 8 次扫描:对 1 Hz 的摄像头布设够用,对 25 fps 就不行,除非在多个工作单元之间分区。这个页面这么说,是因为基准测试就是这么说的。
它做不到什么
没有检测器,也没有重识别模型
TRACE 消费检测结果。产生它们是别人的活。
速度估计有一个下限
它需要速度 × 航向保持远高于位置噪声。比值低于大约 5 时,那就不算估计了,而惰行的好坏完全取决于它。一个真正曲折的目标被一个粗糙的传感器看到时,没有可测的速度 — 这是一条建模上的约束,而不是缺陷,但在对惰行下任何结论之前,都要拿剖面配置来对照检查这一条。
传感器可用性是推断出来的,不是已知的
覆盖缺口是靠有没有任何东西上报来猜的。真实部署知道哪些摄像头宕了,而目前没有办法把这件事说出来。
没有审计日志、访问控制或留存策略
它能被对准的若干对象,属于大规模监控能力。任何要把它用在人身上的部署方,都需要在它周围搭起那套配套设施,而这里刻意没有把它作为默认提供。
CUDA 这条路径只是骨架
src/cuda/kernels.cu 存在,但引擎里没有任何地方调用它。这些内核从未运行过。把它当成一条没做完的分支,而不是一个后端 — 仓库里说的也是同一句话。