iOS加速框架vImage – 性能改进?

我一直在使用OpenCV和Apple的Accelerate框架,并且发现Accelerate的性能很慢,而且苹果的文档有限。 我们举个例子:

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); } } 

这个电话大概需要20ms。 这在我的申请中具有不可用的实际意义。 也许直方图的均衡本来就很慢,但是我也testing了BGRA->灰度,发现OpenCV可以在〜5ms内完成,而vImage需要~20ms。

在其他function的testing中,我发现一个项目,做了一个简单的滑块应用程序与模糊function(要点) ,我清理testing。 粗略地~20ms。

有一些诀窍让这些function更快?

要使用equalizeHistogram函数每秒获得30帧,必须对图像进行解交织(从ARGBxxxx转换为PlanarX),并仅对R(ed)G(reen)B(lue)进行均衡。 如果你平均A(lpha),帧速率将下降到至less24。

以下代码完全按照您的需要进行:

 - (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通道缓冲区分配和参考。 同样的性能和质量提高,不pipe。

另外请注意,在将Planar8缓冲区转换为单个ARGB8888缓冲区后,执行对比度拉伸; 这是因为它比直接使用函数通道更快,就像我在直方图均衡函数中所做的那样,并且获得与单独执行相同的结果(对比度扩展函数不会像直方图均衡一样导致相同的alpha通道失真) 。

如果可以避免,请不要重新分配vImage_Buffer。

对于vImage而言至关重要的一件事是提高性能,就是重用vImage_Buffers。 我不能说在苹果有限的文档提示中看了多less次,但是我绝对不会听。

在前面提到的模糊代码示例中,我重写了testing应用程序,以针对每个图像设置vImage_Bufferinput和输出缓冲区,而不是每次调用boxBlur一次。 每次通话时间less于10毫秒,响应时间差异明显。

这就是说,在开始看到性能提升之前,Accelerate需要时间进行热身。 第一次调用这个方法需要34ms。

 - (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; } 

要在OpenCV中使用vImage,请将OpenCVmatrix的引用传递给像这样的方法:

 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); } 

这很简单,因为这些都是指针引用,所以没有任何“深层复制”。 除了上下文和其他相关的性能考虑之外,它的速度和效率都尽可能的快(我也可以帮助你)。

SIDENOTE:你知道当OpenCV和vImage混合使用时你必须改变通道排列吗? 如果不是,则在调用OpenCVmatrix上的任何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); 

执行相同的调用,映射和全部,以将图像返回到适用于OpenCVmatrix的通道顺序。