실내 맵의 동적 재계획
이 예제는 거리 측정기와 A* 경로 플래너를 이용해 창고 맵에 동적 재계획을 수행하는 방법을 보여줍니다.
창고 맵 생성하기
창고의 평면도를 포함하는 binaryOccupancyMap
을 정의합니다. 이 정보는 창고 입구부터 패키지 픽업까지의 초기 경로를 생성하는 데 사용됩니다.
map = binaryOccupancyMap(100, 80, 1);
occ = zeros(80, 100);
occ(1,:) = 1;
occ(end,:) = 1;
occ([1:30, 51:80],1) = 1;
occ([1:30, 51:80],end) = 1;
occ(40,20:80) = 1;
occ(28:52,[20:21 32:33 44:45 56:57 68:69 80:81]) = 1;
occ(1:12, [20:21 32:33 44:45 56:57 68:69 80:81]) = 1;
occ(end-12:end, [20:21 32:33 44:45 56:57 68:69 80:81]) = 1;
setOccupancy(map, occ)
figure
show(map)
title('Warehouse Floor Plan')
픽업까지의 경로 계획하기
plannerHybridAStar
를 만들고 이전에 만든 평면도를 사용하여 초기 경로를 생성합니다.
% Create map that will be updated with sensor readings estMap = occupancyMap(occupancyMatrix(map)); % Create a map that will inflate the estimate for planning inflateMap = occupancyMap(estMap); vMap = validatorOccupancyMap; vMap.Map = inflateMap; vMap.ValidationDistance = .1; planner = plannerHybridAStar(vMap, 'MinTurningRadius', 4); entrance = [1 40 0]; packagePickupLocation = [63 44 -pi/2]; route = plan(planner, entrance, packagePickupLocation); route = route.States; % Get poses from the route. rsConn = reedsSheppConnection('MinTurningRadius', planner.MinTurningRadius); startPoses = route(1:end-1,:); endPoses = route(2:end,:); rsPathSegs = connect(rsConn, startPoses, endPoses); poses = []; for i = 1:numel(rsPathSegs) lengths = 0:0.1:rsPathSegs{i}.Length; [pose, ~] = interpolate(rsPathSegs{i}, lengths); poses = [poses; pose]; end figure show(planner) title('Initial Route to Package')
경로에 장애물 배치하기
지게차가 패키지까지 이동하게 될 경로에 존재하는 장애물을 맵에 추가합니다.
obstacleWidth = 6;
obstacleHeight = 11;
obstacleBottomLeftLocation = [34 49];
values = ones(obstacleHeight, obstacleWidth);
setOccupancy(map, obstacleBottomLeftLocation, values)
figure
show(map)
title('Warehouse Floor Plan with Obstacle')
거리 측정기 지정하기
rangeSensor
객체를 사용하여 거리 측정기를 생성합니다.
rangefinder = rangeSensor('HorizontalAngle', pi/3);
numReadings = rangefinder.NumReadings;
거리 측정기 측정값을 기준으로 경로 업데이트하기
경로 플래너를 통해 얻은 자세를 사용하여 지게차를 앞으로 전진시킵니다. 거리 측정기에서 찾은 새로운 장애물 탐지 정보를 가져와서 추정된 맵에 삽입합니다. 업데이트된 맵에서 경로가 점유되어 있으면 경로를 다시 계산합니다. 목표 지점에 도달할 때까지 반복합니다.
% Setup visualization. helperViz = HelperUtils; figure show(inflateMap); hold on robotPatch = helperViz.plotRobot(gca, poses(1,:)); rangesLine = helperViz.plotScan(gca, poses(1,:), ... NaN(numReadings,1), ones(numReadings,1)); axesColors = get(gca, 'ColorOrder'); routeLine = helperViz.plotRoute(gca, route, axesColors(2,:)); traveledLine = plot(gca, NaN, NaN); title('Forklift Route to Package') hold off idx = 1; tic; while idx <= size(poses,1) % Insert range finder readings into estimated map. [ranges, angles] = rangefinder(poses(idx,:), map); insertRay(estMap, poses(idx,:), ranges, angles, ... rangefinder.Range(end)); % Reset and reinflate planning map setOccupancy(inflateMap, getOccupancy(estMap)); inflate(inflateMap,1,'grid'); % Update visualization. show(inflateMap, 'FastUpdate', true); helperViz.updateWorldMap(robotPatch, rangesLine, traveledLine, ... poses(idx,:), ranges, angles) drawnow % Regenerate route when obstacles are detected in the current one. isRouteOccupied = any(checkOccupancy(inflateMap, poses(:,1:2))); if isRouteOccupied && (toc > 0.1) % Calculate new route. route = plan(planner, poses(idx,:), packagePickupLocation); route = route.States; % Get poses from the route. startPoses = route(1:end-1,:); endPoses = route(2:end,:); rsPathSegs = connect(rsConn, startPoses, endPoses); poses = []; for i = 1:numel(rsPathSegs) lengths = 0:0.1:rsPathSegs{i}.Length; [pose, ~] = interpolate(rsPathSegs{i}, lengths); poses = [poses; pose]; %#ok<AGROW> end routeLine = helperViz.updateRoute(routeLine, route); idx = 1; tic; else idx = idx + 1; end end