Cuda 硬件实现

一组带有 on-chip 共享内存的 SIMD 多处理器 设备可以被看作一组多处理器,如图所示。每个多处理器使用单指令多数据(SIMD)架构:在任何给定的时钟周期内,多处理器的每个处理器执行同一指令,但操作不同的数据。 每个多处理器使用四个以下类型的on-chip内存: 每个处理器一组 $32$ 位寄存器 并行数据缓存或共享内存,被所有处理器共享实现内存空间共享 通过设备内存的一个只读区域,一个只读常量缓存器被所有处理器共享 通过设备内存的一个只读区域,一个只读纹理缓存器被所有处理器共享 本地和全局内存空间作为设备内存的读写区域,而不被缓冲。 每个多处理器通过纹理单元访问纹理缓冲器,它执行各种各样的寻址模式和数据过滤。 执行模式 一个线程块网格是通过多处理器规划执行的。每个多处理器一个接一个的处理块批处理。一个块只被一个多处理器处理,因此可以对驻留在on-chip共享内存的共享内存空间形成非常快速的访问。 一个批处理中每个多处理器可以处理多少个块,取决于每个线程中分配了多少个寄存器和已知内核中每个时钟需要多少的共享内存,因为多处理器的寄存器和内存在所有的线程中是分开的。如果在至少一个块中,每个多处理器没有足够高的寄存器或共享内存用,那么内核将无法启动。 线程块在一个批处理中被一个多处理器执行,被称为active,每个active块被划分成SIMD线程组,被称为warps;每一条这样的warp包含数量相同的线程,叫做warp大小,并且在SIMD的方式下通过多处理器执行,执行调度程序周期性地从一条warp切换到另一条warp,以达到多处理器计算资源使用的最大化。 块被划分成warp的方式总是相同的;每条warp包含连续的线程,线程索引从第一个warp包含着的线程 0 开始递增。 一个多处理器可以并发地处理几个块,通过划分在它们之中的寄存器和共享内存。更准确地说,每条线程可使用的寄存器数量,等于每个多处理器寄存器总数除以并发的线程数量,并发线程的数量等于并发块的数量乘以每块线程的数量。 在一个线程块网格内的块次序是未定义的,并且在块之间不存在同步机制,因此来自同一个网格的两个不同块的线程不能通过全局内存彼此安全地通讯。 计算兼容性 设备的兼容性由两个参数定义,主要版本号和次要版本号。设备拥有的主要版本号代表相同的核心架构。 次要版本号代表一些改进的核心架构。比如新的特性。 多设备 为一个应用程序使用多GPU作为CUDA设备,必须保证这些CPU是一样的类型。如果系统工作在SLI 模式下,那么只有一个GPU可以作为CUDA设备,由于所有的GPU在驱动堆栈中被底层的融合了。SLI 模式需要在控制面板中关闭,这样才能使多个GPU作为CUDA设备

January 18, 2022 · 1 min · fffzlfk

OpenCV Canny Detector

理论 Canny边缘检测是由John F. Canny在1986年开发的。许多人也将其称为最佳检测器,Canny算法旨在满足三个主要标准。 好的检测:算法能够尽可能多地标识出图像中的实际边缘。 好的定位:标识出的边缘要与实际图像中的实际边缘尽可能接近。 最小响应:图像中的边缘只能标识一次,并且可能存在的图像雜訊不应标识为边缘。 步骤 降噪:使用高斯滤波来达到,下面是一个大小为 $5$ 的高斯核的例子: $$ K = \frac{1}{159}\begin{bmatrix} 2 & 4 & 5 & 4 & 2\\ 4 & 9 & 12 & 9 & 4\\ 5 & 12 & 15 & 12 & 5\\ 4 & 9 & 12 & 9 & 4\\ 2 & 4 & 5 & 4 & 2 \end{bmatrix} $$ 找到图像的亮度梯度:为此,我们遵循一个类似于Sobel的程序: 应用一对卷积masks(在 $x$ 和 $y$ 方向上): $$ G_x = \begin{bmatrix} -1 & 0 & +1\\ -2 & 0 & +2\\ -1 & 0 & +1 \end{bmatrix}, G_y=\begin{bmatrix} -1 & -2 & -1\\ 0 & 0 & 0\\ +1 & +2 & +1 \end{bmatrix} $$ 寻找梯度强度和方向: $$ G=\sqrt{G_x^2+G_y^2}\\ \theta = \arctan(\frac{G_y}{G_x}) $$ 方向被四舍五入为四个可能的角度之一(即 $0\degree$ 、 $45\degree$ 、 $90\degree$ 或 $135\degree$ )。 过滤非最大值:在高斯滤波过程中,边缘有可能被放大了。这个步骤使用一个规则来过滤不是边缘的点,使边缘的宽度尽可能为1个像素点:如果一个像素点属于边缘,那么这个像素点在梯度方向上的梯度值是最大的。否则不是边缘,将灰度值设为 $0$ 。 $$ M_T(m, n) = \begin{cases} M(m, n)& \text{if } M(m, n) \lt T\\ 0 & \text{otherwise} \end{cases} $$...

January 17, 2022 · 2 min · fffzlfk

OpenCV Laplace Operator

理论 在之前的教程中,我们学习了如何使用Sobel算子。它是基于这样一个事实,即在边缘区域,像素强度显示了一个 “跳跃"或强度的高变化。得到强度的一阶导数,我们观察到边缘的特征是一个最大值,如图所示: 那么,如果我们取二阶导数会怎样? 你可以观察到,边缘二阶导数是零! 因此,我们也可以用这个标准来尝试检测图像的边缘。然而,请注意,零值不仅会出现在边缘(它们实际上可以出现在其他无意义的位置);这可以通过在需要时使用过滤来解决。 拉普拉斯算子 从上面的解释中,我们可以推断出,二阶导数可以用来检测边缘。由于图像是二维的,我们需要在两个维度上取导数。这里,拉普拉斯算子就派上用场了。 拉普拉斯算子的定义是: $$ Laplace(f) = \frac{\partial^2f}{\partial x^2}+\frac{\partial^2f}{\partial y^2} $$ 拉普拉斯算子在OpenCV中是由函数Laplacian()实现的。事实上,由于拉普拉斯算子使用图像的梯度,它在内部调用索贝尔算子来进行计算的。 Code 代码链接 Explanation 变量声明 // Declare the variables we are going to use Mat src, src_gray, dst; int kernel_size = 3; int scale = 1; int delta = 0; int ddepth = CV_16S; const char* window_name = "Laplace Demo"; 加载图像 const char* imageName = argc >=2 ? argv[1] : "./images/lena.jpg"; src = imread( samples::findFile( imageName ), IMREAD_COLOR ); // Load an image // Check if image is loaded fine if(src....

January 17, 2022 · 1 min · fffzlfk

OpenCV Sobel Derivatives

理论 在之前的两个教程中,我们已经看到了卷积的应用实例。最重要的卷积之一是计算图像中的导数(或对它们的近似值) 为什么图像中的导数计算可能是重要的?让我们设想一下,我们要检测图像中存在的边缘。比如说: 你可以很容易地注意到,在一个边缘,像素强度的变化是很明显的。一个表达变化的好方法是使用导数。梯度的高变化表示图像中的一个重大变化。 为了更加形象,让我们假设我们有一个一维图像。在下面的图中,一个边缘由强度的 “跳跃 “来表示: 如果我们取第一个导数,可以更容易地看到边缘的 “跳跃”(实际上,这里出现的是一个最大值): 因此,从上面的解释中,我们可以推断出,检测图像中的边缘的方法可以通过定位梯度高于其邻居的像素位置(或者概括地说,高于一个阈值)来进行。 更详细的解释,请参考Bradski和Kaehler的《Learning OpenCV》。 Sobel 算子 Sobel算子是一个离散的微分算子,它计算图像强度函数的梯度的近似值。 Sobel算子结合了Gaussian smoothing和微分。 Formulation 假设要操作的图像为 $I$: 我们计算两个导数: 水平变化:这是通过用奇数大小的内核 $G_x$ 对 $I$ 进行卷积计算的。例如,对于内核大小为 $3$ , $G_x$ 将被计算为: $$ G_x=\begin{bmatrix} -1 & 0 & +1\\ -2 & 0 & +2\\ -1 & 0 & +1 \end{bmatrix} * I $$ 垂直变化:这是通过用奇数大小的内核 $G_y$ 对 $I$ 进行卷积计算的。例如,对于内核大小为 $3$ , $G_y$ 将被计算为: $$ G_y = \begin{bmatrix} -1 & -2 & -1\\ 0 & 0 & 0\\ +1 & +2 & + 1 \end{bmatrix} * I $$ 在图像的每一点上,我们通过结合上述两个结果计算出该点的梯度近似值: $$ G = \sqrt{G_{x}^{2}+G_{y}^{2}} $$ 有时会使用以下更简单的方程式: $$ G = |G_x|+|G_y| $$ 当核的大小为 $3$ 时,上面显示的 Sobel核可能会产生明显的不准确(毕竟, Sobel 只是一个导数的近似值)。OpenCV通过使用 Scharr() 函数来解决这种大小为 $3$ 的核的不精确性。这和标准的 Sobel 函数一样快,但比它更准确。它可以实现以下内核 $$ G_x=\begin{bmatrix} -3 & 0 & +3\\ -10 & 0 & +10\\ -3 & 0 & +3 \end{bmatrix}, G_y=\begin{bmatrix} -3 & -10 & -3\\ 0 & 0 & 0\\ +3 & +10 & +3 \end{bmatrix} $$...

January 16, 2022 · 2 min · fffzlfk

OpenCV Erosion Dilatation

形态学操作 简而言之:一套基于形状处理图像的操作。形态学操作将一个结构化元素应用于输入图像,并生成一个输出图像。 最基本的形态学操作是。腐蚀和膨胀。它们有广泛的用途,即 去除噪音 隔离单个元素和连接图像中不同的元素 寻找图像中的强度凹凸点或洞 我们将以下面的图像为例,简要地解释膨胀和侵蚀。 Dilation(膨胀) 这种操作包括将图像A与一些kernel B进行卷积,内核可以有任何形状或大小,通常是一个正方形或圆形。 kernel B有一个定义的锚点(anchor point),通常是核的中心。 当kernel B在图像上扫描时,我们计算出被B重叠的最大像素值,并用该最大值替换锚点位置的图像像素。正如你可以推断的那样,这种最大化的操作会使图像中的明亮区域 “增长”(因此被称为膨胀)。 膨胀操作: $dst(x, y)=max(x^{’}, y^{’})_{:element(x^{’}, y^{’}) \ne 0} src(x+x^{’}, y+y^{’})$ 以上面的图像为例。应用膨胀的方法,我们可以得到 腐蚀 它在给定内核的区域内计算局部最小值。当内核B在图像上被扫描时,我们计算出被B重叠的最小像素值,并用该最小值替换锚点下的图像像素。 腐蚀操作为:$dst(x, y)=min(x^{’}, y^{’})_{:element(x^{’}, y^{’}) \ne 0} src(x+x^{’}, y+y^{’})$ 与膨胀的例子类似,我们可以对原始图像应用腐蚀算子(如上图)。你可以在下面的结果中看到,图像的亮区变薄了,而暗区变大了。 Code #include "basic/erosion_dilatation.hpp" namespace basic { namespace erosion_dilatation { namespace impl { Mat src, erosion_dst, dilation_dst; int erosion_elem = 0; int erosion_size = 0; int dilation_elem = 0; int dilation_size = 0; int const max_elem = 2; int const max_kernel_size = 21; int work(int argc, char **argv) { CommandLineParser parser(argc, argv, "{@input | ....

January 9, 2022 · 2 min · fffzlfk