title: SUN数据集中提取包含飞机类别图片并计算坐标写入到txt文件中
tags: C++,boost,libxml2,数据处理,搬砖日常
针对sun数据集遍历所有xml解析包含指定类的坐标写入到txt文件并保存相关图片
数据集官网:SUN database
数据集网盘下载查找:聚数力
参考博客:C++ boost库 遍历读取文件夹文件
参考博客:C++ 简单读写文本文件、统计文件的行数、读取文件数据到数组
参考博客:C++复制文件(一)
参考博客:XML 解析中,如何排除控制字符
1.下载完的数据集标注文件是子目录下对应类别中包含xml文件的结构,需要递归访问到所有xml并把路径保存到vector中,对应方法如下:
namespace fs = boost::filesystem; //定义函数recursion_xml,递归遍历指定目录下包含xml的全部文件夹 void recursion_xml(fs::path src_path,vector<string> &saveXmlsPath) { fs::directory_iterator end; //迭代器的终点 int i = 0; for (fs::directory_iterator dir(src_path); dir!=end; dir++) { string xmlFilePath = dir->path().string(); if (dir->path().extension() == ".xml") { cout << ".xml文件全路径:" << xmlFilePath << endl; saveXmlsPath.push_back(xmlFilePath); } if (fs::is_directory(*dir)) recursion_xml(*dir,saveXmlsPath); //检查路径是否与目录对应(是文件夹则递归) } }
2.在解析xml文件,发现特殊字符不能解析报PCDATA invalid Char value 26 in Entity错误,查了很多博客给出了特殊字符表,和简单的正则表达式,并没有给出具体解决办法。最初想到的是重新写入到xml文件,问了leader有什么结合正则对文本的处理方法,给出建议是使用sed命令,查找了相关博客写了一个简单脚本如下:
#!/bin/bash oldstr="potters wheel" #里面包含特殊字符不能复制到命令行,但是可以在文本中复制 newstr="potters_wheel" sed -i "s/$oldstr/$newstr/g" `find . -name *.xml` #注意find命令符号为左上角ESC下面的键
3.SUN中的xml文件是没有经过格式化,所以获取到标签中的内容会存在多个换行(首部换行,尾部两个换行),针对这个问题查找libxml2中有没有预先格式化方法未果,换种思路对内容处理写了简单的方法如下:
//去掉前一个后两个换行符 void remove(char *str){ int len = strlen(str); for(int i=0 ; i<len-2 ; i++){ str[i] = str[i+1]; } str[len-2] = 0; }
4.xml解析使用的是libxml2库中的方法,根据xml文件格式进行解析,因为SUN中标注的是物体的轮廓,需要计算最小x,y,最大x,y作为两个坐标点,具体代码如下:
bool parseXmlForSUNDataSet(string xmlPath,string fileName,string saveTxtsPath) { xmlDocPtr doc = NULL; xmlNodePtr root_node = NULL; xmlNodePtr pstXmlNode = NULL; //object xmlNodePtr pstXmlSubNode = NULL; // name || polygon xmlNodePtr pstXmlPtNode = NULL; // username || pt xmlNodePtr tempNode = NULL; // x || y bool isContainedPlane = false; const char* xmlFilePath = xmlPath.c_str(); //string转const char* doc = xmlReadFile(xmlFilePath, NULL, XML_PARSE_NOBLANKS); if (!doc) { fprintf(stderr, "配置文件读取错误\n"); return isContainedPlane; } root_node = xmlDocGetRootElement(doc); if (!root_node) { fprintf(stderr, "配置文件读取根节点错误\n"); return isContainedPlane; } pstXmlNode = root_node->xmlChildrenNode; while (NULL != pstXmlNode) { if ((!xmlStrcmp(pstXmlNode->name, (const xmlChar *)"object"))) { pstXmlSubNode = pstXmlNode->xmlChildrenNode; while (NULL != pstXmlSubNode) { if ((!xmlStrcmp(pstXmlSubNode->name, (const xmlChar *)"name"))) { char *temp = (char *)xmlNodeGetContent(pstXmlSubNode); remove(temp); if(!strcmp(temp,(char *)"airplane") || !strcmp(temp,(char *)"airplane occluded") || !strcmp(temp,(char *)"airplane crop") || !strcmp(temp,(char *)"airplane part")){ printf("find airplane || airplane occluded || airplane crop || airplane part\n"); }else{ break; } } if ((!xmlStrcmp(pstXmlSubNode->name, (const xmlChar *)"polygon"))) { //cout<<"==polygon=="<<endl; pstXmlPtNode = pstXmlSubNode->xmlChildrenNode; int min_x,min_y; //以最小的x,y作为左上坐标 int max_x,max_y; //以最大的x,y作为右下坐标 bool isFirst = true; //是否第一次读坐标 while(NULL != pstXmlPtNode){ if(!xmlStrcmp(pstXmlPtNode->name, (const xmlChar *)"pt")){ tempNode = pstXmlPtNode->xmlChildrenNode; char *x = (char *)xmlNodeGetContent(tempNode); remove(x); //printf("====x:%s===\n",x); int temp_x = atoi(x); //获取当前Pt节点中x的值转int tempNode = tempNode->next; char *y = (char *)xmlNodeGetContent(tempNode); remove(y); int temp_y = atoi(y); //获取当前Pt节点中y的值转int if(isFirst){ min_x = temp_x; min_y = temp_y; max_x = temp_x; max_y = temp_y; isFirst = false; }else{ //判断更新坐标 if(temp_x < min_x){ min_x = temp_x; } if(temp_x > max_x){ max_x = temp_x; } if(temp_y < min_y){ min_y = temp_y; } if(temp_y > max_y){ max_y = temp_y; } } } pstXmlPtNode = pstXmlPtNode->next; } //如果坐标点全不为0,写入到txt文本中 if(min_x != 0 && min_y != 0 && max_x != 0 && max_y != 0){ string coordinate = ""+ to_string(min_x) + " " + to_string(min_y) + " " + to_string(max_x) + " " + to_string(max_y); string saveTxt = saveTxtsPath + fileName +".txt"; isContainedPlane = true; //说明当前解析的xml文件里面有飞机 //写文件操作 ofstream in; in.open(saveTxt,ios::app); //ios::app打开文件移动到文件尾 in << coordinate <<"\n"; in.close(); } } pstXmlSubNode = pstXmlSubNode->next; } } pstXmlNode = pstXmlNode->next; } if (doc) { xmlFreeDoc(doc); } return isContainedPlane; }
5.解析完xml文件需要把筛选出的对应图片保存到指定目录,具体代码如下:
//定义函数recursion_image,递归遍历指定目录下寻找对应名称的图片文件 void recursion_image(fs::path src_path,string fileName,string saveImagesPath) { fs::directory_iterator end; //迭代器的终点 int i = 0; for (fs::directory_iterator dir(src_path); dir!=end; dir++) { string imageFilePath = dir->path().string(); fs::path filePath(imageFilePath); string imageFileName = filePath.stem().string(); //截取单独文件名称不含后缀 if (!(imageFileName.compare(fileName))) { cout << ".jpg文件全路径:" << imageFilePath << endl; string saveImagePath = saveImagesPath + fileName+".jpg"; int result = CopyFile(((char *)imageFilePath.data()),(char *)(saveImagePath.data())); if(result == 1){ cout<<"copy file success"<<endl; }else{ cout<<"copy file fail"<<endl; } } if (fs::is_directory(*dir)) recursion_image(*dir,fileName,saveImagesPath); //检查路径是否与目录对应(是文件夹则递归) } } //复制文件,将筛选的图片复制到指定目录下 int CopyFile(char *SourceFile, char *NewFile) { std::ifstream in; std::ofstream out; try { in.open(SourceFile, std::ios::binary);//打开源文件 if (in.fail())//打开源文件失败 { std::cout << "Error 1: Fail to open the source file." << std::endl; in.close(); out.close(); return 0; } out.open(NewFile, std::ios::binary);//创建目标文件 if (out.fail())//创建文件失败 { std::cout << "Error 2: Fail to create the new file." << std::endl; out.close(); in.close(); return 0; } else//复制文件 { out << in.rdbuf(); out.close(); in.close(); return 1; } } catch (std::exception e) { } }
6.代码github地址