3、最小二乘法拟合:使用最小二乘法计算校准参数、矩阵运算与求解、误差分析与精度评估

好,咱们接着聊。上一节我们把触摸数据采回来了,一堆 (LCD_x, LCD_y, ADC_x, ADC_y) 的配对点。现在问题来了:怎么从这些离散的点里,算出最靠谱的校准参数?

你可能会想,直接拿两组点解方程不就行了?嗯,理论上可以。但实际采样总有噪声,手指按下去也有偏差。用两组点算出来的参数,换几个点测可能就偏了。我早期做一款 7 寸屏时就吃过这个亏——用 3 个点算参数,结果屏幕左上角准了,右下角差了十几个像素。

所以,我们需要一种方法:让所有采样点都“参与投票”,找出一个整体误差最小的解。这就是最小二乘法干的事。

3.1 最小二乘法的核心思想

说白了,最小二乘法就是找一组参数,让所有采样点的“预测值”和“实际值”的差的平方和最小。为什么用平方?因为平方可以放大偏差,让大的误差更显眼,优化时会优先把它们压下去。

对于触摸校准,我们通常用线性模型:

LCD_x = A * ADC_x + B * ADC_y + C
LCD_y = D * ADC_x + E * ADC_y + F

这里有 6 个未知数(A, B, C, D, E, F)。理论上,3 组不共线的点就能解。但用 N 组点(N > 3),就变成了一个超定方程组。最小二乘法就是解这个超定方程组的最佳工具。

核心公式(矩阵形式)

我们把方程写成矩阵:Y = X * K

其中 X 是 ADC 坐标构成的矩阵,Y 是 LCD 坐标向量,K 是待求参数向量。

最小二乘解为:K = (XT * X)-1 * XT * Y

这个公式你记牢了,它就是校准参数的“标准答案生成器”。

3.2 矩阵运算与求解步骤

别被矩阵吓到。实际做的时候,我们一步步来。我个人习惯用 C 语言手写矩阵运算,因为嵌入式环境不一定有现成的库。

假设我们采集了 N 个点,每个点有 (adc_x, adc_y, lcd_x, lcd_y)。

第一步:构造矩阵 X 和向量 Y

// 对于 X 矩阵,每行是 [adc_x, adc_y, 1]
// 对于 Y 向量,先算 X 方向
// 矩阵大小:X 是 N×3,Y 是 N×1

float X[N][3];
float Yx[N];  // 用于计算 X 方向的参数
float Yy[N];  // 用于计算 Y 方向的参数

for (int i = 0; i < N; i++) {
    X[i][0] = adc_x[i];
    X[i][1] = adc_y[i];
    X[i][2] = 1.0f;
    Yx[i] = lcd_x[i];
    Yy[i] = lcd_y[i];
}

第二步:计算 XT * X 和 XT * Y

这一步是矩阵乘法。XT * X 是一个 3×3 的对称矩阵,XT * Y 是一个 3×1 的向量。

float XTX[3][3] = {0};
float XTYx[3] = {0};
float XTYy[3] = {0};

for (int i = 0; i < N; i++) {
    // XT * X
    XTX[0][0] += X[i][0] * X[i][0];
    XTX[0][1] += X[i][0] * X[i][1];
    XTX[0][2] += X[i][0] * X[i][2];
    XTX[1][0] += X[i][1] * X[i][0];
    XTX[1][1] += X[i][1] * X[i][1];
    XTX[1][2] += X[i][1] * X[i][2];
    XTX[2][0] += X[i][2] * X[i][0];
    XTX[2][1] += X[i][2] * X[i][1];
    XTX[2][2] += X[i][2] * X[i][2];

    // XT * Yx
    XTYx[0] += X[i][0] * Yx[i];
    XTYx[1] += X[i][1] * Yx[i];
    XTYx[2] += X[i][2] * Yx[i];

    // XT * Yy
    XTYy[0] += X[i][0] * Yy[i];
    XTYy[1] += X[i][1] * Yy[i];
    XTYy[2] += X[i][2] * Yy[i];
}

第三步:求解线性方程组

现在我们有 XTX * K = XTY。解这个 3 元一次方程组。我常用高斯消元法,稳定且容易实现。

// 高斯消元求解 3x3 方程组
// 输入:A[3][3] 系数矩阵,b[3] 右侧向量
// 输出:x[3] 解向量

int gauss_3x3(float A[3][3], float b[3], float x[3]) {
    float M[3][4];
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) M[i][j] = A[i][j];
        M[i][3] = b[i];
    }

    // 前向消元
    for (int col = 0; col < 3; col++) {
        // 选主元(这里简化,实际建议加选主元逻辑)
        float pivot = M[col][col];
        if (fabs(pivot) < 1e-10) return -1;  // 奇异矩阵

        for (int row = col + 1; row < 3; row++) {
            float factor = M[row][col] / pivot;
            for (int k = col; k <= 3; k++) {
                M[row][k] -= factor * M[col][k];
            }
        }
    }

    // 回代
    for (int i = 2; i >= 0; i--) {
        x[i] = M[i][3];
        for (int j = i + 1; j < 3; j++) {
            x[i] -= M[i][j] * x[j];
        }
        x[i] /= M[i][i];
    }
    return 0;
}

// 调用两次,分别求 X 方向和 Y 方向的参数
float Kx[3], Ky[3];
gauss_3x3(XTX, XTYx, Kx);  // Kx = [A, B, C]
gauss_3x3(XTX, XTYy, Ky);  // Ky = [D, E, F]

我的经验:在实际项目中,我遇到过 XTX 矩阵接近奇异的情况——比如采样点都挤在一个小区域。这时候解出来的参数会剧烈抖动。解决办法:确保采样点均匀分布在屏幕四个角和中心,至少 5 个点。

3.3 误差分析与精度评估

参数算出来了,但效果怎么样?不能光靠感觉。我们需要量化评估。

残差计算

对每个采样点,用算出的参数反算预测的 LCD 坐标,然后和实际值比较:

float total_error_x = 0;
float total_error_y = 0;
float max_error_x = 0;
float max_error_y = 0;

for (int i = 0; i < N; i++) {
    float pred_x = Kx[0] * adc_x[i] + Kx[1] * adc_y[i] + Kx[2];
    float pred_y = Ky[0] * adc_x[i] + Ky[1] * adc_y[i] + Ky[2];

    float err_x = fabs(pred_x - lcd_x[i]);
    float err_y = fabs(pred_y - lcd_y[i]);

    total_error_x += err_x;
    total_error_y += err_y;

    if (err_x > max_error_x) max_error_x = err_x;
    if (err_y > max_error_y) max_error_y = err_y;
}

float rmse_x = sqrt(total_error_x / N);
float rmse_y = sqrt(total_error_y / N);

评估指标

指标 含义 可接受范围
RMSE(均方根误差) 整体偏差的“平均大小” < 3 像素
最大误差 最差的那个点偏差多少 < 5 像素
平均误差 简单平均偏差 < 2 像素

注意:如果最大误差出现在屏幕边缘,说明边缘区域校准不佳。我曾经遇到一个项目,中心区域误差 1 像素,但右下角误差 8 像素。后来发现是触摸屏本身的非线性问题,单纯靠线性校准已经不够了,需要分段校准。

3.4 知识体系总览

下面这张图把最小二乘法校准的完整流程串起来了,你可以对照着看:

最小二乘法触摸校准流程 最小二乘法触摸校准流程 采集 N 组配对点 构造 X 矩阵和 Y 向量 计算 XᵀX 和 XᵀY 矩阵 高斯消元求解 K 计算残差与 RMSE 关键公式 K = (XᵀX)⁻¹ · XᵀY 残差 = |预测值 - 实际值| RMSE = √(Σ残差² / N) 避坑提醒 采样点均匀分布 N ≥ 5 组 检查 XᵀX 是否奇异

3.5 实战中的几个坑

讲几个我踩过的坑,你遇到了可以少走弯路:

  • 采样点数量:我曾经只用 4 个点,结果参数对采样点拟合很好,但采样点之间的区域偏差很大。后来我改成 9 个点(3×3 网格),效果稳定多了。
  • 浮点精度:在 Cortex-M3 上做浮点运算,如果 ADC 值很大(比如 12 位 ADC,范围 0~4095),XTX 矩阵里的数值会很大。我遇到过因为 float 精度不够导致矩阵求逆失败的情况。解决方案:先把 ADC 值归一化到 0~1 范围再计算。
  • 验证集:不要用参与拟合的点来评估精度。我习惯留 2~3 个点不参与计算,专门用来验证。这样测出来的误差才是真实的。

总结一下:最小二乘法是触摸校准的“黄金标准”。它用所有采样点的集体智慧,算出整体最优的参数。矩阵运算看着复杂,但拆成三步走——构造矩阵、计算 XTX 和 XTY、高斯消元——其实很清晰。最后别忘了做误差分析,用 RMSE 和最大误差两个指标说话。