iOS Accelerate Framework vImage - 性能改进?
Posted
技术标签:
【中文标题】iOS Accelerate Framework vImage - 性能改进?【英文标题】:iOS Accelerate Framework vImage - Performance improvement? 【发布时间】:2015-02-26 10:03:17 【问题描述】:我一直在使用 OpenCV 和 Apple 的 Accelerate 框架,发现 Accelerate 的性能很慢并且 Apple 的文档有限。举个例子:
void equalizeHistogram(const cv::Mat &planar8Image, cv::Mat &equalizedImage)
cv::Size size = planar8Image.size();
vImage_Buffer planarImageBuffer =
.width = static_cast<vImagePixelCount>(size.width),
.height = static_cast<vImagePixelCount>(size.height),
.rowBytes = planar8Image.step,
.data = planar8Image.data
;
vImage_Buffer equalizedImageBuffer =
.width = static_cast<vImagePixelCount>(size.width),
.height = static_cast<vImagePixelCount>(size.height),
.rowBytes = equalizedImage.step,
.data = equalizedImage.data
;
TIME_START(VIMAGE_EQUALIZE_HISTOGRAM);
vImage_Error error = vImageEqualization_Planar8(&planarImageBuffer, &equalizedImageBuffer, kvImageNoFlags);
TIME_END(VIMAGE_EQUALIZE_HISTOGRAM);
if (error != kvImageNoError)
NSLog(@"%s, vImage error %zd", __PRETTY_FUNCTION__, error);
此调用大约需要 20 毫秒。这具有在我的应用程序中无法使用的实际意义。也许直方图的均衡本来就很慢,但我也测试了 BGRA->Grayscale,发现 OpenCV 可以在 ~5ms 内完成,而 vImage 需要 ~20ms。
在测试其他功能时,我发现了一个 project that made a simple slider app 和一个 blur function (gist),我清理了它以进行测试。大约 20 毫秒。
是否有一些技巧可以让这些函数更快?
【问题讨论】:
虽然有些人不喜欢就针对性能的框架的性能提出问题,但我认为这个问题很有价值。 Apple 将 Accelerate 吹捧为一种轻松获取高性能代码的方法,但有关 Accelerate 使用的文档非常薄弱,因此可以通过获取一些与该主题相关的代码示例来改进它。 【参考方案1】:要使用 equalizeHistogram 函数获得每秒 30 帧,您必须对图像进行解交织(从 ARGBxxxx 转换为 PlanarX)并仅均衡 R(ed)G(reen)B(lue);如果你均衡 A(lpha),帧率至少会下降到 24。
这里的代码完全符合你的要求,只要你想的快:
- (CVPixelBufferRef)copyRenderedPixelBuffer:(CVPixelBufferRef)pixelBuffer
CVPixelBufferLockBaseAddress( pixelBuffer, 0 );
unsigned char *base = (unsigned char *)CVPixelBufferGetBaseAddress( pixelBuffer );
size_t width = CVPixelBufferGetWidth( pixelBuffer );
size_t height = CVPixelBufferGetHeight( pixelBuffer );
size_t stride = CVPixelBufferGetBytesPerRow( pixelBuffer );
vImage_Buffer _img =
.data = base,
.height = height,
.width = width,
.rowBytes = stride
;
vImage_Error err;
vImage_Buffer _dstA, _dstR, _dstG, _dstB;
err = vImageBuffer_Init( &_dstA, height, width, 8 * sizeof( uint8_t ), kvImageNoFlags);
if (err != kvImageNoError)
NSLog(@"vImageBuffer_Init (alpha) error: %ld", err);
err = vImageBuffer_Init( &_dstR, height, width, 8 * sizeof( uint8_t ), kvImageNoFlags);
if (err != kvImageNoError)
NSLog(@"vImageBuffer_Init (red) error: %ld", err);
err = vImageBuffer_Init( &_dstG, height, width, 8 * sizeof( uint8_t ), kvImageNoFlags);
if (err != kvImageNoError)
NSLog(@"vImageBuffer_Init (green) error: %ld", err);
err = vImageBuffer_Init( &_dstB, height, width, 8 * sizeof( uint8_t ), kvImageNoFlags);
if (err != kvImageNoError)
NSLog(@"vImageBuffer_Init (blue) error: %ld", err);
err = vImageConvert_ARGB8888toPlanar8(&_img, &_dstA, &_dstR, &_dstG, &_dstB, kvImageNoFlags);
if (err != kvImageNoError)
NSLog(@"vImageConvert_ARGB8888toPlanar8 error: %ld", err);
err = vImageEqualization_Planar8(&_dstR, &_dstR, kvImageNoFlags);
if (err != kvImageNoError)
NSLog(@"vImageEqualization_Planar8 (red) error: %ld", err);
err = vImageEqualization_Planar8(&_dstG, &_dstG, kvImageNoFlags);
if (err != kvImageNoError)
NSLog(@"vImageEqualization_Planar8 (green) error: %ld", err);
err = vImageEqualization_Planar8(&_dstB, &_dstB, kvImageNoFlags);
if (err != kvImageNoError)
NSLog(@"vImageEqualization_Planar8 (blue) error: %ld", err);
err = vImageConvert_Planar8toARGB8888(&_dstA, &_dstR, &_dstG, &_dstB, &_img, kvImageNoFlags);
if (err != kvImageNoError)
NSLog(@"vImageConvert_Planar8toARGB8888 error: %ld", err);
err = vImageContrastStretch_ARGB8888( &_img, &_img, kvImageNoError );
if (err != kvImageNoError)
NSLog(@"vImageContrastStretch_ARGB8888 error: %ld", err);
free(_dstA.data);
free(_dstR.data);
free(_dstG.data);
free(_dstB.data);
CVPixelBufferUnlockBaseAddress( pixelBuffer, 0 );
return (CVPixelBufferRef)CFRetain( pixelBuffer );
请注意,我分配了 Alpha 通道,即使我没有对其执行任何操作;这仅仅是因为在 ARGB8888 和 Planar8 之间来回转换需要 alpha 通道缓冲区分配和参考。相同的性能和质量增强,无论如何。
还请注意,我在将 Planar8 缓冲区转换为单个 ARGB8888 缓冲区后执行了对比度拉伸;这是因为它比逐通道应用函数更快,就像我对直方图均衡函数所做的那样,并且得到与单独执行相同的结果(对比度拉伸函数不会导致与直方图均衡相同的 alpha 通道失真) .
【讨论】:
哦,还有一件事:如果你这样做(即从均衡和对比度拉伸中省略 alpha 通道),图像看起来会好一百倍。出于某种原因,将这些“增强”应用到 Alpha 通道会严重扭曲 ARGB 合成。 这是令人着迷的信息。甚至没有考虑到这一点。您是通过实验发现的吗? 实验是我的强项;在将产品交到某人手中之前,我总是探索各种可能性。而且,正如您刚才所说,结果确实令人着迷。 我喜欢这个答案。 :) 所以你从 ~20 ms (50 fps) 到 ~33 ms (30 fps),还是我读错了?此外,我很惊讶转换为平面和背面比直接在 ARGB 图像上转换更快,但苹果似乎也暗示这更快。是因为您只需要处理为 SIMD 准备的 3 个通道吗?这对我来说似乎有很多复制,但不知何故它仍然更快......【参考方案2】:如果可以避免的话,不要一直重新分配 vImage_Buffer。
对 vImage 加速性能至关重要的一件事是 vImage_Buffers 的重用。我无法说出我在 Apple 有限的文档中阅读了多少次有关此效果的提示,但我绝对没有在听。
在前面提到的模糊代码示例中,我重新设计了测试应用程序,以便为每个图像设置一次 vImage_Buffer 输入和输出缓冲区,而不是为每次调用 boxBlur 设置一次。我每次通话掉线
这表示 Accelerate 需要时间进行预热,然后才能开始看到性能改进。第一次调用此方法需要 34 毫秒。
- (UIImage *)boxBlurWithSize:(int)boxSize
vImage_Error error;
error = vImageBoxConvolve_ARGB8888(&_inputImageBuffer,
&_outputImageBuffer,
NULL,
0,
0,
boxSize,
boxSize,
NULL,
kvImageEdgeExtend);
if (error)
NSLog(@"vImage error %zd", error);
CGImageRef modifiedImageRef = vImageCreateCGImageFromBuffer(&_outputImageBuffer,
&_inputImageFormat,
NULL,
NULL,
kvImageNoFlags,
&error);
UIImage *returnImage = [UIImage imageWithCGImage:modifiedImageRef];
CGImageRelease(modifiedImageRef);
return returnImage;
【讨论】:
加速以任何速度运行。这里的问题是,超过一定大小的新内存实际上只是分配,然后才在以后映射。每次触摸新页面时,操作系统内核都会出错,将整个内容归零,然后换回。这就是减慢 Accelerate 的原因。预分配和重用内存允许向量代码不间断地运行,这意味着它可以完全运行。这对所有事物都是一个问题,而不仅仅是 Accelerate。然而,当你推动光速时,像这样的宇宙尘埃就会成为问题。 @IanOllmann 绝对。我记录这些项目的目标是确定这些关键概念。在存在的少量文档中顺便提到了其中一些主题,但我在网上看到了很多可怕的例子,它们假设它很快,因为它使用的是 Accelerate。由于 Accelerate 的机制在设计上是隐藏的,因此在进行试验时,您可能会为调用的任一方计时而忽略 malloc/free 时间,但由于我们已经建立了 malloc 和 free 并不是真正的性能问题。 @CameronLowellPalmer 在 vImage 缓冲区的reallocation
和 reuse
之间获得清晰度。 1) 我认为这个例子是 vImage 缓冲区 reuse
的好案例 - github.com/Itseez/opencv_for_ios_book_samples/blob/… - 2) 这个例子是 vImage 缓冲区的不正确/低效重新分配的例子 - github.com/Duffycola/opencv-ios-demos/blob/… - 我正确吗?跨度>
@Kiran 一般来说,这些都是注意事项的好例子。如果可以,OpenCV 会重用内存,因此您依赖于 OpenCV 的行为,而代码块中的 malloc 绝对是一个不好的迹象。
使用OpenCV时没有分配vImage_Buffer;没有 alloc、init、malloc 或任何类似的。您只需将 OpenCV 矩阵作为参考(即,在与符号前添加)传递给方法,然后创建一个缓冲区指针,如本文底部的示例中提供的那样。【参考方案3】:
要将 vImage 与 OpenCV 一起使用,请将对 OpenCV 矩阵的引用传递给如下方法:
long contrastStretch_Accelerate(const Mat& src, Mat& dst)
vImagePixelCount rows = static_cast<vImagePixelCount>(src.rows);
vImagePixelCount cols = static_cast<vImagePixelCount>(src.cols);
vImage_Buffer _src = src.data, rows, cols, src.step ;
vImage_Buffer _dst = dst.data, rows, cols, dst.step ;
vImage_Error err;
err = vImageContrastStretch_ARGB8888( &_src, &_dst, 0 );
return err;
从您的 OpenCV 代码块中对该方法的调用如下所示:
- (void)processImage:(Mat&)image;
contrastStretch_Accelerate(image, image);
就这么简单,因为这些都是指针引用,所以没有任何类型的“深度复制”。它尽可能快速和高效,除了所有上下文问题和其他相关的性能考虑因素(我也可以帮助您解决这些问题)。
旁注:您是否知道在将 OpenCV 与 vImage 混合时必须更改通道排列?如果没有,在调用 OpenCV 矩阵上的任何 vImage 函数之前,调用:
const uint8_t map[4] = 3, 2, 1, 0 ;
err = vImagePermuteChannels_ARGB8888(&_img, &_img, map, kvImageNoFlags);
if (err != kvImageNoError)
NSLog(@"vImagePermuteChannels_ARGB8888 error: %ld", err);
执行相同的调用、map 和 all,将图像返回到适合 OpenCV 矩阵的通道顺序。
【讨论】:
是的,我熟悉使用 OpenCV 作为图像的支持,如果您在项目中使用 OpenCV,这将非常有用。 +1以上是关于iOS Accelerate Framework vImage - 性能改进?的主要内容,如果未能解决你的问题,请参考以下文章
iOS - 使用 Accelerate.framework 对向量进行按位异或
如何使用 Accelerate Framework 将 iOS 相机图像转换为灰度?
iOS:使用 Accelerate Framework 从矩阵中追加/删除列或行?
Objective-C - 使用 Accelerate.framework 对两个矩阵进行元素加法(和除法)
有人可以解释这段代码如何使用 Accelerate Framework 将音量转换为分贝吗?
将 AVCaptureAudioDataOutput 数据传递到 vDSP / Accelerate.framework