第一章 原始数据采集与解析:LVDS数据流格式、C语言解析代码、Python读取bin文件、数据可视化

大家好,我是你们的雷达感知算法讲师。今天咱们正式进入《毫米波雷达点云处理与目标跟踪实战》的第一章。

说实话,很多做雷达算法的朋友,上来就调包、跑模型,结果数据不对,折腾半天才发现是原始数据解析出了问题。我当年刚入行时也踩过这个坑——拿着TI的AWR1843开发板,连上DCA1000采集卡,导出一堆bin文件,愣是不知道怎么读。嗯,今天咱们就把这个坎儿迈过去。

1.1 LVDS数据流格式——雷达和电脑之间的“语言”

先说说LVDS。这玩意儿全称是Low-Voltage Differential Signaling,低压差分信号。说白了,就是雷达芯片和采集卡之间高速传数据的一种方式。

TI的毫米波雷达(比如AWR1243、AWR1843)通过LVDS接口,把ADC采样后的原始数据扔给DCA1000采集卡。采集卡再通过以太网或者USB,把数据打包成bin文件存到电脑上。

那这个bin文件里到底存了啥?我给大家拆开看看:

数据块 内容 字节数
文件头 Magic Number、版本号、配置参数 通常32~64字节
帧头 帧序号、时间戳、 chirp数量等 每帧固定大小
chirp数据 I/Q两路采样点,复数形式 每chirp = 采样点数 × 2 × 2字节
帧尾 校验和、填充字节 可选

这里有个关键点:每个采样点都是16位有符号整数。I路和Q路交替存储。我见过有人把I/Q顺序搞反了,结果点云全跑到负半轴去了——你说冤不冤?

核心公式:

一帧数据总字节数 = 帧头大小 + (chirp数量 × 每chirp采样点数 × 4) + 帧尾大小

其中4 = I(2字节) + Q(2字节)

1.2 C语言解析代码——硬核工程师的选择

我个人习惯用C语言做底层解析。为啥?因为嵌入式平台上跑算法,C是绕不开的。而且C的指针操作,处理这种二进制流简直不要太顺手。

下面是我当年在项目里用的一段解析代码,去掉了业务逻辑,保留了核心结构:

#include <stdio.h>
#include <stdint.h>

#define FRAME_HEADER_SIZE 32
#define CHIRP_NUM 128
#define SAMPLES_PER_CHIRP 256

typedef struct {
    uint32_t magic;
    uint32_t frame_id;
    uint32_t timestamp;
    uint8_t  reserved[20];
} FrameHeader;

typedef struct {
    int16_t i;
    int16_t q;
} ComplexSample;

int parse_frame(FILE *fp, ComplexSample *buffer) {
    FrameHeader header;
    fread(&header, sizeof(FrameHeader), 1, fp);
    
    // 检查Magic Number,我习惯用0xAA55开头
    if (header.magic != 0xAA55) {
        printf("帧头错误,数据可能不对齐\n");
        return -1;
    }
    
    // 读取所有chirp数据
    for (int c = 0; c < CHIRP_NUM; c++) {
        fread(buffer + c * SAMPLES_PER_CHIRP, 
              sizeof(ComplexSample), 
              SAMPLES_PER_CHIRP, 
              fp);
    }
    
    return header.frame_id;
}

避坑指南:我曾经在解析时忽略了字节序问题。TI的雷达数据默认是小端序(Little-Endian),但有些采集卡会配置成大端序。如果你发现解析出来的数值大得离谱,先检查一下字节序对不对。

1.3 Python读取bin文件——快速验证的利器

做算法验证的时候,我更喜欢用Python。毕竟numpy一把梭,比C快多了。你想想看,调参的时候每次都要编译C代码,那得多崩溃?

下面这段代码,我几乎每个项目都会复用:

import numpy as np
import struct

def read_ti_bin(file_path, num_chirps=128, num_samples=256):
    """
    读取TI毫米波雷达的bin文件
    返回形状: (num_chirps, num_samples, 2)  -> 最后一维是I/Q
    """
    with open(file_path, 'rb') as f:
        # 跳过文件头,我一般直接读32字节
        f.read(32)
        
        # 计算总采样点数
        total_samples = num_chirps * num_samples
        
        # 读取原始数据,每个采样点4字节(I+Q各2字节)
        raw_data = np.fromfile(f, dtype=np.int16, count=total_samples * 2)
        
        # 重塑为I/Q对
        iq_data = raw_data.reshape(-1, 2)
        
        # 再重塑为(chirp, sample, 2)
        iq_data = iq_data.reshape(num_chirps, num_samples, 2)
        
    return iq_data

# 使用示例
data = read_ti_bin('adc_data.bin')
print(f"数据形状: {data.shape}")  # (128, 256, 2)

这里有个小技巧:np.fromfilestruct.unpack快得多。我测过,处理1GB的bin文件,前者只要几秒,后者可能要几分钟。做数据预处理时,性能差距就体现出来了。

1.4 数据可视化——看一眼就知道对不对

解析完数据,第一件事不是跑算法,而是可视化。我个人的经验是:先画个图看看数据长啥样。如果波形不对,后面的算法全是白搭。

咱们先画一个chirp的I/Q波形:

import matplotlib.pyplot as plt

def plot_iq_waveform(iq_data, chirp_idx=0):
    """
    画单个chirp的I/Q波形
    """
    chirp = iq_data[chirp_idx]
    samples = np.arange(chirp.shape[0])
    
    plt.figure(figsize=(12, 4))
    plt.plot(samples, chirp[:, 0], label='I路', linewidth=1)
    plt.plot(samples, chirp[:, 1], label='Q路', linewidth=1, alpha=0.7)
    plt.xlabel('采样点')
    plt.ylabel('幅度')
    plt.title(f'第{chirp_idx}个chirp的I/Q波形')
    plt.legend()
    plt.grid(True, alpha=0.3)
    plt.show()

# 调用
plot_iq_waveform(data, chirp_idx=0)

正常的I/Q波形应该像正弦波,幅度在几百到几千之间。如果你看到一条直线或者全是噪声,那八成是解析参数配错了。

再进阶一点,咱们画个距离-多普勒图(Range-Doppler Map)的雏形:

def plot_range_doppler_heatmap(iq_data):
    """
    对每个chirp做FFT,生成距离-多普勒图
    """
    # 先对每个chirp做距离维FFT
    range_fft = np.fft.fft(iq_data[:, :, 0] + 1j * iq_data[:, :, 1], axis=1)
    
    # 再对chirp维做FFT(多普勒维)
    doppler_fft = np.fft.fftshift(np.fft.fft(range_fft, axis=0), axes=0)
    
    # 取幅度
    rd_map = np.abs(doppler_fft)
    
    plt.figure(figsize=(10, 8))
    plt.imshow(rd_map, aspect='auto', cmap='jet', 
               extent=[0, rd_map.shape[1], -rd_map.shape[0]//2, rd_map.shape[0]//2])
    plt.xlabel('距离单元')
    plt.ylabel('多普勒单元')
    plt.title('距离-多普勒热力图')
    plt.colorbar(label='幅度')
    plt.show()

plot_range_doppler_heatmap(data)

注意:上面的代码只是演示数据流是否正确的快速验证。真正的RD谱还需要加窗、做CFAR等处理,咱们后面章节会详细讲。这里你只要看到热力图上有明显的亮斑,就说明数据解析没问题。

本章小结

咱们这一章把原始数据从LVDS到bin文件,再到C和Python的解析,最后可视化验证,整个链路走了一遍。说白了,就是教会你:

  • LVDS数据流的结构——帧头、chirp数据、帧尾
  • C语言怎么高效解析——指针操作、结构体映射
  • Python怎么快速读取——numpy的fromfile
  • 可视化验证——画波形、画RD谱

下一章,咱们会深入讲ADC数据预处理,包括直流偏置校正、IQ不平衡补偿这些实战中必用的技巧。到时候我会分享一个我踩过的坑——IQ不平衡没处理好,导致目标角度估计偏了5度,排查了两天才找到原因。

嗯,今天就到这儿。记得动手跑一下代码,光看是学不会的。