0.本文初心:
スイープロボット(エリアカバレッジアルゴリズムに焦点を当てる)とロジスティクスロボット(輸送スケジューリングアルゴリズムに焦点を当てる)についての私のブログ投稿にはいつも友達がいましたが、時間の制約のために完全な応答はありませんでした。この期間中、少しの空き時間があり、残業は7127から変更されました。997、ついに記事が書けるようになりました。
1.予備知識:
一日忙しいプログラマーが帰宅したときに地面がきれいになるようにする方法、スイープロボットを作ってみましょう。難しい?もちろん、難しくはありません、超シンプルです、信じられませんか?
サービス指向のモバイルロボットは、どのようにして屋内パスの完全なカバレッジとスイープを実現しますか?さわやかで清潔な家を提供します(フルバージョンの記録を委託)
1.1ロボットモデル
スイープロボットには2つの主要なモデルがあります。1つは通常の家庭用の2つの車輪を備え、もう1つはスタジアムやスーパーマーケットなどの広いスペース用の4つの車輪を備えています。
ここでは主にキネマティクスモデルの紹介ですが、ダイナミクスは少し難しいです...
注目してください!!!この種のロボットは横に歩くことも、横に歩くことも、横に歩くこともできません!!!
二輪スイープロボットは前後に動き、左右に曲がることはできますが、横方向に移動することはできません。なぜ、この機能をより専門的に説明するのか、その数学モデルは非常に明確な答えを与えます!左右のホイールがどのように回転しても、0になります。
ロボットによる正確な数学的モデルの確立は、このシステムを理解、分析、および制御する上で非常に重要な役割を果たします。
このタイプのロボットの運動モデルには、次の2つのタイプがあります。
これは、左右のホイールの速度に基づいて確立されたモデルです。通常、線形速度と角速度でロボットを分析するために使用されます。
設計時にタイプIIを使用し、ロボットを制御するためにタイプIを使用するのは非常に簡単ですが、これだけでは不十分です。環境マップも知っておく必要があります。次のセクションを参照してください->
1.2環境マップ
不思議な場所にたどり着いたときは地図なしではいられません。慣れ親しんだ場所に戻ったときは地図が記憶に残っており、ポジショニング、ナビゲーション、ルート、ミッションプランニングなどに欠かせない地図であることがわかります。とても重要です。一般的なフラットマップは次のとおりです。
大きな環境:
小さな環境:
では、スイープロボットにはどのようなマップが必要ですか???これらのマップを作成するにはどうすればよいですか???シミュレーションと物理オブジェクトにはそれぞれ2つの方法があります。ここではシミュレーションを紹介し、物理オブジェクトは記事の最後にあります。
「InitiallyMind」の場合の典型的な屋内環境が示されています。これは別のライブラリの場合です。
この環境用にロボットSLAMによって作成された環境マップは次のとおりです。詳細については、対応する詳細なブログ投稿を参照してください。
このマップを使用すると、環境内のロボットのさまざまなパスプランニング関連タスクの設計を実現できます。
パスプランニングとは何ですか???
この写真はどのようにして生まれたのですか???
1.3経路計画アルゴリズム
ナビゲーション
ポイントAからポイントBへのナビゲーションパスの計画:
カバー
簡単に言えば、カバレッジとは、ナビゲーションポイントが、ロボットがマップエリア内で移動できるすべてのポイントをカバーすることを意味し、ロボットはすべてを移動する必要があります。
この場所への訪問はなんとつらいことでしょう。
2016年のブログ投稿への具体的な参照:スイープロボットアルゴリズムのいくつかのアイデアとテスト
2.技術分析:
スイープロボットのコアは、エリアカバレッジアルゴリズムがロボットモデルと一致する必要があることです。次に例を示します。
ロボット半径とクリーニング半径が異なるエリアカバレッジマップは、次のように設定されます。
このとき、ジャムなどの極端な状況を防ぐために、小さなドアのある部屋は清掃計画の範囲外であることが発見されました。
清掃は非常に徹底的ですが、経路計画は非常に集中的です。
単純または複雑な環境のマップの場合、同じアルゴリズムを適用できる場合、そのアルゴリズムは非常に適応性があります。!!
簡単な地図:
すべてのアルゴリズムテストは、単純なものから複雑なものまでのプロセスを経る必要があり、成功のために急いではいけません。
複雑なマップ:
これが「最初の心」の環境から構築された地図です:
特定の洗浄効果は何ですか?色を区別するために、見てみましょう:
なに、掃引速度が遅すぎる???このように、ロボットは724でしか動作できません。「電気」が十分である限り、ロボットは24時間動作できます。
もっと速くなりたいのなら?このブログ投稿を参照してください:https://zhangrelay.blog.csdn.net/article/details/76850690
3台のロボットが一緒に掃除するのはどうですか?
それでも満足できない場合は、実際のロボットを使用して、スイープロボットのパスプランニングアルゴリズムをデバッグする必要がありますか?これを見てください、多分それはあなたを助けることができます:
3.参考文献
-
フルカバレッジパスプランナー(FCPP)
4.コアコード
- full_coverage_path_planner.cpp
//
// Copyright [2020] Nobleo Technology" [legal/copyright]
//
#include <list>
#include <vector>
#include "full_coverage_path_planner/full_coverage_path_planner.h"
/* *** Note the coordinate system ***
* grid[][] is a 2D-vector:
* where ix is column-index and x-coordinate in map,
* iy is row-index and y-coordinate in map.
*
* Cols [ix]
* _______________________
* |__|__|__|__|__|__|__|__|
* |__|__|__|__|__|__|__|__|
* Rows |__|__|__|__|__|__|__|__|
* [iy] |__|__|__|__|__|__|__|__|
* |__|__|__|__|__|__|__|__|
*y-axis |__|__|__|__|__|__|__|__|
* ^ |__|__|__|__|__|__|__|__|
* ^ |__|__|__|__|__|__|__|__|
* | |__|__|__|__|__|__|__|__|
* | |__|__|__|__|__|__|__|__|
*
* O --->> x-axis
*/
// #define DEBUG_PLOT
// Default Constructor
namespace full_coverage_path_planner
{
FullCoveragePathPlanner::FullCoveragePathPlanner() : initialized_(false)
{
}
void FullCoveragePathPlanner::publishPlan(const std::vector<geometry_msgs::PoseStamped>& path)
{
if (!initialized_)
{
ROS_ERROR("This planner has not been initialized yet, but it is being used, please call initialize() before use");
return;
}
// create a message for the plan
nav_msgs::Path gui_path;
gui_path.poses.resize(path.size());
if (!path.empty())
{
gui_path.header.frame_id = path[0].header.frame_id;
gui_path.header.stamp = path[0].header.stamp;
}
// Extract the plan in world co-ordinates, we assume the path is all in the same frame
for (unsigned int i = 0; i < path.size(); i++)
{
gui_path.poses[i] = path[i];
}
plan_pub_.publish(gui_path);
}
void FullCoveragePathPlanner::parsePointlist2Plan(const geometry_msgs::PoseStamped& start,
std::list<Point_t> const& goalpoints,
std::vector<geometry_msgs::PoseStamped>& plan)
{
geometry_msgs::PoseStamped new_goal;
std::list<Point_t>::const_iterator it, it_next, it_prev;
int dx_now, dy_now, dx_next, dy_next, move_dir_now = 0, move_dir_prev = 0, move_dir_next = 0;
bool do_publish = false;
float orientation = eDirNone;
ROS_INFO("Received goalpoints with length: %lu", goalpoints.size());
if (goalpoints.size() > 1)
{
for (it = goalpoints.begin(); it != goalpoints.end(); ++it)
{
it_next = it;
it_next++;
it_prev = it;
it_prev--;
// Check for the direction of movement
if (it == goalpoints.begin())
{
dx_now = it_next->x - it->x;
dy_now = it_next->y - it->y;
}
else
{
dx_now = it->x - it_prev->x;
dy_now = it->y - it_prev->y;
dx_next = it_next->x - it->x;
dy_next = it_next->y - it->y;
}
// Calculate direction enum: dx + dy*2 will give a unique number for each of the four possible directions because
// of their signs:
// 1 + 0*2 = 1
// 0 + 1*2 = 2
// -1 + 0*2 = -1
// 0 + -1*2 = -2
move_dir_now = dx_now + dy_now * 2;
move_dir_next = dx_next + dy_next * 2;
// Check if this points needs to be published (i.e. a change of direction or first or last point in list)
do_publish = move_dir_next != move_dir_now || it == goalpoints.begin() ||
(it != goalpoints.end() && it == --goalpoints.end());
move_dir_prev = move_dir_now;
// Add to vector if required
if (do_publish)
{
new_goal.header.frame_id = "map";
new_goal.pose.position.x = (it->x) * tile_size_ + grid_origin_.x + tile_size_ * 0.5;
new_goal.pose.position.y = (it->y) * tile_size_ + grid_origin_.y + tile_size_ * 0.5;
// Calculate desired orientation to be in line with movement direction
switch (move_dir_now)
{
case eDirNone:
// Keep orientation
break;
case eDirRight:
orientation = 0;
break;
case eDirUp:
orientation = M_PI / 2;
break;
case eDirLeft:
orientation = M_PI;
break;
case eDirDown:
orientation = M_PI * 1.5;
break;
}
new_goal.pose.orientation = tf::createQuaternionMsgFromYaw(orientation);
if (it != goalpoints.begin())
{
previous_goal_.pose.orientation = new_goal.pose.orientation;
// republish previous goal but with new orientation to indicate change of direction
// useful when the plan is strictly followed with base_link
plan.push_back(previous_goal_);
}
ROS_DEBUG("Voila new point: x=%f, y=%f, o=%f,%f,%f,%f", new_goal.pose.position.x, new_goal.pose.position.y,
new_goal.pose.orientation.x, new_goal.pose.orientation.y, new_goal.pose.orientation.z,
new_goal.pose.orientation.w);
plan.push_back(new_goal);
previous_goal_ = new_goal;
}
}
}
else
{
new_goal.header.frame_id = "map";
new_goal.pose.position.x = (goalpoints.begin()->x) * tile_size_ + grid_origin_.x + tile_size_ * 0.5;
new_goal.pose.position.y = (goalpoints.begin()->y) * tile_size_ + grid_origin_.y + tile_size_ * 0.5;
new_goal.pose.orientation = tf::createQuaternionMsgFromYaw(0);
plan.push_back(new_goal);
}
/* Add poses from current position to start of plan */
// Compute angle between current pose and first plan point
double dy = plan.begin()->pose.position.y - start.pose.position.y;
double dx = plan.begin()->pose.position.x - start.pose.position.x;
// Arbitrary choice of 100.0*FLT_EPSILON to determine minimum angle precision of 1%
if (!(fabs(dy) < 100.0 * FLT_EPSILON && fabs(dx) < 100.0 * FLT_EPSILON))
{
// Add extra translation waypoint
double yaw = std::atan2(dy, dx);
geometry_msgs::Quaternion quat_temp = tf::createQuaternionMsgFromYaw(yaw);
geometry_msgs::PoseStamped extra_pose;
extra_pose = *plan.begin();
extra_pose.pose.orientation = quat_temp;
plan.insert(plan.begin(), extra_pose);
extra_pose = start;
extra_pose.pose.orientation = quat_temp;
plan.insert(plan.begin(), extra_pose);
}
// Insert current pose
plan.insert(plan.begin(), start);
ROS_INFO("Plan ready containing %lu goals!", plan.size());
}
bool FullCoveragePathPlanner::parseGrid(nav_msgs::OccupancyGrid const& cpp_grid_,
std::vector<std::vector<bool> >& grid,
float robotRadius,
float toolRadius,
geometry_msgs::PoseStamped const& realStart,
Point_t& scaledStart)
{
int ix, iy, nodeRow, nodeColl;
uint32_t nodeSize = dmax(floor(toolRadius / cpp_grid_.info.resolution), 1); // Size of node in pixels/units
uint32_t robotNodeSize = dmax(floor(robotRadius / cpp_grid_.info.resolution), 1); // RobotRadius in pixels/units
uint32_t nRows = cpp_grid_.info.height, nCols = cpp_grid_.info.width;
ROS_INFO("nRows: %u nCols: %u nodeSize: %d", nRows, nCols, nodeSize);
if (nRows == 0 || nCols == 0)
{
return false;
}
// Save map origin and scaling
tile_size_ = nodeSize * cpp_grid_.info.resolution; // Size of a tile in meters
grid_origin_.x = cpp_grid_.info.origin.position.x; // x-origin in meters
grid_origin_.y = cpp_grid_.info.origin.position.y; // y-origin in meters
// Scale starting point
scaledStart.x = static_cast<unsigned int>(clamp((realStart.pose.position.x - grid_origin_.x) / tile_size_, 0.0,
floor(cpp_grid_.info.width / tile_size_)));
scaledStart.y = static_cast<unsigned int>(clamp((realStart.pose.position.y - grid_origin_.y) / tile_size_, 0.0,
floor(cpp_grid_.info.height / tile_size_)));
// Scale grid
for (iy = 0; iy < nRows; iy = iy + nodeSize)
{
std::vector<bool> gridRow;
for (ix = 0; ix < nCols; ix = ix + nodeSize)
{
bool nodeOccupied = false;
for (nodeRow = 0; (nodeRow < robotNodeSize) && ((iy + nodeRow) < nRows) && (nodeOccupied == false); ++nodeRow)
{
for (nodeColl = 0; (nodeColl < robotNodeSize) && ((ix + nodeColl) < nCols); ++nodeColl)
{
int index_grid = dmax((iy + nodeRow - ceil(static_cast<float>(robotNodeSize - nodeSize) / 2.0))
* nCols + (ix + nodeColl - ceil(static_cast<float>(robotNodeSize - nodeSize) / 2.0)), 0);
if (cpp_grid_.data[index_grid] > 65)
{
nodeOccupied = true;
break;
}
}
}
gridRow.push_back(nodeOccupied);
}
grid.push_back(gridRow);
}
return true;
}
} // namespace full_coverage_path_planner