模块 A 数据增强实现说明
main_augment.cpp 使用公共 read_radar_csv / write_radar_csv 完成文件输入输出,data_augment.cpp 完成滑动窗口增强算法。
1. 模块定位与职责
对原始雷达航迹数据进行滑动窗口增强,生成多组训练样本。它是整条处理流水线的第一阶段,输入为 label0.csv 和 label1.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.csv 和 data/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 可以达到的功能
当前版本可以实现以下功能:
- 从默认
data/input目录或命令行指定目录读取label0.csv和label1.csv。 - 分别为
label0和label1配置 README 要求的增强参数。 - 使用
DataAugmentor将输入雷达点按track_id分组。 - 对每条航迹执行多组
(stride, x)滑动窗口截取。 - 自动跳过长度不足的航迹,避免越界访问。
- 在内存中把每个窗口表示为一个新的航迹片段。
- 在窗口内部保留首点航迹号,其余点设置为
-1。 - 统计原始航迹数量和生成窗口数量,并输出到控制台。
- 按 README.md 命名规则生成
aug_label*_stride*_x*.csv文件。 - 为后续模块提供增强后的训练样本来源。
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.cpp 用 track_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 参数配置优化
当前 label0 和 label1 的参数用手动 push_back 写死。未来如果类别变多,可以用配置统一管理。
可能改进:
std::vector<Config> configs = {
{"label0", {1, 2}, {5, 7, 11}},
{"label1", {1, 2, 3}, {5, 7, 11, 13, 17}}
};
5.6 具体航迹的初步分类处理
当前模块 A 只按文件名区分 label0 和 label1,没有根据航迹质量、长度、时间跨度等做初步筛选。后续可以在增强前增加航迹级过滤或分类。
可能改进:
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 文件,是后续统计合并、数据清洗、速度计算和特征提取的基础。
当前版本代码结构直接,依赖少,便于理解。进一步完善时,需要考虑优先处理航迹边界识别、输出格式统一和重复计算优化,进一步提高代码在实际使用当中的健壮性。