TRACE · v0.2

多数跟踪器止步于“在哪里”。 这一个继续往下走。

真实世界里的跟踪从来不是连续的。一辆车开到楼后面,一个人走出摄像头的范围。大多数系统把这当成航迹的终点。TRACE 不这样:它把一条航迹保存成那个东西仍然在场的概率,而不是一个是或否,所以航迹是在缺口里慢慢变淡,而不是在缺口处结束,还能在另一头重新接上。

核心里没有任何东西知道自己在跟踪什么。摄像头、船舶、托盘、动物和球员,是同一个问题换了一套参数。下面的 MOT17 和 MOT20 数字是多目标跟踪准确度(MOTA),也就是这个领域用来表示一个跟踪器干得怎么样的那一个数。

语言C++23
MOT17 MOTA53.0%
MOT20 MOTA62.5%
已评分的检测框1.47M
最近一次推送不久前
这个名字是什么意思
TTracking 跟踪 — 每样东西在哪里,以及我们有多确定
RRe-identification 重识别 — 同一个东西,在丢失之后
AAssociation 关联 — 哪一次目击属于哪一条航迹
CConvergence 汇合 — 谁即将与谁碰面
EEvents 事件 — 值得有人关注的行为
大白话

一分钟说清这是什么

问题所在

传感器会漏。一个摄像头不会拍到每一个走过它的人,一艘船可以关掉应答器,一条走廊可能根本没有摄像头。大多数跟踪器把“没看见”读成“不在那里”,于是那个东西再出现时,就被记成了一个新的。它去过哪里、见过谁、这对它来说正不正常 — 这些都再也答不上来了。

解决办法

TRACE 给每一条航迹记着一个数字:那个东西仍然在场的可能性有多大。一次漏检会把这个数字压低,并把它可能所在的范围放大;它不删除任何东西。航迹靠预测惰行下去,不确定度老老实实地增长,等到有东西再次出现,引擎再判断它是不是同一个。这里两个演示都让你把这件事一直推到它判断错为止。

这是给谁用的

任何要跨不止一个传感器、而传感器之间还留着缺口去跟踪东西的人:一个摄像头覆盖不到每条走廊的场区,或者一艘船可以在航途中关掉应答器的海上监控。核心并不知道自己在跟踪什么 — 摄像头、船舶、托盘、动物和球员,是同一个问题换了一套参数。它接收检测结果;把检测结果做出来是别人的活。

覆盖上的一个缺口,不等于不在场。 本页余下的部分是工程细节:引擎本身在你的浏览器里跑两个场景、基准测试数字、每条航迹要花多少、以及一份它做不到什么的直白清单。
问题所在

一漏检就被删掉的航迹,从来就不算航迹

传感器会漏掉东西。摄像头有的是检测概率,不是保证;船可以关掉自己的应答器;走廊里可能根本没有摄像头。覆盖一旦出现缺口,把“没看见”等同于“不存在”的跟踪器就会丢掉身份,等该实体重新出现时又给它一个新的。所有下游的问题 — 它去过哪里,和谁碰过面,这对它来说正常吗 — 现在都答不上来了。

TRACE 把多数跟踪器混为一谈的两件事分开。每条航迹都带有 r,即它究竟存不存在的概率,与它在哪里分开保存。一次漏掉的扫描会降低 r 并让位置估计变宽;它不会删除任何东西。航迹转入惰行,而在惰行的同时,它的不确定度会诚实地增长。

下面两个演示给你看的正是这件事。那个虚线圆圈就是引擎在说 它就在这一圈里的某个地方,而我每一秒都更不确定 — 这是一句有用的话,也正是一条被删掉的航迹没法告诉你的话。

存在性

Bernoulli,不是布尔值

一个跑在 320 粒子滤波器上的泊松多伯努利混合(PMBM)跟踪器。存在性是一个概率,会因漏检而衰减,也会因证据而恢复,所以一次遮挡损失的是置信度,不是身份。

可能性

对证据的第二种意见

在概率之外,还并行跑着一个可能性意义上的存在度,它跟踪的是证据的 质量 而不是数量。当两者出现分歧时,就说明大量微弱的检测被洗成了虚假的确定性。

引擎本身

一片摄像头布设,其中有没人盯着的走廊

迷宫是真实摄像头布设的一个廉价替身,而它把让多摄像头跟踪变难的每一种性质都给齐了:墙迫使路线变得非线性,每一块彩色面板就是一台摄像头,所以越过边界就是一次真正的交接,而带斜纹的面板则处于 关闭 状态 — 那是盲区走廊,引擎必须在完全没有任何信息的情况下保住身份,并在另一端重新接上。

trace::Engine — WebAssembly

加载中…
真值 — 目标对象 真值 — 其他人 航迹,正被看见 航迹,盲态惰行 摄像头已关闭

对照真值评分

检测率
—
占摄像头所给的比例
—
平均误差
—
身份切换次数
—
幽灵航迹
—
扫描时间中位数
—

本次扫描

目前检测率
—
目前误差
—

已触发事件 —

预测的下一次会面 —

占摄像头所给的比例 用的才是诚实的分母:没有哪个跟踪器能报出任何传感器都没检测到的实体,所以原始检测率会把跟踪器自己的失败和这片布设的失败混在一起。超过 100% 说明引擎在没有任何东西看见它们的扫描里也保住了实体。把摄像头关掉,看着它往上爬。

trace::Engine 取自 libtrace_core.a,即原生工具所链接的同一个库 — —。整次运行是一次性算完再回放的,所以动画速度不是引擎的速度;旁边那个扫描时间中位数才是。
它是怎么工作的

五个阶段,一份报告

T · A

跟踪与关联

一个带 Bernoulli 存在性的 PMBM 跟踪器,跑在 320 粒子的混合 Ornstein–Uhlenbeck 滤波器上 — 速度服从一个 OU 过程,而它处在哪种运动模式(步行、乘车、静止)本身又是一条马尔可夫链,“混合”指的就是这件事。用 Gibbs 采样做一对一指派,扫过 14 轮,然后合并重复项。关联是 按传感器分别进行的,因为排他性是关于某个传感器的事实,而不是关于世界的事实 — 两台视野重叠的摄像头同时报出一个人,那是相互印证,不是两个人。在全局强制排他性的代价是每次扫描 2.08 条幽灵航迹;按传感器来则是 0.08。

R

靠生活规律做重识别

休眠的航迹靠它的生活模式重新捕获 — 对每个实体在小时、x 和 y 上建一个高斯混合。外观描述子是支持的,但在公开基准上刻意处于 关闭 状态,因为在那里即使换上完美的先知描述子,分数也纹丝不动:88% 的失分是漏检,而外观对漏检无能为力。

C

三个汇合预测器,叠在一起

几何拦截法对两个相向而行的实体是精确的,只要有一方机动就完全没用。接近率外推能处理弯曲的路线。生活模式交叉预测是唯一能在双方都还静止不动时就给出结果的。它们各自在不同情况下失效,所以三个都跑,最有把握的那个胜出。

E

八种行为,可热插拔

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,89153.0%108.2%
MOT20 训练集,4 个序列,每帧 62–226 人1,134,61462.5%114.7%
只有训练集划分上的数字。 这些都是在本地回放并评分的。没有任何东西提交给 MOTChallenge 的评测服务器,而要得到一个能和公开排行榜相比的数字,就必须那样做。所谓上限,是指一个完美的跟踪器把拿到的每一个检测原样回显所能得到的分数;超过它才是全部的工作,而做到这一点靠的是在检测器漏掉的帧上惰行。
费用

对人群规模实际上是线性的

航迹数每次扫描 ms 中位数每条航迹 µs
101.7135
12026.6152
27072.1159
400127.5159

单个处理器核心,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 存在,但引擎里没有任何地方调用它。这些内核从未运行过。把它当成一条没做完的分支,而不是一个后端 — 仓库里说的也是同一句话。

源码可得

GNU Affero 通用公共许可证,旁边还摆着一份商业授权

AGPL-3.0+,和这里其他所有东西一样的条款:读它、跑它、查它。如果 AGPL 不适合你想做的东西,还有一份分级商业授权。