其他
【强基固本】OpenCV高性能计算基础介绍
“强基固本,行稳致远”,科学研究离不开理论基础,人工智能学科更是需要数学、物理、神经科学等基础学科提供有力支撑,为了紧扣时代脉搏,我们推出“强基固本”专栏,讲解AI领域的基础知识,为你的科研学习提供助力,夯实理论基础,提升原始创新能力,敬请关注。
作者:https://zhuanlan.zhihu.com/p/429109879
cv::Mat OpenCV中最基础的数据结构,具有存储多种数据类型的多维矩阵的能力,也可用来表示图像。在CPU端,SIMD (Single instruction, multiple data) 是数值计算常用的加速方式,然而即使是相同架构的CPU,所支持的宽度和指令都不相同,为每一种目标指令集写一份加速代码代价巨大。在OpenCV中,这种差距被其 Universal Intrinsic 的抽象填平了。OpenCV Universal Intrinsic 将不同SIMD指令的向量 (vector) 封装成了统一的数据结构,重载了各种运算符,并将向量宽度描述为一个随编译环境自动变化的变量。从此,开发者不再需要过多关注不同CPU的SIMD指令间的差异,同一套SIMD加速过的代码能够通过不同的编译选项生成对应不同目标指令集的程序。此外,OpenCV的一些方法也提供多线程实现,并允许用户使用cv::parallel_for_()封装自己的方法。 cv::UMat 于OpenCV3.0 引入,T-API (Transparent API) 的基础数据结构。T-API是曾经的OCL模块的替代,旨在允许开发者通过非常简单的代码修改将现有OpenCV程序无缝迁移到OpenCL上,从而利用强大的异构算力获取数倍的加速。 cv::gapi::GMat 于OpenCV4.0引入,G-API (Graph API) 的基础结构。与其他的Mat类不同,GMat并不会存储实际的数据,而是会记录用户对GMat进行的操作,并最终组合多个GMat生成一个计算图用以处理真正的计算。基于图的计算,是G-API的核心思想。计算图将计算式声明与计算分离,可以带来至少两个好处:一是OpenCV可以在内部提供分散函数无法提供的跨函数优化,如算术操作的合并,高速缓存的复用和避免多次分配buffer;二是统一的接口使得用户可以相对简单地选择计算时的后端,如Halide和OCL等。目前G-API仍处于活跃的开发阶段,可能会产生不兼容的改动,建议稳定后再使用。 cv::cuda::GpuMat 顾名思义,GPU上的Mat,代表数据指针指向CUDA的Device memory的二维矩阵,是OpenCV多个CUDA相关模块(均在opencv_contrib)的基础数据结构之一。OpenCV的CUDA模块已经开发了近10年,功能相当丰富,用户能够方便地用其改写现有项目,也能将其数据结构集成到自定义的CUDA Kernel中,实现极致的性能优化。如何利用OpenCV CUDA模块进行快速的自定义高性能图像算法开发将是本专栏的重点内容。
cv::Mat src(100, 100, CV_32FC1); //原始矩阵
cv::Mat shared1(src); //共享了内存
cv::Mat shared2 = src; //共享了内存
cv::Mat shared3(src(cv::Rect(10, 10, 10, 10))); //共享了内存
cv::Mat independent1 = src.clone(); //申请了新的内存
cv::Mat independent2;
src.copyTo(independent2); //申请了新的内存
提供setSize()接口,由用户决定设定所有中间变量的尺寸的时机。 仅在在用户调用filter本身时对输入尺寸进行检查,当现在的buffer尺寸与输入不同,buffer重新分配内存。
void cv::cuda::GpuMat::create(int _rows, int _cols, int _type) {
if (rows == _rows && cols == _cols && type() == _type && data) return; //尺寸符合条件时直接返回
if (data) release(); //减少引用计数
allocator->allocate(this, rows, cols, esz); //重新分配内存,data将指向新的空间
if (refcount) *refcount = 1; //为新分配的空间启用引用计数
}
cv::AutoBuffer 一个临时栈堆结合缓冲区类,其一个模板参数接收期望栈缓冲区的尺寸(OpenCV 4.5.4 中默认为1024字节左右),当后续需求的缓冲区大小小于栈缓冲区尺寸时,可用栈缓冲区作为目标缓冲区,否则申请堆内存。此类可以用来存储一些较小的临时变量,而不会产生额外的malloc()/free()开销。 cv::cuda::BufferPool CUDA模块的内存池,由全局变量cv::cuda::DefaultDeviceInitializer initializer管理各个设备上的内存池大小,分配内存时使用类似于栈分配的StackAllocator,因此内存释放必须遵循 LIFO(Last In, First Out) 顺序。由于CUDA上的malloc()通常远比CPU上更耗时,内存池能极大减小需要临时缓冲区的CUDA操作的额外开销。但我仍推荐开发者在能够选择接口形式时使用线程安全的缓冲区(考虑到多线程的需求)。
“强基固本”历史文章
迈向可验证的 AI: 形式化方法的五大挑战
深度学习必须掌握的 13 种概率分布
第三代神经网络初探:脉冲神经网络(Spiking Neural Networks)
浅谈图像分割调优
理解Jacobian矩阵与行列式
理解Tensor Core
损失函数 | 交叉熵损失函数
多场景建模
深度学习优化背后包含哪些数学知识?
利用宇宙的能力来处理数据!「物理网络」远胜深度神经网络
入门 | 异常检测Anomaly Detection
通俗易懂的解释Sparse Convolution过程
现在的人工智能是否走上了数学的极端?
神经网络训练中的拓扑演化
更多强基固本专栏文章,
请点击文章底部“阅读原文”查看
分享、点赞、在看,给个三连击呗!