Sensor-Modell zur Simulation eines LIDAR-Range-Sensors

Datei: sense.m

function [ rangeVec, coordinateVec, endOfRayCoordVec ] = sense(xSensor, ySensor, thetaSensor, alphaStart, alphaEnd,
							       map, numberRays, sensorRange, sensorNoiseVar)
%SENSE    LIDAR range sensor.
%   SENSE(x,y,theta,start,end,map,n,senseRange,noiseVar) simulates a LIDAR 
%   range sensor at position ('x','y') and orientation 'theta' in the
%   binary map 'map'. Orientation is set in radians.
%   The LIDAR sensor casts 'n' rays uniformly distributed between the
%   angles 'start' and 'end'. Positive angles are defined clockwise
%   measured in the local coordinate system in sensor x-direction (so a
%   'theta' equal to zero has the same orientation in the map as the sensor
%   itself).
%    NOTE:      positive x-axis in map points to the right, positive y-axis
%            points downwards, thus positive angles are clockwise.
%   The sensor can only sense objects up to the distance 'senseRange'
%   measured in units of 'map'. Assuming 'map' was created from a pixel
%   image, then the unit is pixels. If 1 pixel represents 1cm, then
%   'senseRange' will obviously be set in cm.
%   Sensor noise is modelled as additive 1D Gaussian with zero mean and
%   variance 'noiseVar' for each dimension separately. A value of zero
%    indicates no noise and thus perfect measurement.
%   The first return value is a n-dimensional vector containing the ranges
%   (euclidian distance) from the sensor to a sensed object within its
%   sensor range 'senseRange'. If no object was hit by sensor ray, the
%   range is returned as 'Inf'. Please keep in mind for further processing
%   of the data.
%   The second return value is a n-by-2-dimensional matrix. The first
%   column containing x-coordinates, the second column containing
%   y-coordinates of the sensed object. Both measured in the global
%   coordinate system defined by 'map'. If no object was sensed, the
%   coordinates are both set to 'Inf'. The second return value is optional.
%   The third return value is a a n-by-2-dimensional matrix. It contains
%   the x- and y-coordinates of the farthest points of each ray. The first
%   column contains all the x-coordinates, the second column all the
%   y-coordinates. NOTE: No checking on the coordinates being inside the
%   map boundaries will be done whatsoever.
%   Example:
%           load('mapLoops.mat');
%           xRobot  = int32(100);
%           yRobot  = int32(80);
%           heading = 0;
%           noRays  = int32(8);
%           [myRange,myCoord,myEndCoord] = sense(xRobot, yRobot, heading, -pi/2, pi/2, mapLoops, noRays, 500, 0.0);
%           figure();
%           imshow(mapLoops);
%           hold on;
%           plot(xRobot, yRobot, 'bh');
%           for i = 1:length(myEndCoord)
%               plot( [xRobot, myEndCoord(i,1)], [yRobot, myEndCoord(i,2)], 'g-');
%           end
%           plot(myCoord(:,1), myCoord(:,2), 'rx');
%   Copyright 2012 Jonathan Bryan Schmalhofer
%   $Revision: 0.3 $  $Date: 2012-09-02 23:33:00 $

    %% Check & Recondition Input Arguments
    % check number input arguments
    if nargin ~= 9
        error('Wrong number of input arguments. All nine have to be set.');
    % Coordinates of sensor need to be integers
    if ~isinteger(xSensor) || ~isinteger(ySensor)        
        error('Sensor positions x and y need both to be integers.');
    % if orientation of sensor was not set but is empty, we set it to zero
    if isempty(thetaSensor)
        thetaSensor = 0;
    % if start angle of sensor was not set but is empty, we set it to zero
    if isempty(alphaStart)
        alphaStart = 0;
    % if end angle of sensor was not set but is empty, we set it to zero
    if isempty(alphaEnd)
        alphaEnd = 0;
    % angles with an absolute value bigger than 2*pi make no sense, so we use modulo-division to truncate them
    %     NOTE:    we allow negative values for angles, so to not mess up with the signs, instead of mod(a,b) we use
    %            MATLAB function rem(a,b)
    thetaSensor    = rem(thetaSensor, 2.0 * pi);
    alphaStart     = rem(alphaStart, 2.0 * pi);
    alphaEnd     = rem(alphaEnd, 2.0 * pi);
    % check if map is empty
    if isempty(map)
        error('The argument `map` must not be empty.');
    % check if map is a binary map (type logical in MATLAB)
    if ~islogical(map)
        error('mapNoBinary', 'The argument `map` needs to be a binary matrix (MATLAB type `logical`).');
    % check number of arrays
    if numberRays <= 0 || ~isinteger(numberRays) || isempty(numberRays)
        error('The number of rays to be casted must be a positive non-zero integer. It must not be empty.');
    % check sensor range
    if sensorRange <= 0 || isempty(sensorRange)
        error('The sensor range must be positive non-zero and must not be empty.');
    % check sensor noise - set to zero if not set
    if isempty(sensorNoiseVar)
        sensorNoiseVar = 0;
    % check sensor noise for valid value
    if sensorNoiseVar < 0
        error('sensorNoiseNeg', 'The sensor noise must be zero or positive.');
    %% Sense Step

    % check if we are stuck in a wall, if yes: return just zeros (no further calculation needed)
    if map(ySensor,xSensor) == 1
        rangeVec         = zeros(numberRays,1);
        coordinateVec    = zeros(numberRays,2);
        endOfRayCoordVec= zeros(numberRays,2);

    % initialize the return-vectors with 'Inf'
    rangeVec         = Inf(numberRays,1);
    coordinateVec    = Inf(numberRays,2);
    % convert sensor coordinates and 'numberRays'to double, so they can be
    % used for multiplication with other doubles
    numberRays      = double(numberRays);
    xSensor         = double(xSensor);
    ySensor         = double(ySensor);

    % if two or more rays are casted, they are uniformly distributed and casted starting with and at
	% 'alphaStart' clockwise up to 'alphaEnd'
    if numberRays >= 2
        deltaAlpha = (alphaEnd - alphaStart) / (numberRays - 1);
        deltaAlpha = 0;
    % vector containing angles of all n arrays in global coordinate system
    % (first entry is the ray at angle 'alphaStart' relative to 'thetaSensor',
    % next entry is rotated by 'deltaAlpha' clockwise and so on...)
    allAlpha    = ones(numberRays,1) * (thetaSensor + alphaStart) + [0:(numberRays-1)]' .* deltaAlpha;

    radiusSteps    = ceil( sqrt( size(map,1)^2 + size(map,2)^2 ) );        % how many points should be picked along
	% the ray (we calculate the length of the diagonal of the map and round up - assuming that is the longest
	% possible distance)
    % matrices containing all points points X and Y along the rays - each row in a matrix contains all points of
	% a dimension (X or Y) along a ray starting from the sensor
    allX        = ones(numberRays,radiusSteps) * xSensor + (ones(numberRays,1) * linspace(0,sensorRange,
				radiusSteps)) .* (cos(allAlpha)*ones(1,radiusSteps));
    allY        = ones(numberRays,radiusSteps) * ySensor + (ones(numberRays,1) * linspace(0,sensorRange,
				radiusSteps)) .* (sin(allAlpha)*ones(1,radiusSteps));
    % save coordinates of endpoints of all rays for third return vector
    endOfRayCoordVec(:,1) = allX(:,end);
    endOfRayCoordVec(:,2) = allY(:,end);
    % because all our coordinates above are indices of matrix 'map', we have to
    % round them to integers and mark all indices which would be out of bounds of 'map'
    maxX            = size(map, 2);
    maxY            = size(map, 1);
    allX            = round(allX);        % first we round
    allX(allX<1)    = NaN;                % now all indices which are to small are marked as 'NaN'
    allX(allX>maxX)    = NaN;                % now all indices which are to big are marked as 'NaN'
    allY            = round(allY);        % first we round
    allY(allY<1)    = NaN;                % now all indices which are to small are marked as 'NaN'
    allY(allY>maxY)    = NaN;                % now all indices which are to big are marked as 'NaN'
    % loop all arays (unfortunately it seems it can't be done with at least one for-loop)
    for i = 1:numberRays
        % now save all points (x,y) in a long (n*radiusSteps)-by-2-matrix
        allPoints         = [allX(i,:)',allY(i,:)'];
        % delete every point that has at least one entry 'NaN' - WHY? Because the NaN indicates it is outside the
		% bounds of 'map'
        allPoints         = allPoints(~any(isnan(allPoints), 2), :);
        % get indices in 'map' of the points specified in 'allPoints' - these indices represent the pixels the
		% ray reaches
        % NOTE: instead of row and column index we use linear index
		% (see: )
        linIndex = sub2ind(size(map), allPoints(:,2), allPoints(:,1));
        % now get the first entry along the ray that hits a wall
        num = find(map(linIndex) == 1, 1, 'first');
        % if we found one...
        if ~isempty(num)
            % ...we transform the linear index back to a row and column (equals y and x coordinates) and save it
            [r,c]                 = ind2sub(size(map),linIndex(num));
            c                   = round( c + randn(1) * sensorNoiseVar );    % add noise
            r                   = round( r + randn(1) * sensorNoiseVar );    % add noise
            c(c<=0)             = 1;
            r(r<=0)             = 1;
            coordinateVec(i,:)    = [c, r];
            % furthermore, we calculate the euclidian distance from that point where we hit the wall up to the sensor
            rangeVec(i)            = sqrt( (coordinateVec(i,1)-xSensor)^2 + (coordinateVec(i,2)-ySensor)^2 );

Sensor-Modell zur Simulation eines LIDAR-Range-Sensors oder ähnlichen Range-Sensoren

Umsetzung in MatLab, weitestgehend vektorisierte Umsetzung

  • Simples Sensor-Modell für LIDAR u.ä.
  • vektorisierte Befehle statt for-loops
  • schnellere Ausführung als andere Modelle
  • beliebige Anzahl an Sensoren möglich
  • beliebige Sensorausrichtung möglich
  • beliebige Sensorreichweite möglich