LTOBDemo

时间序列降采样算法LTOB示例项目

作者
WildPointer
更新
2025-12-09
类型
自有项目
仓库
0 stars / 0 forks(抓取快照)

LTOBDemo

时间序列降采样算法LTOB示例项目

语言组成

C++
100.0%
C
0.0%

文件结构

查看完整文件清单(15 个文件)
LTOBDemo.cpp
LTOBDemo.h
LTOBDemo.qrc
LTOBDemo.sln
LTOBDemo.ui
LTOBDemo.vcxproj
LTOBDemo.vcxproj.filters
README.md
Worker.cpp
Worker.h
ltob_downsample.cpp
ltob_downsample.h
main.cpp
qCustomPlot/qcustomplot.cpp
qCustomPlot/qcustomplot.h

关键源码

ltob_downsample.h
ltob_downsample.h
#pragma once#include <QVector>// 使用 LTOB(Largest Triangle One Bucket)算法对二维数据进行降采样。// 该方法在每个桶中选择与相邻均值点组成的三角形面积最大的点,// 在尽量保留形状特征的同时显著降低点数。void ltobDownsample(const QVector<double>& xIn,                    const QVector<double>& yIn,                    QVector<double>& xOut,                    QVector<double>& yOut,                    int targetCount);
ltob_downsample.cpp
ltob_downsample.cpp
#include "ltob_downsample.h"#include <cmath>// LTOB(Largest Triangle One Bucket)降采样实现。// 思路:// - 保留首尾点// - 将剩余点均匀划分为若干桶// - 对每个桶,从上一个已选点出发,选择与后续均值点构成三角形面积最大的点// 这样可以在较少点的情况下保留曲线轮廓的关键变化。/**   @brief 计算三角形面积*   @param[in] ax:顶点A的X轴坐标*   @param[in] ay:顶点A的Y轴坐标*   @param[in] bx:顶点B的X轴坐标*   @param[in] by:顶点B的Y轴坐标*   @param[in] cx:顶点C的X轴坐标*   @param[in] cy:顶点C的Y轴坐标*/static inline double triArea(double ax, double ay, double bx, double by, double cx, double cy){    return std::abs((ax - cx) * (by - ay) - (ax - bx) * (cy - ay));}/**   @brief LTOB时间序列降采样算法*   @param[in] xIn:输入的X轴数据*   @param[in] yIn:输入的Y轴数据*   @param[out] xOut:输出的X轴数据*   @param[out] yOut:输出的Y轴数据*   @param[in] targetCount:目标点数*/void ltobDownsample(const QVector<double>& xIn, const QVector<double>& yIn,                    QVector<double>& xOut, QVector<double>& yOut, int targetCount){    xOut.clear();    yOut.clear();    const int n = xIn.size();   // 输入数据大小    if (n == 0 || targetCount <= 0) {   // 空数据或目标数量非法直接返回        return;    }    if (targetCount >= n) {         // 不需要降采样:直接返回原始数据        xOut = xIn;        yOut = yIn;        return;    }    if (targetCount == 1) {        // 只取首个点        xOut.push_back(xIn.front());        yOut.push_back(yIn.front());        return;    }    xOut.reserve(targetCount);    yOut.reserve(targetCount);    // 保留第一个点    xOut.push_back(xIn[0]);    yOut.push_back(yIn[0]);    int a = 0; // 上一个已选点的索引    const double bucketSize = double(n - 2) / double(targetCount - 2);    for (int i = 0; i < targetCount - 2; ++i) {        const int bucketStart = int(std::floor(1 + i * bucketSize));        const int bucketEnd = int(std::floor(1 + (i + 1) * bucketSize));        // 计算下一桶的均值点(用于与候选点构成三角形)        double avgX = 0.0, avgY = 0.0;        int avgStart = int(std::floor(1 + (i + 1) * bucketSize));        int avgEnd = int(std::floor(1 + (i + 2) * bucketSize));        if (avgEnd > n - 1) {            avgEnd = n - 1;        }        int avgCount = std::max(1, avgEnd - avgStart);        for (int k = avgStart; k < avgEnd; ++k) {            avgX += xIn[k];            avgY += yIn[k];        }        avgX /= avgCount;        avgY /= avgCount;        // 在当前桶中选择面积最大的候选点        double maxArea = -1.0;        int chosen = bucketStart;        int bEnd = std::max(bucketStart + 1, bucketEnd);        for (int j = bucketStart; j < bEnd; ++j) {            double area = triArea(xIn[a], yIn[a], xIn[j], yIn[j], avgX, avgY);            if (area > maxArea) {                maxArea = area;                chosen = j;            }        }        xOut.push_back(xIn[chosen]);        yOut.push_back(yIn[chosen]);        a = chosen;    }    // 保留最后一个点    xOut.push_back(xIn[n - 1]);    yOut.push_back(yIn[n - 1]);}
Worker.cpp
Worker.cpp
#include "Worker.h"#include "ltob_downsample.h"#include <QRandomGenerator>// 后台数据生成与降采样的实现。// 每次运行都会随机化趋势、幅值、频率、相位与噪声强度,确保数据不同。// 构造函数,保持默认行为Worker::Worker(QObject* parent) : QObject(parent) {}void Worker::run(int targetCount){    // 原始数据点数量(较大以体现降采样效果)    const int N = 10000;    QVector<double> xIn;    QVector<double> yIn;    xIn.resize(N);    yIn.resize(N);    // 随机化数据参数,使每次生成的数据形态不同    auto rng = QRandomGenerator::global();    const double slope = 0.0002 + rng->generateDouble() * 0.0018;    // 线性趋势斜率    const double amp   = 5.0    + rng->generateDouble() * 15.0;      // 正弦信号幅值    const double freq  = 0.005  + rng->generateDouble() * 0.020;     // 正弦信号频率    const double phase = rng->generateDouble() * 6.283185307179586;  // 相位(0~2π)    const double noiseScale = 0.5 + rng->generateDouble() * 2.0;     // 噪声强度系数    for (int i = 0; i < N; ++i) {        xIn[i] = i;        double trend = slope * i;                                     // 线性趋势        double signal = std::sin(i * freq + phase) * amp;             // 正弦成分        double noise = noiseScale * (rng->bounded(-100, 100) / 100.0);// 随机噪声        yIn[i] = trend + signal + noise;        // 间隔触发进度更新(上限 50%),剩余进度在计算完成后补齐        if (i % (N / 10) == 0) emit progress(i * 50 / N);    }    QVector<double> xOut;    QVector<double> yOut;    // 使用 LTOB 算法将数据降采样到 targetCount    ltobDownsample(xIn, yIn, xOut, yOut, targetCount);    emit progress(100);    emit resultReady(xIn, yIn, xOut, yOut);    emit finished();}
LTOBDemo.cpp
LTOBDemo.cpp
#include "LTOBDemo.h"#include "Worker.h"#include "ltob_downsample.h"#include <QPen>#include <QTimer>#include "qCustomPlot/qcustomplot.h"// 主窗口实现:负责配置图表、接线信号与槽以及展示结果。LTOBDemo::LTOBDemo(QWidget *parent)    : QWidget(parent)    , ui(new Ui::LTOBDemoClass()){    ui->setupUi(this);    // 配置“原始数据”图表样式并添加标题    ui->plotOriginal->addGraph();    ui->plotOriginal->graph(0)->setPen(QPen(QColor(0, 180, 0), 2));    ui->plotOriginal->graph(0)->setScatterStyle(QCPScatterStyle::ssNone);    ui->plotOriginal->addGraph();    ui->plotOriginal->graph(1)->setPen(QPen(QColor(200, 0, 0), 2));    ui->plotOriginal->graph(1)->setScatterStyle(QCPScatterStyle::ssNone);    ui->plotOriginal->graph(0)->setName("原始数据");    ui->plotOriginal->graph(1)->setName("降采样数据");    ui->plotOriginal->xAxis->grid()->setVisible(true);    ui->plotOriginal->yAxis->grid()->setVisible(true);    ui->plotOriginal->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom);    ui->plotOriginal->axisRect()->setRangeDragAxes(ui->plotOriginal->xAxis, ui->plotOriginal->yAxis);    ui->plotOriginal->axisRect()->setRangeZoomAxes(ui->plotOriginal->xAxis, ui->plotOriginal->yAxis);    ui->plotOriginal->plotLayout()->insertRow(0);    ui->plotOriginal->plotLayout()->addElement(0, 0, new QCPTextElement(ui->plotOriginal, "原始数据与降采样数据"));    ui->plotOriginal->legend->setVisible(true);    ui->plotOriginal->axisRect()->insetLayout()->addElement(ui->plotOriginal->legend, Qt::AlignTop | Qt::AlignRight);    // 绑定按钮点击以启动后台生成任务    connect(ui->btnGenerate, &QPushButton::clicked, this, &LTOBDemo::onGenerateClicked);}LTOBDemo::~LTOBDemo(){    if (workerThread) {        // 优雅退出线程并回收资源        workerThread->quit();        workerThread->wait();        workerThread->deleteLater();        workerThread = nullptr;    }    delete ui;}void LTOBDemo::onGenerateClicked(){    if (busy) return;    busy = true;    ui->btnGenerate->setEnabled(false);    ui->progressBar->setValue(0);    int targetCount = ui->spinTargetCount->value();    // 创建 Worker 并将其移动到新线程    Worker* worker = new Worker();    workerThread = new QThread(this);    worker->moveToThread(workerThread);    // 在线程启动时调用 Worker::run,连接信号用于更新 UI 与结束处理    connect(workerThread, &QThread::started, [worker, targetCount]() {        worker->run(targetCount);    });    connect(worker, &Worker::progress, this, &LTOBDemo::onProgress);    connect(worker, &Worker::resultReady, this, &LTOBDemo::onResultReady);    connect(worker, &Worker::finished, this, &LTOBDemo::onWorkerFinished);    connect(worker, &Worker::finished, workerThread, &QThread::quit);    connect(workerThread, &QThread::finished, worker, &QObject::deleteLater);    workerThread->start();}void LTOBDemo::onProgress(int percent){    ui->progressBar->setValue(percent);}void LTOBDemo::onResultReady(const QVector<double>& xIn,                             const QVector<double>& yIn,                             const QVector<double>& xOut,                             const QVector<double>& yOut){    // 更新原始数据曲线    ui->plotOriginal->graph(0)->setData(xIn, yIn);    ui->plotOriginal->graph(1)->setData(xOut, yOut);    ui->plotOriginal->rescaleAxes();    ui->plotOriginal->replot();}void LTOBDemo::onWorkerFinished(){    // 恢复按钮与忙状态标记    ui->btnGenerate->setEnabled(true);    busy = false;}
main.cpp
main.cpp
#include "LTOBDemo.h"#include <QtWidgets/QApplication>// 程序入口:创建 Qt 应用并显示主窗口。int main(int argc, char *argv[]){    QApplication a(argc, argv);    LTOBDemo w;    w.show();    return a.exec();}
WildPointer

专注系统编程、工程实践与底层技术,记录 C++、Qt、OpenCV 与 VTK 的学习和实践。