我们将首先实现一维卷积,然后对其进行矢量化。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(值);
}
}
-
我们首先设置变量并在 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);
-
对于主循环,我们选择一个索引 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];
}
}
}
-
在我们的例子中,内核是一个浮点数。由于内核的数据类型最大,我们将 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);
-
现在,对于内核中的每一列,我们计算该值与所有长度为的窗口向量的标量乘积。我们将这些值添加到已存储的值中
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);
}
-
我们首先初始化变量,并在 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();
-
对于每一行,我们计算其上方和下方行的一维卷积。然后,我们将这些值添加到 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];
}
}
-
我们最终将 dst 矩阵转换为 8 位矩阵
unsigned char
const int alpha = 1;
dst.convertTo(dst, CV_8UC1, alpha);
结果
在本教程中,我们使用了水平梯度内核。对于这两种方法,我们获得了相同的输出图像。
运行时的改进各不相同,取决于 CPU 中可用的 SIMD 功能。
在线教程
- 麻省理工学院人工智能视频教程 – 麻省理工人工智能课程
- 人工智能入门 – 人工智能基础学习。Peter Norvig举办的课程
- EdX 人工智能 – 此课程讲授人工智能计算机系统设计的基本概念和技术。
- 人工智能中的计划 – 计划是人工智能系统的基础部分之一。在这个课程中,你将会学习到让机器人执行一系列动作所需要的基本算法。
- 机器人人工智能 – 这个课程将会教授你实现人工智能的基本方法,包括:概率推算,计划和搜索,本地化,跟踪和控制,全部都是围绕有关机器人设计。
- 机器学习 – 有指导和无指导情况下的基本机器学习算法
- 机器学习中的神经网络 – 智能神经网络上的算法和实践经验
- 斯坦福统计学习
有需要的小伙伴,可以点击下方链接免费领取或者V扫描下方二维码免费领取🆓
人工智能书籍
- OpenCV(中文版).(布拉德斯基等)
- OpenCV+3计算机视觉++Python语言实现+第二版
- OpenCV3编程入门 毛星云编著
- 数字图像处理_第三版
- 人工智能:一种现代的方法
- 深度学习面试宝典
- 深度学习之PyTorch物体检测实战
- 吴恩达DeepLearning.ai中文版笔记
- 计算机视觉中的多视图几何
- PyTorch-官方推荐教程-英文版
- 《神经网络与深度学习》(邱锡鹏-20191121)
- …
第一阶段:零基础入门(3-6个月)
新手应首先通过少而精的学习,看到全景图,建立大局观。 通过完成小实验,建立信心,才能避免“从入门到放弃”的尴尬。因此,第一阶段只推荐4本最必要的书(而且这些书到了第二、三阶段也能继续用),入门以后,在后续学习中再“哪里不会补哪里”即可。
第二阶段:基础进阶(3-6个月)
熟读《机器学习算法的数学解析与Python实现》并动手实践后,你已经对机器学习有了基本的了解,不再是小白了。这时可以开始触类旁通,学习热门技术,加强实践水平。在深入学习的同时,也可以探索自己感兴趣的方向,为求职面试打好基础。
第三阶段:工作应用
这一阶段你已经不再需要引导,只需要一些推荐书目。如果你从入门时就确认了未来的工作方向,可以在第二阶段就提前阅读相关入门书籍(对应“商业落地五大方向”中的前两本),然后再“哪里不会补哪里”。
有需要的小伙伴,可以点击下方链接免费领取或者V扫描下方二维码免费领取🆓