Sobel图像滤波器是一种简单的卷积滤波器,主要用于边缘检测算法。
样例结果
输入图像
输出图像
算法
一种做图像边缘检测的技术是,找出图像的梯度。大梯度值的区域对应图像色彩或密度的剧变区域。典型地,这些区域是边缘。
如果你对于幅图像卷积两个Sobel算子,你会得到两个输出:
> X方向的梯度(dX)
> Y方向的梯度(dY)
通常梯度被合并,给出一个总的梯度图。
Sobel算子是:
dX:
-1 0 1 -2 0 2 -1 0 1
1 2 1 0 0 0 -1 -2 -1
实现
填充
值得注意的是,我们在此并不考虑填充。在两个维度,输出图像相比小两个像素。因为,每个输出需要围绕它的像素,计算边沿像素的输出是不可能的。在此样例中,我们简单地把边缘输出像素用其初始值。
有时需要输出信号的大小与输入信号的大小一样,这种种情况下,"填充"必须应用到输入中,考虑到滤波应用的实际本性上是减少大小的。填充策略会有不同,但是对于图像,通常的选择是在所有边上重复边缘值(例如,最外层的像素集),或者在一些边上(在某些情况下)。
实现细节
简单起见,我们在一个8比特通道上实现Sobel滤波器。为了在RGB图像上做Sobel滤波,你可以独立地在每个通道上运行Sobel滤波,然后组合结果。
在这个例子中,我们取了一张RGB图像,转换成8位辉度图,然后发送到GPU。
每个Sobel计算给出一个中心掩码像素的输出。中心像素的输出值是围绕像素的3x3网格上的像素值乘以Sobel掩码的和。这可以被分成三个阶段,通过为网格上的每一行独立地做乘法和累加(这一方法被使用在这个样例中)。
代码
注意:我们考虑矢量化是在本教程中使用的主要优化技术。为了显示使用适量的重要性,一个不包含矢量的Sobel滤波器的OpenCL实现被包含在sobel_no_vectors sample(sobel_no_vectors.cpp和sobel_no_vectors.cl)。在Mali-T600系列GPU上,矢量化的版本快很多。
除非另作说明,否则所有的代码片段来自sobel.cl中的OpenCL内核。
1. 选择内核大小
我们在内核中使用向量类型,因此我们实际上每个内核输出16个结果。看下面更多的向量化细节。内核使用3x3的Sobel滤波器到一个输入图像中的18x3的窗,在两个输出图像中来生成16x1结果,代表梯度的dx和dy。
/* * Each kernel calculates 16 output pixels in the same row (hence the ‘* 16‘). * column is in the range [0, width] in steps of 16. * row is in the range [0, height]. */ const int column = get_global_id(0) * 16; const int row = get_global_id(1) * 1; /* Offset calculates the position in the linear data for the row and the column. */ const int offset = row * width + column;当我们在sobel.cpp中排队内核时,相应地减少工作大小:
/* * Each instance of the kernel operates on a 16 * 1 portion of the image. * Therefore, the global work size must be width / 16 by height / 1 work items. */ size_t globalWorksize[2] = {width / 16, height / 1};
Mali-T600系列GPU具有128位矢量寄存器,可以在矢量类型上做运算。因此,我们使用OpenCL矢量,来使得更加有效地使用硬件,从而有更高的性能。
这里我们从一行数据中做矢量加载:
/* * First row of input. * In a scalar Sobel calculation you would load 1 value for leftLoad, middleLoad and rightLoad. * In the vector case we load 16 values for each. * leftLoad, middleLoad and rightLoad load 16-bytes of data from the first row. * The data they load overlaps. e.g. for the first column and row, leftLoad is 0->15, middleLoad is 1->16 and rightLoad is 2->17. * So we‘re actually loading 18-bytes of data from the first row. */ uchar16 leftLoad = vload16(0, inputImage + (offset + 0)); uchar16 middleLoad = vload16(0, inputImage + (offset + 1)); uchar16 rightLoad = vload16(0, inputImage + (offset + 2));
在Mali-T600系列GPU上,扩展和收缩数据类型是一个很容易地操作。在此,我们将8比特每像素数据转换成16比特每像素。
/* * Convert the data from unsigned chars to shorts (8-bit unsigned to 16-bit signed). * The calculations can overflow 8-bits so we require larger intermediate storage. * Additionally, the values can become negative so we need a signed type. */ short16 leftData = convert_short16(leftLoad); short16 middleData = convert_short16(middleLoad); short16 rightData = convert_short16(rightLoad);
然后,我们在计算16个像素的结果。在Mali-T600系列GPU上,每个向量计算可以作为一个单一操作完成。
/* * Calculate the results for the first row. * Looking at the Sobel masks above for the first line of input, * the dX calculation is the sum of 1 * leftData, 0 * middleData, and -1 * rightData. * The dY calculation is the sum of 1 * leftData, 2 * middleData, and 1 * rightData. * This is what is being calculated below, except we have removed the * unnecessary calculations (multiplications by 1 or 0) and we are calculating 16 values at once. * This pattern repeats for the other 2 rows of data. */ short16 dx = rightData - leftData; short16 dy = rightData + leftData + middleData * (short)2;
最后,我们收缩和存储数据。我们使用一个向量存储一次写16个结果:
/* * Store the results. * The range of outputs from our Sobel calculations is [-1020, 1020]. * In order to output this as an 8-bit signed char we must divide it by 8 (or shift right 3 times). * This gives the range [-128, 128]. Depending on what type of output you require, * (signed/unsigned, seperate/combined gradients) it is possible to do more of the calculations on the GPU using OpenCL. * In this sample we‘re assuming that the application requires signed uncombined gradient outputs. */ vstore16(convert_char16(dx >> 3), 0, outputImageDX + offset + width + 1); vstore16(convert_char16(dy >> 3), 0, outputImageDY + offset + width + 1);因为数据作为两个分开的梯度图返回,我们在CPU上将它们先组合,然后将它们作为一个位图写入。
运行样例
参见样例1。
《Mali OpenCL SDK v1.1.0》教程样例之六“索贝尔滤波器”,布布扣,bubuko.com
《Mali OpenCL SDK v1.1.0》教程样例之六“索贝尔滤波器”
原文:http://blog.csdn.net/cloud_desktop/article/details/21236095