iPhone SDK - 优化for循环

时间:2011-02-07 19:14:41

标签: iphone objective-c

我正在开发一个图像处理应用程序,我正在寻找一个建议来调整我的代码。

我需要将图像分割成块(80x80),并为每个块计算平均颜色。

我的第一个方法包含调用第二个方法的主循环:

- (NSArray*)getRGBAsFromImage:(UIImage *)image {
int width       = image.size.width;
int height  = image.size.height;

int blocPerRow  = 80;
int blocPerCol  = 80;

int pixelPerRowBloc = width  / blocPerRow;
int pixelPerColBloc = height / blocPerCol;

int xx,yy;

// Row loop
for (int i=0; i<blocPerRow; i++) {

    xx = (i * pixelPerRowBloc) + 1;

    // Colon loop
    for (int j=0; j<blocPerCol; j++) {

        yy = (j * pixelPerColBloc) +1;

        [self getRGBAsFromImageBloc:image 
                    atX:xx 
                    andY:yy 
                    withPixelPerRow:pixelPerRowBloc 
                    AndPixelPerCol:pixelPerColBloc];
    }
}
// return my NSArray not done yet !
}

我的第二种方法浏览像素块并返回ColorStruct:

- (ColorStruct*)getRGBAsFromImageBloc:(UIImage*)image 
                            atX:(int)xx 
                            andY:(int)yy 
                            withPixelPerRow:(int)pixelPerRow 
                            AndPixelPerCol:(int)pixelPerCol {

// First get the image into your data buffer
CGImageRef imageRef = [image CGImage];

NSUInteger width = CGImageGetWidth(imageRef);
NSUInteger height = CGImageGetHeight(imageRef);

CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

unsigned char *rawData = malloc(height * width * 4);

NSUInteger bytesPerPixel = 4;
NSUInteger bytesPerRow = bytesPerPixel * width;

    NSUInteger bitsPerComponent = 8;

CGContextRef context = CGBitmapContextCreate(rawData, width, height,
                bitsPerComponent, bytesPerRow, colorSpace,
                kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);

CGColorSpaceRelease(colorSpace);

CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
CGContextRelease(context);

// Now your rawData contains the image data in the RGBA8888 pixel format.
int byteIndex = (bytesPerRow * yy) + xx * bytesPerPixel;

    int red = 0;
    int green = 0;
    int blue = 0;
    int alpha = 0;
    int currentAlpha;

    // bloc loop
    for (int i = 0 ; i < (pixelPerRow*pixelPerCol) ; ++i) {
        currentAlpha = rawData[byteIndex + 3];

        red   += (rawData[byteIndex]        )   * currentAlpha;
        green += (rawData[byteIndex + 1]) * currentAlpha;
        blue  += (rawData[byteIndex + 2]) * currentAlpha;
        alpha += currentAlpha;

        byteIndex += 4;

        if ( i == pixelPerRow ) {
            byteIndex += (width-pixelPerRow) * 4;
        }
    }
    red     /= alpha;
    green /= alpha;
    blue    /= alpha;

    ColorStruct *bColorStruct = newColorStruct(red, blue, green);

    free(rawData);

    return bColorStruct;
   }

ColorStruct:

typedef struct {
  int red;
    int blue;
    int green;
} ColorStruct;

使用构造函数:

ColorStruct *newColorStruct(int red, int blue, int green) {
ColorStruct *ret = malloc(sizeof(ColorStruct));
ret->red = red;
    ret->blue = blue;
ret->green = green;
return ret;
}

正如您所看到的,我有三个循环级别:行循环,冒号循环和bloc循环。

我已经测试了我的代码,320x480图片大约需要5到6秒。

欢迎任何帮助。

谢谢, Bahaaldine

5 个答案:

答案 0 :(得分:2)

似乎是一个完美的问题,可以给它Grand Central Dispatch吗?

答案 1 :(得分:1)

我认为此代码中的主要问题是图像读取太多。整个图像被加载到每个(!)块的内存中(malloc也很昂贵)。您应该预加载一次图像数据(缓存它),然后在getRGBAsFromImageBloc()中使用该内存。现在,对于320x480图片,你有4 x 6 = 24块。因此,只需使用缓存,您就可以将应用程序加速多倍。

答案 2 :(得分:1)

在一天结束时,拍摄图像并依次对每个像素执行三次乘法和五次加法总是相对较慢。

幸运的是,您正在做的事情可以被认为是将图像从一种尺寸插值到另一种尺寸的特殊情况 - 即图像的平均像素与调整大小为1x1的图像相同(假设调整大小是使用某种形式的线性插值,但这通常是执行它的标准方法)并且有一些高度优化(或至少比你可能没有付出巨大努力的优化)选项来做到这一点iPhone的图形库。首先,我尝试使用Quartz方法调整图像大小:

    CGImageRef sourceImage = yourImage;

int numBytesPerPixel = 4;
u_char* scaledImageData = (u_char*)malloc(numBytesPerPixel);

CGColorSpaceRef colorspace = CGImageGetColorSpace(sourceImage);
CGContextRef context = CGBitmapContextCreate (scaledImageData, 1, 1, 8, numBytesPerPixel, colorspace, kCGImageAlphaNoneSkipFirst);
CGColorSpaceRelease(colorspace);
CGContextDrawImage(context, CGRectMake(0,0,1,1), sourceImage);

int a = scaledImageData[0];
int r = scaledImageData[1];
int g = scaledImageData[2];
int b = scaledImageData[3];

(这只是将原始图像缩小到1像素并且没有显示子区域的裁剪,但遗憾的是我现在没有时间使用该代码 - 如果你试图实现它并且卡住了添加一个评论,我可以告诉你如何做到这一点。)

如果这不起作用,您可以尝试使用OpenGL ES来执行此操作(从图像中创建需要缩放的纹理,将其渲染到1x1缓冲区,并从缓冲区测试结果) 。这要复杂得多,但可能会有一些优势,因为它可以让您访问GPU,对于大型图像来说可能要快得多。

希望有意义并有所帮助......

P.S。 - 绝对遵循y0prst的建议,只能一次阅读图片 - 这是一个简单的解决方案,可以为你带来很多性能。

P.P.S - 我没有对代码进行测试,因此通常需要注意。

答案 3 :(得分:0)

你正在检查每个像素 - 无论你如何遍历它,它看起来会花费大致相同的时间(假设你只检查每个像素一次)。

我建议在bloc中使用随机采样 - 每个“n”个像素,这会减少循环时间(和精度),或允许可调整的粒度。

现在,如果有一个现有的算法来计算一组像素的平均值 - 那么可以考虑作为替代方案。

答案 4 :(得分:0)

您可以通过不在循环中调用方法来加快速度。只需将代码包含在内。

ADDED:另外,如果你有足够的内存,你可能只尝试一次绘制图像,而不是在循环中重复。

执行此操作后,您可以尝试从内循环中提升一些乘法以及一些额外的性能(尽管编译器可能会为您优化其中一些)。