|
|
#include "LoadImage.hpp"
|
|
|
#include "Image_ReadAndChange.h"
|
|
|
#include <iostream>
|
|
|
#include <map>
|
|
|
#include <sys/time.h>
|
|
|
|
|
|
// 目录名常量
|
|
|
namespace
|
|
|
{
|
|
|
constexpr const char *LEFT_DIR_NAME = "left";
|
|
|
constexpr const char *RIGHT_DIR_NAME = "right";
|
|
|
constexpr const char *PRODUCT_DIR_PREFIX = "__DB__";
|
|
|
}
|
|
|
|
|
|
// 静态成员定义
|
|
|
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<fs::path> &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<std::mutex> 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<std::mutex> 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 (dir_name.find(PRODUCT_DIR_PREFIX) == 0)
|
|
|
{
|
|
|
// 跳过前缀,返回剩余部分作为产品 ID
|
|
|
return dir_name.substr(std::char_traits<char>::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" -> "CH1")
|
|
|
size_t sep_pos = name.rfind('^');
|
|
|
if (sep_pos != std::string::npos && sep_pos < name.length() - 1)
|
|
|
{
|
|
|
return name.substr(sep_pos + 1);
|
|
|
}
|
|
|
|
|
|
// Step 3: 其次尝试用 '_' 分隔符提取(如 "img_01_CH1" -> "CH1")
|
|
|
size_t under_pos = name.rfind('_');
|
|
|
if (under_pos != std::string::npos && under_pos < name.length() - 1)
|
|
|
{
|
|
|
return name.substr(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<LoadProductImages> LoadImage::LoadALLImg(
|
|
|
const std::string &root_dir,
|
|
|
const LoadConfig &config,
|
|
|
void *pImageReadChannel,
|
|
|
const std::string &target_product_id)
|
|
|
{
|
|
|
std::vector<LoadProductImages> 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<fs::path> 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<std::string, ImagePair> images; // filename -> ImagePair
|
|
|
};
|
|
|
|
|
|
std::map<std::string, ProductData> 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<Image_ReadChannel *>(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;
|
|
|
}
|