iOS 加速框架 vImage - 性能改进?
iOS Accelerate Framework vImage - Performance improvement?
我一直在使用 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 with a blur function (gist),我清理了它以进行测试。大约也是 20 毫秒。
是否有一些技巧可以使这些功能更快?
如果可以避免,请不要继续重新分配vImage_Buffer。
对 vImage 加速性能至关重要的一件事是 vImage_Buffer 的重用。我不能说我在 Apple 的有限文档中读了多少次这种效果的提示,但我绝对没有在听。
在前面提到的模糊代码示例中,我重新设计了测试应用程序以针对每个图像设置一次 vImage_Buffer 输入和输出缓冲区,而不是每次调用 boxBlur 时都设置一次。我将每次调用的时间缩短了 <10 毫秒,这在响应时间上产生了显着差异。
这表示在您开始看到性能改进之前,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;
}
要使用 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 通道失真) .
要将 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);
}
就这么简单,因为这些都是指针引用,所以没有任何类型的 "deep copying"。它尽可能地快速和高效,所有关于上下文和其他相关性能考虑的问题都放在一边(我也可以帮助您解决这些问题)。
旁注:您知道在将 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);
执行相同的调用、映射和所有操作,将图像 return 转换为适合 OpenCV 矩阵的通道顺序。
我一直在使用 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 with a blur function (gist),我清理了它以进行测试。大约也是 20 毫秒。
是否有一些技巧可以使这些功能更快?
如果可以避免,请不要继续重新分配vImage_Buffer。
对 vImage 加速性能至关重要的一件事是 vImage_Buffer 的重用。我不能说我在 Apple 的有限文档中读了多少次这种效果的提示,但我绝对没有在听。
在前面提到的模糊代码示例中,我重新设计了测试应用程序以针对每个图像设置一次 vImage_Buffer 输入和输出缓冲区,而不是每次调用 boxBlur 时都设置一次。我将每次调用的时间缩短了 <10 毫秒,这在响应时间上产生了显着差异。
这表示在您开始看到性能改进之前,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;
}
要使用 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 通道失真) .
要将 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);
}
就这么简单,因为这些都是指针引用,所以没有任何类型的 "deep copying"。它尽可能地快速和高效,所有关于上下文和其他相关性能考虑的问题都放在一边(我也可以帮助您解决这些问题)。
旁注:您知道在将 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);
执行相同的调用、映射和所有操作,将图像 return 转换为适合 OpenCV 矩阵的通道顺序。