You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

463 lines
15 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#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 = "";
}
// 静态成员定义
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(PRODUCT_DIR_PREFIX == "") return dir_name;
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^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<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;
}