C++之曲线拟合与离散点生成

问题描述

在平面上有9个点p1到p9,其x坐标依次减小。p1和p2的y坐标相同,p4的切向量已知。要求用直线连接p1和p2,其余点用样条曲线连接,最后生成离散点集。

解决方案

步骤分解

  1. 直线段生成:连接p1和p2的直线段。
  2. 坐标转换 :将p2到p9的点转换到x'坐标系 x ′ = − x x' = -x x′=−x,使x'递增。
  3. 导数计算
    • p4点的导数根据给定切向量计算。
    • 其他点的导数通过中心差分或单侧差分计算。
  4. 埃尔米特样条生成:对每对相邻点生成埃尔米特曲线段。
  5. 坐标转换回原系:将样条曲线点转换回原坐标系。
  6. 合并结果:将直线段和样条曲线段的点合并。

公式推导

埃尔米特插值公式

埃尔米特插值通过两个点及其导数生成三次多项式。对于点qi和qi+1,其参数方程为:

x ′ ( t ) = x i ′ + t ⋅ ( x i + 1 ′ − x i ′ ) y ′ ( t ) = h 00 ( t ) ⋅ y i + h 10 ( t ) ⋅ m i ⋅ ( x i + 1 ′ − x i ′ ) + h 01 ( t ) ⋅ y i + 1 + h 11 ( t ) ⋅ m i + 1 ⋅ ( x i + 1 ′ − x i ′ ) \begin{aligned} x'(t) &= x'i + t \cdot (x'{i+1} - x'i) \\ y'(t) &= h{00}(t) \cdot y_i + h_{10}(t) \cdot m_i \cdot (x'{i+1} - x'i) \\ &\quad + h{01}(t) \cdot y{i+1} + h_{11}(t) \cdot m_{i+1} \cdot (x'_{i+1} - x'_i) \end{aligned} x′(t)y′(t)=xi′+t⋅(xi+1′−xi′)=h00(t)⋅yi+h10(t)⋅mi⋅(xi+1′−xi′)+h01(t)⋅yi+1+h11(t)⋅mi+1⋅(xi+1′−xi′)

其中,基函数为:

h 00 ( t ) = 2 t 3 − 3 t 2 + 1 h 10 ( t ) = t 3 − 2 t 2 + t h 01 ( t ) = − 2 t 3 + 3 t 2 h 11 ( t ) = t 3 − t 2 \begin{aligned} h_{00}(t) &= 2t^3 - 3t^2 + 1 \\ h_{10}(t) &= t^3 - 2t^2 + t \\ h_{01}(t) &= -2t^3 + 3t^2 \\ h_{11}(t) &= t^3 - t^2 \end{aligned} h00(t)h10(t)h01(t)h11(t)=2t3−3t2+1=t3−2t2+t=−2t3+3t2=t3−t2

参数t在 [ 0 , 1 ] [0, 1] [0,1]范围内。

导数计算

对于p4点(在转换后的坐标系中为qk),其导数根据给定切向量计算:

m k = − t y t x m_k = -\frac{ty}{tx} mk=−txty

其他点的导数计算如下:

  • 端点:使用单侧差分。
  • 内部点:使用中心差分。

代码实现

cpp 复制代码
#include <QVector>
#include <QPointF>
#include <cmath>

QVector<QPointF> generateLinePoints(const QPointF& p1, const QPointF& p2) {
    QVector<QPointF> points;
    int steps = 100; // 可调整步数以改变离散密度
    for (int i = 0; i <= steps; ++i) {
        qreal t = static_cast<qreal>(i) / steps;
        qreal x = p1.x() + t * (p2.x() - p1.x());
        qreal y = p1.y() + t * (p2.y() - p1.y());
        points.append(QPointF(x, y));
    }
    return points;
}

QVector<QPointF> generateCurve(const QVector<QPointF>& points, const QPointF& p4Tangent) {
    QVector<QPointF> result;

    // 1. 生成p1到p2的直线段
    if (points.size() < 2) return result;
    QPointF p1 = points[0];
    QPointF p2 = points[1];
    result = generateLinePoints(p1, p2);

    // 2. 处理p2到p9的样条曲线
    QVector<QPointF> qPoints;
    for (int i = 1; i < points.size(); ++i) {
        qreal x_prime = -points[i].x();
        qreal y = points[i].y();
        qPoints.append(QPointF(x_prime, y));
    }

    if (qPoints.size() < 2) return result;

    // 3. 计算导数
    QVector<qreal> m(qPoints.size(), 0.0);
    int k = 2; // p4在qPoints中的索引

    // 处理p4的导数
    qreal tx = p4Tangent.x();
    qreal ty = p4Tangent.y();
    if (tx == 0) {
        // 垂直切线的处理,这里简化为极大值
        m[k] = (ty > 0) ? 1e10 : -1e10;
    } else {
        qreal dy_dx = ty / tx;
        m[k] = -dy_dx; // 转换到x'坐标系
    }

    // 计算其他点的导数
    for (int i = 0; i < qPoints.size(); ++i) {
        if (i == k) continue;

        if (i == 0) {
            // 第一个点,前向差分
            qreal dx = qPoints[i+1].x() - qPoints[i].x();
            qreal dy = qPoints[i+1].y() - qPoints[i].y();
            m[i] = dy / dx;
        } else if (i == qPoints.size() - 1) {
            // 最后一个点,后向差分
            qreal dx = qPoints[i].x() - qPoints[i-1].x();
            qreal dy = qPoints[i].y() - qPoints[i-1].y();
            m[i] = dy / dx;
        } else {
            // 中心差分
            qreal dx_prev = qPoints[i].x() - qPoints[i-1].x();
            qreal dy_prev = qPoints[i].y() - qPoints[i-1].y();
            qreal dx_next = qPoints[i+1].x() - qPoints[i].x();
            qreal dy_next = qPoints[i+1].y() - qPoints[i].y();
            m[i] = (dy_prev/dx_prev + dy_next/dx_next) / 2;
        }
    }

    // 4. 生成样条曲线
    QVector<QPointF> splinePoints;
    for (int i = 0; i < qPoints.size() - 1; ++i) {
        QPointF q_start = qPoints[i];
        QPointF q_end = qPoints[i+1];
        qreal x_start = q_start.x();
        qreal y_start = q_start.y();
        qreal x_end = q_end.x();
        qreal y_end = q_end.y();
        qreal m_start = m[i];
        qreal m_end = m[i+1];

        int steps = 100; // 每段的采样步数
        for (int j = 0; j <= steps; ++j) {
            qreal t = static_cast<qreal>(j) / steps;
            qreal h00 = 2 * t*t*t - 3 * t*t + 1;
            qreal h10 = t*t*t - 2 * t*t + t;
            qreal h01 = -2 * t*t*t + 3 * t*t;
            qreal h11 = t*t*t - t*t;

            qreal x_prime = x_start + t * (x_end - x_start);
            qreal y_prime = h00 * y_start + h10 * m_start * (x_end - x_start) +
                            h01 * y_end + h11 * m_end * (x_end - x_start);

            // 转换回原坐标系
            qreal x = -x_prime;
            qreal y = y_prime;
            splinePoints.append(QPointF(x, y));
        }
    }

    // 合并结果,去除重复的p2点
    if (!splinePoints.isEmpty()) {
        splinePoints.removeFirst();
        result += splinePoints;
    }

    return result;
}

总结

该方案通过将问题分解为直线段生成和样条曲线生成两部分,利用埃尔米特插值在满足p4点切向量条件的同时,生成平滑的曲线。通过坐标转换简化了样条曲线的构造过程,并通过合理的导数计算确保了曲线的连续性和光滑性。

相关推荐
yuanbenshidiaos1 小时前
面试问题总结:qt工程师/c++工程师
c++·qt·面试
半盏茶香1 小时前
启幕数据结构算法雅航新章,穿梭C++梦幻领域的探索之旅——堆的应用之堆排、Top-K问题
java·开发语言·数据结构·c++·python·算法·链表
小竹子141 小时前
L1-1 天梯赛座位分配
数据结构·c++·算法
v维焓3 小时前
C++(思维导图更新)
开发语言·c++·算法
CYRUS_STUDIO3 小时前
Frida Hook Native:jobjectArray 参数解析
android·c++·逆向
榆榆欸3 小时前
4.Socket类、InetAddr类、Epoll类实现模块化
linux·c++·tcp/ip
..过云雨3 小时前
11. 【C++】模板进阶(函数模板特化、类模板全特化和偏特化、模板的分离编译)
开发语言·c++
BC橡木4 小时前
C++ IO流
c++
m0_555762904 小时前
struct 中在c++ 和c中用法区别
java·c语言·c++
月亮有痕迹诶5 小时前
【C++】智能指针
开发语言·c++·c++11