解读canny算法opencl实现
在看完了canny 算法 c++ 实现细节以及完成 pytorch 版本的实现后,我在考虑如何对算法进行并行加速,我知道有opencl 加速 与 cuda 加速(pytorch api 可以实现),打算先考虑opencl 的加速。我之前已经知道 opencv 中是包含了对 opencl 的支持,我比较好奇如何使用 opencl 对 canny 算法的加速。在github 找到一个实现,虽然不是特别好,但是用了解 opencl 的使用还是可以的。
代码
源码解读
代码解读,drawio 格式 , 下载后可在 https://app.diagrams.net/ 打开,方便查看。
代码解读,svg格式 , 可在浏览器打开,不过查看不方便,因为画幅太大了。
总结
流程 | 总结 |
---|---|
1 创建对象 | 类构构造函数执行步骤: -1 获取设备信息 -2 使用设备信息,创建命令队列 -3 用于提交内核对象到设备执行生成内核对象(高斯滤波、sobel、极大值抑制、滞后阈值) |
1.3 生成内核对象 | 步骤: -1 读取 kernel 文件 -2 构造一个字符串对象,将文件全部内容转换为字符串 -3 由 字符串对象构造 kernel 程序对象,再使用 OpenCL 编程框架编译并构建一个内核对象 (cl::Kernel) |
1.3.1 kernel 文件 | 这里以高斯模糊内核函数源码为例来解释 kernel 文件是c/c++ 代码,定义了工作组(后面解释)中的工作项的执行过程,你可以想象每个工作项用来执行一个像素相关的操作,而工作项是可以并行的 ,这意味着全部的像素可以再同时执行相关的操作,这就是并行。 步骤: -1 每个工作组的工作项的边长是16,这里的工作项是: 16*16 ,待处理的数据会划分给多个工作组,每个组会执行16*16 个工作项。-2 内核函数的参数: __global uchar *data __global uchar *out 分别表示全局的输入数据的指针与全局输出数据指针-3 每个工作项会执行一遍内核函数,完成一个像素的相关操作(比如卷积)然后将数据存入 out 指针所指向的数组的对应位置 |
2 加载图像,设置缓存 | 步骤: -1 裁剪图片,保证图片可以划分为工作组的整数倍 -2 将裁剪后的图片拷贝一份,保存到input_, 这样可以保证工作组索引不会引用错误的像素。 -3 创建两个缓存,该缓存的生命周期由opencl 的运行时管理,不用担心内存回收的问题 -4 这两个缓存是个循环缓存,分别代表kernel 对象的输入与输出,后续还有几个kernel 对象需要执行,它们是串连的,将这个两个缓存的的名称转换,原来的输出变为输入,原来的输入将保存当前 kernel 的输出。最开始的输入就是原始裁剪后的图片。 -5 这里两个缓存是沟通主机内存与设备显存的桥梁,它们有opencl 运行时实现,缓存可以实现在主机申请内存,但设备需要数据时,自动把数据传输到设备。同样可以把设备的线显存的数据输出到主机内存。 |
3 opencl 队列 执行内核对象 | 步骤: - 1 传参:待处理的数据由 PrevBuff() 提供,对应1.3.1 中的__global uchar *data ,保存输出数据到 NextBuff() 对应1.3.1 中的__global uchar *out 。而这里两个缓存就是加载图像,设置缓存时所提到的两个缓存-2 执行完内核函数后,会执行 AdvanceBuff(); 将这个两个缓存的的名称转换,原来的输出变为输入,原来的输入将保存当前 kernel 的输出 , 此时PrevBuff() 指向当前内核函数的输出,而 NextBuff() 指向当前内核函数的输入,不过 NextBuff() 指向的内容不会再用到,会被下一个 kernel 函数执行返回的数据覆盖。 |
4 opencl 队列所有需要执行的内核对象 | - Gaussian(); - Sobel(); - NonMaxSuppression(); - HysteresisThresholding(); 这些内核对象 就是canny 算法的需要执行的步骤,执行完这些内核对象,可以从 PrevBuff() 指向的缓存获取最终的输出 |
5 从缓存对象中获取最终的输出 | 读取 PrevBuff()的内容,并返回 |