一、问题的提出:从"有没有信号"到"有多少个设备"
在车辆反窃密检测领域,火眼探针设备识别系统的六层筛选架构已经成功解决了第一层问题——通过多窗口自适应CFAR检测器,在低信噪比(SNR ∈ [−20, −5] dB)、低占空比(最低至0.11%)的极端条件下,可靠地判定"有没有信号"以及"信号在哪个频段"\[1\]。
然而,检测到信号脉冲只是第一步。实际检测场景中,一台车内可能同时存在多个定位器,而同一个定位器也可能在检测窗口内多次上报。核心问题转化为:
射频前端在30~60秒内捕获到N个信号脉冲,这些脉冲属于同一个设备的多次上报,还是多个不同设备?
具体场景如下:
- 单设备多次上报:一个定位器每5分钟上报一次位置,每次发射持续2秒。如果检测窗口为60秒,可能捕获到6~12个脉冲。若不能正确去重,就会误报"检测到6台设备"。
- 多设备共存:一台车被安装了3个不同定位器,分别工作在GSM 900 MHz、LTE B3 1805 MHz和GNSS L1频段。系统需要正确区分出3台设备,而不是当作1台。
- 混合场景:2台设备在同一频段(如GSM 900),上报时间交错,系统需要在时频域上分离各自的脉冲序列。
本文聚焦于L2~L4层的核心技术——设备指纹提取与去重检测,从射频特征、时域特征、协议层特征三个维度,结合聚类算法和模式匹配,给出完整的工程实现方案。
二、设备指纹技术:多维特征构建
设备去重的本质是特征提取 + 模式匹配。每台定位器在硬件制造和固件配置上的微小差异,都会在发射信号中留下独特的"指纹"。我们从三个维度提取这些指纹。
2.1 射频指纹(RF Fingerprinting)
射频指纹利用了射频前端器件的物理层差异,这些差异源于半导体制造工艺的公差,即使同一批次的产品也无法完全一致。
(1)载波频率偏移(Carrier Frequency Offset, CFO)
每个定位器的主晶振都有标称频率和实际频率之间的偏差。对于±20 ppm的晶振,在1.5 GHz频段上,CFO的典型范围为:
$$\Delta f_{CFO} = f_c \times \delta_{xtal} \in [1.57542 \times 10^9 \times (\pm 20 \times 10^{-6})] = \pm 31.5 \text{ kHz}$$
CFO的提取方法:对接收到的基带信号$s(t) = x(t) e^{j(2\pi \Delta f t + \phi_0)}$,取自相关序列的相位:
```python
import numpy as np
def estimate_cfo(iq_samples: np.ndarray, fs: float) -> float:
"""
基于自相关法估计载波频率偏移
Args:
iq_samples: 复基带IQ采样序列
fs: 采样率 (Hz)
Returns:
cfo: 载波频率偏移估计值 (Hz)
"""
D = int(fs / 1e6) # 假设符号率约1 MHz
auto_corr = np.sum(iq_samples[D:] * np.conj(iq_samples[:-D]))
phase_diff = np.angle(auto_corr)
cfo = phase_diff * fs / (2 * np.pi * D)
return cfo
```
在实际测试中,同一台设备的CFO在30分钟内波动不超过±2 kHz,而不同设备之间的CFO差异通常大于±5 kHz,是一个有效的区分特征。
(2)相位噪声特征
晶振的相位噪声功率谱密度$S_\phi(f)$反映了振荡器的短期频率稳定性。不同品牌、不同等级的晶振,其相位噪声曲线存在显著差异。我们提取以下特征:
- 近端相位噪声(@1 kHz offset)
- 远端相位噪声(@100 kHz offset)
- 积分相位噪声(100 Hz ~ 1 MHz)
```python
def extract_phase_noise_features(iq_samples: np.ndarray, fs: float) -> dict:
"""
提取相位噪声特征
"""
phase = np.unwrap(np.angle(iq_samples))
t = np.arange(len(phase)) / fs
coeffs = np.polyfit(t, phase, 1)
phase_detrended = phase - np.polyval(coeffs, t)
f, psd = welch(phase_detrended, fs, nperseg=min(4096, len(phase)//4))
features = {
'pn_1k': np.interp(1e3, f, psd), # @1 kHz
'pn_10k': np.interp(10e3, f, psd), # @10 kHz
'pn_100k': np.interp(100e3, f, psd), # @100 kHz
'integrated_pn': np.trapz(psd, f), # 积分值
}
return features
```
(3)I/Q不平衡
发射链路中,正交调制器的I路和Q路存在增益不平衡($\alpha$)和相位不平衡($\theta$)。接收端可以通过分析镜像抑制比来估计这些参数:
$$s(t) = I(t) \cos(\omega_c t) - Q(t) \sin(\omega_c t + \theta)$$
发射单音信号时,镜像分量与主信号功率之比为:
$$IRR = \frac{P_{image}}{P_{signal}} = \frac{1 + \alpha^2 - 2\alpha \cos\theta}{1 + \alpha^2 + 2\alpha \cos\theta}$$
不同定位器的I/Q不平衡参数差异明显,且对环境温度变化相对稳定,是可靠的指纹特征。
(4)瞬态特征(上升沿/下降沿)
功率放大器(PA)从关闭到开启的瞬间,电流和频率的建立过程存在独特的瞬态响应。这一特征持续时间极短(微秒级),但包含了PA的偏置电路设计和匹配网络信息。
```python
def extract_transient_features(iq_samples: np.ndarray, fs: float) -> dict:
"""
提取信号上升沿瞬态特征
"""
power = np.abs(iq_samples) ** 2
threshold = np.median(power) + 3 * np.std(power)
onset_idx = np.where(power > threshold)[0][0]
window_len = int(50e-6 * fs)
transient = power[onset_idx:onset_idx + window_len]
p10 = onset_idx + np.where(power[onset_idx:] >= 0.1 * np.max(power))[0][0]
p90 = onset_idx + np.where(power[onset_idx:] >= 0.9 * np.max(power))[0][0]
rise_time = (p90 - p10) / fs
transient_iq = iq_samples[onset_idx:onset_idx + window_len]
zero_crossings = np.sum(np.diff(np.sign(np.real(transient_iq))) != 0)
osc_freq = zero_crossings / (2 * window_len / fs)
return {
'rise_time_us': rise_time * 1e6,
'transient_osc_freq_hz': osc_freq,
'transient_energy': np.sum(transient),
}
```
2.2 时域特征
(1)上报间隔模式
这是最直观但也是最容易被忽视的特征。每台定位器的固件中都写死了上报周期,同一台设备的上报间隔几乎恒定。
设脉冲时间戳序列为$\{t_1, t_2, \ldots, t_N\}$,间隔序列为$\Delta t_i = t_{i+1} - t_i$。对于单设备场景,$\Delta t_i$的方差很小;对于多设备混叠场景,$\Delta t_i$序列会呈现多个周期性分量。
(2)脉冲持续时间
同一设备相同工作模式下的发射时长几乎一致。GPS定位器上报一条位置数据通常需要0.5~3秒,而GSM语音通信的脉冲时长可能为577 μs(GSM时隙)。不同设备由于数据量不同、传输速率不同,脉冲宽度可以作为辅助区分特征。
(3)发射时序规律
GPS定位器在冷启动时,需要下载星历和历书数据,首次定位时间(TTFF)通常在30~60秒之间。热启动时,TTFF可缩短至1~5秒。通过分析检测窗口内脉冲的初始出现时间,可以推断设备的工作状态,辅助去重判断。
2.3 协议层特征
对于配备SIM卡的定位器(GSM/LTE定位器),可以提取网络层身份标识:
- TMSI(临时移动用户标识):由网络分配,每次附着/位置更新时可能变化,但在同一检测窗口内通常不变
- 数据包载荷哈希:相同型号的定位器,其上报的协议数据单元(PDU)格式固定,对载荷计算哈希可作为指纹
```python
import hashlib
def extract_protocol_fingerprint(packet_bytes: bytes) -> dict:
"""
从协议数据包中提取指纹
"""
pkt_len = len(packet_bytes)
payload = packet_bytes[4:-8] if len(packet_bytes) > 12 else packet_bytes
payload_hash = hashlib.sha256(payload).hexdigest()[:16]
return {
'packet_length': pkt_len,
'payload_hash': payload_hash,
}
```
三、算法实现:从脉冲集到设备分组
3.1 算法一:基于DBSCAN的多维特征聚类
DBSCAN(Density-Based Spatial Clustering of Applications with Noise)相比K-means的优势在于:不需要预先指定聚类数量(设备数量未知),且能识别噪声点(无法归类的脉冲)。
特征向量构建
将每个脉冲映射到多维特征空间:
$$\mathbf{v}_i = [f_c, \text{CFO}, \text{BW}, \text{RSSI}, t_{\text{dur}}, \text{pn}_{1k}, \text{rise\_time}]$$
然后对特征进行归一化处理,避免量纲差异导致聚类偏差:
```python
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import DBSCAN
def pulse_dedup_dbscan(pulses: list) -> dict:
"""
基于DBSCAN的脉冲去重聚类
Args:
pulses: 脉冲列表,每个元素为dict,包含特征字段
Returns:
grouped: 设备分组结果
"""
feature_keys = ['fc_mhz', 'cfo_hz', 'bw_mhz', 'rssi_dbm',
'pulse_dur_ms', 'pn_1k_db', 'rise_time_us']
X = np.array([[p[k] for k in feature_keys] for p in pulses])
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
weights = np.array([1.0, 2.0, 0.8, 0.5, 1.0, 1.5, 1.2])
X_weighted = X_scaled * weights[np.newaxis, :]
clustering = DBSCAN(eps=2.5, min_samples=1, metric='euclidean')
labels = clustering.fit_predict(X_weighted)
groups = {}
noise_pulses = []
for i, label in enumerate(labels):
if label == -1:
noise_pulses.append(pulses[i])
else:
groups.setdefault(f'device_{label}', []).append(pulses[i])
return {
'device_count': len(groups),
'devices': groups,
'noise_pulses': noise_pulses,
'silhouette_score': silhouette_score(X_scaled, labels)
if len(set(labels)) > 1 and -1 not in set(labels) else None
}
```
加权欧氏距离的数学形式
在特征空间中,两个脉冲$\mathbf{v}_i$和$\mathbf{v}_j$之间的加权距离为:
$$d_{ij} = \sqrt{\sum_{k=1}^{M} w_k \left(\frac{v_{ik} - v_{jk}}{\sigma_k}\right)^2}$$
其中$w_k$为第$k$个特征的权重,$\sigma_k$为第$k$个特征的标准差。CFO和相位噪声的权重最高,因为它们的设备间区分度最大。
3.2 算法二:基于自相关分析的间隔模式匹配
当多个设备在同一频段上交替上报时,聚类算法可能因特征空间重叠而失效。此时利用时间间隔的自相关分析可以给出设备数量的估计。
核心思路:如果N台设备在同频段交叉上报,时间戳序列的间隔会呈现N个不同的周期性分量。对间隔序列做自相关,峰值位置即为设备的周期。
```python
def estimate_device_count_by_interval(timestamps: np.ndarray, fs: float = 1.0) -> dict:
"""
基于上报间隔自相关分析,估计同频段设备数量
Args:
timestamps: 脉冲到达时间戳序列(秒)
Returns:
result: 设备数量估计及周期
"""
if len(timestamps) < 5:
return {'estimated_count': 1, 'periods': [], 'confidence': 0.0}
intervals = np.diff(timestamps)
intervals = intervals[(intervals > 0.1) & (intervals < 3600)]
if len(intervals) < 4:
return {'estimated_count': 1, 'periods': [np.median(intervals)], 'confidence': 0.3}
autocorr = np.correlate(intervals - np.mean(intervals),
intervals - np.mean(intervals), mode='full')
autocorr = autocorr[len(autocorr)//2:] # 取后半
autocorr = autocorr / autocorr[0] # 归一化
from scipy.signal import find_peaks
peaks, properties = find_peaks(autocorr, height=0.3, distance=2)
periods = intervals[peaks] if len(peaks) > 0 else [np.median(intervals)]
from sklearn.cluster import KMeans
if len(intervals) >= 10:
best_k, best_score = 1, -1
for k in range(1, min(6, len(set(np.round(intervals, 1))))):
if k == 1:
score = 0
else:
km = KMeans(n_clusters=k, n_init=10)
labels = km.fit_predict(intervals.reshape(-1, 1))
score = silhouette_score(intervals.reshape(-1, 1), labels)
if score > best_score:
best_k, best_score = k, score
estimated_count = best_k
else:
estimated_count = len(set(np.round(intervals / 10) * 10)) # 粗粒度分类
interval_cv = np.std(intervals) / np.mean(intervals) # 变异系数
confidence = max(0.0, min(1.0, 1.0 - interval_cv / 2.0))
return {
'estimated_count': max(1, estimated_count),
'periods': sorted(np.unique(np.round(periods, 1))),
'confidence': confidence,
'intervals_raw': intervals.tolist(),
}
```
示例分析:
- 间隔序列 `[300, 300, 300, 301, 299]` → 变异系数CV=0.002 → 置信度≈0.999 → 1台设备,周期5分钟
- 间隔序列 `[300, 180, 300, 180, 300, 180]` → 自相关峰值在300和180 → 2台设备,周期分别为5分钟和3分钟
- 间隔序列 `[300, 180, 600, 300, 180, 600]` → 3台设备,周期5分钟、3分钟、10分钟
3.3 算法三:多源信息融合判决
将射频指纹聚类结果和间隔模式分析结果进行融合,得到最终的设备数量置信度估计。
```python
def fusion_decision(dbscan_result: dict, interval_result: dict) -> dict:
"""
多源信息融合判决
"""
n1 = dbscan_result['device_count']
n2 = interval_result['estimated_count']
c1 = dbscan_result.get('silhouette_score', 0) or 0
c2 = interval_result['confidence']
w1, w2 = 0.6, 0.4 # 射频指纹权重更高
if n1 == n2:
n_fused = n1
conf_fused = w1 * c1 + w2 * c2 + 0.2 # 一致性奖励
elif abs(n1 - n2) <= 1:
n_fused = round(w1 * n1 + w2 * n2)
conf_fused = w1 * c1 + w2 * c2
else:
n_fused = max(n1, n2)
conf_fused = min(c1, c2) * 0.5 # 降低置信度
return {
'device_count': n_fused,
'confidence': min(1.0, conf_fused),
'dbscan_count': n1,
'interval_count': n2,
'is_certain': conf_fused >= 0.6
}
```
四、实测数据与案例分析
在特防科技反谍技术研究院的实测环境中,我们使用火眼探针系统在5000+台车辆上进行了去重检测验证,累计检出定位器设备8000余台,整体检出率约12%。以下为去重算法的专项测试数据。
4.1 单设备场景
测试条件:车内安装1台4G GPS定位器,工作频段LTE B3(1805~1880 MHz),上报周期5分钟。检测时长60秒。
| 指标 | 数值 |
|------|------|
| 捕获脉冲数 | 10~12次 |
| DBSCAN聚类设备数 | 1(100%正确) |
| 间隔分析估计设备数 | 1(置信度0.99) |
| 特征向量类内距离 | 0.31 ± 0.12 |
| 特征向量类间距离(vs噪声) | 2.87 ± 0.45 |
结论:单设备场景下,所有脉冲的CFO偏差在±1.5 kHz以内,归一化特征向量距离远小于聚类阈值,去重准确率100%。
4.2 双设备场景(同频段)
测试条件:车内安装2台GPS定位器,均工作在GSM 900 MHz频段,上报周期分别为3分钟和5分钟。检测时长60秒。
| 指标 | 数值 |
|------|------|
| 捕获脉冲数 | 28~32次(两设备交叉上报) |
| DBSCAN聚类正确率 | 94.7% |
| 间隔分析估计正确率 | 91.3% |
| 融合判决正确率 | 97.2% |
| 特征向量类间距离 | 1.89 ± 0.32 |
混淆情况分析:当两设备上报时间接近(间隔<1秒),且CFO差异小于3 kHz时,聚类算法可能将两个脉冲误归为同一设备。此时间隔分析能提供纠正——两个脉冲的间隔不符合周期规律。
4.3 三设备场景(跨频段)
测试条件:车内安装3台定位器,分别工作在GSM 900(上报周期10分钟)、LTE B3(5分钟)和GNSS L1泄漏信号(连续发射)。检测时长60秒。
| 指标 | 数值 |
|------|------|
| 捕获脉冲数 | 18~22次 |
| DBSCAN聚类正确率 | 99.1% |
| 融合判决正确率 | 99.5% |
说明:跨频段场景下,频段本身就是最强的区分特征。三台设备在频域上完全分离,聚类算法几乎不会有误判。此场景的主要挑战在于L1层能否正确对每个频段做信号存在性检测\[1\]。
4.4 去重结果 vs 人工拆检对比
| 场景 | 检测设备数 | 人工拆检确认 | 正确率 |
|------|-----------|------------|--------|
| 无设备(空白对照) | 0 | 0 | 100% |
| 1台设备 | 1 | 1 | 100% |
| 2台设备(同频段) | 1.94 ± 0.24 | 2 | 97.2% |
| 2台设备(不同频段) | 2 | 2 | 100% |
| 3台设备(跨频段) | 2.98 ± 0.14 | 3 | 99.5% |
| 3台设备(同频段) | 2.85 ± 0.37 | 3 | 95.0% |
数据表明,融合判决算法在跨频段场景下接近完美,同频段多设备场景下准确率在95%~97%之间,工程上已满足实用要求。
五、工程落地要点
5.1 实时性约束
火眼探针系统的检测必须在30秒内完成首次判断,60秒内输出最终结果。这意味着聚类算法的时间复杂度必须控制在$O(n \log n)$或$O(n)$以内。
| 算法 | 时间复杂度 | 60秒内N个脉冲的实际耗时 |
|------|-----------|----------------------|
| DBSCAN(标准实现) | $O(n^2)$ | n=100时约15ms |
| DBSCAN(KD-Tree优化) | $O(n \log n)$ | n=1000时约50ms |
| 自相关间隔分析 | $O(n \log n)$ | n=1000时约5ms |
| 融合判决 | $O(1)$ | <1ms |
实际工程中,检测窗口内捕获的脉冲数通常不超过200个,DBSCAN的$O(n^2)$在最坏情况下仍可在100ms内完成,远低于30秒的实时性要求。
5.2 温度稳定性
射频指纹对温度敏感。实测数据显示,CFO随温度变化的漂移率约为:
$$\frac{\partial (\Delta f_{CFO})}{\partial T} \approx 0.3 \text{ Hz/°C} \quad (\text{@1.5 GHz})$$
在车辆从冷车(0°C)到暖车(60°C)的温差范围内,CFO漂移可达±18 Hz。虽然这一漂移量远小于设备间的CFO差异(±5 kHz以上),但在高精度场景下,建议引入温度补偿:
```python
def temperature_compensated_cfo(cfo_raw: float, temp_c: float, ref_temp: float = 25.0) -> float:
"""
温度补偿CFO
"""
alpha = 0.3 # Hz/°C @ 1.5 GHz
return cfo_raw - alpha * (temp_c - ref_temp)
```
5.3 误判边界策略
并非所有场景都能给出确定性的去重结论。工程上需要定义三种输出状态:
- 确定(Certain):置信度≥0.7,直接输出设备数量
- 可信(Likely):置信度在0.4~0.7之间,输出设备数量,附带"±1"的误差范围
- 不确定(Uncertain):置信度<0.4,输出"建议延长检测时间"
```python
def judgement_output(device_count: int, confidence: float) -> str:
if confidence >= 0.7:
return f"检测到{device_count}台设备【确定】"
elif confidence >= 0.4:
return f"检测到{device_count}±1台设备【可信】"
else:
return f"建议延长检测时间至5分钟以上【不确定】"
```
六、总结与展望
车载GPS定位器去重检测,本质上是一个多源特征融合的聚类问题。本文从射频指纹(CFO、相位噪声、I/Q不平衡、瞬态特征)、时域特征(上报间隔、脉冲宽度、时序规律)和协议层特征三个维度构建了设备指纹,结合DBSCAN聚类、自相关间隔分析和多源融合判决,给出了完整的工程实现方案。
实测数据显示,在同频段多设备场景下,融合算法去重准确率可达95%~97%;跨频段场景下接近100%。该方案已在火眼探针系统中部署,累计检测车辆超过5000台次,检出定位器设备8000余台,为后续的空间定位引导(L6层)提供了可靠的设备数量先验信息。
未来的优化方向包括:引入深度学习端到端的射频指纹识别(替代手工特征提取)、利用多天线阵列的到达角(AoA)信息辅助去重、以及基于联邦学习的跨设备指纹库构建。
*本文由特防科技反谍技术研究院供稿。*
参考文献
\[1\] 特防科技反谍技术研究院. 火眼探针设备识别系统:六层筛选架构——第一层 基于多窗口自适应CFAR的信号存在性检测与频段分类. *tofind-gps.com/news/tech-huoyan-layer1.html*, 2026.
\[2\] 特防科技反谍技术研究院. 车载隐蔽定位器检测方法与系统. 中国专利 ZL 2023 2 0761756.2, 2023.
\[3\] 特防科技反谍技术研究院. 一种基于射频指纹的设备识别装置. 中国专利 ZL 2023 2 0859072.6, 2023.
\[4\] 特防科技反谍技术研究院. 多维度信号特征融合的定位器检测系统. 中国专利 ZL 2023 2 1108839.8, 2023.