Files
agv-control-slam/docs/fixes/CSV_LOAD_FIX.md
CaiXiang af65c2425d initial
2025-11-14 16:09:58 +08:00

7.4 KiB
Raw Blame History

CSV加载闪退问题修复方案

问题分析

经过代码审查,发现"Load from CSV"功能闪退的可能原因:

  1. Windows路径编码问题(最可能的原因)

    • examples/qt_gui_demo.cpp第309行和326行使用了QString::toStdString()
    • 在Windows MINGW环境下当文件路径包含中文字符或特殊字符时这种转换可能产生错误的编码
    • 导致文件路径无法正确打开,或在某些情况下导致程序崩溃
  2. 单点路径处理问题

    • src/path_curve.cppsetPathPoints函数中当CSV文件只包含1个数据点时该点的theta和kappa不会被正确初始化
  3. 潜在的异常处理不完整

    • CSV解析过程中的某些异常可能未被完全捕获

修复方案

修复1: 更正文件路径编码(重要)

文件: examples/qt_gui_demo.cpp

第309行 需要修改为:

// 原代码 (第308-317行):
if (!filename.isEmpty()) {
    if (custom_path_.loadFromCSV(filename.toStdString(), true)) {
        custom_path_loaded_ = true;
        QMessageBox::information(this, "Success",
            QString("Loaded %1 points from CSV!").arg(
                custom_path_.getPathPoints().size()));
    } else {
        QMessageBox::warning(this, "Error", "Failed to load CSV file!");
    }
}

// 修改为:
if (!filename.isEmpty()) {
    // 使用toLocal8Bit以正确处理Windows路径包括中文路径
    std::string filepath = filename.toLocal8Bit().constData();
    if (custom_path_.loadFromCSV(filepath, true)) {
        custom_path_loaded_ = true;
        QMessageBox::information(this, "Success",
            QString("Loaded %1 points from CSV!").arg(
                custom_path_.getPathPoints().size()));
    } else {
        QMessageBox::warning(this, "Error", "Failed to load CSV file!");
    }
}

第326行 需要修改为:

// 原代码 (第325-329行):
if (!filename.isEmpty() && custom_path_loaded_) {
    if (custom_path_.saveToCSV(filename.toStdString())) {
        QMessageBox::information(this, "Success", "Path saved!");
    }
}

// 修改为:
if (!filename.isEmpty() && custom_path_loaded_) {
    // 使用toLocal8Bit以正确处理Windows路径包括中文路径
    std::string filepath = filename.toLocal8Bit().constData();
    if (custom_path_.saveToCSV(filepath)) {
        QMessageBox::information(this, "Success", "Path saved!");
    }
}

修复2: 改进单点路径处理

文件: src/path_curve.cpp

setPathPoints函数第106-134行添加单点处理逻辑

void PathCurve::setPathPoints(const std::vector<PathPoint>& points) {
    path_points_ = points;

    // 计算每个点的切线方向和曲率
    for (size_t i = 0; i < path_points_.size(); ++i) {
        if (i == 0 && path_points_.size() > 1) {
            // 第一个点
            double dx = path_points_[i + 1].x - path_points_[i].x;
            double dy = path_points_[i + 1].y - path_points_[i].y;
            path_points_[i].theta = std::atan2(dy, dx);
        } else if (i == path_points_.size() - 1 && path_points_.size() > 1) {
            // 最后一个点
            double dx = path_points_[i].x - path_points_[i - 1].x;
            double dy = path_points_[i].y - path_points_[i - 1].y;
            path_points_[i].theta = std::atan2(dy, dx);
        } else if (path_points_.size() > 2) {
            // 中间点
            double dx = path_points_[i + 1].x - path_points_[i - 1].x;
            double dy = path_points_[i + 1].y - path_points_[i - 1].y;
            path_points_[i].theta = std::atan2(dy, dx);

            // 计算曲率(使用三点法)
            if (i > 0 && i < path_points_.size() - 1) {
                path_points_[i].kappa = computeCurvature(
                    path_points_[i - 1], path_points_[i], path_points_[i + 1]);
            }
        }
        // 添加: 处理只有单个点的情况
        else if (path_points_.size() == 1) {
            // 单个点保持其原有的theta和kappa值通常为0
            // 不需要额外计算
        }
    }
}

修复3: 添加更完善的异常处理

文件: src/path_curve_custom.cpp

loadFromCSV函数中添加更完善的错误处理:

bool PathCurve::loadFromCSV(const std::string& filename, bool has_header) {
    try {
        std::ifstream file(filename);
        if (!file.is_open()) {
            std::cerr << "Error: Cannot open file " << filename << std::endl;
            return false;
        }

        std::vector<PathPoint> points;
        std::string line;
        int line_num = 0;

        // 跳过表头
        if (has_header && std::getline(file, line)) {
            line_num++;
        }

        while (std::getline(file, line)) {
            line_num++;
            // 跳过空行和注释行
            if (line.empty() || line[0] == '#') {
                continue;
            }

            std::stringstream ss(line);
            std::string token;
            std::vector<double> values;
            bool parse_error = false;

            // 解析CSV行
            while (std::getline(ss, token, ',')) {
                try {
                    // 去除前后空格
                    size_t start = token.find_first_not_of(" \t\r\n");
                    size_t end = token.find_last_not_of(" \t\r\n");
                    if (start == std::string::npos) {
                        // 空token跳过整行
                        parse_error = true;
                        break;
                    }
                    std::string trimmed = token.substr(start, end - start + 1);
                    values.push_back(std::stod(trimmed));
                } catch (const std::exception& e) {
                    std::cerr << "Error parsing line " << line_num << ": " << line
                              << " (reason: " << e.what() << ")" << std::endl;
                    parse_error = true;
                    break;
                }
            }

            // 如果解析出错或值数量不足,跳过整行
            if (parse_error) {
                continue;
            }

            // 根据列数创建路径点
            if (values.size() >= 2) {
                PathPoint p;
                p.x = values[0];
                p.y = values[1];
                p.theta = (values.size() >= 3) ? values[2] : 0.0;
                p.kappa = (values.size() >= 4) ? values[3] : 0.0;
                points.push_back(p);
            }
        }

        file.close();

        if (points.empty()) {
            std::cerr << "Error: No valid path points loaded from " << filename << std::endl;
            return false;
        }

        // 设置路径点会自动计算theta和kappa
        setPathPoints(points);

        std::cout << "Successfully loaded " << points.size() << " points from " << filename << std::endl;
        return true;

    } catch (const std::exception& e) {
        std::cerr << "Exception in loadFromCSV: " << e.what() << std::endl;
        return false;
    }
}

测试建议

修复后,建议测试以下场景:

  1. 加载包含中文路径的CSV文件
  2. 加载只有2列x, y的CSV文件
  3. 加载完整4列x, y, theta, kappa的CSV文件
  4. 加载只有1个数据点的CSV文件
  5. 加载空的CSV文件只有header

编译和重新生成

修改完成后,需要重新编译项目:

cd build
cmake --build . --config Release
# 或
cmake --build . --config Debug

编译完成后,运行 agv_qt_gui.exe 并测试CSV加载功能。