Main Content

Esta página se ha traducido mediante traducción automática. Haga clic aquí para ver la última versión en inglés.

Filtro Design Fusion para sensores personalizados

Este ejemplo presenta cómo personalizar los modelos de sensores utilizados con un objeto insEKF .

Usando el objeto insEKF, puede fusionar datos de medición de múltiples tipos de sensores usando los modelos de sensores INS integrados, incluido el insAccelerometer, insGyrospace, insMagnetomer y insGPS objetos. Aunque estos objetos de sensores cubren una variedad de modelos de sensores INS, es posible que desee fusionar datos de medición de un tipo diferente de sensor. Usando el objeto insEKF a través de un marco extendido, puede definir sus propios modelos de sensores y modelos de movimiento utilizados por el filtro. En este ejemplo, aprenderá a personalizar tres modelos de sensores en unos pocos pasos. Puede aplicar pasos similares para definir un modelo de movimiento.

Este ejemplo requiere la Sensor Fusion and Tracking Toolbox o la Navigation Toolbox. Este ejemplo también utiliza opcionalmente MATLAB Coder para acelerar el ajuste del filtro.

Descripción del problema

Considere que está intentando estimar la posición de un objeto que se mueve en una dimensión. Para estimar la posición, se utiliza un sensor de velocidad y se fusionan los datos de ese sensor para determinar la posición. Normalmente, la aplicación de este enfoque puede conducir a una estimación deficiente de la posición porque pequeños errores en la estimación de la velocidad pueden integrarse para formar errores más grandes en la estimación de la posición. Como se muestra más adelante en este ejemplo, combinar mediciones de múltiples sensores puede mejorar los resultados.

Para comenzar, defina una trayectoria ground-truth básica simple para el objeto y visualice cómo se mueve.

groundTruth = exampleHelperMakeGroundTruth();
plot(groundTruth.Time, groundTruth.Position);
xlabel("Time (s)");
ylabel("Position (m)");
title("Ground Truth");

Figure contains an axes object. The axes object with title Ground Truth, xlabel Time (s), ylabel Position (m) contains an object of type line.

snapnow;

Sensor de velocidad sencillo

Considere los datos de un sensor de velocidad simple que mide la velocidad pero está corrompido por un pequeño sesgo y un ruido blanco gaussiano aditivo.

velBias = exampleHelperVelocityWithBias(groundTruth);

Para el enfoque más simple, cree un filtro que trate las mediciones de velocidad simplemente como velocidad más ruido blanco. Es probable que esto no produzca resultados deseables, pero vale la pena probar primero el modelo más simple. Para crear un modelo de sensor de velocidad para el objeto insEKF , debe definir una clase que herede de la clase de interfaz positioning.INSSensorModel . Como mínimo, debe implementar un método measurement que toma el objeto sensor y el objeto de filtro como entradas. El método measurement devuelve un vector z as una estimación de la medición del sensor, basada en variables de estado.

classdef exampleHelperVelocitySensor < positioning.INSSensorModel
%EXAMPLEHELPERVELOCITYSENSOR A simple velocity sensor for insEKF
% This class is for internal use only. It may be removed in the future.

%   Copyright 2021 The MathWorks, Inc.    

    methods 
        function z = measurement(~, filt)
            % The measurement is just the velocity estimate
            z = stateparts(filt, 'Velocity');          
        end
    end
end

Este es el mínimo requerido para definir un sensor que funcione con el insEKF object. Opcionalmente, puede definir la medida jacobiana en el método measurementJacobian , que puede mejorar la velocidad de cálculo y posiblemente mejorar la precisión de la estimación. Sin implementar el método measurementJacobian , estás confiando en el insEKF para calcular numéricamente el jacobiano.

El filtro se construye con un modelo de movimiento de velocidad constante 1-D simple definido usando una clase BasicConstantVelocityMotion . Esta clase es un ejemplo de cómo implementar sus propios modelos de movimiento personalizados heredando de la clase de interfaz positioning.INSMotionModel . La definición de modelos de movimiento personalizados requiere implementar solo un subconjunto de los métodos necesarios para implementar un modelo de sensor personalizado.

Ahora puede crear el filtro, ajustar sus parámetros de ruido y ver cómo funciona. Puede nombrar el sensor de velocidad "VelocityWithBias" usando el objeto insOptions .

basicOpts = insOptions(SensorNamesSource="property", SensorNames={'VelocityWithBias'});
basicfilt = insEKF(exampleHelperVelocitySensor, exampleHelperConstantVelocityMotion, ...
    basicOpts);

El objeto comienza en reposo con una posición cero. Establezca la covarianza de estado para la posición y la velocidad en un valor más bajo antes de realizar el ajuste.

statecovparts(basicfilt, "Position", 1e-2);
statecovparts(basicfilt, "Velocity", 1e-2);

Ajusteel filtro después de especificar una estructura de ruido y un objeto tunerconfig .

mn = tunernoise(basicfilt);
tunerIters = 30;
cfg = tunerconfig(basicfilt, MaxIterations=tunerIters, Display='none');
tunedmn = tune(basicfilt, mn, velBias, groundTruth, cfg);

estBasic = estimateStates(basicfilt, velBias, tunedmn);
figure;
plot(groundTruth.Time, groundTruth.Position, 'g', estBasic.Time, ...
    estBasic.Position, 'k')
title("Basic Velocity Sensor Estimate");
err = sqrt(mean((estBasic.Position - groundTruth.Position).^2));
subtitle("RMS Error = " + err);
legend("Ground Truth", "Basic Sensor Estimate");

Figure contains an axes object. The axes object with title Basic Velocity Sensor Estimate contains 2 objects of type line. These objects represent Ground Truth, Basic Sensor Estimate.

snapnow;

Los resultados de la estimación son deficientes como se esperaba, ya que el filtro no tiene en cuenta el sesgo. Cuando el sesgo de velocidad no contabilizado se integra en la posición, la estimación diverge rápidamente de ground-truth.

Sensor de velocidad con polarización

Ahora puedes explorar un modelo de sensor más complejo que tenga en cuenta el sesgo del sensor. Para hacer esto, define un método sensorstates para informar al insEKF que rastree el sesgo del sensor en su vector de estado, guardado en el State propiedad del filtro. Puede consultar el sesgo del sensor con la función de objeto stateparts del filtro y utilizar la estimación de sesgo en la función measurement .

classdef exampleHelperVelocitySensorWithBias < positioning.INSSensorModel
%EXAMPLEHELPERVELOCITYSENSORWITHBIAS Velocity sensor assuming constant bias
% This class is for internal use only. It may be removed in the future.

%   Copyright 2021 The MathWorks, Inc.    

    methods 
        function s = sensorstates(~,~)
            % Assume there is a constant bias corrupting the velocity
            % measurement. Estimate that bias as part of the filter
            % computation.
            s = struct('Bias', 0);
        end        
        function z = measurement(sensor, filt)
            % Measurement is velocity plus bias. Obtain the velocity from
            % the filter state.
            velocity = stateparts(filt, 'Velocity');

            % Obtain the sensor bias from the filter state by directly
            % using the sensor object as an input to the stateparts object
            % function. In this way, knowledge of the SensorName associated
            % with this sensor is not required. See the reference page for
            % stateparts for more details.
            bias = stateparts(filt, sensor, 'Bias');
          
            % Form the measurement
            z = velocity + bias;
        end        
        function dzdx = measurementJacobian(sensor, filt)
            % Compute the Jacobian as the partial derivates of the Bias
            % state relative to all other states.
            N = numel(filt.State);  % Number of states

            % Initialize a matrix of partial derivatives. This matrix has
            % one row because the sensor state is a scalar.
            dzdx = zeros(1,N);  
 
            % The partial derivative of z with respect to the Bias is 1. So
            % put a 1 at the Bias index in the dzdx matrix:
            bidx = stateinfo(filt, sensor, 'Bias');
            dzdx(:,bidx) = 1;

            % The partial derivative of z with respect to the Velocity is
            % also 1. So put a 1 at the Bias index in the dzdx matrix:
            vidx = stateinfo(filt, 'Velocity');
            dzdx(:,vidx) = 1;
        end
    end

end

En lo anterior, implementó el método measurementJacobian en lugar de depender de un jacobiano numérico. La matriz jacobiana contiene las derivadas parciales de la medición del sensor con respecto al estado del filtro, la cual es una matriz M-por- N , donde M es el número de elementos en el vector devuelto por el método measurement y N es el número de estados del filtro. Ahora puedes crear un filtro con el sensor personalizado.

sensorVelWithBias = exampleHelperVelocitySensorWithBias;
velWithBiasFilt = insEKF(sensorVelWithBias, ...
    exampleHelperConstantVelocityMotion, basicOpts);

Puede establecer el estado de polarización inicial del filtro con una estimación de la polarización, basada en los datos recopilados mientras el sensor está estacionario, utilizando la función de objeto stateparts .

stationaryLen = 1e7;
stationary = timetable(zeros(stationaryLen,1), zeros(stationaryLen,1), ...
    TimeStep=groundTruth.Properties.TimeStep, ...
    VariableNames={'Position', 'Velocity'});
biasCalibrationData = exampleHelperVelocityWithBias(stationary);

stateparts(velWithBiasFilt,  sensorVelWithBias, "Bias", ...
    mean(biasCalibrationData.VelocityWithBias));

Nuevamente, establezca la covarianza de estado para estos estados en valores más pequeños antes de realizar el ajuste.

statecovparts(velWithBiasFilt, "Position", 1e-2);
statecovparts(velWithBiasFilt, "Velocity", 1e-2);

Puede ajustar este filtro como antes, pero también puede ajustarlo más rápido usando MATLAB Coder. Si bien la función de objeto tune no admite la generación de código, puede usar una función de coste acelerado por MEX para aumentar considerablemente la velocidad de ajuste. Puede especificar la función tune para usar una función de coste personalizada a través de la entrada tunerconfig . El objeto insEKF tiene funciones de objeto para ayudar a crear una función de coste personalizada. La función de objeto createTunerCostTemplate crea una función de coste, que intenta minimizar el error RMS de las estimaciones de estado, en un nuevo documento en el Editor. Luego puede generar código para esa función con la ayuda de la función de objeto tunerCostFcnParam , que crea un ejemplo del primer argumento de una función de coste.

% Use MATLAB Coder to accelerate tuning by MEXing the cost function.
% To run the MATLAB Coder accelerated path, prior to running the example,
% type:
%   exampleHelperUseCodegenForCost(true);
% To avoid using MATLAB Coder, prior to the example, type:
%   exampleHelperUseCodegenForCost(false);

useCodegenForTuning = exampleHelperUseCodegenForCost();

if useCodegenForTuning
    createTunerCostTemplate(velWithBiasFilt); % A new cost function in the editor
    exampleHelperSaveCostFunction;
    p = tunerCostFcnParam(velWithBiasFilt); 
    % Now generate a mex file
    codegen tunercost.m -args {p, velBias, groundTruth};
    % Use the Custom Cost Function
    cfg2 = tunerconfig(velWithBiasFilt, MaxIterations=tunerIters, ...
        Cost="custom", CustomCostFcn=@tunercost_mex, Display='none');
else
    % Use the default cost function
    cfg2 = tunerconfig(velWithBiasFilt, MaxIterations=tunerIters, ...
        Display='none'); %#ok<*UNRCH> 
end
mn = tunernoise(velWithBiasFilt);
tunedmn = tune(velWithBiasFilt, mn, velBias, groundTruth, cfg2);
estWithBias = estimateStates(velWithBiasFilt, velBias, tunedmn);
figure;
plot(groundTruth.Time, groundTruth.Position, 'k', estWithBias.Time, ...
    estWithBias.Position, 'b')
title("Velocity Sensor Estimate With Bias Tracking");
err = sqrt(mean((estWithBias.Position - groundTruth.Position).^2));
subtitle("RMS Error = " + err);

Figure contains an axes object. The axes object with title Velocity Sensor Estimate With Bias Tracking contains 2 objects of type line.

snapnow;

Según los resultados, la estimación de la posición ha mejorado pero aún no es la ideal. El filtro estima el sesgo, pero cualquier error en la estimación del sesgo se trata como velocidad real, lo que corrompe la estimación de la posición. Un modelo de sensor más sofisticado puede mejorar aún más los resultados.

Sensor de velocidad con ruido Gauss-Markov

Considere un nuevo modelo de sensor de velocidad que está corrompido con ruido Gauss-Markov de primer orden.

velGM = exampleHelperVelocityWithGaussMarkov(groundTruth);
figure
plot(velGM.Time, velGM.VelocityWithGM, 'r', groundTruth.Time, ...
    groundTruth.Velocity, 'k');
title("Velocity Sensor With Gauss-Markov Noise");
xlabel("Time (s)");
ylabel("Velocity (m/s)");
legend("True Velocity", "Velocity With G-M Noise");

Figure contains an axes object. The axes object with title Velocity Sensor With Gauss-Markov Noise, xlabel Time (s), ylabel Velocity (m/s) contains 2 objects of type line. These objects represent True Velocity, Velocity With G-M Noise.

snapnow;

El ruido de Gauss-Markov de primer orden se puede modelar en tiempo discreto como:

xk=(1-β*dt)xk-1+wk

donde wk es ruido blanco. La formulación de tiempo continuo equivalente es:

ddtx=-βx(t)+w(t)

donde β es la constante de tiempo del proceso, y w(t) y x(t) son las versiones en tiempo continuo del estado discreto xk y ruido de proceso wk, respectivamente.

Puede comprobar que se trata de un par discreto-continuo válido aplicando la integración de Euler en la segunda ecuación entre el tiempo k y k-1 para obtener la primera ecuación. Consulte la Referencia [1] para obtener una explicación más detallada. Para simplificar, establezca β como 0,002 para este ejemplo. Dado que el proceso Gauss-Markov evoluciona con el tiempo, es necesario implementarlo en el método stateTransition de la clase positioning.INSSensorModel . El método stateTransition describe cómo los elementos de estado descritos en sensorstates evolucionan con el tiempo. La función stateTransition genera una estructura con los mismos campos que la estructura de salida del método sensorstates , y cada campo contiene la derivada del estado con respecto al tiempo. Cuando el método stateTransition no se implementa explícitamente, el objeto insEKF asume que el estado es constante en el tiempo.

classdef exampleHelperVelocitySensorWithGM < positioning.INSSensorModel
%EXAMPLEHELPERVELOCITYSENSORWITHGM Velocity sensor with Gauss-Markov noise
% This class is for internal use only. It may be removed in the future.

%   Copyright 2021 The MathWorks, Inc.    

    properties (Constant)
        Beta =0.002; % First-order Gauss-Markov time constant
    end
    methods
        function s = sensorstates(~,~)
            % Assume the velocity measurement is corrupted by a first-order
            % Gauss-Markov random process. Estimate the state of the
            % Gauss-Markov process as part of the filtering.
            s = struct('GMProc', 0);
        end
        function z = measurement(sensor, filt)
            % Measurement of velocity plus Gauss-Markov process noise
            velocity = stateparts(filt, 'Velocity');
            gm = stateparts(filt, sensor, 'GMProc');
          
            z = velocity  + gm;
        end
        function dhdx = measurementJacobian(sensor, filt)
            % Compute the Jacobian of partial derivates of the measurement
            % relative to all states.
            N = numel(filt.State);  % Number of states

            % Initialize a matrix of partial derivatives. The matrix only
            % has one row because the sensor state is a scalar.
            dhdx = zeros(1,N);  

            % Get the indices of the Velocity and GMProc states in the
            % state vector.
            vidx = stateinfo(filt, 'Velocity');
            gmidx = stateinfo(filt, sensor, 'GMProc');
            dhdx(:,gmidx) = 1;
            dhdx(:,vidx) = 1;
        end
        function sdot = stateTransition(sensor, filt, ~, varargin)
            % Define the state transition function for each sensorstates
            % defined in this class. Since the insEKF is a
            % continuous-discrete EKF, the output is the derivative of the
            % state in the form of a structure with field names matching
            % the field names of the output structure of sensorstates. The
            % first-order Gauss-Markov process has a state transition of
            %      sdot = -1*beta*s
            % where beta is the reciprocal of the time constant of the
            % process.
            sdot.GMProc = -1*(sensor.Beta) * ...
                stateparts(filt, sensor, 'GMProc');                
        end
        function dfdx = stateTransitionJacobian(sensor, filt, ~, varargin)
            % Compute the Jacobian of the partial derivates of the GMProc
            % state transition relative to all states.

            % Find the number of states and initialize an array of the same
            % size with all elements equal to zero.
            N = numel(filt.State);
            dfdx.GMProc = zeros(1,N);
         
            % Find the index of the Gauss-Markov state, GMProc, in the
            % state vector.
            gmidx = stateinfo(filt, sensor, 'GMProc');

            % The derivative of the GMProc stateTransition function with
            % respect to GMProc is -1*Beta
            dfdx.GMProc(gmidx) = -1*(sensor.Beta);
        end
    end
end

Tenga en cuenta que aquí también se implementa un método stateTranstionJacobian . El método genera una estructura con sus campos que contienen las derivadas parciales de sensorstates con respecto al vector de estado del filtro. Si esta función no está implementada, el insEKF calcula estas matrices numéricamente. Puede intentar comentar esta función para ver los efectos del uso del jacobiano numérico.

gmOpts = insOptions(SensorNamesSource="property", SensorNames={'VelocityWithGM'});
filtWithGM = insEKF(exampleHelperVelocitySensorWithGM, ...
    exampleHelperConstantVelocityMotion, gmOpts);

Puede volver a ajustar el filtro siguiendo cualquiera de los procesos anteriores. Calcule el estado utilizando la función de objeto estimateStates .

statecovparts(filtWithGM, "Position", 1e-2);
statecovparts(filtWithGM, "Velocity", 1e-2);
gmMeasNoise = tunernoise(filtWithGM);
cfg3 =  tunerconfig(filtWithGM, MaxIterations=tunerIters, Display='none');
gmTunedNoise = tune(filtWithGM, gmMeasNoise, velGM, groundTruth, cfg3);

estGM = estimateStates(filtWithGM, velGM, gmTunedNoise);
figure
plot(estGM.Time, estGM.Position, 'c', groundTruth.Time, ...
    groundTruth.Position, 'k');
title("Velocity Sensor Estimate With Gauss-Markov Noise Tracking");
err = sqrt(mean((estGM.Position - groundTruth.Position).^2));
subtitle("RMS Error = " + err);

Figure contains an axes object. The axes object with title Velocity Sensor Estimate With Gauss-Markov Noise Tracking contains 2 objects of type line.

snapnow;

Nuevamente el filtro no puede distinguir entre el ruido de Gauss-Markov y la velocidad real. El ruido de Gauss-Markov no es observable de forma independiente y, por tanto, corrompe la estimación de la posición. Sin embargo, puede reutilizar los sensores diseñados anteriormente con datos de medición de múltiples sensores para obtener una mejor estimación de la posición.

Fusión multisensor

Para obtener una estimación precisa de la posición, se utilizan varios sensores. Aplica los modelos de sensores que ya ha diseñado en un nuevo objeto de filtro insEKF .

combinedOpts =  insOptions(SensorNamesSource="property", SensorNames={'VelocityWithBias', 'VelocityWithGM'});
sensorWithBias = exampleHelperVelocitySensorWithBias;
sensorWithGM = exampleHelperVelocitySensorWithGM;
filtCombined = insEKF(sensorWithBias, sensorWithGM, ...
    exampleHelperConstantVelocityMotion, combinedOpts);

% Combine the two sets of sensor measurements.
sensorData = synchronize(velBias, velGM, "first");
% Initialize the bias state with an estimate.
stateparts(filtCombined,  sensorWithBias, "Bias", ...
    mean(biasCalibrationData.VelocityWithBias));

mnCombined = tunernoise(filtCombined);
cfg4 =  tunerconfig(filtCombined, MaxIterations=tunerIters, Display='none');
combinedTunedNoise = tune(filtCombined, mnCombined, sensorData, ...
    groundTruth, cfg4);

estBoth = estimateStates(filtCombined, sensorData, combinedTunedNoise); 
figure;
plot(estBoth.Time, estBoth.Position, 'm', groundTruth.Time, ...
    groundTruth.Position, 'k');
title("Multi-Sensor Estimate");
err = sqrt(mean((estBoth.Position - groundTruth.Position).^2));
subtitle("RMS Error = " + err);

Figure contains an axes object. The axes object with title Multi-Sensor Estimate contains 2 objects of type line.

snapnow;

Debido a que el filtro utiliza dos sensores, tanto la polarización como el ruido de Gauss-Markov son observables. Como resultado, el filtro obtiene una estimación de velocidad más precisa y, por tanto, una estimación de posición más precisa.

Conclusión

En este ejemplo, aprendió el marco insEKF y cómo personalizar los modelos de sensores utilizados con el objeto insEKF. Implementar un modelo de movimiento personalizado es casi el mismo proceso que implementar un nuevo sensor, excepto que se hereda de la clase de interfaz positioning.INSMotionModel en lugar de la clase de interfaz positioning.INSSensorModel . Diseñar filtros de fusión de sensores personalizados es sencillo con el marco insEKF y es sencillo construir sensores reutilizables para usar en todos los proyectos.

Referencias

[1] Una comparación entre diferentes modelos de errores de MEMS aplicados a sistemas integrados GPS/INS. A. Quinchia, G. Falco, E. Falletti, F. Dovis, C. Ferrer, Sensores 2013.