# DeepCam - VisionProcessor C++ Library 这个项目将Qwen多模态大模型的视觉预处理Python代码转换为高性能的C++实现。VisionProcessor库提供了完整的图像和视频预处理功能,以及Qwen2VL处理器,专为多模态AI应用设计。 ## 🎯 特性 - **🖼️ 图像处理**: 支持多种图像格式(JPG, PNG, BMP, TIFF, WebP等) - **🎬 视频处理**: 支持主流视频格式(MP4, AVI, MOV, MKV等) - **🧠 智能调整大小**: 基于像素约束和纵横比的智能调整算法 - **🌐 多种输入源**: 支持本地文件、HTTP/HTTPS URL和Base64编码 - **⚡ 高性能**: 使用OpenCV优化的图像处理算法 - **🎛️ 灵活配置**: 丰富的参数配置选项 - **🔒 内存安全**: RAII和现代C++内存管理 - **📹 智能帧采样**: 高效的视频帧采样算法 - **🤖 Qwen2VL处理器**: 完整的多模态处理器,支持图像、视频和文本的联合处理 ## 📁 项目结构 ``` project-root/ # 项目根目录 ├── CMakeLists.txt # 主CMake配置文件 ├── README.md # 项目说明文档 ├── deepcam/ # DeepCam源代码目录 │ └── sources/ # 源代码子目录 │ ├── CMakeLists.txt # 库CMake配置 │ ├── vision_process.hpp # 视觉处理头文件 │ ├── vision_process.cc # 视觉处理实现文件 │ ├── qwen2_vl_processor.hpp # Qwen2VL处理器头文件 │ └── qwen2_vl_processor.cc # Qwen2VL处理器实现文件 └── test/ # 测试和示例目录 ├── CMakeLists.txt # 测试CMake配置 ├── vision_process_example.cpp # 视觉处理使用示例 └── qwen2_vl_example.cpp # Qwen2VL处理器使用示例 ``` ## 🛠️ 依赖 ### 必需依赖 - **OpenCV 4.x**: 图像和视频处理 (`libopencv-dev`) - **libcurl**: HTTP请求支持 (`libcurl4-openssl-dev`) - **CMake 3.10+**: 构建系统 - **C++17**: 现代C++标准支持 ### 可选依赖 - **jsoncpp**: JSON配置文件解析 (`libjsoncpp-dev`) - AutoProcessor功能需要 ### 系统要求 - **Linux**: Ubuntu 18.04+ / CentOS 7+ / 其他主流发行版 - **macOS**: 10.14+ (支持Homebrew) - **编译器**: GCC 7+ / Clang 6+ / MSVC 2017+ ## 📦 安装依赖 ### Ubuntu/Debian ```bash sudo apt-get update sudo apt-get install libopencv-dev libcurl4-openssl-dev cmake build-essential pkg-config # 可选:安装jsoncpp以支持AutoProcessor sudo apt-get install libjsoncpp-dev ``` ### macOS (使用Homebrew) ```bash brew install opencv curl cmake pkg-config # 可选:安装jsoncpp以支持AutoProcessor brew install jsoncpp ``` ### CentOS/RHEL ```bash sudo yum install opencv-devel libcurl-devel cmake3 gcc-c++ pkgconfig # 可选:安装jsoncpp以支持AutoProcessor (EPEL仓库) sudo yum install epel-release sudo yum install jsoncpp-devel # 或者在较新版本上使用 dnf sudo dnf install opencv-devel libcurl-devel cmake gcc-c++ pkgconf-pkg-config jsoncpp-devel ``` ## 🔨 编译 ```bash # 进入项目根目录 cd project-root # 创建构建目录 mkdir build && cd build # 配置CMake cmake .. # 编译 make -j$(nproc) # 可选:安装到系统 sudo make install ``` ### 编译选项 ```bash # Debug模式编译 cmake -DCMAKE_BUILD_TYPE=Debug .. # Release模式编译(默认) cmake -DCMAKE_BUILD_TYPE=Release .. # 指定OpenCV路径(如果需要) cmake -DOpenCV_DIR=/path/to/opencv .. ``` ## 🚀 快速开始 ### 基本用法 ```cpp #include "vision_process.hpp" int main() { // 创建处理器实例 VisionProcessor processor; // 处理单张图像 std::map imageConfig; imageConfig["image"] = "/path/to/image.jpg"; imageConfig["min_pixels"] = "3136"; imageConfig["max_pixels"] = "12845056"; try { cv::Mat processedImage = processor.fetchImage(imageConfig); std::cout << "图像处理成功!尺寸: " << processedImage.cols << "x" << processedImage.rows << std::endl; } catch (const std::exception& e) { std::cerr << "错误: " << e.what() << std::endl; } return 0; } ``` ### 处理视频 ```cpp // 配置视频处理参数 std::map videoConfig; videoConfig["video"] = "/path/to/video.mp4"; videoConfig["fps"] = "2.0"; // 采样帧率 videoConfig["min_frames"] = "4"; // 最少帧数 videoConfig["max_frames"] = "32"; // 最多帧数 videoConfig["video_start"] = "10.0"; // 开始时间(秒) videoConfig["video_end"] = "60.0"; // 结束时间(秒) std::vector frames = processor.fetchVideo(videoConfig); std::cout << "提取了 " << frames.size() << " 帧" << std::endl; ``` ### Qwen2VL处理器用法 ```cpp #include "qwen2_vl_processor.hpp" int main() { // 创建模拟的处理器组件 auto image_processor = std::make_shared(); auto video_processor = std::make_shared(); auto tokenizer = std::make_shared(); // 创建Qwen2VL处理器 Qwen2VLProcessor processor(image_processor, tokenizer, video_processor); // 处理包含图像的文本 TextInput text = "描述这张图片: <|image_pad|> 你看到了什么?"; cv::Mat image = cv::imread("/path/to/image.jpg"); ImageInput image_input = image; // 配置处理参数 ProcessorKwargs kwargs; kwargs.text_kwargs["return_tensors"] = "pt"; kwargs.text_kwargs["return_mm_token_type_ids"] = "true"; try { // 进行多模态处理 BatchFeature result = processor(image_input, text, std::nullopt, kwargs); std::cout << "处理成功!" << std::endl; std::cout << "结果包含的数据键: "; for (const auto& [key, value] : result.data) { std::cout << key << " "; } std::cout << std::endl; } catch (const std::exception& e) { std::cerr << "错误: " << e.what() << std::endl; } return 0; } ``` ### 计算多模态token数量 ```cpp // 计算图像和视频所需的token数量 std::vector> image_sizes = {{224, 224}, {448, 448}}; std::vector> video_sizes = {{8, 224, 224}}; MultiModalData mm_data = processor.getNumMultimodalTokens(image_sizes, video_sizes); if (mm_data.num_image_tokens.has_value()) { std::cout << "图像token数量: "; for (int count : mm_data.num_image_tokens.value()) { std::cout << count << " "; } std::cout << std::endl; } ``` ### 批量处理 ```cpp // 处理包含多媒体的会话数据 std::vector> conversations; // 添加图像 std::map imageItem; imageItem["type"] = "image"; imageItem["image"] = "/path/to/image.jpg"; conversations.push_back(imageItem); // 添加视频 std::map videoItem; videoItem["type"] = "video"; videoItem["video"] = "/path/to/video.mp4"; videoItem["fps"] = "2.0"; conversations.push_back(videoItem); // 处理所有媒体 ProcessResult result = processor.processVisionInfo(conversations, true); std::cout << "处理结果:" << std::endl; std::cout << "- 图像数量: " << result.imageInputs.size() << std::endl; std::cout << "- 视频数量: " << result.videoInputs.size() << std::endl; ``` ### AutoProcessor用法(工厂模式) ```cpp #include "auto_processor.hpp" int main() { try { // 方法1: 从预训练模型目录加载 FromPretrainedKwargs kwargs; kwargs.trust_remote_code = true; auto processor = AutoProcessor::fromPretrained("/path/to/model", kwargs); // 方法2: 注册自定义处理器 AutoProcessor::registerProcessor("custom_model", [](const std::string& path, const FromPretrainedKwargs& kwargs) { // 创建自定义处理器的逻辑 return createCustomProcessor(path, kwargs); }); // 使用处理器 TextInput text = "描述这个图像: <|image_pad|>"; cv::Mat image = cv::imread("image.jpg"); ProcessorKwargs proc_kwargs; auto result = processor(ImageInput(image), text, std::nullopt, proc_kwargs); std::cout << "处理完成!" << std::endl; } catch (const std::exception& e) { std::cerr << "错误: " << e.what() << std::endl; } return 0; } ``` ## 📖 API 参考 ### VisionProcessor 类 #### 主要方法 | 方法 | 说明 | 返回值 | |------|------|--------| | `fetchImage()` | 处理单张图像 | `cv::Mat` | | `fetchVideo()` | 处理视频文件 | `std::vector` | | `processVisionInfo()` | 批量处理会话数据 | `ProcessResult` | #### 工具函数 | 函数 | 说明 | 参数 | |------|------|------| | `smartResize()` | 智能调整尺寸 | `height, width, factor, minPixels, maxPixels` | | `roundByFactor()` | 按因子四舍五入 | `number, factor` | | `ceilByFactor()` | 按因子向上取整 | `number, factor` | | `floorByFactor()` | 按因子向下取整 | `number, factor` | ### 配置参数 #### 图像配置 ```cpp std::map config; config["image"] = "path/to/image.jpg"; // 图像路径 config["image_url"] = "http://example.com/image.jpg"; // 或使用URL config["min_pixels"] = "3136"; // 最小像素数 (默认: 4*28*28) config["max_pixels"] = "12845056"; // 最大像素数 (默认: 16384*28*28) config["resized_height"] = "224"; // 指定高度 config["resized_width"] = "224"; // 指定宽度 ``` #### 视频配置 ```cpp std::map config; config["video"] = "path/to/video.mp4"; // 视频路径 config["fps"] = "2.0"; // 采样帧率 (默认: 2.0) config["nframes"] = "16"; // 或直接指定帧数 config["min_frames"] = "4"; // 最小帧数 (默认: 4) config["max_frames"] = "768"; // 最大帧数 (默认: 768) config["video_start"] = "10.0"; // 开始时间(秒) config["video_end"] = "60.0"; // 结束时间(秒) config["min_pixels"] = "100352"; // 最小像素数 (默认: 128*28*28) config["max_pixels"] = "602112"; // 最大像素数 (默认: 768*28*28) ``` ## 🎨 支持的输入格式 ### 图像格式 - **本地文件**: `"/path/to/image.jpg"` - **HTTP URL**: `"http://example.com/image.jpg"` - **HTTPS URL**: `"https://example.com/image.jpg"` - **Base64编码**: `"data:image/jpeg;base64,/9j/4AAQ..."` - **File URI**: `"file:///path/to/image.jpg"` ### 视频格式 - **本地文件**: `"/path/to/video.mp4"` - **File URI**: `"file:///path/to/video.mp4"` ### 支持的文件扩展名 - **图像**: `.jpg`, `.jpeg`, `.png`, `.bmp`, `.tiff`, `.tif`, `.webp` - **视频**: `.mp4`, `.avi`, `.mov`, `.mkv`, `.wmv`, `.flv`, `.webm` ## ⚙️ 高级配置 ### 环境变量 ```bash # 设置视频处理的最大像素限制 export VIDEO_MAX_PIXELS=100000000 # 运行程序 ./vision_process_example ``` ### 常量配置 ```cpp // 可在头文件中修改的常量 static constexpr int IMAGE_FACTOR = 28; // 图像尺寸因子 static constexpr int MIN_PIXELS = 4 * 28 * 28; // 图像最小像素 static constexpr int MAX_PIXELS = 16384 * 28 * 28; // 图像最大像素 static constexpr int MAX_RATIO = 200; // 最大宽高比 static constexpr int VIDEO_MIN_PIXELS = 128 * 28 * 28; // 视频最小像素 static constexpr int VIDEO_MAX_PIXELS = 768 * 28 * 28; // 视频最大像素 static constexpr int FRAME_FACTOR = 2; // 帧数因子 static constexpr double FPS = 2.0; // 默认帧率 ``` ## 🧪 测试 运行示例程序: ```bash # 编译后运行测试 ./build/test/vision_process_example # 或者使用CMake测试 cd build ctest ``` ## 🐛 错误处理 库使用标准C++异常处理机制: ```cpp try { auto result = processor.fetchImage(config); // 处理结果 } catch (const std::invalid_argument& e) { std::cerr << "参数错误: " << e.what() << std::endl; } catch (const std::runtime_error& e) { std::cerr << "运行时错误: " << e.what() << std::endl; } catch (const std::exception& e) { std::cerr << "未知错误: " << e.what() << std::endl; } ``` ### 常见错误类型 - `std::invalid_argument`: 参数错误或配置无效 - `std::runtime_error`: 文件读取、网络请求或处理失败 - `cv::Exception`: OpenCV相关错误 ## 🚀 性能优化 ### 内存管理 - 使用RAII自动管理内存 - 预分配向量容量减少重分配 - 及时释放大型Mat对象 ### 处理优化 - 批量处理多个文件时重用processor实例 - 对于大视频文件,考虑分段处理 - 使用多线程处理多个独立任务 ### 示例:批量处理优化 ```cpp VisionProcessor processor; // 重用实例 for (const auto& config : imageConfigs) { try { auto image = processor.fetchImage(config); // 处理图像... } catch (const std::exception& e) { // 记录错误但继续处理其他图像 std::cerr << "跳过图像: " << e.what() << std::endl; } } ``` ## 🤝 贡献 欢迎提交Issue和Pull Request! ### 开发设置 ```bash # 克隆仓库 git clone cd project-root # 创建开发分支 git checkout -b feature/your-feature # 编译并测试 mkdir build && cd build cmake -DCMAKE_BUILD_TYPE=Debug .. make ./test/vision_process_example ``` ## 📄 许可证 本项目基于原始Qwen多模态Python代码改编,请遵循相应的开源许可证条款。 ## 🆘 常见问题 ### Q: 编译时找不到OpenCV A: 确保安装了OpenCV开发包,或使用`-DOpenCV_DIR`指定路径。 ### Q: libcurl链接错误 A: 安装libcurl开发包:`sudo apt-get install libcurl4-openssl-dev` ### Q: 视频文件无法打开 A: 确保OpenCV编译时启用了相应的视频编解码器支持。 ### Q: 内存使用过高 A: 对于大文件,考虑降低max_pixels设置或分批处理。 ### Q: Base64解码失败 A: 确保Base64字符串格式正确,包含正确的MIME类型前缀。 --- **🌟 如果这个项目对您有帮助,请给我们一个Star!** ### Qwen2VLProcessor 类 #### 主要方法 | 方法 | 说明 | 返回值 | |------|------|--------| | `operator()` | 主处理方法,处理多模态输入 | `BatchFeature` | | `getNumMultimodalTokens()` | 计算多模态token数量 | `MultiModalData` | | `batchDecode()` | 批量解码token序列 | `std::vector` | | `decode()` | 解码单个token序列 | `std::string` | | `postProcessImageTextToText()` | 后处理生成的文本 | `std::vector` | | `getModelInputNames()` | 获取模型输入名称 | `std::vector` | #### 支持的数据结构 ```cpp // 批处理特征结构 struct BatchFeature { std::map>> data; std::string tensor_type; }; // 多模态数据信息 struct MultiModalData { std::optional> num_image_tokens; std::optional> num_image_patches; std::optional> num_video_tokens; std::optional> num_video_patches; }; // 处理器配置参数 struct ProcessorKwargs { std::map images_kwargs; std::map videos_kwargs; std::map text_kwargs; }; ``` #### 抽象接口 如需自定义处理器,需要实现以下抽象接口: ```cpp class ImageProcessor { public: virtual std::map>> processImages( const ImageInput& images, const std::map& kwargs) = 0; virtual int getMergeSize() const = 0; virtual int getNumberOfImagePatches(int height, int width, const std::map& kwargs) const = 0; virtual std::vector getModelInputNames() const = 0; }; class VideoProcessor { public: virtual std::map>> processVideos( const VideoInput& videos, const std::map& kwargs) = 0; virtual int getMergeSize() const = 0; virtual int getNumberOfVideoPatches(int num_frames, int height, int width, const std::map& kwargs) const = 0; }; class Tokenizer { public: virtual std::map>> tokenize( const std::vector& texts, const std::map& kwargs) = 0; virtual std::vector batchDecode( const std::vector>& token_ids, bool skip_special_tokens = true, bool clean_up_tokenization_spaces = false) = 0; virtual std::string decode( const std::vector& token_ids, bool skip_special_tokens = true, bool clean_up_tokenization_spaces = false) = 0; virtual int convertTokensToIds(const std::string& token) = 0; virtual std::vector getModelInputNames() const = 0; }; ``` #### 工具函数