模块 A 数据增强实现说明

main_augment.cpp 使用公共 read_radar_csv / write_radar_csv 完成文件输入输出,data_augment.cpp 完成滑动窗口增强算法。

1. 模块定位与职责

对原始雷达航迹数据进行滑动窗口增强,生成多组训练样本。它是整条处理流水线的第一阶段,输入为 label0.csvlabel1.csv,输出为多个 aug_*.csv 文件,供后续模块 B 继续统计合并。

文件 主要职责
src/data_augment.cpp 实现 DataAugmentor 类的参数保存、航迹分组、滑动窗口截取和统计计数
src/main_augment.cpp 读取输入文件、配置 label0/label1 参数、调用增强类、按参数组合写出 CSV
src/data_augment.h 声明 DataAugmentor 类接口
src/radar_types.cpp 提供公共 CSV 读写函数

DataAugmentor 是一个普通 C++ 类,通过私有成员保存参数和统计信息,通过公开成员函数向主程序提供接口。

关键类结构:

class DataAugmentor {
public:
    DataAugmentor();
    ~DataAugmentor();

    void set_params(const std::vector<int>& strides,
                    const std::vector<int>& x_values);

    std::vector<std::vector<RadarPoint> > augment(
        const std::vector<RadarPoint>& input);

    int get_num_tracks() const;
    int get_num_windows() const;

private:
    std::vector<int> strides_;
    std::vector<int> x_values_;
    int num_tracks_;
    int num_windows_;
};

2. README 要求与当前代码实现逐条对应

2.1 类定义:DataAugmentor

README 要求模块 A 使用 DataAugmentor 类,提供参数设置、增强执行、统计查询等接口。

  • src/data_augment.h:声明类接口。
  • src/data_augment.cpp:实现构造、析构、参数设置、增强算法和统计接口。
DataAugmentor::DataAugmentor()
    : strides_(), x_values_(), num_tracks_(0), num_windows_(0) {}

void DataAugmentor::set_params(const std::vector<int> &strides,
                               const std::vector<int> &x_values) {
  strides_ = strides;
  x_values_ = x_values;
}

int DataAugmentor::get_num_tracks() const { return num_tracks_; }

int DataAugmentor::get_num_windows() const { return num_windows_; }

2.2 输入数据类型:std::vector<RadarPoint>

输入为原始雷达点序列,每个点包含方位角、斜距、高度、径向速率、RCS、时间和航迹序号。

当前实现中,main_augment.cpp 读取 CSV 后得到:

std::vector<RadarPoint> points;
if (!read_radar_csv(input_file, points, error_msg)) {
  std::cerr << "读取文件失败: " << error_msg << std::endl;
  continue;
}

随后传给增强器:

std::vector<std::vector<RadarPoint>> results = augmentor.augment(points);

文件读取函数不在模块 A 源文件中实现,而是通过 radar_types.cpp 提供的公共函数完成。

2.3 按航迹序号分组

README 要求先把输入雷达点按 track_id 分组,属于同一航迹的点作为一个序列处理。

当前 data_augment.cpp 的实现方式是:遍历输入点,当 track_id 发生变化时,结束当前航迹并开始新航迹。

关键代码:

std::vector<std::vector<RadarPoint>> tracks;
std::vector<RadarPoint> current_track;
int current_tid = -999;

for (size_t i = 0; i < input.size(); ++i) {
  if (input[i].track_id != current_tid && !current_track.empty()) {
    tracks.push_back(current_track);
    current_track.clear();
  }
  current_track.push_back(input[i]);
  current_tid = input[i].track_id;
}

if (!current_track.empty()) {
  tracks.push_back(current_track);
}

num_tracks_ = static_cast<int>(tracks.size());

按代码逻辑已实现“根据 track_id 变化分组”。实现依赖 read_radar_csv 已经把空白航迹号补成连续点的 track_id,并且依赖不同航迹之间的 track_id 值发生变化。如果输入文件中多条航迹使用相同航迹号,则当前版本会把它们合并为一条航迹。

2.4 对每个 (stride, x) 参数组合处理每条航迹

stride 表示窗口内点间隔,x 表示窗口大小,并对所有参数组合进行处理。

当前实现中,DataAugmentor 通过两层循环遍历所有参数组合:

for (size_t si = 0; si < strides_.size(); ++si) {
  int stride = strides_[si];

  for (size_t xi = 0; xi < x_values_.size(); ++xi) {
    int x = x_values_[xi];

    for (size_t ti = 0; ti < tracks.size(); ++ti) {
      const std::vector<RadarPoint> &track = tracks[ti];
      int track_len = static_cast<int>(track.size());
      // 对当前航迹执行窗口截取
    }
  }
}

2.5 过滤条件:航迹长度必须满足 track_len >= x * stride

只有航迹长度足够时才执行窗口截取。

if (track_len < x * stride) {
  continue;
}

长度不足的航迹会被跳过,不会生成无效窗口。

2.6 滑动窗口索引公式

窗口内第 k 个点的索引为:

idx = start + k * stride

当前实现:

for (int k = 0; k < x; ++k) {
  int idx = start + k * stride;
  RadarPoint p = track[idx];
  window.push_back(p);
}

2.7 窗口起始位置与步进

示例中窗口起始位置为 start=0, 3, ...,说明窗口向后移动的步进为 3。当前代码注释写的是“每次向后移动 x 个位置”,实际代码也是 start += x

int start = 0;
while (start + (x - 1) * stride < track_len) {
  std::vector<RadarPoint> window;
  // 生成窗口
  all_results.push_back(window);
  num_windows_++;
  start += x;
}

当前代码实现的是“窗口步进等于 x”。当 示例参数 x=3 时,表现为 start=0,3,6...,与示例一致,且更符合注释里的“步进为窗口大小”策略。

2.8 航迹号处理:窗口首点保留,其他点设为 -1

每个窗口第一个点保留原 track_id,其他点设置为 -1

当前实现:

RadarPoint p = track[idx];
if (k == 0) {
  // 保留原track_id
} else {
  p.track_id = -1;
}
window.push_back(p);

算法内存结构已达到。每个窗口内部只有首点保留原航迹号,其余点被设置为 -1

2.9 输出类型:std::vector<std::vector<RadarPoint>>

增强结果是多组数据,每组是一个航迹片段。

std::vector<std::vector<RadarPoint>> all_results;
// 每生成一个窗口:
all_results.push_back(window);
return all_results;

每个 window 是一个 std::vector<RadarPoint>,所有窗口组成二维向量。

2.10 label0 参数配置

README 要求 label0.csv 使用:

strides = {1, 2}
x_values = {5, 7, 11}

当前 main_augment.cpp 实现:

Config config0;
config0.label_prefix = "label0";
config0.strides.push_back(1);
config0.strides.push_back(2);
config0.x_values.push_back(5);
config0.x_values.push_back(7);
config0.x_values.push_back(11);
configs.push_back(config0);

2.11 label1 参数配置

README 要求 label1.csv 使用:

strides = {1, 2, 3}
x_values = {5, 7, 11, 13, 17}

当前实现:

Config config1;
config1.label_prefix = "label1";
config1.strides.push_back(1);
config1.strides.push_back(2);
config1.strides.push_back(3);
config1.x_values.push_back(5);
config1.x_values.push_back(7);
config1.x_values.push_back(11);
config1.x_values.push_back(13);
config1.x_values.push_back(17);
configs.push_back(config1);

2.12 输入文件路径

默认使用 data/input/label0.csvdata/input/label1.csv,也可切换到完整数据目录。

当前实现:

std::string base_path = "data/";

if (argc > 1) {
  base_path = std::string(argv[1]);
  if (!base_path.empty() && base_path[base_path.size() - 1] != '/') {
    base_path += "/";
  }
}

std::string input_file = base_path + "input/" + cfg.label_prefix + ".csv";

2.13 输出文件命名

输出命名规则:

data/output/aug_{label_prefix}_stride{s}_x{x}.csv

当前实现:

char filename[256];
snprintf(filename, sizeof(filename), "%soutput/aug_%s_stride%d_x%d.csv",
         base_path.c_str(), cfg.label_prefix.c_str(), stride, x);

2.14 每种参数组合生成独立文件

README 要求每种 (stride, x) 参数组合独立输出一个文件。

当前实现方式是主程序外层读取一次文件并统计一次整体结果,随后对每个参数组合再次创建临时增强器,只设置当前组合,并写出当前组合的结果。

DataAugmentor temp_augmentor;
std::vector<int> temp_strides;
std::vector<int> temp_x_values;

temp_strides.push_back(stride);
temp_x_values.push_back(x);
temp_augmentor.set_params(temp_strides, temp_x_values);

std::vector<std::vector<RadarPoint>> temp_results =
    temp_augmentor.augment(points);

if (temp_results.empty()) {
  continue;
}

每个有窗口结果的参数组合会生成独立文件。若某个组合没有生成任何窗口,当前代码会 continue,不会创建空 CSV 文件。

2.15 输出 CSV 写入

输出 CSV 与输入格式相同,但数据经过滑动窗口截取。

当前主程序先把多个窗口展平成一个 std::vector<RadarPoint>,再调用公共写出函数:

std::vector<RadarPoint> combined_points;
for (size_t wi = 0; wi < temp_results.size(); ++wi) {
  for (size_t pi = 0; pi < temp_results[wi].size(); ++pi) {
    RadarPoint p = temp_results[wi][pi];
    combined_points.push_back(p);
  }
}

if (!write_radar_csv(filename, combined_points, error_msg)) {
  std::cerr << "写入文件失败: " << error_msg << std::endl;
}

输出内容来自滑动窗口结果,列结构由公共写出函数保证。

3. 当前模块 A 可以达到的功能

当前版本可以实现以下功能:

  1. 从默认 data/input 目录或命令行指定目录读取 label0.csvlabel1.csv
  2. 分别为 label0label1 配置 README 要求的增强参数。
  3. 使用 DataAugmentor 将输入雷达点按 track_id 分组。
  4. 对每条航迹执行多组 (stride, x) 滑动窗口截取。
  5. 自动跳过长度不足的航迹,避免越界访问。
  6. 在内存中把每个窗口表示为一个新的航迹片段。
  7. 在窗口内部保留首点航迹号,其余点设置为 -1
  8. 统计原始航迹数量和生成窗口数量,并输出到控制台。
  9. 按 README.md 命名规则生成 aug_label*_stride*_x*.csv 文件。
  10. 为后续模块提供增强后的训练样本来源。

4. 对其他模块的支撑作用

模块 A 是后续处理的输入扩展层。它把原始有限航迹切分成更多短片段,使后续统计和特征提取有更多训练样本。

模块 B 需要读取模块 A 生成的多个 aug_*.csv 文件,并把它们与原始输入进行统计合并。模块 A 的输出文件数量、命名规则和 CSV 列格式直接决定模块 B 能否批量定位并读取增强数据。

模块 C 在模块 B 合并后的数据基础上做清洗和速度计算。模块 A 生成更多短航迹片段后,模块 C 可以计算更多速度记录,但前提是窗口内部的航迹边界表达必须稳定。

模块 D 提取速度、径向动力学、航向角等特征。模块 A 的窗口长度 x 决定每个样本内可用于统计的点数,因此会影响特征稳定性。

模块 E 是完整流水线。模块 A 的输入路径、输出文件、命名约定和统计结果是流水线自动化运行的第一步。如果模块 A 输出缺失或格式不统一,后续流水线会出现级联失败。

5. 当前实现中的注意点

5.1 航迹分组依赖 track_id 数值变化

当前 data_augment.cpptrack_id 变化切分航迹。如果输入文件中多条航迹的航迹号相同,当前实现会把它们当成同一条航迹。

可能改进:

struct RadarPointWithStart {
  RadarPoint point;
  bool is_track_start;
};

if (row_has_track_id && !current_track.empty()) {
  tracks.push_back(current_track);
  current_track.clear();
}
current_track.push_back(point);

5.2 输出格式中 -1 与空白列的统一

当前算法把非首点设为 -1,但 README 的 CSV 展示希望非首点航迹序号为空。更稳妥的方式是提供模块 A 专用写出逻辑,或者让公共写出函数支持输出模式。

可能改进:

enum class TrackIdWriteMode {
  WriteChangedTrackId,
  EmptyWhenNegativeOne
};

bool write_radar_csv_ext(const std::string& filepath,
                         const std::vector<RadarPoint>& points,
                         TrackIdWriteMode mode);

5.3 当前主程序重复计算增强结果

main_augment.cpp 先对所有参数组合整体 augment(points) 一次用于统计,又在每个组合内重新 augment(points) 用于写文件。这种实现简单,但在完整数据集上会重复计算。

可能改进:

for (int stride : cfg.strides) {
  for (int x : cfg.x_values) {
    DataAugmentor augmentor;
    augmentor.set_params({stride}, {x});
    auto windows = augmentor.augment(points);
    write_windows(windows);
    total_windows += windows.size();
  }
}

5.4 大量数据处理加速

完整数据集中 label0 可能有大量航迹。当前实现会把所有输入点和所有窗口都放入内存,适合课程项目和中小数据,但面对完整数据时可以优化。

可能改进方向:

// 思路:按航迹流式处理,生成一个窗口就立即写出,避免保存所有窗口。
for (Track track : read_tracks_stream(input_file)) {
  for (Window window : generate_windows(track, stride, x)) {
    writer.write_window(window);
  }
}

5.5 参数配置优化

当前 label0label1 的参数用手动 push_back 写死。未来如果类别变多,可以用配置统一管理。

可能改进:

std::vector<Config> configs = {
  {"label0", {1, 2}, {5, 7, 11}},
  {"label1", {1, 2, 3}, {5, 7, 11, 13, 17}}
};

5.6 具体航迹的初步分类处理

当前模块 A 只按文件名区分 label0label1,没有根据航迹质量、长度、时间跨度等做初步筛选。后续可以在增强前增加航迹级过滤或分类。

可能改进:

enum class TrackQuality {
  TooShort,
  Normal,
  LongTrack,
  TimeAbnormal
};

TrackQuality classify_track(const std::vector<RadarPoint>& track) {
  if (track.size() < min_points) return TrackQuality::TooShort;
  if (track.back().time - track.front().time <= 0) return TrackQuality::TimeAbnormal;
  if (track.size() > long_track_threshold) return TrackQuality::LongTrack;
  return TrackQuality::Normal;
}

5.7 并行处理参数组合

不同 (stride, x) 组合之间互不依赖,可以并行处理。但并行写文件时要保证每个线程写入不同文件,避免竞争。

可能改进:

std::vector<std::future<void>> tasks;
for (Config cfg : configs) {
  for (int stride : cfg.strides) {
    for (int x : cfg.x_values) {
      tasks.push_back(std::async(std::launch::async, [=]() {
        DataAugmentor augmentor;
        augmentor.set_params({stride}, {x});
        auto windows = augmentor.augment(points);
        write_output_file(cfg, stride, x, windows);
      }));
    }
  }
}

6. 总结

当前模块 A 已经具备数据增强主流程:读取输入、配置参数、按航迹分组、滑动窗口截取、生成多参数组合输出文件。它能够为模块 B 提供增强后的 CSV 文件,是后续统计合并、数据清洗、速度计算和特征提取的基础。

当前版本代码结构直接,依赖少,便于理解。进一步完善时,需要考虑优先处理航迹边界识别、输出格式统一和重复计算优化,进一步提高代码在实际使用当中的健壮性。