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.

299 lines
11 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.

#pragma once
#include <string>
#include <vector>
#include <filesystem>
#include <algorithm>
#include <cctype>
#include <mutex>
#include <atomic>
#include <opencv2/opencv.hpp>
class Image_ReadChannel;
namespace fs = std::filesystem;
/// @brief 单张图片对信息(左图 + 右图配对)
struct LoadImagePairInfo
{
std::string product_id; ///< 产品 ID
std::string image_name; ///< 图片名称(不含目录)
std::string original_channel; ///< 原始通道名称(从文件名提取)
std::string channel_name; ///< 映射后的通道名称
std::string camera_side; ///< 相机方位:"left" | "right" | "merge"
// 左图信息
std::string left_path; ///< 左图完整路径
cv::Mat left_img; ///< 左图数据
// 右图信息
std::string right_path; ///< 右图完整路径
cv::Mat right_img; ///< 右图数据
/// @brief 默认构造函数,初始化所有成员
LoadImagePairInfo() { Init(); }
/// @brief 初始化成员变量,释放图片内存
void Init()
{
product_id = "";
image_name = "";
original_channel = "";
channel_name = "";
camera_side = "";
left_path = "";
right_path = "";
if (!left_img.empty())
left_img.release();
if (!right_img.empty())
right_img.release();
}
/// @brief 拷贝另一个对象的数据(深拷贝)
/// @param tem 源对象
void copy(const LoadImagePairInfo &tem)
{
this->product_id = tem.product_id;
this->image_name = tem.image_name;
this->original_channel = tem.original_channel;
this->channel_name = tem.channel_name;
this->camera_side = tem.camera_side;
this->left_path = tem.left_path;
this->right_path = tem.right_path;
if (!tem.left_img.empty())
{
this->left_img = tem.left_img.clone();
}
if (!tem.right_img.empty())
{
this->right_img = tem.right_img.clone();
}
}
/// @brief 打印图片对信息到控制台
/// @param str 前缀标识字符串
void print(const std::string &str) const
{
printf("%s>> product_id: %s, image: %s, channel: %s (original: %s, side: %s)\n",
str.c_str(), product_id.c_str(), image_name.c_str(), channel_name.c_str(), original_channel.c_str(), camera_side.c_str());
printf(" Left: %s\n", left_path.c_str());
if (!left_img.empty())
{
printf(" %dx%d, type: %d\n", left_img.cols, left_img.rows, left_img.type());
}
printf(" Right: %s\n", right_path.c_str());
if (!right_img.empty())
{
printf(" %dx%d, type: %d\n", right_img.cols, right_img.rows, right_img.type());
}
printf("\n");
}
/// @brief 获取图片对信息字符串
std::string GetInfo(const std::string &str) const
{
std::string info = str + ">> " + product_id + " | " +
image_name + " | " + channel_name + " (orig: " + original_channel + ", side: " + camera_side + ")\n";
info += " Left: " + left_path + "\n";
info += " Right: " + right_path + "\n";
return info;
}
};
/// @brief 一个产品的所有图片对(产品 - 图片两层结构)
struct LoadProductImages
{
std::string product_id; ///< 产品 ID
int camera_Num; ///< 相机数量
std::vector<LoadImagePairInfo> images; ///< 图片对列表
/// @brief 默认构造函数,初始化所有成员
LoadProductImages() { Init(); }
/// @brief 初始化成员变量,清空图片列表
void Init()
{
product_id = "";
images.clear();
images.shrink_to_fit();
camera_Num = 0;
}
/// @brief 拷贝另一个对象的数据(深拷贝)
/// @param tem 源对象
void copy(const LoadProductImages &tem)
{
this->product_id = tem.product_id;
this->camera_Num = tem.camera_Num;
this->images.clear();
for (size_t i = 0; i < tem.images.size(); i++)
{
LoadImagePairInfo img;
img.copy(tem.images[i]);
this->images.push_back(img);
}
}
void print(const std::string &str)
{
printf("╔═══════════════════════════════════════════════════════════\n");
printf("║ Product: %-50s camera_Num %d \n", product_id.c_str(), camera_Num);
printf("║ Total Channel Pairs: %zu\n", images.size());
printf("╠═══════════════════════════════════════════════════════════\n");
for (size_t i = 0; i < images.size() && i < 5; i++)
{
printf("║ [%2zu] camera %-10s Channel: %-10s\n",
i, images[i].camera_side.c_str(), images[i].channel_name.c_str());
printf("║ Left: %-30s\n",
images[i].left_path.empty() ? "(none)" : images[i].left_path.c_str());
printf("║ Right: %-30s\n",
images[i].right_path.empty() ? "(none)" : images[i].right_path.c_str());
}
if (images.size() > 5)
{
printf("║ ... (%zu more image pairs)\n", images.size() - 5);
}
printf("╚═══════════════════════════════════════════════════════════\n\n");
}
/// @brief 获取产品信息字符串
std::string GetInfo(const std::string &str)
{
return str + ">> Product: " + product_id + " |camera_Num" + std::to_string(camera_Num) +
" | Image Pairs: " + std::to_string(images.size()) + "\n";
}
};
/// @brief 加载配置
struct LoadConfig
{
std::vector<std::string> extensions; ///< 支持的图片扩展名列表
bool recursive; ///< 是否递归搜索子目录
size_t max_products; ///< 最多加载的产品数量0 表示不限制)
/// @brief 默认构造函数,初始化所有成员
LoadConfig() { Init(); }
/// @brief 初始化配置参数
void Init()
{
extensions.clear();
extensions.push_back("tif");
extensions.push_back("tiff");
extensions.push_back("jpg");
extensions.push_back("jpeg");
extensions.push_back("png");
extensions.push_back("bmp");
recursive = true;
max_products = 0;
}
/// @brief 拷贝另一个对象的数据
/// @param tem 源对象
void copy(const LoadConfig &tem)
{
this->extensions = tem.extensions;
this->recursive = tem.recursive;
this->max_products = tem.max_products;
}
};
/// @brief 图片加载器类
/// @detail 线程安全说明:
/// - LoadALLImg() 可安全地在多个线程中同时调用
/// - total_loaded_ 计数器使用原子操作,线程安全
/// - last_error_ 由互斥锁保护
/// - 注意:返回的结果向量由调用者自行管理
class LoadImage
{
public:
/// @brief 构造函数
LoadImage();
/// @brief 析构函数
~LoadImage();
long getcurTime();
/// @brief 初始化成员变量
void Init();
/// @brief 拷贝另一个对象的数据(深拷贝)
/// @param tem 源对象
void copy(const LoadImage &tem);
/// @brief 加载指定目录下的所有图片
/// @param root_dir 根目录路径
/// @param config 加载配置(可选,默认为默认配置)
/// @param pImageReadChannel 通道名称映射对象(可选,用于通道过滤和名称映射)
/// @param target_product_id 目标产品 ID可选为空时加载所有产品
/// @return 产品图片列表
std::vector<LoadProductImages> LoadALLImg(
const std::string &root_dir,
const LoadConfig &config = LoadConfig(),
void *pImageReadChannel = nullptr,
const std::string &target_product_id = "");
/// @brief 获取最后错误信息
/// @return 错误信息字符串的引用
const std::string &getLastError() const;
/// @brief 获取已加载图片总数
/// @return 图片总数
size_t getTotalLoaded() const;
/// @brief 打印加载器信息到控制台
/// @param str 前缀标识字符串
void print(const std::string &str);
/// @brief 获取加载器信息字符串
/// @param str 前缀标识字符串
/// @return 格式化后的信息字符串
std::string GetInfo(const std::string &str);
/// @brief 解析图片路径,提取图片对信息
/// @param file_path 文件路径
/// @param out_info 输出图片对信息
/// @param product_id 产品 ID
/// @param image_name 图片名称
/// @param channel_name 通道名称
/// @param is_left 是否为左图
/// @param load_data 是否加载图片数据(默认为 true
/// @return 解析是否成功
static bool parseImagePath(
const fs::path &file_path,
LoadImagePairInfo &out_info,
const std::string &product_id,
const std::string &image_name,
const std::string &channel_name,
bool is_left,
bool load_data = true);
/// @brief 从目录名提取产品 ID
/// @param dir_name 目录名
/// @return 产品 ID 字符串
static std::string extractProductId(const std::string &dir_name);
/// @brief 从文件名提取通道名称
/// @param filename 文件名
/// @return 通道名称字符串
static std::string extractChannelName(const std::string &filename);
private:
// 预编译的扩展名常量(小写,不含点号)
static constexpr const char *EXTENSIONS[] = {
"tif", "tiff", "jpg", "jpeg", "png", "bmp"};
static constexpr size_t EXTENSION_COUNT = 6;
/// @brief 检查文件名是否匹配的扩展名
/// @param filename 文件名
/// @return 是否匹配
bool matchesExtension(
const std::string &filename) const;
/// @brief 加载图片文件
/// @param path 文件路径
/// @return OpenCV Mat 图片数据
cv::Mat loadImageFile(const fs::path &path) const;
/// @brief 设置错误信息
/// @param error 错误信息字符串
void setLastError(const std::string &error);
std::string last_error_; ///< 最后错误信息
std::atomic<size_t> total_loaded_{0}; ///< 已加载图片总数(原子操作)
mutable std::mutex mutex_; ///< 互斥锁,保护共享状态
};