OpenCV中的矢量化卷积

我们将首先实现一维卷积,然后对其进行矢量化。2-D 矢量化卷积将在各行之间执行 1-D 卷积以产生正确的结果。

一维卷积:标量

void conv1d(Mat src, Mat &dst, Mat 内核)

{

​ int len = src.cols;

dst = Mat(1, len, CV_8UC1);

​ int sz = kernel.cols / 2;

copyMakeBorder(src, src, 0, 0, sz, sz, BORDER_REPLICATE);

​ for (int i = 0; i < len; i++)

​ {

​ double 值 = 0;

​ for (int k = -sz; k <= sz; k++)

值 += src.ptr(0)[i + k + sz] * kernel.ptr(0)[k + sz];

dst.ptr(0)[i] = saturate_cast(值);

​ }

}

  1. 我们首先设置变量并在 src 矩阵的两侧创建边框,以处理边缘情况。

    ​ int len = src.cols;

    dst = Mat(1, len, CV_8UC1);

    ​ int sz = kernel.cols / 2;

    copyMakeBorder(src, src, 0, 0, sz, sz, BORDER_REPLICATE);

  2. 对于主循环,我们选择一个索引 i,并使用 k 变量将其与内核一起偏移。我们将值存储在 value 中,并将其添加到 dst 矩阵中。

    ​ for (int i = 0; i < len; i++)

    ​ {

    ​ double 值 = 0;

    ​ for (int k = -sz; k <= sz; k++)

    值 += src.ptr(0)[i + k + sz] * kernel.ptr(0)[k + sz];

    dst.ptr(0)[i] = saturate_cast(值);

    ​ }

    一维卷积:矢量

我们现在将看一维卷积的矢量化版本。

void conv1dsimd(Mat src, Mat 内核, float *ans, int row = 0, int rowk = 0, int len = -1)

{

​ 如果 (len == -1)

len = src.cols;

Mat src_32,kernel_32;

​ const int alpha = 1;

src.convertTo(src_32, CV_32FC1, alpha);

​ int ksize = kernel.cols, sz = kernel.cols / 2;

copyMakeBorder(src_32, src_32, 0, 0, sz, sz, BORDER_REPLICATE);

​ int 步骤 = VTraits<v_float32x4>::vlanes();

​ 浮点数 *sptr = src_32.ptr(row), *kptr = kernel.ptr(rowk);

​ for (int k = 0; k < ksize; k++)

​ {

v_float32 kernel_wide = vx_setall_f32(kptr[k]);

​ int i;

​ for (i = 0; i + 步长 < len; i += 步长)

​ {

v_float32窗口 = vx_load(sptr + i + k);

v_float32和 = v_add(vx_load(ans + i), v_mul(kernel_wide, 窗口));

v_store(ans + i,总和);

​ }

​ 对于 (;我<len;i++)

​ {

*(ans + i) += sptr[i + k]*kptr[k];

​ }

​ }

}

  1. 在我们的例子中,内核是一个浮点数。由于内核的数据类型最大,我们将 src 转换为 float32,形成 src_32。我们也像对待幼稚的案件一样划定边界。

    Mat src_32,kernel_32;

    ​ const int alpha = 1;

    src.convertTo(src_32, CV_32FC1, alpha);

    ​ int ksize = kernel.cols, sz = kernel.cols / 2;

    copyMakeBorder(src_32, src_32, 0, 0, sz, sz, BORDER_REPLICATE);

  2. 现在,对于内核中的每一列,我们计算该值与所有长度为的窗口向量的标量乘积。我们将这些值添加到已存储的值中

    step
    

    ​ int 步骤 = VTraits<v_float32x4>::vlanes();

    ​ 浮点数 *sptr = src_32.ptr(row), *kptr = kernel.ptr(rowk);

    ​ for (int k = 0; k < ksize; k++)

    ​ {

    v_float32 kernel_wide = vx_setall_f32(kptr[k]);

    ​ int i;

    ​ for (i = 0; i + 步长 < len; i += 步长)

    ​ {

    v_float32窗口 = vx_load(sptr + i + k);

    v_float32和 = v_add(vx_load(ans + i), v_mul(kernel_wide, 窗口));

    v_store(ans + i,总和);

    ​ }

    ​ for (; i < len; i++)

    ​ {

    ​ *(ans + i) += sptr[i + k]*kptr[k];

    ​ }

    ​ }

    • We declare a pointer to the src_32 and kernel and run a loop for each kernel element

      ​ int step = VTraits<v_float32x4>::vlanes();

      ​ float *sptr = src_32.ptr(row), *kptr = kernel.ptr(rowk);

      ​ for (int k = 0; k < ksize; k++)

      ​ {

    • We load a register with the current kernel element. A window is shifted from 0 to len - step and its product with the kernel_wide array is added to the values stored in ans. We store the values back into ans

      v_float32 kernel_wide = vx_setall_f32(kptr[k]);

      ​ int i;

      ​ for (i = 0; i + step < len; i += step)

      ​ {

      v_float32 window = vx_load(sptr + i + k);

      v_float32 sum = v_add(vx_load(ans + i), v_mul(kernel_wide, window));

      v_store(ans + i, sum);

      ​ }

    • Since the length might not be divisible by steps, we take care of the remaining values directly. The number of tail values will always be less than step and will not affect the performance significantly. We store all the values to ans which is a float pointer. We can also directly store them in a object

      Mat
      

      ​ for (; i < len; i++)

      ​ {

      ​ *(ans + i) += sptr[i + k]*kptr[k];

      ​ }

    • Here is an iterative example:

        For example:
        kernel: {k1, k2, k3}
        src:           ...|a1|a2|a3|a4|...
      
      
        iter1:
        for each idx i in (0, len), 'step' idx at a time
            kernel_wide:          |k1|k1|k1|k1|
            window:               |a0|a1|a2|a3|
            ans:               ...| 0| 0| 0| 0|...
            sum =  ans + window * kernel_wide
                =  |a0 * k1|a1 * k1|a2 * k1|a3 * k1|
      
        iter2:
            kernel_wide:          |k2|k2|k2|k2|
            window:               |a1|a2|a3|a4|
            ans:               ...|a0 * k1|a1 * k1|a2 * k1|a3 * k1|...
            sum =  ans + window * kernel_wide
                =  |a0 * k1 + a1 * k2|a1 * k1 + a2 * k2|a2 * k1 + a3 * k2|a3 * k1 + a4 * k2|
      
        iter3:
            kernel_wide:          |k3|k3|k3|k3|
            window:               |a2|a3|a4|a5|
            ans:               ...|a0 * k1 + a1 * k2|a1 * k1 + a2 * k2|a2 * k1 + a3 * k2|a3 * k1 + a4 * k2|...
            sum =  sum + window * kernel_wide
                =  |a0*k1 + a1*k2 + a2*k3|a1*k1 + a2*k2 + a3*k3|a2*k1 + a3*k2 + a4*k3|a3*k1 + a4*k2 + a5*k3|
      
  • Note

    The function parameters also include row, rowk and len. These values are used when using the function as an intermediate step of 2-D convolution

2-D Convolution

假设我们的内核有 ksize 行。为了计算特定行的值,我们计算前一行 ksize/2 和下一行 ksize/2 的一维卷积,以及相应的内核行。最终值只是单个一维卷积的总和

void convolute_simd(Mat src, Mat &dst, Mat 内核)

{

​ int rows = src.rows, cols = src.cols;

​ int ksize = kernel.rows, sz = ksize / 2;

dst = Mat(行、列、CV_32FC1);

copyMakeBorder(src, src, sz, sz, 0, 0, BORDER_REPLICATE);

​ int 步骤 = VTraits<v_float32x4>::vlanes();

​ for (int i = 0; i <行; i++)

​ {

​ for (int k = 0; k < ksize; k++)

​ {

​ 浮点数 ans[N] = {0};

conv1dsimd(src, 内核, ans, i + k, k, cols);

​ 国际J;

​ for (j = 0; j + 步长 < 列; j += 步长)

​ {

v_float32 总和 = v_add(vx_load(&dst.ptr(i)[j]), vx_load(&ans[j]));

v_store(&dst.ptr<浮点数>(i)[j], 总和);

​ }

​ 对于 (;J < cols;j++)

dst.ptr<浮点数>(i)[j] += ans[j];

​ }

​ }

​ const int alpha = 1;

dst.convertTo(dst, CV_8UC1, alpha);

}

  1. 我们首先初始化变量,并在 src 矩阵的上方和下方创建一个边框。左右两侧由一维卷积函数处理。

    ​ int rows = src.rows, cols = src.cols;

    ​ int ksize = kernel.rows, sz = ksize / 2;

    dst = Mat(行、列、CV_32FC1);

    copyMakeBorder(src, src, sz, sz, 0, 0, BORDER_REPLICATE);

    ​ int 步骤 = VTraits<v_float32x4>::vlanes();

  2. 对于每一行,我们计算其上方和下方行的一维卷积。然后,我们将这些值添加到 DST 矩阵中。

    ​ for (int i = 0; i <行; i++)

    ​ {

    ​ for (int k = 0; k < ksize; k++)

    ​ {

    ​ 浮点数 ans[N] = {0};

    conv1dsimd(src, 内核, ans, i + k, k, cols);

    ​ 国际J;

    ​ for (j = 0; j + 步长 < 列; j += 步长)

    ​ {

    v_float32 总和 = v_add(vx_load(&dst.ptr(i)[j]), vx_load(&ans[j]));

    v_store(&dst.ptr<浮点数>(i)[j], 总和);

    ​ }

    ​ 对于 (;J < cols;j++)

    dst.ptr<浮点数>(i)[j] += ans[j];

    ​ }

    ​ }

  3. 我们最终将 dst 矩阵转换为 8 位矩阵

    unsigned char
    

    ​ const int alpha = 1;

    dst.convertTo(dst, CV_8UC1, alpha);

    结果

    在本教程中,我们使用了水平梯度内核。对于这两种方法,我们获得了相同的输出图像。

运行时的改进各不相同,取决于 CPU 中可用的 SIMD 功能。

在线教程

请添加图片描述

人工智能书籍

第一阶段:零基础入门(3-6个月)

新手应首先通过少而精的学习,看到全景图,建立大局观。 通过完成小实验,建立信心,才能避免“从入门到放弃”的尴尬。因此,第一阶段只推荐4本最必要的书(而且这些书到了第二、三阶段也能继续用),入门以后,在后续学习中再“哪里不会补哪里”即可。

第二阶段:基础进阶(3-6个月)

熟读《机器学习算法的数学解析与Python实现》并动手实践后,你已经对机器学习有了基本的了解,不再是小白了。这时可以开始触类旁通,学习热门技术,加强实践水平。在深入学习的同时,也可以探索自己感兴趣的方向,为求职面试打好基础。

第三阶段:工作应用

这一阶段你已经不再需要引导,只需要一些推荐书目。如果你从入门时就确认了未来的工作方向,可以在第二阶段就提前阅读相关入门书籍(对应“商业落地五大方向”中的前两本),然后再“哪里不会补哪里”。

有需要的小伙伴,可以点击下方链接免费领取或者V扫描下方二维码免费领取🆓

在这里插入图片描述

  • 57
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值