一、背景
由于项目需要训练大量数据集,所以将数据集提前分类保存在不同文件夹,此时作者需要将这些文件夹一次性生成训练集与测试集。
二、需求与思路
前期工作获取到:
(1)文件夹名称.txt文件
(2)对应文件夹中所含图片数量.txt文件
处理要求:
(1)获取到符合数量要求(100<图片数量<=1000)的文件夹名称
(2)提取100张图片按8:2将图片分成训练集(train文件夹)和测试集(test文件夹)
(3)将其以原文件夹名称、原图片名称保存到新的总文件夹下
具体思路如下:
(1)打开两个.txt文件并获取其中的信息分别赋值到两个列表;
(2)同时遍历两个列表的信息;
(3)当读取到的count数据中符合筛选要求,则输出对应的文件夹名称,形成新的遍历路径
(4)通过(3)中获取到的文件夹路径遍历其中的图片
(5)计数100,前80次保存到train文件夹,后20次保存到test文件夹
三、C++代码
C++代码实现如下:
#include <iostream>
#include <fstream>
#include <filesystem>
#include <experimental/filesystem>
#include <opencv4/opencv2/opencv.hpp>
#include <opencv4/opencv2/highgui.hpp>
#include <opencv4/opencv2/imgproc.hpp>
#include <vector>
#include <sys/stat.h>
namespace fs = std::experimental::filesystem;
std::vector<std::string> imgs_path_list;
std::vector<std::string> bird_kind;
std::vector<std::string> bird_count;
int main()
{
//打开种类名称txt文件
std::string folderNameFilePath = "/bird_kind_.txt";
std::ifstream folderNameFile(folderNameFilePath);
//打开对应种类文件夹图片张数txt文件
std::string imageCountFilePath = "/count.txt";
std::ifstream imageCountFile(imageCountFilePath);
//读取txt文件中的文本信息
std::string bird_kind_line;
for (int i = 1; i <= 830; i++)//从第1行读到第830行
{
std::getline(folderNameFile, bird_kind_line);
bird_kind.push_back(bird_kind_line);//将获取到的文本赋到bird_kind这个列表里面
}
std::string bird_count_line;
for (int i = 1; i <= 830; i++)
{
std::getline(imageCountFile, bird_count_line);
bird_count.push_back(bird_count_line);//将获取到的文本赋到bird_count这个列表里面
}
int i = 0;
std::string path = "/folderpath";//更换成需要遍历的目标文件夹路径
for (auto bird_k: bird_kind)//遍历名称列表,每次读取将列表的值赋予bird_k(变量)
{
if (std::stoi(bird_count[i]) > 100 && std::stoi(bird_count[i]) <= 1000)//自定义筛选条件,此处筛选条件为图片张数大于100小于等于1000的文件夹
{
std::string imgs_path = path + bird_k;//此处为路径拼接操作,可对照需求修改自定义
std::string imgs_train_path =path + "bird_100/train/" + bird_k;
std::string imgs_test_path =path + "bird_100/test/" + bird_k;
imgs_path_list.push_back(imgs_path);//获取到符合要求需要进行提取图片操作的文件夹路径,并保存到imgs_path_list列表下
fs::create_directories(imgs_train_path);//在获取到符合要求的文件夹名称后生成训练集和测试集文件夹
fs::create_directories(imgs_test_path);
}
i++;
}
std::string inputFolder = "/folderpath"; // 更换成目标文件夹路径
std::string outputTrainFolder = "/folderpath"; // 更换成新生成的训练集文件夹路径
std::string outputTestFolder = "/folderpath"; // 更换成新生成的测试集文件夹路径
int trainCount = 0;#训练集计数
int testCount = 0;#测试集计数
for (const auto& item :imgs_path_list)//遍历需要进行提取操作的文件夹名称列表
{
std::string folderPath = item;//将列表的值赋予变量folderpath
std::string path = folderPath;
// 因为需要在train和test文件夹下生成一个与原文件夹同名的文件夹,所以此处从路径中提取出文件夹名称(如:/路径1/路径2/路径3/路径3/文件夹)
// 查找最后一个斜杠的位置
size_t lastSlashPos = path.find_last_of('/');
std::string New_path;
if (lastSlashPos != std::string::npos)
{
// 提取最后一个斜杠后面的字符串
std::string extractedString = path.substr(lastSlashPos + 1);
New_path=extractedString;//因为此处extractedString为if函数下的局部变量,所以将其赋值到全局变量New_path中以便后续使用
} else
{
std::cerr << "No slash found in the path." << std::endl;
}
int count = 0;//提取图片计数
for (const auto &file: fs::directory_iterator(folderPath))//遍历需要提取的文件夹
{
std::cout<<file.path().extension()<<std::endl;
if (file.path().extension() == ".jpg")//判断文件拓展名是否为.jpg
{
cv::Mat image = cv::imread(file.path().string());//获取原始图片
if (count < 100)
{
std::string fileName = file.path().stem().string();
if (count < 80)
{
// 将图片保存到train文件夹
std::string outputPath = outputTrainFolder + New_path + "/" + fileName + ".jpg";//自定义图片保存路径
std::cout<<outputPath<<std::endl;//输出图片保存路径
cv::imwrite(outputPath, image);
trainCount++;
} else
{
// 将图片保存到test文件夹
std::string outputPath = outputTestFolder + New_path + "/" + fileName + ".jpg";//自定义图片保存路径
std::cout<<outputPath<<std::endl;//输出图片保存路径
cv::imwrite(outputPath, image);
testCount++;
}
count++;
} else
{
break; // 完成遍历,跳出循环
}
}
}
}
return 0;
}
ps:可以在调试代码时通过“std::cout<<XX<<std::endl”输出变量值或者获取到的文本信息以检查代码逻辑以及是否正确运行
四、更多思路
原本想通过构建两个函数,readInfo和ExtractAndSplit分别实现文本读取和数据分割的任务,但实际操作中遇到了许多声明和定义上的麻烦,但我相信这种思路是可行的,由于时间问题没有办法继续往下实现,有时间的朋友可以按照本文的思路试着去构建这两个函数并调用。