You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在C++中用cv::Mat高效实现3维cv::Mat嵌入4维cv::Mat?

在C++中用cv::Mat高效实现3维卷积核转4维深度可分离卷积核逻辑

我明白你要做的事——把对应depthwise卷积的3维cv::Mat核,转换成标准4维卷积核的形式,就像PyTorch里那段代码实现的:每个通道的核只作用于自身通道,所以4维核里只有(i,i,:,:)的位置是原3维核的切片,其他位置保持初始值。

先说说你之前尝试遇到的问题:

  • 尝试1kernel4d.at<float>(i,i)只传了两个索引,OpenCV的at方法对于4维Mat会默认把后面的维度索引设为0,所以你只修改了(i,i,0,0)这个单个像素,不是整个切片。
  • 尝试2:OpenCV的at模板函数本身不支持直接传4个索引参数(最多支持3个,对应3维Mat),所以这种写法编译器就会报错,更别说执行了。

下面给你两种高效的实现方式,分别兼顾可读性和性能:

方法1:用子Mat视图拷贝(可读性优先)

利用OpenCV的operator()提取高维Mat的子视图,然后直接拷贝整个切片,代码直观,不用手动写多层循环:

#include <opencv2/opencv.hpp>

int main() {
    int channels = 3;
    int kernel_size = 5; // 对应你示例中的5x5核,可替换为3x3等

    // 初始化3维卷积核:维度顺序 {channels, kernel_size, kernel_size}
    int size3d[] = {channels, kernel_size, kernel_size};
    cv::Mat kernel3d = cv::Mat(3, size3d, CV_32F, cv::Scalar(0));
    // 给3维核赋值测试(示例数据)
    kernel3d.at<cv::Vec<float, 1>>(cv::Vec<int, 3>(0, 1, 2)) = 1.0f;
    kernel3d.at<cv::Vec<float, 1>>(cv::Vec<int, 3>(1, 3, 4)) = 2.0f;
    kernel3d.at<cv::Vec<float, 1>>(cv::Vec<int, 3>(2, 0, 0)) = 3.0f;

    // 初始化4维卷积核:维度顺序 {channels, channels, kernel_size, kernel_size},初始值设为1
    int size4d[] = {channels, channels, kernel_size, kernel_size};
    cv::Mat kernel4d = cv::Mat(4, size4d, CV_32F, cv::Scalar(1));

    // 核心赋值逻辑
    for (int i = 0; i < channels; ++i) {
        // 提取4维核中第i个输出通道、第i个输入通道的完整切片
        cv::Mat sub4d = kernel4d(
            cv::Range(i, i+1), 
            cv::Range(i, i+1), 
            cv::Range::all(), 
            cv::Range::all()
        );
        // 提取3维核中第i个通道的完整切片
        cv::Mat sub3d = kernel3d(
            cv::Range(i, i+1), 
            cv::Range::all(), 
            cv::Range::all()
        );
        // 将3维子切片拷贝到4维子切片中(reshape统一维度后拷贝)
        sub3d.copyTo(sub4d.reshape(2));
    }

    // 验证结果(可选)
    float val = kernel4d.at<cv::Vec<float,1>>(cv::Vec<int,4>(0,0,1,2));
    std::cout << "Expected 1.0, got: " << val << std::endl;
    return 0;
}

方法2:直接内存拷贝(性能优先)

如果你的场景对性能要求极高,直接操作内存块是最快的方式,前提是确保Mat是连续存储的(初始化的Mat默认都是连续的,子Mat需要额外判断):

#include <opencv2/opencv.hpp>
#include <cstring>

int main() {
    int channels = 3;
    int kernel_size = 5;

    int size3d[] = {channels, kernel_size, kernel_size};
    cv::Mat kernel3d = cv::Mat(3, size3d, CV_32F, cv::Scalar(0));
    kernel3d.at<cv::Vec<float,1>>(cv::Vec<int,3>(0,1,2)) = 1.0f;
    kernel3d.at<cv::Vec<float,1>>(cv::Vec<int,3>(1,3,4)) = 2.0f;
    kernel3d.at<cv::Vec<float,1>>(cv::Vec<int,3>(2,0,0)) = 3.0f;

    int size4d[] = {channels, channels, kernel_size, kernel_size};
    cv::Mat kernel4d = cv::Mat(4, size4d, CV_32F, cv::Scalar(1));

    // 确保Mat是连续存储的,否则需要先clone成连续的
    CV_Assert(kernel3d.isContinuous() && kernel4d.isContinuous());

    float* ptr4d = kernel4d.ptr<float>();
    float* ptr3d = kernel3d.ptr<float>();

    int slice_size = kernel_size * kernel_size; // 每个3x3/5x5切片的元素个数
    int channel_block_size = channels * slice_size; // 4维核中单个输出通道对应的所有输入通道的总元素数

    for (int i = 0; i < channels; ++i) {
        // 计算4维核中(i,i,:,:)切片的起始地址
        float* dest = ptr4d + i * channel_block_size + i * slice_size;
        // 计算3维核中(i,:,:)切片的起始地址
        float* src = ptr3d + i * slice_size;
        // 直接拷贝整个切片的内存
        memcpy(dest, src, slice_size * sizeof(float));
    }

    // 验证
    float val = kernel4d.at<cv::Vec<float,1>>(cv::Vec<int,4>(1,1,3,4));
    std::cout << "Expected 2.0, got: " << val << std::endl;
    return 0;
}

补充:关于之前尝试2的修正(不推荐,仅作参考)

如果你一定要用at方法访问4维Mat,需要传递一个cv::Vec<int,4>类型的索引,但这种三层循环的方式效率很低,不建议在实际代码中使用:

for (int i = 0; i < channels; ++i) {
    cv::Vec<int,4> idx4d;
    cv::Vec<int,3> idx3d;
    idx4d[0] = i;
    idx4d[1] = i;
    idx3d[0] = i;
    for (int k = 0; k < kernel_size; ++k) {
        idx4d[2] = k;
        idx3d[1] = k;
        for (int l = 0; l < kernel_size; ++l) {
            idx4d[3] = l;
            idx3d[2] = l;
            kernel4d.at<cv::Vec<float,1>>(idx4d) = kernel3d.at<cv::Vec<float,1>>(idx3d);
        }
    }
}

内容的提问来源于stack exchange,提问作者Julz

火山引擎 最新活动