为什么 ScriptIntrinsicBlur 比我的方法快?
why the ScriptIntrinsicBlur is faster than my method?
我使用 Renderscript 在图像上进行高斯模糊。
但不管我做了什么。 ScriptIntrinsicBlur 更快。
为什么会这样? ScriptIntrinsicBlur 使用的是另一种方法吗?
这个 id 我的 RS 代码:
#pragma version(1)
#pragma rs java_package_name(top.deepcolor.rsimage.utils)
//aussian blur algorithm.
//the max radius of gaussian blur
static const int MAX_BLUR_RADIUS = 1024;
//the ratio of pixels when blur
float blurRatio[(MAX_BLUR_RADIUS << 2) + 1];
//the acquiescent blur radius
int blurRadius = 0;
//the width and height of bitmap
uint32_t width;
uint32_t height;
//bind to the input bitmap
rs_allocation input;
//the temp alloction
rs_allocation temp;
//set the radius
void setBlurRadius(int radius)
{
if(1 > radius)
radius = 1;
else if(MAX_BLUR_RADIUS < radius)
radius = MAX_BLUR_RADIUS;
blurRadius = radius;
/**
calculate the blurRadius by Gaussian function
when the pixel is far way from the center, the pixel will not contribute to the center
so take the sigma is blurRadius / 2.57
*/
float sigma = 1.0f * blurRadius / 2.57f;
float deno = 1.0f / (sigma * sqrt(2.0f * M_PI));
float nume = -1.0 / (2.0f * sigma * sigma);
//calculate the gaussian function
float sum = 0.0f;
for(int i = 0, r = -blurRadius; r <= blurRadius; ++i, ++r)
{
blurRatio[i] = deno * exp(nume * r * r);
sum += blurRatio[i];
}
//normalization to 1
int len = radius + radius + 1;
for(int i = 0; i < len; ++i)
{
blurRatio[i] /= sum;
}
}
/**
the gaussian blur is decomposed two steps:1
1.blur in the horizontal
2.blur in the vertical
*/
uchar4 RS_KERNEL horizontal(uint32_t x, uint32_t y)
{
float a, r, g, b;
for(int k = -blurRadius; k <= blurRadius; ++k)
{
int horizontalIndex = x + k;
if(0 > horizontalIndex) horizontalIndex = 0;
if(width <= horizontalIndex) horizontalIndex = width - 1;
uchar4 inputPixel = rsGetElementAt_uchar4(input, horizontalIndex, y);
int blurRatioIndex = k + blurRadius;
a += inputPixel.a * blurRatio[blurRatioIndex];
r += inputPixel.r * blurRatio[blurRatioIndex];
g += inputPixel.g * blurRatio[blurRatioIndex];
b += inputPixel.b * blurRatio[blurRatioIndex];
}
uchar4 out;
out.a = (uchar) a;
out.r = (uchar) r;
out.g = (uchar) g;
out.b = (uchar) b;
return out;
}
uchar4 RS_KERNEL vertical(uint32_t x, uint32_t y)
{
float a, r, g, b;
for(int k = -blurRadius; k <= blurRadius; ++k)
{
int verticalIndex = y + k;
if(0 > verticalIndex) verticalIndex = 0;
if(height <= verticalIndex) verticalIndex = height - 1;
uchar4 inputPixel = rsGetElementAt_uchar4(temp, x, verticalIndex);
int blurRatioIndex = k + blurRadius;
a += inputPixel.a * blurRatio[blurRatioIndex];
r += inputPixel.r * blurRatio[blurRatioIndex];
g += inputPixel.g * blurRatio[blurRatioIndex];
b += inputPixel.b * blurRatio[blurRatioIndex];
}
uchar4 out;
out.a = (uchar) a;
out.r = (uchar) r;
out.g = (uchar) g;
out.b = (uchar) b;
return out;
}
Renderscript 内在函数的实现方式与您使用自己的脚本实现的方式截然不同。这有几个原因,但主要是因为它们是由各个设备的 RS 驱动程序开发人员以尽可能最好地利用特定 hardware/SoC 配置的方式构建的,并且很可能对硬件进行低级调用在 RS 编程层根本不可用。
Android 确实提供了这些内在函数的通用实现,以防万一没有可用的低级硬件实现时 "fall back"。看看这些通用函数是如何完成的会让你更好地了解这些内在函数是如何工作的。例如,您可以在此处查看 3x3 卷积内在函数的通用实现的源代码 rsCpuIntrinsicConvolve3x3.cpp.
仔细查看该源文件第 98 行开始的代码,注意它们如何使用 no for loops 进行卷积。这称为展开循环,您可以在代码中显式地添加和乘以 9 个相应的内存位置,从而避免需要 for 循环结构。这是优化并行代码时必须考虑的第一条规则。您需要摆脱内核中的所有分支。看看你的代码,你有很多 if
和 for
导致分支——这意味着程序的控制流不是从头到尾直接通过的。
如果展开 for 循环,您会立即看到性能提升。请注意,通过删除 for 结构,您将无法再针对所有可能的半径量对内核进行泛化。在那种情况下,您将不得不为不同的半径创建固定的内核,这就是 确切地 为什么您会看到单独的 3x3 和 5x5 卷积内在函数,因为这正是它们所做的。 (请参阅 rsCpuIntrinsicConvolve5x5.cpp 处的 5x5 内在函数的第 99 行)。
此外,您拥有两个独立的内核这一事实也无济于事。如果你正在做高斯模糊,卷积核确实是可分离的,你可以像你在那里做的那样做 1xN + Nx1 卷积,但我建议将两个通道放在同一个内核中。
不过请记住,即使使用这些技巧,也可能不会像实际内在函数那样快速获得结果,因为它们可能已经针对您的特定设备进行了高度优化。
我使用 Renderscript 在图像上进行高斯模糊。 但不管我做了什么。 ScriptIntrinsicBlur 更快。 为什么会这样? ScriptIntrinsicBlur 使用的是另一种方法吗? 这个 id 我的 RS 代码:
#pragma version(1)
#pragma rs java_package_name(top.deepcolor.rsimage.utils)
//aussian blur algorithm.
//the max radius of gaussian blur
static const int MAX_BLUR_RADIUS = 1024;
//the ratio of pixels when blur
float blurRatio[(MAX_BLUR_RADIUS << 2) + 1];
//the acquiescent blur radius
int blurRadius = 0;
//the width and height of bitmap
uint32_t width;
uint32_t height;
//bind to the input bitmap
rs_allocation input;
//the temp alloction
rs_allocation temp;
//set the radius
void setBlurRadius(int radius)
{
if(1 > radius)
radius = 1;
else if(MAX_BLUR_RADIUS < radius)
radius = MAX_BLUR_RADIUS;
blurRadius = radius;
/**
calculate the blurRadius by Gaussian function
when the pixel is far way from the center, the pixel will not contribute to the center
so take the sigma is blurRadius / 2.57
*/
float sigma = 1.0f * blurRadius / 2.57f;
float deno = 1.0f / (sigma * sqrt(2.0f * M_PI));
float nume = -1.0 / (2.0f * sigma * sigma);
//calculate the gaussian function
float sum = 0.0f;
for(int i = 0, r = -blurRadius; r <= blurRadius; ++i, ++r)
{
blurRatio[i] = deno * exp(nume * r * r);
sum += blurRatio[i];
}
//normalization to 1
int len = radius + radius + 1;
for(int i = 0; i < len; ++i)
{
blurRatio[i] /= sum;
}
}
/**
the gaussian blur is decomposed two steps:1
1.blur in the horizontal
2.blur in the vertical
*/
uchar4 RS_KERNEL horizontal(uint32_t x, uint32_t y)
{
float a, r, g, b;
for(int k = -blurRadius; k <= blurRadius; ++k)
{
int horizontalIndex = x + k;
if(0 > horizontalIndex) horizontalIndex = 0;
if(width <= horizontalIndex) horizontalIndex = width - 1;
uchar4 inputPixel = rsGetElementAt_uchar4(input, horizontalIndex, y);
int blurRatioIndex = k + blurRadius;
a += inputPixel.a * blurRatio[blurRatioIndex];
r += inputPixel.r * blurRatio[blurRatioIndex];
g += inputPixel.g * blurRatio[blurRatioIndex];
b += inputPixel.b * blurRatio[blurRatioIndex];
}
uchar4 out;
out.a = (uchar) a;
out.r = (uchar) r;
out.g = (uchar) g;
out.b = (uchar) b;
return out;
}
uchar4 RS_KERNEL vertical(uint32_t x, uint32_t y)
{
float a, r, g, b;
for(int k = -blurRadius; k <= blurRadius; ++k)
{
int verticalIndex = y + k;
if(0 > verticalIndex) verticalIndex = 0;
if(height <= verticalIndex) verticalIndex = height - 1;
uchar4 inputPixel = rsGetElementAt_uchar4(temp, x, verticalIndex);
int blurRatioIndex = k + blurRadius;
a += inputPixel.a * blurRatio[blurRatioIndex];
r += inputPixel.r * blurRatio[blurRatioIndex];
g += inputPixel.g * blurRatio[blurRatioIndex];
b += inputPixel.b * blurRatio[blurRatioIndex];
}
uchar4 out;
out.a = (uchar) a;
out.r = (uchar) r;
out.g = (uchar) g;
out.b = (uchar) b;
return out;
}
Renderscript 内在函数的实现方式与您使用自己的脚本实现的方式截然不同。这有几个原因,但主要是因为它们是由各个设备的 RS 驱动程序开发人员以尽可能最好地利用特定 hardware/SoC 配置的方式构建的,并且很可能对硬件进行低级调用在 RS 编程层根本不可用。
Android 确实提供了这些内在函数的通用实现,以防万一没有可用的低级硬件实现时 "fall back"。看看这些通用函数是如何完成的会让你更好地了解这些内在函数是如何工作的。例如,您可以在此处查看 3x3 卷积内在函数的通用实现的源代码 rsCpuIntrinsicConvolve3x3.cpp.
仔细查看该源文件第 98 行开始的代码,注意它们如何使用 no for loops 进行卷积。这称为展开循环,您可以在代码中显式地添加和乘以 9 个相应的内存位置,从而避免需要 for 循环结构。这是优化并行代码时必须考虑的第一条规则。您需要摆脱内核中的所有分支。看看你的代码,你有很多 if
和 for
导致分支——这意味着程序的控制流不是从头到尾直接通过的。
如果展开 for 循环,您会立即看到性能提升。请注意,通过删除 for 结构,您将无法再针对所有可能的半径量对内核进行泛化。在那种情况下,您将不得不为不同的半径创建固定的内核,这就是 确切地 为什么您会看到单独的 3x3 和 5x5 卷积内在函数,因为这正是它们所做的。 (请参阅 rsCpuIntrinsicConvolve5x5.cpp 处的 5x5 内在函数的第 99 行)。
此外,您拥有两个独立的内核这一事实也无济于事。如果你正在做高斯模糊,卷积核确实是可分离的,你可以像你在那里做的那样做 1xN + Nx1 卷积,但我建议将两个通道放在同一个内核中。
不过请记住,即使使用这些技巧,也可能不会像实际内在函数那样快速获得结果,因为它们可能已经针对您的特定设备进行了高度优化。