2025-06-26 13:47:54 +08:00
2025-06-26 13:47:54 +08:00
2025-06-26 13:47:54 +08:00
2025-06-26 13:47:54 +08:00
2025-06-26 13:47:54 +08:00
2025-06-26 13:47:54 +08:00
2025-06-26 13:47:54 +08:00
2025-06-26 13:47:54 +08:00
2025-06-26 13:47:54 +08:00
2025-06-26 13:47:54 +08:00

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

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)

brew install opencv curl cmake pkg-config

# 可选安装jsoncpp以支持AutoProcessor  
brew install jsoncpp

CentOS/RHEL

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

🔨 编译

# 进入项目根目录
cd project-root

# 创建构建目录
mkdir build && cd build

# 配置CMake
cmake ..

# 编译
make -j$(nproc)

# 可选:安装到系统
sudo make install

编译选项

# Debug模式编译
cmake -DCMAKE_BUILD_TYPE=Debug ..

# Release模式编译默认
cmake -DCMAKE_BUILD_TYPE=Release ..

# 指定OpenCV路径如果需要
cmake -DOpenCV_DIR=/path/to/opencv ..

🚀 快速开始

基本用法

#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;
}

处理视频

// 配置视频处理参数
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处理器用法

#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数量

// 计算图像和视频所需的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;
}

批量处理

// 处理包含多媒体的会话数据
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用法工厂模式

#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

配置参数

图像配置

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";             // 指定宽度

视频配置

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

⚙️ 高级配置

环境变量

# 设置视频处理的最大像素限制
export VIDEO_MAX_PIXELS=100000000

# 运行程序
./vision_process_example

常量配置

// 可在头文件中修改的常量
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;               // 默认帧率

🧪 测试

运行示例程序:

# 编译后运行测试
./build/test/vision_process_example

# 或者使用CMake测试
cd build
ctest

🐛 错误处理

库使用标准C++异常处理机制:

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实例
  • 对于大视频文件,考虑分段处理
  • 使用多线程处理多个独立任务

示例:批量处理优化

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

开发设置

# 克隆仓库
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>

支持的数据结构

// 批处理特征结构
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;
};

抽象接口

如需自定义处理器,需要实现以下抽象接口:

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;
};

工具函数

Description
deepcam的代码仓库
Readme 435 KiB
Languages
C++ 97%
Python 2.5%
CMake 0.5%