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 缓冲区的 reallocationreuse 之间获得清晰度。 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