load/save multiple images from/in a directory - opencv c++ - c++

i want to load a lot of images (not sequential names though) from a directory. edit them and then save them in a different directory with their original names if possible.
I load them like that:
glob("/photos/field_new/*.jpg", fn, false);
size_t count = fn.size(); //number of jpg files in images folder
for (size_t i=0; i<count; i++)
images.push_back(imread(fn[i]));
any ideas how i can save them in the directory /photos/results/ ?
and if possible with their original names?

If you have C++17 it will be easy as there is filesystem library in standard (std::filesystem). Other wise I would recomend you to get boost::filesystem which is very similar (you should be good with replacing all of std::filesystem to boost::filesystem).
To load all images from certain folder there are 2 helper functions:
#include <filesystem> //for boost change this to #include <boost/filesystem> and all std:: to boost::
#include <opencv2/opencv.hpp>
bool isSupportedFileType(const std::filesystem::path& pathToFile,
const std::vector<std::string>& extensions)
{
auto extension = pathToFile.extension().string();
std::transform(extension.begin(), extension.end(), extension.begin(), [](char c)
{
return static_cast<char>(std::tolower(c));
});
return std::find(extensions.begin(), extensions.end(), extension) != extensions.end();
}
std::tuple<std::vector<cv::Mat>, std::vector<std::filesystem::path>> loadImages(const std::filesystem::path& path,
const std::vector<std::string>& extensions)
{
std::vector<cv::Mat> images;
std::vector<std::filesystem::path> names;
for (const auto& dirIt : filesystem::DirectoryIterator(path))
{
if (std::filesystem::is_regular_file(dirIt.path()) && isSupportedFileType(dirIt.path(), extensions))
{
auto mask = cv::imread(dirIt.path().string(), cv::IMREAD_UNCHANGED);
if (mask.data != nullptr) //there can be problem and image is not loaded
{
images.emplace_back(std::move(mask));
names.emplace_back(dirIt.path().stem());
}
}
}
return {images, names};
}
You can use it like this (assuming C++17):
auto [images, names] = loadImages("/photos/field_new/", {".jpg", ".jpeg"});
Or (C++11)
auto tupleImageName = loadImages("/photos/field_new/", {".jpg", ".jpeg"});
auto images = std::get<0>(tupleImageName);
auto names = std::get<1>(tupleImageName);
To save you can use this function:
void saveImages(const std::filesystem::path& path,
const std::vector<cv::Mat>& images,
const std::vector<std::filesystem::path>& names)
{
for(auto i = 0u; i < images.size(); ++i)
{
cv::imwrite((path / names[i]).string(), images[i]);
}
}
Like this:
saveImages("pathToResults",images,names);
In this save function it would be good to perform some validation if the number of images is the same as names, otherwise there might be a problem with stepping outside vector boundary.

Since C++ is compatible with C, and if you are using a Unix system, you could use dirent.h (https://pubs.opengroup.org/onlinepubs/7908799/xsh/dirent.h.html). It should be something like this:
#include <dirent.h>
#include <opencv2/opencv.hpp>
#include <string.h>
using namespace std;
using namespace cv;
int main(){
DIR *d;
struct dirent *dir;
string dirname_input="dirname/input";
string dirname_output="dirname/output";
d=opendir(dirname_input.c_str());
if (d){
while ((dir=readdir(d))!=NULL){
string filename=dir->d_name;
if (filename.substr(filename.size() - 4)==".jpg"){
Mat image;
string fullpath_input=dirname_input+'/'+filename;
string fullpath_output=dirname_output+'/'+filename;
image=imread(fullpath_input,1);
// process image
imwrite(fullpath_output,image);
}
}
}
return 0;
}

Related

Cross platform file list using wildcard

I'm looking for a cross platform function that supports wildcard listing of a directory contents similar to what FindFirstFile on windows.
Is the wildcard pattern accepted in windows very specific to windows? I want something that supports FindFirstFile wildcard pattern but he working in Linux as well.
If C++17 and above:
You can "walk" a directory using a directory iterator, and match walked file names with a regex, like this:
static std::optional<std::string> find_file(const std::string& search_path, const std::regex& regex) {
const std::filesystem::directory_iterator end;
try {
for (std::filesystem::directory_iterator iter{search_path}; iter != end; iter++) {
const std::string file_ext = iter->path().extension().string();
if (std::filesystem::is_regular_file(*iter)) {
if (std::regex_match(file_ext, regex)) {
return (iter->path().string());
}
}
}
}
catch (std::exception&) {}
return std::nullopt;
}
Usage would be for example, for finding the first file, that ends in .txt:
auto first_file = find_file("DocumentsDirectory", std::regex("\\.(?:txt)"));
Similarly, if you are interested in more than matching by extension, the function line
const std::string file_ext = iter->path().extension().string();
should be modified to something that captures the part of the filename you are interested in (or the whole path to the file)
This could then be used in a function, which performs the wildcard listing by directory.
Here is a recursive variant.
It calls a functional f for each file in the list and returns the number of files found.
It is also recursive: it descends sub directories to the max depth specified.
Note that the search filter does a filename is matched.
The try-catch block in removed so that the caller can catch and process any problems.
#include <string>
#include <regex>
#include <filesystem>
// recursively call a functional *f* for each file that matches the expression
inline int foreach_file(const std::string& search_path, const std::regex& regex, int depth, std::function<void(std::string)> f) {
int n = 0;
const std::filesystem::directory_iterator end;
for (std::filesystem::directory_iterator iter{ search_path }; iter != end; iter++) {
const std::string filename = iter->path().filename().string();
if (std::filesystem::is_regular_file(*iter)) {
if (std::regex_match(filename, regex)) {
n++;
f(iter->path().string());
}
}
else if (std::filesystem::is_directory(*iter) && depth>0) {
n += foreach_file(iter->path().string(), regex, depth - 1, f);
}
}
return n;
}
Example:
void do_something(string filename) {
...
}
void do_all_json_that_start_with_z() {
// regex matches the whole filename
regex r("z.*.json", regex::ECMAScript | regex::icase); // ignoring case
foreach_file(R"(C:\MyFiles\)", r, 99, do_something); // max depth 99
}
// can use lambdas
void do_all_json_that_start_with_z() {
int n=0;
foreach_file(
R"(C:\MyFiles\)", // using raw string - for windows
regex("z.*.json"),
0, // do not descend to sub-directories
[&n](string s) { printf("%d) %s\n", ++n, s.c_str()); });
}

Changing file extensions while copying into a new sub directory C++

I have a directory that contains files (mostly images) and am trying to create a new sub directory named Thumbnails that will copy all images and replace the file extension from ".jpg" || ".png" to ".thumb".
So far my code will copy all the files into the new sub directory but I can't find a way to ignore all the files that are not images (it's copying every single file in the original directory) and to change the extension of the files that where copied.
#include <iostream>
#include <filesystem>
using namespace std;
namespace fs = std::experimental::filesystem;
//Predefined Functions
void createThumbnails(string path);
int main(void)
{
string DailyFolderPath = ".../images";
createThumbnails(DailyFolderPath);
return 0;
}
void createThumbnails(string path)
{
//Create Thumbnail Sub Directory
string newThumbDir = path + "/Thumbnails";
fs::create_directories(newThumbDir);
//Copy files into Thumbnail Directory
fs::copy(path, newThumbDir);
for (const auto & p : fs::directory_iterator(newThumbDir))
cout << p.path().filename() << endl;
}
Your attempt proposed in comment cannot work because you are passing only filenames instead the full paths to files:
fs::copy_file(p.path().filename(), p.path().filename().replace_extension(".thumb"));
^^^^^ your app doesn't see these files
You can try something like this:
void createThumbnails(string source)
{
fs::path newThumbDir = source + "/Thumbnails";
fs::create_directories(newThumbDir);
for (const auto & p : fs::directory_iterator(source)) // iterate over all files in source dir
{
fs::path fileToCopy = p; // make copy of path to source file
if (fileToCopy.extension() == ".png" || fileToCopy.extension() == ".jpg")
{
fileToCopy.replace_extension(".thumb"); // change extension
fs::path target = newThumbDir / fileToCopy.filename(); // targetDir/name.thumb
fs::copy_file(p,target); // pass full paths
}
}
}

Using cv::glob for multiple extensions

I'm trying to use cv::glob to find images in a folder system. What I now want is to search for multiple file extensions at once (let's say .jpg and .png). Is there a way to do this?
The opencv documentation on this method doesn't specify the pattern parameter.
At the moment I'm using the ugly and inefficient method of searching for each extension seperately and combining the results. See:
vector<cv::String> imageNames;
vector<string> allowedExtensions = { ".jpg", ".png" };
for (int i = 0; i < allowedExtensions.size(); i++) {
vector<cv::String> imageNamesCurrentExtension;
cv::glob(
inputFolder + "*" + allowedExtensions[i],
imageNamesCurrentExtension,
true
);
imageNames.insert(
imageNames.end(),
imageNamesCurrentExtension.begin(),
imageNamesCurrentExtension.end()
);
}
Open CV or even OS file system API has no build-in way to do that. You can improve your code by eliminating multiple iteration over the folder/folders. For example you can use boost::filesystem::recursive_directory_iterator and some filter function to retrieve all the files in a single iteration.
Here is the sample:
#include <boost/filesystem.hpp>
#include <set>
namespace fs = ::boost::filesystem;
void GetPictures(const fs::path& root, const std::set<string>& exts, vector<fs::path>& result)
{
if(!fs::exists(root) || !fs::is_directory(root))
{
return;
}
fs::recursive_directory_iterator it(root);
fs::recursive_directory_iterator endit;
while(it != endit)
{
if(fs::is_regular_file(*it) && exts.find(it->path().extension()) != exts.end())
{
result.push_back(it->path());
}
++it;
}
}
It's only a sample, you should take care of string casing, error handling, etc.

The best way to ignore files with other extensions when using the C++ experimental <filesystem>?

With the future C++, is there a better way to ignore files with other than wanted extensions than the one shown in the code snippet below?
I am learning the C++ experimental <filesystem> (http://en.cppreference.com/w/cpp/experimental/fs) while writing a simple program that transforms text files from one directory to text file in another directory. The program takes input and output directories via command-line arguments. Only the files with certain extensions (like .csv, .txt, ...) should be processed. The output files should have the .xxx extension.
#include <filesystem>
namespace fs = std::tr2::sys; // the implementation from Visual Studio 2015
...
fs::path srcpath{ argv[1] };
fs::path destpath{ argv[2] };
...
for (auto name : fs::directory_iterator(srcpath))
{
if (!fs::is_regular_file(name))
continue; // ignore the non-files
fs::path fnameIn{ name }; // input file name
// Ignore unwanted extensions (here lowered because of Windows).
string ext{ lower(fnameIn.extension().string()) };
if (ext != ".txt" && ext != ".csv")
continue;
// Build the output filename path.
fs::path fnameOut{ destpath / fnameIn.filename().replace_extension(".xxx") };
... processing ...
}
Basically, your question boils down to, "given a string, how do I determine if it matches one of a number of possibilities?" That's pretty trivial: put the possibilities in a std::set:
//Before loop
std::set<std::string> wanted_exts = {".txt", ".csv"};
//In loop
string ext{ lower(fnameIn.extension().string()) };
if (wanted_exts.find(ext) == wanted_exts.end())
continue;
You can of course keep wanted_exts around for as long as you like, since it probably won't change. Also, if you have Boost.Containers, I would suggest making wanted_exts a flat_set. That will help minimize allocations.
std::tr2::sys was the namespace MSVC used in VS2013 to ship the filesystem TS, but that is actually supposed to be in the std::experimental::v1 namespace; the old namespace has been retained for backwards compatibility. v1 is an inline namespace, so you can drop that from the name and say
namespace fs = std::experimental::filesystem;
Assuming using boost is an option, you can perform filtering of the directory entries using Boost.Range adaptors. And testing for any one of several extensions can be done using boost::algorithm::any_of_equal.
#include <boost/algorithm/cxx11/any_of.hpp>
#include <boost/range/adaptors.hpp>
for(auto const& p :
boost::make_iterator_range(fs::directory_iterator(srcpath), {})
| boost::adaptors::transformed([](auto const& d) {
return fs::path(d); })
| boost::adaptors::filtered([](auto const& p) {
return fs::is_regular_file(p); })
| boost::adaptors::filtered([](auto const& p) {
auto const& exts = { ".txt", ".csv" };
return boost::algorithm::any_of_equal(exts, p.extension().string()); })
) {
// all filenames here will have one of the extensions you tested for
}
The solution of the loop that I have finally chosen...
#include <filesystem>
namespace fs = std::experimental::filesystem;
...
set<string> extensions{ ".txt", ".csv" };
for (auto const& name : fs::directory_iterator(srcpath))
{
if (!fs::is_regular_file(name))
continue;
fs::path fnameIn{ name };
string ext{ lower(fnameIn.extension().string()) };
if (extensions.find(ext) != extensions.end())
{
fs::path fnameOut{ destpath / fnameIn.filename().replace_extension(".xxx") };
processing(fnameIn, fnameOut);
}
}

How to get the stem of a filename from a path?

I want to extract a const char* filename from a const char* filepath. I tried with regex but failed:
const char* currentLoadedFile = "D:\files\file.lua";
char fileName[256];
if (sscanf(currentLoadedFile, "%*[^\\]\\%[^.].lua", fileName)) {
return (const char*)fileName; // WILL RETURN "D:\files\file!!
}
The issue is that "D:\files\file" will be returned and not the wanted "file"(note: without ".lua")
What about using std::string?
e.g.
std::string path("d:\\dir\\subdir\\file.ext");
std::string filename;
size_t pos = path.find_last_of("\\");
if(pos != std::string::npos)
filename.assign(path.begin() + pos + 1, path.end());
else
filename = path;
Just use boost::filesystem.
#include <boost/filesystem.hpp>
std::string filename_noext;
filename_noext = boost::filesystem::path("D:\\files\\file.lua").stem().string().
const char* result_as_const_char = filename_noext.c_str();
or alternatively, if you want to introduce bugs yourself :
// have fun defining that to the separator of the target OS.
#define PLATFORM_DIRECTORY_SEPARATOR '\\'
// the following code is guaranteed to have bugs.
std::string input = "D:\\files\\file.lua";
std::string::size_type filename_begin = input.find_last_of(PLATFORM_DIRECTORY_SEPERATOR);
if (filename_begin == std::string::npos)
filename_begin = 0;
else
filename_begin++;
std::string::size_type filename_length = input.find_last_of('.');
if (filename_length != std::string::npos)
filename_length = filename_length - filename_begin;
std::string result = input.substr(filename_begin, filename_length);
const char* bugy_result_as_const_char = result.c_str();
You can do this portably and easily using the new filesystem library in C++17.
#include <cstdint>
#include <cstdio>
#include <filesystem>
int main()
{
std::filesystem::path my_path("D:/files/file.lua");
std::printf("filename: %s\n", my_path.filename().u8string().c_str());
std::printf("stem: %s\n", my_path.stem().u8string().c_str());
std::printf("extension: %s\n", my_path.extension().u8string().c_str());
}
Output:
filename: file.lua
stem: file
extension: .lua
Do note that for the time being you may need to use #include <experimental/fileystem> along with std::experimental::filesystem instead until standard libraries are fully conforming.
For more documentation on std::filesystem check out the filesystem library reference.
You can easily extract the file:
int main()
{
char pscL_Dir[]="/home/srfuser/kush/folder/kushvendra.txt";
char pscL_FileName[50];
char pscL_FilePath[100];
char *pscL;
pscL=strrchr(pscL_Dir,'/');
if(pscL==NULL)
printf("\n ERROR :INvalid DIr");
else
{
strncpy(pscL_FilePath,pscL_Dir,(pscL-pscL_Dir));
strcpy(pscL_FileName,pscL+1);
printf("LENTH [%d}\n pscL_FilePath[%s]\n pscL_FileName[%s]",(pscL-pscL_Dir),pscL_FilePath,pscL_FileName);
}
return 0;
}
output:
LENTH [25}
pscL_FilePath[/home/srfuser/kush/folder]
pscL_FileName[kushvendra.txt
Here you can find an example. I'm not saying it's the best and I'm sure you could improve on that but it uses only standard C++ (anyway at least what's now considered standard).
Of course you won't have the features of the boost::filesystem (those functions in the example play along with plain strings and do not guarantee/check you'll actually working with a real filesystem path).
// Set short name:
char *Filename;
Filename = strrchr(svFilename, '\\');
if ( Filename == NULL )
Filename = svFilename;
if ( Filename[0] == '\\')
++Filename;
if ( !lstrlen(Filename) )
{
Filename = svFilename;
}
fprintf( m_FileOutput, ";\n; %s\n;\n", Filename );
You could use the _splitpath_s function to break a path name into its components. I don't know if this is standard C or is Windows specific. Anyway this is the function:
#include <stdlib.h>
#include <string>
using std::string;
bool splitPath(string const &path, string &drive, string &directory, string &filename, string &extension) {
// validate path
drive.resize(_MAX_DRIVE);
directory.resize(_MAX_DIR);
filename.resize(_MAX_FNAME);
extension.resize(_MAX_EXT);
errno_t result;
result = _splitpath_s(path.c_str(), &drive[0], drive.size(), &directory[0], directory.size(), &filename[0], filename.size(), &extension[0], extension.size());
//_splitpath(path.c_str(), &drive[0], &directory[0], &filename[0], &extension[0]); //WindowsXp compatibility
_get_errno(&result);
if (result != 0) {
return false;
} else {
//delete the blank spaces at the end
drive = drive.c_str();
directory = directory.c_str();
filename = filename.c_str();
extension = extension.c_str();
return true;
}
}
It is a lot easier and safe to use std::string but you could modify this to use TCHAR* (wchar, char)...
For your specific case:
int main(int argc, char *argv[]) {
string path = argv[0];
string drive, directory, filename, extension;
splitPath(path, drive, directory, filename, extension);
printf("FILE = %s%s", filename.c_str(), extension.c_str());
return 0;
}
If you are going to display a filename to the user on Windows you should respect their shell settings (show/hide extension etc).
You can get a filename in the correct format by calling SHGetFileInfo with the SHGFI_DISPLAYNAME flag.