4、线性校准模型:两点校准法(Scale and Offset)的数学推导与实现

上一章我们聊了单点校准,说白了就是只调个偏移量。但实际项目中你会发现,单点校准根本不够用。为什么?因为触摸屏的物理特性和 ADC 采样电路,往往会让触摸坐标既偏了位置,又缩放了比例。

我记得有一次做一款 7 寸工业平板,单点校准后屏幕中心点倒是准了,但四个角偏差越来越大。当时我排查了半天,最后发现是 ADC 参考电压的温漂导致的增益误差。嗯,这就是典型的「既有偏移又有缩放」的场景。

两点校准法,就是专门解决这个问题的。它用一个线性模型,同时修正偏移(Offset)和缩放(Scale)。说白了,就是找到一条直线,把原始坐标映射到目标坐标上。

4.1 为什么需要两点?

先想一个问题:一条直线需要几个点来确定?两个。单点只能确定一个常数偏移,但无法确定斜率。两点校准的本质,就是通过两个已知的参考点,解出这条直线的斜率和截距。

你想想看,触摸屏的原始坐标 (Xraw, Yraw) 和实际物理坐标 (Xphys, Yphys) 之间,如果只考虑线性关系,那就是:

Xphys = a * Xraw + b
Yphys = c * Yraw + d

这里 a 和 c 是缩放因子(Scale),b 和 d 是偏移量(Offset)。四个未知数,需要四个方程。两个点,每个点提供 X 和 Y 两个方程,正好四个方程,解四个未知数。

核心思想:两点校准法假设触摸屏的误差是线性的——即原始坐标与实际坐标之间存在一个固定的缩放和偏移关系。这个假设在大多数电阻屏和部分电容屏上是成立的。

4.2 数学推导:从几何到代数

我们以 X 轴为例来推导。Y 轴完全同理。

假设我们在屏幕上点了两个参考点:

  • 点 A:物理坐标 (X1, Y1),触摸原始坐标 (x1, y1)
  • 点 B:物理坐标 (X2, Y2),触摸原始坐标 (x2, y2)

对于 X 轴,我们有:

X1 = a * x1 + b
X2 = a * x2 + b

两式相减消去 b:

X1 - X2 = a * (x1 - x2)
a = (X1 - X2) / (x1 - x2)

然后代入求 b:

b = X1 - a * x1

Y 轴同理:

c = (Y1 - Y2) / (y1 - y2)
d = Y1 - c * y1

就这么简单?嗯,数学上确实简单。但实际工程中,坑都在细节里。

我的经验:计算 a 和 c 时,分母 (x1 - x2) 和 (y1 - y2) 不能太小。如果两个参考点靠得太近,分母接近零,计算出的缩放因子会剧烈抖动。我一般要求两个参考点至少相距屏幕对角线长度的 60% 以上。

4.3 参考点的选择策略

参考点选在哪里,直接影响校准精度。我见过不少工程师随便选两个点,结果校准完边缘还是偏的。

下面是我总结的参考点选择原则:

原则 说明 我的建议
距离最大化 两个点尽量远离,覆盖屏幕对角区域 左上角和右下角,或左下角和右上角
避开边缘 不要选在屏幕最边缘,那里非线性失真大 距离边缘 10%-15% 屏幕尺寸的位置
对称分布 两个点尽量关于屏幕中心对称 这样能平均分配误差
可重复性 参考点位置要容易点击准确 使用十字准星或靶心图案

我个人习惯用左上和右下两个点。为什么?因为这样 X 和 Y 方向都能覆盖到最大范围,而且计算时分母最大,数值稳定性最好。

4.4 代码实现:C 语言版

下面是我在实际项目中用的一段代码。它接受两个参考点的物理坐标和原始坐标,计算出校准参数,然后应用校准。

#include <stdint.h>

typedef struct {
    float scale_x;   // X 轴缩放因子 a
    float offset_x;  // X 轴偏移 b
    float scale_y;   // Y 轴缩放因子 c
    float offset_y;  // Y 轴偏移 d
} calib_params_t;

typedef struct {
    uint16_t x;
    uint16_t y;
} point_t;

// 计算两点校准参数
// ref_phys: 物理坐标(参考点)
// ref_raw:  触摸原始坐标
// 返回值:校准参数结构体
calib_params_t calib_two_point(
    const point_t ref_phys[2],
    const point_t ref_raw[2])
{
    calib_params_t params;
    
    // 计算 X 轴参数
    float dx_raw = (float)(ref_raw[1].x - ref_raw[0].x);
    float dx_phys = (float)(ref_phys[1].x - ref_phys[0].x);
    
    // 防止除零
    if (dx_raw == 0.0f) {
        dx_raw = 0.001f;  // 极小值保护
    }
    
    params.scale_x = dx_phys / dx_raw;
    params.offset_x = (float)ref_phys[0].x - params.scale_x * (float)ref_raw[0].x;
    
    // 计算 Y 轴参数
    float dy_raw = (float)(ref_raw[1].y - ref_raw[0].y);
    float dy_phys = (float)(ref_phys[1].y - ref_phys[0].y);
    
    if (dy_raw == 0.0f) {
        dy_raw = 0.001f;
    }
    
    params.scale_y = dy_phys / dy_raw;
    params.offset_y = (float)ref_phys[0].y - params.scale_y * (float)ref_raw[0].y;
    
    return params;
}

// 应用校准
// raw: 原始触摸坐标
// params: 校准参数
// 返回值:校准后的物理坐标
point_t apply_calib(const point_t raw, const calib_params_t *params)
{
    point_t phys;
    
    phys.x = (uint16_t)(params->scale_x * (float)raw.x + params->offset_x + 0.5f);
    phys.y = (uint16_t)(params->scale_y * (float)raw.y + params->offset_y + 0.5f);
    
    return phys;
}

注意:上面的代码中,我加了 0.5f 做四舍五入。如果不加,直接截断取整,坐标会整体偏左偏上 1 个像素。这个细节我曾经忽略过,结果校准完总觉得光标位置差半个像素,排查了半天才发现是取整方式的问题。

4.5 两点校准的局限性

两点校准虽然比单点好很多,但它不是万能的。我遇到过一些情况,两点校准完全不够用:

  • 屏幕有旋转误差:触摸屏安装时歪了,或者 LCD 和触摸层之间有角度偏差。两点校准只能处理平移和缩放,处理不了旋转。
  • 非线性失真严重:有些廉价触摸屏,边缘区域的非线性误差很大。两点校准假设全屏线性,遇到这种情况边缘还是会偏。
  • X 轴和 Y 轴耦合:有些触摸屏的 X 轴读数会受到 Y 轴位置的影响(比如某些老式电阻屏)。两点校准假设 X 和 Y 独立,无法处理耦合。

遇到这些情况怎么办?别急,后面章节会讲三点校准、多点校准,甚至非线性校准模型。两点校准只是起点,不是终点。

4.6 本章知识体系

下面这张图,把两点校准法的核心逻辑串起来了。从输入到输出,每一步都清晰可见。

两点校准法知识体系 两点校准法核心逻辑 原始触摸坐标 两个参考点 数学推导 a = (X1-X2)/(x1-x2) b = X1 - a*x1 c = (Y1-Y2)/(y1-y2) d = Y1 - c*y1 校准参数 scale_x, offset_x scale_y, offset_y 存储到非易失区 应用校准 Xphys = a*Xraw + b 校准后物理坐标 注意事项 • 参考点间距 > 60% 对角线 • 避开屏幕边缘 10%-15% • 防止分母为零 局限性 • 无法处理旋转误差 • 无法处理非线性失真 • 无法处理 XY 耦合

4.7 工程实现中的避坑指南

代码写完了,数学推导也清楚了,但真正上机调试时,你可能会遇到下面这些问题。这些都是我踩过的坑,分享给你:

  1. 坐标系方向搞反:触摸屏的 X 轴方向和 LCD 的 X 轴方向可能相反。计算出的 scale 如果是负数,说明方向反了。我一般会在校准后先打印一下 scale 值,正数正常,负数就交换参考点的顺序。
  2. 浮点数精度问题:在资源受限的 MCU 上,float 运算可能很慢。如果 MCU 没有 FPU,建议把 scale 和 offset 转成定点数。比如 scale 放大 1024 倍存为整数,计算时用移位代替除法。
  3. 校准参数的持久化:校准参数算出来后,一定要存到非易失存储(如 EEPROM、Flash)里。不然每次重启都要重新校准,用户体验极差。
  4. 校准触发时机:不要每次触摸都重新计算校准参数。参数只在校准时计算一次,之后一直复用。除非用户主动触发重新校准。

一个小技巧:在校准界面,让用户点击参考点时,不要只取一次点击坐标。我一般会连续采样 5-10 次,去掉最大最小值,然后取平均。这样能有效抑制触摸噪声,提高校准精度。

好了,两点校准法就讲到这里。它的数学很简单,但工程实现中需要注意的细节不少。掌握了这个基础,后面讲三点校准和仿射变换时,你会理解得更快。