光栅尺测量数据的修正

光栅尺测量数据的修正

最近有个视觉项目,相机要在一个直线轨道上运动。要求这个直线轨道的运动精度非常的高。300mm 的运动范围内重复定位精度做到3um 以内。还要求绝对定位精度 10um 以内。

重复定位精度相对来说好达到,只要导轨选的好,伺服电机的分辨率足够,一般是能达到 3um 重复定位精度的。
绝对定位精度就难很多了。为了绝对定位精度,平台上安装了 1um 分辨率的光栅尺。但是即使这样,长距离行走时误差还是很大,实测 300mm 行程时误差能达到 50-60um。 为了消除这个误差,就需要分段补偿。下面就讲讲分段补偿的方法。

首先,我们需要一个能测出行走误差的仪器。我们这个项目是用了一把 1um 精度的玻璃线纹尺。玻璃线纹尺上面有刻度,我们认为这个刻度是精确的。用我们的光学系统对这些刻度进行拍照。比如说让图像的中心对准 0mm 处,这时光栅尺的读书为 A0,然后移动平台,让图像中心对准 10mm 处,这时光栅尺读数为 A1。 理论上,A1-A0 = 10mm。如果不是10mm,说明我们的光栅尺的读数与真实的位移是有误差的。这个误差决定了绝对定位精度。

理论上来说,我们可以把玻璃线纹尺的原点与光栅尺的原点对齐,正方向也都同向。这时以此将光学系统移动到玻璃线纹尺的整数刻度处,然后记录当前的光栅尺读数。可以得到一个类似下面的表格。

玻璃线纹尺光栅尺
0.000.00
50.0049.98
100.00100.03
150.00150.05
200.00199.99
250.00250.03
300.00300.06

有了这个表格,我们可以构造两个分段线性插值函数。第一个函数用来将真实位置(玻璃尺坐标系)转换为光栅尺的读数。比如我们要去 100.00 位置处,那么我们就走到光栅尺读数为 100.03 的位置就可以了。第二个函数是将光栅尺读数转换为真实位置的。

这两个插值函数用最简单的分段线性插值就可以了。甚至我们都可以不需要自己来写一个线性插值算法。我下面的代码直接用了 GSL(GNU Scientific Library) 里面的功能。

另外,我们知道光栅尺虽然有误差,但是误差不会很大,我们在标定时只要保证玻璃尺的读数为整数,那么上面的表格的第一列就不需要记录。比如光栅尺读数为150.05 ,那么对应的真实位置一定是150mm。我们只要对光栅尺读数取个整就好了。

另外,我们也不需要将玻璃尺的原点与光栅尺的原点精确对齐。比如下面的数据。

玻璃线纹尺光栅尺
0.004.002
2.006.01
3.007.003
5.009.031
11.0015.03

这个数据只有光栅尺 4.002 到 15.03 之间的标定数据。那么在这一段做插值。两边做外插值就行了。另外,我们可以把玻璃尺的数据挪一下。

玻璃线纹尺光栅尺
4.0024.002
6.0026.01
7.0027.003
9.0029.031
15.00215.03

这样挪动相当于认为 4.002 以前的数据全都是准确的,相当于我们补了个 0。15.002 之后的数据直接外插。

玻璃线纹尺光栅尺
00
4.0024.002
6.0026.01
7.0027.003
9.0029.031
15.00215.03

下面给出代码,主要用到了 GSL 里面的 gsl_interp_eval 函数。大家可以读这个函数的帮助文档。

#ifndef LECALIBRATOR_H
#define LECALIBRATOR_H
#include <gsl/gsl_interp.h>
#include <QVector>
/**
 * @brief The LECalibrator class 光栅尺(Linear Encoder)输出位移修正器。
 *                               由于各种原因,导致光栅尺的测量结果与真实值有一定的差距。
 *                               本 C++类可以对光栅尺的测量结果进行一定程度的修正。
 * 要求校准点之间的真实距离是 1mm 的整数倍,比如有如下校准点: 4.002 6.01  7.003 9.031 15.03
 * 那么程序默认6.01 到 4.002 的距离是 2mm, 7.003 到 4.002 的距离是 3mm, 9.031 到4.002 的距离是 5mm
 * 并以此为依据来修正其他的点。其中最小的点保持不变,也就是 4.002 修正后的结果还是 4.002,
 * 那么 9.031 修正的结果是 9.002.
 */
class LECalibrator
{
public:
    LECalibrator();
    ~LECalibrator();

    /**
     * @brief loadPoints 将校准点传入
     * @param points 校准点,要求这些校准点之间的真实距离是 1mm 的整数倍。校正点不需要排序,程序内部会自动排序。
     *                校准点也不需要是等间隔的,但是从修正效果考虑,建议为实际距离等间隔点
     */
    void loadPoints(QVector<double> points);

    void loadPoints(double p[], int N);
    /**
     * @brief encoderPosToTruePos 对光栅尺的结果进行修正
     * @param value 光栅尺的原始数值
     * @return 修正后的数值
     */
    double encoderPosToTruePos(double value);
    double truePosToEncoderPos(double value);
    void print();/// 测试用
private:
    void reset();
    void build();
    gsl_interp * m_interp;
    gsl_interp * m_interp_rev;
    gsl_interp_accel * m_accel;
    gsl_interp_accel * m_accel_rev;
    double * m_xa;
    double * m_ya;
    int m_size;
};

#endif // LECALIBRATOR_H

#include "LECalibrator.h"
#include <QtAlgorithms>
#include <QDebug>
#include <gsl/gsl_sort_double.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_nan.h>

LECalibrator::LECalibrator()
    : m_xa(nullptr),
      m_ya(nullptr),
      m_size(0),
      m_interp(nullptr),
      m_interp_rev(nullptr),
      m_accel(nullptr),
      m_accel_rev(nullptr)
{

}

LECalibrator::~LECalibrator()
{
    reset();
}

void LECalibrator::print()
{
    for(int i = 0; i < m_size; i ++)
    {
        qDebug() << "(" << m_xa[i] << ", " << m_ya[i] << ")";
    }
}

double LECalibrator::encoderPosToTruePos(double value)
{
    if(m_size == 0 || value <= m_xa[0])
    {
        return value;
    }
    if(value >= m_xa[m_size - 1]) //如果超出最后一个点,则外插
    {
        return m_ya[m_size - 1] + (value - m_xa[m_size - 1]);
    }

    double ret = 0;
    if(m_interp)
    {
        ret = gsl_interp_eval (m_interp, m_xa, m_ya, value, m_accel);

        if(ret == GSL_NAN)
        {
            return value;
        }
        return ret;
    }
    return value;
}

double LECalibrator::truePosToEncoderPos(double value)
{
    if(m_size == 0 || value <= m_ya[0])
    {
        return value;
    }
    if(value >= m_ya[m_size - 1]) //如果超出最后一个点,则外插
    {
        return m_xa[m_size - 1] + (value - m_ya[m_size - 1]);
    }

    double ret = 0;
    if(m_interp_rev)
    {
        ret = gsl_interp_eval (m_interp_rev, m_ya, m_xa, value, m_accel_rev);

        if(ret == GSL_NAN)
        {
            return value;
        }
        return ret;
    }
    return value;
}


void LECalibrator::reset()
{
    if(m_xa)
    {
        delete[] m_xa;
        delete[] m_ya;
        m_xa = nullptr;
        m_ya = nullptr;
    }
    m_size = 0;
    if(m_interp)
    {
        gsl_interp_free(m_interp);
        gsl_interp_free(m_interp_rev);
        gsl_interp_accel_free(m_accel);
        gsl_interp_accel_free(m_accel_rev);
        m_interp = nullptr;
        m_accel = nullptr;
        m_accel_rev = nullptr;
    }
}

inline double round( double x)
{
    int ret = static_cast<int>(x + 0.5);
    return ret;
}

void LECalibrator::build()
{
    gsl_sort(m_xa, 1, m_size);

    m_ya[0] = m_xa[0];
    for(int i = 1; i < m_size; i++)
    {
        m_ya[i] = round( m_xa[i] - m_xa[0] ) + m_xa[0];
    }
    m_interp = gsl_interp_alloc(gsl_interp_linear, m_size);
    m_interp_rev = gsl_interp_alloc(gsl_interp_linear, m_size);
    gsl_interp_init(m_interp, m_xa, m_ya, m_size);
    gsl_interp_init(m_interp_rev, m_ya, m_xa, m_size);
    m_accel = gsl_interp_accel_alloc();
    m_accel_rev = gsl_interp_accel_alloc();
}

void LECalibrator::loadPoints(double p[], int N)
{
    if(N < 2)
    {
        return;
    }

    reset();
    m_size = N;
    m_xa = new double[m_size];
    m_ya = new double[m_size];

    for(int i = 0; i < m_size; i++)
    {
        m_xa[i] = p[i];
    }

    build();
}

void LECalibrator::loadPoints(QVector<double> points)
{
    if(points.size() < 2)
    {
        return;
    }

    reset();
    m_size = points.size();
    m_xa = new double[m_size];
    m_ya = new double[m_size];

    for(int i = 0; i < m_size; i++)
    {
        m_xa[i] = points.at(i);
    }
    gsl_sort(m_xa, 1, m_size);

    build();
}


### 光栅尺信号处理方法及其实现技术 光栅尺是一种高精度位移测量装置,其核心原理是通过光学干涉或衍射现象来检测位置变化并将其转化为电信号。为了提高测量精度和可靠性,在实际应用中通常采用特定的信号处理技术和算法。 #### 1. 数据采集与预处理 在基于 PLC 的光栅尺数据采集系统中,首先需要将光栅尺产生的模拟信号转换为数字信号以便后续处理。这一过程涉及模数转换 (ADC),并通过滤波器去除噪声干扰[^1]。常见的预处理步骤包括: - **低通滤波**:用于消除高频噪声。 - **增益调整**:放大弱信号以适应 ADC 输入范围。 这些操作可以显著提升原始信号的质量,从而减少误差传播到后续阶段的可能性。 #### 2. 长度校准计算 对于每一段已知区域内的光栅尺读数,需引入补偿系数来进行修正。具体而言,就是利用预先测定好的各分区段对应之补偿因子K_i与其未经矫正前测得该分区内总长L'_i相乘得出最终经过校正后的长度值 L_i= K_i * L'_i 。此步骤确保即使存在制造缺陷或者环境因素影响下仍能保持较高的定位准确性。 #### 3. 数字化编码解码逻辑设计 当完成上述物理层面的操作之后,则进入到软件部分即如何解析由硬件传来的二进制串流代表的确切距离信息上来了。一般会采取格雷码译码方案因为相比普通的二进制它具有相邻状态间仅改变一位的特点所以更加适合这种增量型传感器输出形式;另外还有绝对式编码方式能够直接给出当前位置而无需参考起点作为对比依据因此适用于更广泛场合比如断电重启后不需要重新寻找零点等情况下的连续跟踪记录功能需求场景之中[^2]。 以下是简单的伪代码展示了一个基本框架用来演示整个工作流程: ```python def process_grating_signal(raw_data): filtered_data = apply_low_pass_filter(raw_data) # 应用低通滤波器 calibrated_lengths = [] for segment_index in range(number_of_segments): compensation_factor = get_compensation_coefficient(segment_index) uncalibrated_length = calculate_uncalibrated_length(filtered_data, segment_index) corrected_length = compensation_factor * uncalibrated_length calibrated_lengths.append(corrected_length) encoded_position = encode_to_gray_code(calibrated_lengths[-1]) if use_incremental_encoding else absolute_encode(calibrated_lengths[-1]) return encoded_position # 假设函数定义如下 def apply_low_pass_filter(data): ... def get_compensation_coefficient(index): ... def calculate_uncalibrated_length(data, index): ... def encode_to_gray_code(value): ... def absolute_encode(value): ... ``` #### 4. 实际应用场景中的注意事项 考虑到工业现场可能存在电磁干扰等问题可能会影响到正常通讯链路稳定性以及长期运行期间由于温度漂移等原因造成参数偏离初始设定值的情况发生时应该定期执行自检程序并对异常状况作出及时响应措施例如自动重置某些关键变量回到安全区间范围内等等[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值