#include "LoadImage.hpp" #include "Image_ReadAndChange.h" #include #include #include // 目录名常量 namespace { constexpr const char *LEFT_DIR_NAME = "left"; constexpr const char *RIGHT_DIR_NAME = "right"; constexpr const char *PRODUCT_DIR_PREFIX = ""; } // 静态成员定义 constexpr const char *LoadImage::EXTENSIONS[]; /// @brief 递归查找所有 left/right 相机目录 /// @param root_path 根目录 /// @param camera_dirs 输出相机目录列表(包含 left 和 right) /// @param max_depth 最大递归深度 static void findCameraDirectories( const fs::path &root_path, std::vector &camera_dirs, int max_depth = 5) { try { for (const auto &entry : fs::directory_iterator(root_path)) { if (!entry.is_directory()) continue; const std::string &dir_name = entry.path().filename().string(); // 找到 left/right 目录 if (dir_name == LEFT_DIR_NAME || dir_name == RIGHT_DIR_NAME) { camera_dirs.push_back(entry.path()); continue; } // 递归查找子目录(限制深度) if (max_depth > 0) { findCameraDirectories(entry.path(), camera_dirs, max_depth - 1); } } } catch (const std::exception &e) { // 忽略权限等错误 } } LoadImage::LoadImage() { Init(); } LoadImage::~LoadImage() { } long LoadImage::getcurTime() { struct timeval tv; gettimeofday(&tv, NULL); return ((long)tv.tv_sec) * 1000 + ((long)tv.tv_usec) / 1000; } /// @brief 初始化成员变量 void LoadImage::Init() { std::lock_guard lock(mutex_); // 加锁保护 last_error_ = ""; total_loaded_.store(0, std::memory_order_relaxed); // 原子写入 } /// @brief 拷贝另一个对象的数据(深拷贝) /// @param tem 源对象 void LoadImage::copy(const LoadImage &tem) { this->last_error_ = tem.last_error_; this->total_loaded_.store(tem.total_loaded_.load(), std::memory_order_relaxed); } /// @brief 设置错误信息 /// @param error 错误信息字符串 void LoadImage::setLastError(const std::string &error) { std::lock_guard lock(mutex_); // 加锁保护 last_error_ = error; } /// @brief 获取最后错误信息 /// @return 错误信息字符串的引用 const std::string &LoadImage::getLastError() const { // 注意:这里不加锁,调用者需确保线程安全 return last_error_; } /// @brief 获取已加载图片总数 /// @return 图片总数 size_t LoadImage::getTotalLoaded() const { return total_loaded_.load(std::memory_order_relaxed); // 原子读取 } /// @brief 打印加载器状态到控制台 /// @param str 前缀标识字符串 void LoadImage::print(const std::string &str) { printf("%s>> LoadImage Status:\n", str.c_str()); printf(" Total Loaded: %zu\n", total_loaded_.load(std::memory_order_relaxed)); printf(" Last Error: %s\n", last_error_.c_str()); printf("\n"); } /// @brief 获取加载器状态信息字符串 /// @param str 前缀标识字符串 /// @return 格式化后的信息字符串 std::string LoadImage::GetInfo(const std::string &str) { return str + ">> LoadImage: total=" + std::to_string(total_loaded_.load(std::memory_order_relaxed)) + " error=" + last_error_ + "\n"; } /// @brief 从目录名提取产品 ID /// @detail 支持 "__DB__" 前缀的目录名格式,如 "__DB__PID001" 提取为 "PID001" /// @param dir_name 目录名 /// @return 产品 ID 字符串,如果格式不匹配则返回空字符串 std::string LoadImage::extractProductId(const std::string &dir_name) { // 检查是否以 "__DB__" 开头 if(PRODUCT_DIR_PREFIX == "") return dir_name; if (dir_name.find(PRODUCT_DIR_PREFIX) == 0) { // 跳过前缀,返回剩余部分作为产品 ID return dir_name.substr(std::char_traits::length(PRODUCT_DIR_PREFIX)); } // 格式不匹配,返回空字符串 return ""; } /// @brief 从文件名提取通道名称 /// @detail 优先查找 '^' 分隔符,其次查找 '_' 分隔符 /// @param filename 文件名(可包含扩展名) /// @return 通道名称字符串 std::string LoadImage::extractChannelName(const std::string &filename) { std::string name = filename; // Step 1: 移除文件扩展名 size_t ext_pos = name.rfind('.'); if (ext_pos != std::string::npos) { name = name.substr(0, ext_pos); } // Step 2: 优先尝试用 '^' 分隔符提取(如 "img_01^CH1^Org" -> "CH1") size_t last_sep_pos = name.rfind('^'); size_t sep_pos = name.rfind('^', last_sep_pos - 1); if (sep_pos != std::string::npos && sep_pos < name.length() - 1) { return name.substr(sep_pos + 1, last_sep_pos - sep_pos - 1); } // Step 3: 其次尝试用 '_' 分隔符提取(如 "img_01_CH1_Org" -> "CH1") size_t last_under_pos = name.rfind('_'); size_t under_pos = name.rfind('_', last_under_pos - 1); if (under_pos != std::string::npos && under_pos < name.length() - 1) { return name.substr(under_pos + 1, last_under_pos - under_pos - 1); } // Step 4: 如果没有分隔符,返回整个文件名(不含扩展名) return name.empty() ? "" : name; } /// @brief 检查文件名是否匹配支持的扩展名 /// @param filename 文件名 /// @return 匹配返回 true,否则返回 false bool LoadImage::matchesExtension( const std::string &filename) const { // 查找最后一个点号位置(扩展名分隔符) size_t pos = filename.rfind('.'); if (pos == std::string::npos || pos == filename.size() - 1) { return false; // 无扩展名或点号在末尾 } // 提取扩展名(不含点号) std::string_view ext(filename.c_str() + pos + 1); // 快速比较(无分配,静态数组) for (size_t i = 0; i < EXTENSION_COUNT; i++) { const std::string_view std_ext(EXTENSIONS[i]); if (ext.size() == std_ext.size()) { // 逐字符比较(忽略大小写) bool match = true; for (size_t j = 0; j < ext.size() && match; j++) { if (std::tolower(ext[j]) != std::tolower(std_ext[j])) { match = false; } } if (match) return true; } } return false; } /// @brief 使用 OpenCV 加载图片文件 /// @param path 文件路径 /// @return Mat 图片数据,加载失败返回空 Mat cv::Mat LoadImage::loadImageFile(const fs::path &path) const { // 使用 IMREAD_UNCHANGED 保留原图通道和位深信息 return cv::imread(path.string(), cv::IMREAD_UNCHANGED); } /// @brief 加载指定目录下的所有图片(产品 - 图片对两层结构) /// @detail 自动查找 left/right 目录,按文件名配对左右图 /// @param root_dir 根目录路径 /// @param config 加载配置 /// @param pImageReadChannel 通道名称映射对象(用于通道过滤和名称映射) /// @param target_product_id 目标产品 ID(为空时加载所有产品) /// @return 产品图片对列表 std::vector LoadImage::LoadALLImg( const std::string &root_dir, const LoadConfig &config, void *pImageReadChannel, const std::string &target_product_id) { std::vector result; Init(); fs::path root_path(root_dir); if (!fs::exists(root_path) || !fs::is_directory(root_path)) { setLastError("无效目录:" + root_dir); return result; } long t_start = getcurTime(); if (target_product_id.empty()) { printf("[LoadImage] 开始加载图片,根目录:%s\n", root_dir.c_str()); } else { printf("[LoadImage] 开始加载图片,根目录:%s,目标产品 ID: %s\n", root_dir.c_str(), target_product_id.c_str()); } // 步骤 1:递归查找所有 left/right 相机目录(统一处理) std::vector camera_dirs; findCameraDirectories(root_path, camera_dirs, 5); long t_find = getcurTime(); printf("[LoadImage] 找到 %zu 个相机目录 (left/right),耗时:%ldms\n", camera_dirs.size(), t_find - t_start); if (camera_dirs.empty()) { setLastError("未找到 left/right 目录"); return result; } // 步骤 2:中间结构 - 产品 ID -> (文件名 -> 图片对) struct ImagePair { std::string original_channel; // 原始通道名称 std::string channel; std::string camera_side; // 相机方位 std::string left_path; std::string right_path; }; struct ProductData { std::map images; // filename -> ImagePair }; std::map product_map; // 步骤 3:统一遍历所有相机目录(left 和 right 一起处理) for (const auto &camera_dir : camera_dirs) { // 判断是左图还是右图目录 const std::string camera_name = camera_dir.filename().string(); const bool is_left = (camera_name == LEFT_DIR_NAME); const bool is_right = (camera_name == RIGHT_DIR_NAME); // 如果不是 left 或 right,跳过(理论上不会发生) if (!is_left && !is_right) { continue; } try { // 遍历相机目录下的产品目录 for (const auto &product_entry : fs::directory_iterator(camera_dir)) { if (!product_entry.is_directory()) continue; const std::string product_id = extractProductId(product_entry.path().filename().string()); if (product_id.empty()) continue; // 如果指定了目标产品 ID,只加载该产品 if (!target_product_id.empty() && product_id != target_product_id) { continue; } auto &product = product_map[product_id]; // 遍历产品目录下的图片文件 for (const auto &file_entry : fs::directory_iterator(product_entry.path())) { if (!file_entry.is_regular_file()) continue; const std::string filename = file_entry.path().filename().string(); if (!matchesExtension(filename)) continue; // 提取原始通道名称 std::string original_channel = extractChannelName(filename); if (original_channel.empty()) continue; std::string channel = original_channel; // 使用 m_image_ReadChannel 进行通道过滤和名称映射 if (pImageReadChannel != nullptr) { Image_ReadChannel *pChannel = static_cast(pImageReadChannel); std::string mapped_channel = pChannel->strDetName(channel); // 如果映射结果为空,说明该通道不在检测列表中,过滤掉 if (mapped_channel.empty()) { printf("[LoadImage] 跳过未配置的通道:%s (文件:%s)\n", original_channel.c_str(), filename.c_str()); continue; } channel = mapped_channel; } // 根据目录类型设置左图或右图路径 auto &img_pair = product.images[filename + camera_name]; img_pair.original_channel = original_channel; img_pair.channel = channel; if (is_left) { img_pair.left_path = file_entry.path().string(); } else if (is_right) { img_pair.right_path = file_entry.path().string(); } // 设置相机方位(在都设置了之后判断) if (!img_pair.left_path.empty() && !img_pair.right_path.empty()) { img_pair.camera_side = "merge"; } else if (!img_pair.left_path.empty()) { img_pair.camera_side = "left"; } else if (!img_pair.right_path.empty()) { img_pair.camera_side = "right"; } else { img_pair.camera_side = "unknown"; } total_loaded_.fetch_add(1, std::memory_order_relaxed); } } } catch (const std::exception &e) { std::cerr << "[LoadImage] 处理相机目录错误 [" << camera_dir << "]: " << e.what() << std::endl; } } // 步骤 4:转换为最终结果(允许单图) result.reserve(product_map.size()); size_t loaded_count = 0; for (auto &[product_id, product] : product_map) { if (config.max_products > 0 && loaded_count >= config.max_products) { printf("[LoadImage] 已达到最大产品数量限制:%zu,停止加载\n", config.max_products); break; } LoadProductImages out_product; out_product.product_id = product_id; out_product.images.reserve(product.images.size()); out_product.camera_Num = camera_dirs.size(); for (auto &[filename, img_pair] : product.images) { if (!img_pair.left_path.empty() || !img_pair.right_path.empty()) { LoadImagePairInfo info; info.product_id = product_id; info.image_name = filename; size_t ext_pos = filename.rfind('.'); if (ext_pos != std::string::npos) { info.image_name = filename.substr(0, ext_pos); } info.original_channel = img_pair.original_channel; info.channel_name = img_pair.channel; info.camera_side = img_pair.camera_side; info.left_path = img_pair.left_path; info.right_path = img_pair.right_path; out_product.images.push_back(std::move(info)); } } if (!out_product.images.empty()) { result.push_back(std::move(out_product)); loaded_count++; } } long t_complete = getcurTime(); size_t total_images = 0; for (const auto &prod : result) { total_images += prod.images.size(); } printf("[LoadImage] 加载完成,产品数:%zu, 图片对数:%zu, 总耗时:%ldms\n", result.size(), total_images, t_complete - t_start); return result; }