vImage与dispatch_apply的正常循环具有相同的性能

时间:2014-08-16 05:36:12

标签: ios objective-c image-processing computer-vision accelerate-framework

此代码在嵌套循环中每次运行9600次,并且必须在iPhone4S上以不到30ms的总时间运行:

vImage_Buffer source = { sourceArea.data, patchSide, patchSide, patchSide };
vImage_Buffer destination = { (uchar*)malloc(patchSide * patchSide * sizeof(uchar)), patchSide, patchSide, patchSide };
vImage_AffineTransform transform = { warpingMatrix(0,0), warpingMatrix(0,1), warpingMatrix(1,0), warpingMatrix(1,1), 0, 0 };

if (vImageAffineWarp_Planar8(&source, &destination, NULL, &transform, 0, kvImageBackgroundColorFill) != kvImageNoError)
{
    NSLog(@"Error in warping!");
}

它看起来不那么快,因为10x10补丁需要大约0.0002秒。我是否忽略了一些重大的性能错误?

我想解决的问题是后向扭曲图像的匹配,这是第一步。贴片在一个点周围的64个不同位置弯曲64次,最多150个点。

3 个答案:

答案 0 :(得分:3)

10x10是一个非常小的图像。您很容易将大部分时间花在开销/ malloc上。仪器时间跟踪应该有助于确定时间的进展。

4s上的向量ALU也是5或5s的宽度的一半,所以不能提供与标量相同的胜利。

答案 1 :(得分:1)

如果可以重用缓冲区,vimage会更快。因此,如果可能的话,在循环外声明并分配缓冲区(或相关数据)。

unsigned char *sourceData = (unsigned char*)malloc(patchSide * patchSide * sizeof(uchar));
vImage_Buffer source = {sourceData, patchSide, patchSide, patchSide};

unsigned char *destinationData = (unsigned char*)malloc(patchSide * patchSide * sizeof(uchar));
vImage_Buffer destination = {destinationData, patchSide, patchSide, patchSide};

loop{
   //fill sourceData e.g. through memcpy
   memcpy(sourceData, somedata, patchSide * patchSide * sizeof(uchar));

   if (vImageAffineWarp_Planar8(&source, &destination, NULL, &transform, 0, kvImageBackgroundColorFill) != kvImageNoError)
   {
     NSLog(@"Error in warping!");
   }
   //destinationData contains the result
}

答案 2 :(得分:0)

在不对代码进行太多更改的情况下,可以进行两项更改来提高性能:使用vImage框架分配源/目标缓冲区并创建临时缓冲区以便在循环内重用。第三个更改可能是更改您的磁贴大小(请参阅此答案的结尾)。

vImage / Accelerate Framework文档建议使用vImageBuffer_Init(请参阅vImage_Utilities.h)初始化缓冲区以确保实际缓冲区为

  

大小并对齐以获得最佳性能

而不是自己分配内存:

vImage_Buffer buffer;
vImage_Error err = vImageBuffer_Init(&buffer, height, width, 8 * sizeof(pixel), kvImageNoFlags);

您的案例中pixelPixel_8,因为您使用的是*_Planar8函数。请注意,完成后仍需要释放buffer.data

因此,您应该在循环之外初始化源和目标:

vImage_Buffer source;
vImage_Buffer destination;
vImage_Error err = vImageBuffer_Init(&source, patchSide, patchSide, 8 * sizeof(Pixel_8), kvImageNoFlags);
err = vImageBuffer_Init(&destination, patchSide, patchSide, 8 * sizeof(Pixel_8), kvImageNoFlags);

将您的数据从sourceArea.data复制到source.data。请注意,source.rowBytes不太可能等于source.width

您还应该创建一个临时缓冲区,以便vImageAffineWarp_Planar8可以在每次迭代时重复使用它,而不是为每次迭代分配它,因为您已将NULL作为第三个参数传入。要确定临时缓冲区的维度,可以在操作期间完全调用该函数,但使用标记kvImageGetTempBufferSize,因为不同的参数/标志可能需要不同的缓冲区大小(请参阅@constant kvImageGetTempBufferSize中的vImage_Types.h }):

size_t tempBufferSize = vImageAffineWarp_Planar8(&source, &destination, NULL, &transform, 0, kvImageBackgroundColorFill | kvImageGetTempBufferSize);

然后您将分配临时缓冲区:

void *tempBuffer = malloc(tempBufferSize);

最后在你的循环中你每次都会使用tempBuf

if (vImageAffineWarp_Planar8(&source, &destination, &tempBuffer, &transform, 0, kvImageBackgroundColorFill) != kvImageNoError)
{
    NSLog(@"Error in warping!");
}

因此,重新上限,源,目标和tempBuf都是在循环之前使用vImageBuffer_Init预先分配的,其中通过调用vImageAffineWarp_Planar8来确定所需的tempBuf大小就像你将在循环中一样,但附加标志kvImageGetTempBufferSize。希望这会加快你的速度!

如果你的算法支持它,你可能会看到最后一件事就是处理更大的图块或图像条纹(参见Tiling / Strip Mining and Multithreading中的vImage.h部分)。