【ROS-Navigation】系列(X)——map_server源碼解讀

一、概述

下圖為 map_server 整個(gè)包的內(nèi)容逃默,主要分為三塊內(nèi)容:map_server(加載地圖)、image_loader 和 map_saver(保存地圖)。

其功能就是地圖服務(wù)器咧欣,原始代碼圖像數(shù)據(jù)經(jīng)由 SDL_Image 讀取,map_server 節(jié)點(diǎn)主要就是為了加載 pgm 格式的地圖轨帜,用 yaml 文件描述的魄咕,同時(shí)發(fā)布 map_metadata 和 map 話題,以及 static_map 服務(wù) 蚌父,其目的都是為了方便其他節(jié)點(diǎn)獲取到地圖數(shù)據(jù)哮兰。

二、文件結(jié)構(gòu)分析

查看包下的 CMakeLists.txt 文件苟弛,查看當(dāng)前包添加了幾個(gè)可執(zhí)行文件 add_executable喝滞。當(dāng)前包有兩個(gè)可執(zhí)行文件,map_server(涉及到源文件 main.cpp)和 map_saver(涉及到源文件map_saver.cpp)膏秫。

add_executable(map_server src/main.cpp)
add_dependencies(map_server ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
target_link_libraries(map_server
    map_server_image_loader
    ${YAMLCPP_LIBRARIES}
    ${catkin_LIBRARIES}
)

add_executable(map_server-map_saver src/map_saver.cpp)
add_dependencies(map_server-map_saver ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
set_target_properties(map_server-map_saver PROPERTIES OUTPUT_NAME map_saver)
target_link_libraries(map_server-map_saver
    ${catkin_LIBRARIES}
)

三右遭、map_server

首先從程序運(yùn)行、函數(shù)調(diào)用順序的角度出發(fā)對(duì) map_server節(jié)點(diǎn)進(jìn)行學(xué)習(xí)。節(jié)點(diǎn)的起源在 main 函數(shù)窘哈,如下言蛇。

int main(int argc, char **argv)
{
    ros::init(argc, argv, "map_server", ros::init_options::AnonymousName);
    // Part 1 -------------------------------------
    if(argc != 3 && argc != 2)
    {
        ROS_ERROR("%s", USAGE);
        exit(-1);
    }
    if (argc != 2) {
        ROS_WARN("Using deprecated map server interface. Please switch to new interface.");
    }
    std::string fname(argv[1]);
    double res = (argc == 2) ? 0.0 : atof(argv[2]);
    // Part 2 -------------------------------------
    try
    {
        MapServer ms(fname, res);
        ros::spin();
    }
    catch(std::runtime_error& e)
    {
        ROS_ERROR("map_server exception: %s", e.what());
        return -1;
    }
    return 0;
}

3.1 命令行運(yùn)行參數(shù)賦值解析

rosrun map_server map_server map.yaml

主要的變量有

  • fname即 argv[1],命令行執(zhí)行程序名后的第一個(gè)參數(shù) map.yaml宵距,后邊會(huì)通過(guò)其加載地圖腊尚;

  • res 通過(guò) argc 命令行參數(shù)個(gè)數(shù)判斷,上述命令行指令為2個(gè)參數(shù)满哪,及 res 為零婿斥。如果參數(shù)個(gè)數(shù)不等于2,后邊程序會(huì)選擇使用棄用的 map_server 接口 map_server (不關(guān)注)哨鸭。

3.2 構(gòu)造函數(shù)

創(chuàng)建 MapServer 類對(duì)象 ms民宿,MapServer ms(fname, res);,程序運(yùn)行 MapServer 構(gòu)造函數(shù) MapServer(const std::string& fname, double res)像鸡。

MapServer(const std::string& fname, double res)
{
    std::string mapfname = "";
    double origin[3];
    int negate;
    double occ_th, free_th;
    MapMode mode = TRINARY;
    std::string frame_id;
    ros::NodeHandle private_nh("~");
    private_nh.param("frame_id", frame_id, std::string("map"));
    deprecated = (res != 0);  // false

通過(guò)命令行參數(shù)個(gè)數(shù)賦值變量 res活鹰,判斷需要加載的地圖格式是現(xiàn)用的還是現(xiàn)在棄用的。如果使用現(xiàn)用格式只估,則變量 deprecated為 false志群,通過(guò)第三方庫(kù)YAML 加載 map.yaml 文件,并解析賦值給相應(yīng)變量 res蛔钙、negate锌云、occ_th 等等,針對(duì)這部分變量含義在文末會(huì)詳細(xì)解析吁脱。

    if (!deprecated) {
        //mapfname = fname + ".pgm";
        //std::ifstream fin((fname + ".yaml").c_str());
        std::ifstream fin(fname.c_str());
        if (fin.fail()) {
            ROS_ERROR("Map_server could not open %s.", fname.c_str());
            exit(-1);
        }
#ifdef HAVE_YAMLCPP_GT_0_5_0
        // The document loading process changed in yaml-cpp 0.5.
        YAML::Node doc = YAML::Load(fin);
#else
        YAML::Parser parser(fin);
        YAML::Node doc;
        parser.GetNextDocument(doc);
#endif
        try {
            doc["resolution"] >> res;
        } catch (YAML::InvalidScalar) {
            ROS_ERROR("The map does not contain a resolution tag or it is invalid.");
            exit(-1);
        }
        try {
            doc["negate"] >> negate;
        } catch (YAML::InvalidScalar) {
            ROS_ERROR("The map does not contain a negate tag or it is invalid.");
            exit(-1);
        }
        try {
            doc["occupied_thresh"] >> occ_th;
        } catch (YAML::InvalidScalar) {
            ROS_ERROR("The map does not contain an occupied_thresh tag or it is invalid.");
            exit(-1);
        }
        try {
            doc["free_thresh"] >> free_th;
        } catch (YAML::InvalidScalar) {
            ROS_ERROR("The map does not contain a free_thresh tag or it is invalid.");
            exit(-1);
        }
        try {
            std::string modeS = "";
            doc["mode"] >> modeS;

            if(modeS=="trinary")
                mode = TRINARY;
            else if(modeS=="scale")
                mode = SCALE;
            else if(modeS=="raw")
                mode = RAW;
            else{
                ROS_ERROR("Invalid mode tag \"%s\".", modeS.c_str());
                exit(-1);
            }
        } catch (YAML::Exception) {
            ROS_DEBUG("The map does not contain a mode tag or it is invalid... assuming Trinary");
            mode = TRINARY;
        }
        try {
            doc["origin"][0] >> origin[0];
            doc["origin"][1] >> origin[1];
            doc["origin"][2] >> origin[2];
        } catch (YAML::InvalidScalar) {
            ROS_ERROR("The map does not contain an origin tag or it is invalid.");
            exit(-1);
        }
        try {
            doc["image"] >> mapfname;
            // TODO: make this path-handling more robust
            if(mapfname.size() == 0)
            {
                ROS_ERROR("The image tag cannot be an empty string.");
                exit(-1);
            }
            if(mapfname[0] != '/')
            {
                // dirname can modify what you pass it
                char* fname_copy = strdup(fname.c_str());
                mapfname = std::string(dirname(fname_copy)) + '/' + mapfname;
                free(fname_copy);
            }
        } catch (YAML::InvalidScalar) {
            ROS_ERROR("The map does not contain an image tag or it is invalid.");
            exit(-1);
        }
    } else {
        private_nh.param("negate", negate, 0);
        private_nh.param("occupied_thresh", occ_th, 0.65);
        private_nh.param("free_thresh", free_th, 0.196);
        mapfname = fname;
        origin[0] = origin[1] = origin[2] = 0.0;
    }

通過(guò)loadMapFromFile()函數(shù)根據(jù)參數(shù)讀入地圖文件

    // Part 4 --------------------------------------------
    ROS_INFO("Loading map from image \"%s\"", mapfname.c_str());
    try
    {
        map_server::loadMapFromFile(&map_resp_,mapfname.c_str(),res,negate,occ_th,free_th, origin, mode);
    }
    catch (std::runtime_error e)
    {
        ROS_ERROR("%s", e.what());
        exit(-1);
    }

函數(shù)loadMapFromFile()在 image_loader.cpp 內(nèi)定義的 map_server 命名空間內(nèi)桑涎。\

函數(shù)首先使用 SDL 庫(kù) IMG_Load 函數(shù)加載 fname (map.png) 圖片信息 img,然后使用 setEulerZYX 將圖片的航向信息轉(zhuǎn)換為姿態(tài)四元數(shù)兼贡。

void loadMapFromFile(nav_msgs::GetMap::Response* resp,
                const char* fname, double res, bool negate,
                double occ_th, double free_th, double* origin,
                MapMode mode)
{
    SDL_Surface* img;

    unsigned char* pixels;
    unsigned char* p;
    unsigned char value;
    int rowstride, n_channels, avg_channels;
    unsigned int i,j;
    int k;
    double occ;
    int alpha;
    int color_sum;
    double color_avg;

    // Load the image using SDL.  If we get NULL back, the image load failed.
    if(!(img = IMG_Load(fname)))
    {
        std::string errmsg = std::string("failed to open image file \"") +
            std::string(fname) + std::string("\": ") + IMG_GetError();
        throw std::runtime_error(errmsg);
    }

    // Copy the image data into the map structure
    resp->map.info.width = img->w;
    resp->map.info.height = img->h;
    resp->map.info.resolution = res;
    resp->map.info.origin.position.x = *(origin);
    resp->map.info.origin.position.y = *(origin+1);
    resp->map.info.origin.position.z = 0.0;
    btQuaternion q;
    // setEulerZYX(yaw, pitch, roll)
    q.setEulerZYX(*(origin+2), 0, 0);
    resp->map.info.origin.orientation.x = q.x();
    resp->map.info.origin.orientation.y = q.y();
    resp->map.info.origin.orientation.z = q.z();
    resp->map.info.origin.orientation.w = q.w();

加載地圖分三種模式:TRINARY攻冷,SCALE,RAW遍希。

  • TRINARY模式:占據(jù)(Occupied)像素用100表示等曼,自由(Free)像素用0表示,不明(Unknow)像素用-1表示孵班;
  • SCALE模式:占據(jù)(Occupied)像素用100表示涉兽,自由(Free)像素用0表示,不明(Unknow)像素用(0, 100)篙程;
  • RAW模式:所有像素用 [0, 255] 表示枷畏,直接輸出像素值為柵格值。

接著通過(guò)圖片信息計(jì)算 rgb 平均值 color_avg虱饿,根據(jù)計(jì)算公式 occ = (255 - color_avg) / 255.0拥诡;計(jì)算每個(gè)像素的占用概率 occ触趴。

在TRINARY 模式下,如果 occ 大于占用概率閾值 occ_th渴肉,則當(dāng)前像素被占用(用100表示)冗懦,小于 free_th(用0表示)、不確定(用-1表示)仇祭。

    // Allocate space to hold the data
    resp->map.data.resize(resp->map.info.width * resp->map.info.height);

    // Get values that we'll need to iterate through the pixels
    rowstride = img->pitch;
    n_channels = img->format->BytesPerPixel;

    // NOTE: Trinary mode still overrides here to preserve existing behavior.
    // Alpha will be averaged in with color channels when using trinary mode.
    if (mode==TRINARY || !img->format->Amask)
        avg_channels = n_channels;
    else
        avg_channels = n_channels - 1;

    // Copy pixel data into the map structure
    pixels = (unsigned char*)(img->pixels);
    for(j = 0; j < resp->map.info.height; j++)
    {
        for (i = 0; i < resp->map.info.width; i++)
        {
            // Compute mean of RGB for this pixel
            p = pixels + j*rowstride + i*n_channels;
            color_sum = 0;
            for(k=0;k<avg_channels;k++)
                color_sum += *(p + (k));
            color_avg = color_sum / (double)avg_channels;

            if (n_channels == 1)
                alpha = 1;
            else
                alpha = *(p+n_channels-1);

            if(negate)
                color_avg = 255 - color_avg;

            if(mode==RAW){
                value = color_avg;
                resp->map.data[MAP_IDX(resp->map.info.width,i,resp->map.info.height - j - 1)] = value;
                continue;
            }

            // If negate is true, we consider blacker pixels free, and whiter
            // pixels free.  Otherwise, it's vice versa.
            occ = (255 - color_avg) / 255.0;

            // Apply thresholds to RGB means to determine occupancy values for
            // map.  Note that we invert the graphics-ordering of the pixels to
            // produce a map with cell (0,0) in the lower-left corner.
            if(occ > occ_th)
                value = +100;
            else if(occ < free_th)
                value = 0;
            else if(mode==TRINARY || alpha < 1.0)
                value = -1;
            else {
                double ratio = (occ - free_th) / (occ_th - free_th);
                value = 99 * ratio;
            }
            resp->map.data[MAP_IDX(resp->map.info.width,i,resp->map.info.height - j - 1)] = value;
        }
    }
    SDL_FreeSurface(img);
}

最后將地圖信息發(fā)布出去披蕉,共三個(gè)topic。

  • /map_metadata: 發(fā)布地圖的描述信息
  • /map: 發(fā)布鎖存的地圖消息
  • static_map: 用于請(qǐng)求和響應(yīng)當(dāng)前的靜態(tài)地圖乌奇。
    // To make sure get a consistent time in simulation
    ros::Time::waitForValid();
    map_resp_.map.info.map_load_time = ros::Time::now();
    map_resp_.map.header.frame_id = frame_id;
    map_resp_.map.header.stamp = ros::Time::now();
    ROS_INFO("Read a %d X %d map @ %.3lf m/cell",
             map_resp_.map.info.width,
             map_resp_.map.info.height,
             map_resp_.map.info.resolution);
    meta_data_message_ = map_resp_.map.info;

    service = n.advertiseService("static_map", &MapServer::mapCallback, this);
    //pub = n.advertise<nav_msgs::MapMetaData>("map_metadata", 1,

    // Latched publisher for metadata
    metadata_pub= n.advertise<nav_msgs::MapMetaData>("map_metadata", 1, true);
    metadata_pub.publish( meta_data_message_ );

    // Latched publisher for data
    map_pub = n.advertise<nav_msgs::OccupancyGrid>("map", 1, true);
    map_pub.publish( map_resp_.map );
}

四没讲、map_saver

rosrun map_server map_saver -f map

只考慮常用的-f參數(shù),保存地圖礁苗。 此節(jié)點(diǎn)主要函數(shù)是類 MapGenerator 的構(gòu)造函數(shù)爬凑,訂閱來(lái)自建圖發(fā)布的 /map topic,從而寫地圖 map.png/map.pgm 和 map.yaml 文件试伙。

MapGenerator(const std::string& mapname, int threshold_occupied = 100, int threshold_free = 0)
    : mapname_(mapname), saved_map_(false), threshold_occupied_(threshold_occupied), threshold_free_(threshold_free)
{
    ros::NodeHandle n;
    ROS_INFO("Waiting for the map");
    map_sub_ = n.subscribe("map", 1, &MapGenerator::mapCallback, this);
}

---
    
for(unsigned int y = 0; y < map->info.height; y++) {
    for(unsigned int x = 0; x < map->info.width; x++) {
        unsigned int i = x + (map->info.height - y - 1) * map->info.width;
        if (map->data[i] >= 0 && map->data[i] <= threshold_free_) { //occ [0,0.1)
            fputc(254, out);
        } else if (map->data[i] <= 100 && map->data[i] >= threshold_occupied_) { //occ (0.65,1]
            fputc(000, out);
        } else { //occ [0.1,0.65]
            fputc(205, out);
        }
    }
}

map.yaml寫入函數(shù)

fprintf(yaml, "image: %s\nresolution: %f\norigin: [%f, %f, %f]\nnegate: 0\noccupied_thresh: 0.65\nfree_thresh: 0.196\n\n", mapdatafile.c_str(), map->info.resolution, map->info.origin.position.x, map->info.origin.position.y, yaw);

// map.yaml
image: map.pgm
resolution: 0.050000
origin: [-5.781524, -6.753651, 0.000000]
negate: 0
occupied_thresh: 0.65
free_thresh: 0.196

map.yaml中參數(shù)含義:

image: map.png
resolution: 0.04
origin: [-20.499653, -9.56729, 0.0]
negate: 0
free_thresh: 0.196
occupied_thresh: 0.65
  • image: map.png 包含占用數(shù)據(jù)的圖像文件的路徑嘁信; 可以是絕對(duì)的,或相對(duì)于 YAML 文件的位置
  • resolution: 0.04 地圖的分辨率疏叨,米/像素
  • origin: [-20.499653, -9.56729, 0.0] (x, y, yaw)(地圖左下角在地圖上的位置)初始點(diǎn)位置 在地圖上的坐標(biāo)以及航偏潘靖。yaw逆時(shí)針旋轉(zhuǎn)。航偏是指角度 rad考廉。
  • occupied_thresh: 0.65 當(dāng)像素占據(jù)的概率大于 0.65 時(shí)候認(rèn)為是完全占據(jù)的秘豹。
  • free_thresh: 0.196 當(dāng)像素占據(jù)概率小于0.196的時(shí)候,認(rèn)為完全是空的
  • negate: 0 0代表 白色為空閑 黑色為占據(jù)昌粤,白/黑自由/占用語(yǔ)義是否應(yīng)該被反轉(zhuǎn)(閾值的解釋不受影響)

圖像像素被占據(jù)的概率是這么計(jì)算的:occ = (255 - color_avg) / 255.0,其中color_avg 是用8位數(shù)表示的來(lái)自于所有通道的平均值啄刹;

例如像素顏色是 0xeeeeee涮坐,則占用概率是0.07,這意味著幾乎沒有被占用誓军。

將 0xeeeeee 十六進(jìn)制表示的顏色轉(zhuǎn)為 rgb 為 238/238/238袱讹,occ = (255-238)/255 = 0.07

轉(zhuǎn)載:https://blog.csdn.net/Csdn_Darry/article/details/111773701

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市昵时,隨后出現(xiàn)的幾起案子捷雕,更是在濱河造成了極大的恐慌,老刑警劉巖壹甥,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件救巷,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡句柠,警方通過(guò)查閱死者的電腦和手機(jī)浦译,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門棒假,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人精盅,你說(shuō)我怎么就攤上這事帽哑。” “怎么了叹俏?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵妻枕,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我粘驰,道長(zhǎng)佳头,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任晴氨,我火速辦了婚禮康嘉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘籽前。我一直安慰自己亭珍,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布枝哄。 她就那樣靜靜地躺著肄梨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪挠锥。 梳的紋絲不亂的頭發(fā)上众羡,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音蓖租,去河邊找鬼粱侣。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蓖宦,可吹牛的內(nèi)容都是我干的齐婴。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼稠茂,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼柠偶!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起睬关,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤诱担,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后电爹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蔫仙,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年藐不,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了匀哄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片秦效。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖涎嚼,靈堂內(nèi)的尸體忽然破棺而出阱州,到底是詐尸還是另有隱情,我是刑警寧澤法梯,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布苔货,位于F島的核電站,受9級(jí)特大地震影響立哑,放射性物質(zhì)發(fā)生泄漏夜惭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一铛绰、第九天 我趴在偏房一處隱蔽的房頂上張望诈茧。 院中可真熱鬧,春花似錦捂掰、人聲如沸敢会。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)鸥昏。三九已至,卻和暖如春姐帚,著一層夾襖步出監(jiān)牢的瞬間吏垮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工罐旗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留膳汪,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓尤莺,卻偏偏與公主長(zhǎng)得像旅敷,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子颤霎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容