deepcam/README.md

561 lines
17 KiB
Markdown
Raw Normal View History

2025-06-26 13:47:54 +08:00
# DeepCam - VisionProcessor C++ Library
2025-06-03 10:29:28 +08:00
2025-06-26 13:47:54 +08:00
这个项目将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<std::string, std::string> 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<std::string, std::string> 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<cv::Mat> 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<MockImageProcessor>();
auto video_processor = std::make_shared<MockVideoProcessor>();
auto tokenizer = std::make_shared<MockTokenizer>();
// 创建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<std::pair<int, int>> image_sizes = {{224, 224}, {448, 448}};
std::vector<std::tuple<int, int, int>> 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<std::map<std::string, std::string>> conversations;
// 添加图像
std::map<std::string, std::string> imageItem;
imageItem["type"] = "image";
imageItem["image"] = "/path/to/image.jpg";
conversations.push_back(imageItem);
// 添加视频
std::map<std::string, std::string> 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<cv::Mat>` |
| `processVisionInfo()` | 批量处理会话数据 | `ProcessResult` |
#### 工具函数
| 函数 | 说明 | 参数 |
|------|------|------|
| `smartResize()` | 智能调整尺寸 | `height, width, factor, minPixels, maxPixels` |
| `roundByFactor()` | 按因子四舍五入 | `number, factor` |
| `ceilByFactor()` | 按因子向上取整 | `number, factor` |
| `floorByFactor()` | 按因子向下取整 | `number, factor` |
### 配置参数
#### 图像配置
```cpp
std::map<std::string, std::string> 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<std::string, std::string> 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 <repository-url>
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<std::string>` |
| `decode()` | 解码单个token序列 | `std::string` |
| `postProcessImageTextToText()` | 后处理生成的文本 | `std::vector<std::string>` |
| `getModelInputNames()` | 获取模型输入名称 | `std::vector<std::string>` |
#### 支持的数据结构
```cpp
// 批处理特征结构
struct BatchFeature {
std::map<std::string, std::vector<std::vector<int>>> data;
std::string tensor_type;
};
// 多模态数据信息
struct MultiModalData {
std::optional<std::vector<int>> num_image_tokens;
std::optional<std::vector<int>> num_image_patches;
std::optional<std::vector<int>> num_video_tokens;
std::optional<std::vector<int>> num_video_patches;
};
// 处理器配置参数
struct ProcessorKwargs {
std::map<std::string, std::string> images_kwargs;
std::map<std::string, std::string> videos_kwargs;
std::map<std::string, std::string> text_kwargs;
};
```
#### 抽象接口
如需自定义处理器,需要实现以下抽象接口:
```cpp
class ImageProcessor {
public:
virtual std::map<std::string, std::vector<std::vector<int>>> processImages(
const ImageInput& images,
const std::map<std::string, std::string>& kwargs) = 0;
virtual int getMergeSize() const = 0;
virtual int getNumberOfImagePatches(int height, int width,
const std::map<std::string, std::string>& kwargs) const = 0;
virtual std::vector<std::string> getModelInputNames() const = 0;
};
class VideoProcessor {
public:
virtual std::map<std::string, std::vector<std::vector<int>>> processVideos(
const VideoInput& videos,
const std::map<std::string, std::string>& kwargs) = 0;
virtual int getMergeSize() const = 0;
virtual int getNumberOfVideoPatches(int num_frames, int height, int width,
const std::map<std::string, std::string>& kwargs) const = 0;
};
class Tokenizer {
public:
virtual std::map<std::string, std::vector<std::vector<int>>> tokenize(
const std::vector<std::string>& texts,
const std::map<std::string, std::string>& kwargs) = 0;
virtual std::vector<std::string> batchDecode(
const std::vector<std::vector<int>>& token_ids,
bool skip_special_tokens = true,
bool clean_up_tokenization_spaces = false) = 0;
virtual std::string decode(
const std::vector<int>& 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<std::string> getModelInputNames() const = 0;
};
```
#### 工具函数