IPB

Bienvenue invité ( Connexion | Inscription )

 
Reply to this topicStart new topic
> Optimisation ObjectiveC, Sur une boucle de traitement d'image par pixel
Options
SuperCed
posté 28 Jan 2021, 15:34
Message #1


Macbidouilleur d'Or !
*****

Groupe : Membres
Messages : 2 831
Inscrit : 19 Jul 2001
Lieu : Живим у Греноблу
Membre no 519



Hop, sujet intéressant pour ceux qui aiment l'optimisation. Ca reste un problème classique je pense.

En gros, j'ai un traitement équivalent sur chaque pixel d'une image.
Je me demandais s'il existait une solution simple pour optimiser un peu le code.

Genre les décalages à droite au lieu de multiplier par 255.
Ou mieux, utiliser la partie vectorielle des processeurs. Attention, c'est pour iPhone, donc il faut que ça fonctionne sur ARM.

Je sais qu'à une époque, j'avais fait des optimisations avec altivec.

Je me demande s'il existe pas aujourd'hui des solutions plus simple pour paralléliser les traitement ou faire du calcul vectoriel avec le compilo LLVM et ObjectiveC.

Voici le code, pas du tout optimisé pour le moment. Et je veux rester sur quelque chose de simple et compréhensible, et pas des créations de thread à la main ou d'assembleur.
Je suis dans une UIIMage
Code
-(UIImage *) convertRGBLayersWithForgroundColor:(UIColor*)foregroundColor backgroundColor:(UIColor*)backgroundColor strokeColor:(UIColor*)strokeColor
{
    
    //const int ALPHA = 0;
    const int BLUE = 1;
    const int GREEN = 2;
    const int RED = 3;
    
    
    CGFloat bgRed = 0.0, bgGreen = 0.0, bgBlue = 0.0, bgAlpha =0.0;
    [backgroundColor getRed:&bgRed green:&bgGreen blue:&bgBlue alpha:&bgAlpha];
    
    CGFloat fgRed = 0.0, fgGreen = 0.0, fgBlue = 0.0, fgAlpha =0.0;
    [foregroundColor getRed:&fgRed green:&fgGreen blue:&fgBlue alpha:&fgAlpha];
    
    CGFloat stRed = 0.0, stGreen = 0.0, stBlue = 0.0, stAlpha =0.0;
    [strokeColor getRed:&stRed green:&stGreen blue:&stBlue alpha:&stAlpha];

    //Create image rectangle with current image width/height
    CGRect imageRect = CGRectMake(0, 0, self.size.width * self.scale, self.size.height * self.scale);

    int width = imageRect.size.width;
    int height = imageRect.size.height;

    // the pixels will be painted to this array
    uint32_t *pixels = (uint32_t *) malloc(width * height * sizeof(uint32_t));

    // clear the pixels so any transparency is preserved
    memset(pixels, 0, width * height * sizeof(uint32_t));

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

    // create a context with RGBA pixels
    CGContextRef context = CGBitmapContextCreate(pixels, width, height, 8, width * sizeof(uint32_t), colorSpace,
                                                 kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedLast);

    // paint the bitmap to our context which will fill in the pixels array
    CGContextDrawImage(context, CGRectMake(0, 0, width, height), [self CGImage]);

    for(int y = 0; y < height; y++) {
        for(int x = 0; x < width; x++) {
            uint8_t *rgbaPixel = (uint8_t *) &pixels[y * width + x];
            
            uint8_t originalR, originalG, originalB;
            originalR = rgbaPixel[RED];
            originalG = rgbaPixel[GREEN];
            originalB = rgbaPixel[BLUE];

            uint16_t divider = originalR + originalG + originalB;
            rgbaPixel[RED] = (uint8_t)(((originalR*stRed) + (originalG*fgRed) + (originalB*bgRed))*255/divider);
            rgbaPixel[GREEN] = (uint8_t)(((originalR*stGreen) + (originalG*fgGreen) + (originalB*bgGreen))*255/divider);
            rgbaPixel[BLUE] = (uint8_t)(((originalR*stBlue) + (originalG*fgBlue) + (originalB*bgBlue))*255/divider);
        
        }
    }

    // create a new CGImageRef from our context with the modified pixels
    CGImageRef image = CGBitmapContextCreateImage(context);

    // we're done with the context, color space, and pixels
    CGContextRelease(context);
    CGColorSpaceRelease(colorSpace);
    free(pixels);

    // make a new UIImage to return
    UIImage *resultUIImage = [UIImage imageWithCGImage:image
                                                 scale:self.scale
                                           orientation:UIImageOrientationUp];

    // we're done with image now too
    CGImageRelease(image);

    return resultUIImage;
}


Ce message a été modifié par SuperCed - 28 Jan 2021, 15:35.


--------------------
Хајде Јано коло да играмо
iMac 27 mi 2010
Macbook air mi 2011
Mac Mini M1
Go to the top of the page
 
+Quote Post
audionuma
posté 28 Jan 2021, 21:44
Message #2


Macbidouilleur d'Or !
*****

Groupe : Membres
Messages : 2 031
Inscrit : 27 Apr 2004
Membre no 18 176



Peut-être avec Accelerate ?
Soit vImage, soit vDSP ?


--------------------
Membre du club des AIPBP (Anciens Inscrits Pas Beaucoup de Posts) Voir la liste
Futur ex-macbidouilleur, sous Dell / Ubuntu depuis 2021 !
Go to the top of the page
 
+Quote Post
SuperCed
posté 29 Jan 2021, 08:31
Message #3


Macbidouilleur d'Or !
*****

Groupe : Membres
Messages : 2 831
Inscrit : 19 Jul 2001
Lieu : Живим у Греноблу
Membre no 519



J'ai déjà fait une optimisation avec simd. Je me suis rendu compte que mon calcul correspondait à une multiplication entre une matrice et un vecteur...

Le résultat en dessous, même si je pense qu'il doit y avoir encore mieux.
J'aurais pu aussi utiliser une matrice 3x3 vu que je n'utilise pas la valeur d'alpha, mais bon, je me suis dit que 3x3 ou 4x4 en simd, il n'y a pas de coût supplémentaire de performance ou très peu.

D'autre part, pour la boucle, je pense qu'il faudrait un truc genre Grand Central pour paralléliser sans complexifier le code.
Mais je crois que ça a été abandonné au profit d'une syntaxe spécifique pour swift. Le problème, c'est que dans mon cas, je suis en ObjectiveC (encore...) . Ca fait partie de quelque chose que je ne peux pas changer sur cet exemple.

Code
-(UIImage *) convertRGBLayersWithForgroundColor:(UIColor*)foregroundColor backgroundColor:(UIColor*)backgroundColor strokeColor:(UIColor*)strokeColor
{
    
    const int ALPHA = 0;
    const int BLUE = 1;
    const int GREEN = 2;
    const int RED = 3;
    
    
    CGFloat bgRed = 0.0, bgGreen = 0.0, bgBlue = 0.0, bgAlpha =0.0;
    [backgroundColor getRed:&bgRed green:&bgGreen blue:&bgBlue alpha:&bgAlpha];
    
    CGFloat fgRed = 0.0, fgGreen = 0.0, fgBlue = 0.0, fgAlpha =0.0;
    [foregroundColor getRed:&fgRed green:&fgGreen blue:&fgBlue alpha:&fgAlpha];
    
    CGFloat stRed = 0.0, stGreen = 0.0, stBlue = 0.0, stAlpha =0.0;
    [strokeColor getRed:&stRed green:&stGreen blue:&stBlue alpha:&stAlpha];
    
    simd_float4 st = simd_make_float4(stRed, stGreen, stBlue, 0);
    simd_float4 fg = simd_make_float4(fgRed, fgGreen, fgBlue, 0);
    simd_float4 bg = simd_make_float4(bgRed, bgGreen, bgBlue, 0);
    simd_float4 alpha = simd_make_float4(0, 0, 0, 1);
    simd_float4x4 mat;
    mat.columns[0] = st;
    mat.columns[1] = fg;
    mat.columns[2] = bg;
    mat.columns[3] = alpha;
    
    
    

    //Create image rectangle with current image width/height
    CGRect imageRect = CGRectMake(0, 0, self.size.width * self.scale, self.size.height * self.scale);

    int width = imageRect.size.width;
    int height = imageRect.size.height;

    // the pixels will be painted to this array
    uint32_t *pixels = (uint32_t *) malloc(width * height * sizeof(uint32_t));

    // clear the pixels so any transparency is preserved
    memset(pixels, 0, width * height * sizeof(uint32_t));

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

    // create a context with RGBA pixels
    CGContextRef context = CGBitmapContextCreate(pixels, width, height, 8, width * sizeof(uint32_t), colorSpace,
                                                 kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedLast);

    // paint the bitmap to our context which will fill in the pixels array
    CGContextDrawImage(context, CGRectMake(0, 0, width, height), [self CGImage]);

    for(int y = 0; y < height; y++) {
        for(int x = 0; x < width; x++) {
            uint8_t *rgbaPixel = (uint8_t *) &pixels[y * width + x];
            
            uint8_t originalR, originalG, originalB, originalA;
            originalR = rgbaPixel[RED];
            originalG = rgbaPixel[GREEN];
            originalB = rgbaPixel[BLUE];
            originalA = rgbaPixel[ALPHA];
            
            simd_float4 originalVector = simd_make_float4(originalR, originalG, originalB, originalA);
            simd_float4 finalVector = simd_mul(mat,originalVector);
            
            uint16_t divider = originalR + originalG + originalB;
            finalVector *= 255/divider;
            
            rgbaPixel[RED] = (uint8_t)finalVector.x;
            rgbaPixel[GREEN] = (uint8_t)finalVector.y;
            rgbaPixel[BLUE] = (uint8_t)finalVector.z;
            
            /*rgbaPixel[RED] = (uint8_t)(((originalR*stRed) + (originalG*fgRed) + (originalB*bgRed))*255/divider);
            rgbaPixel[GREEN] = (uint8_t)(((originalR*stGreen) + (originalG*fgGreen) + (originalB*bgGreen))*255/divider);
            rgbaPixel[BLUE] = (uint8_t)(((originalR*stBlue) + (originalG*fgBlue) + (originalB*bgBlue))*255/divider);*/
        
        }
    }

    // create a new CGImageRef from our context with the modified pixels
    CGImageRef image = CGBitmapContextCreateImage(context);

    // we're done with the context, color space, and pixels
    CGContextRelease(context);
    CGColorSpaceRelease(colorSpace);
    free(pixels);

    // make a new UIImage to return
    UIImage *resultUIImage = [UIImage imageWithCGImage:image
                                                 scale:self.scale
                                           orientation:UIImageOrientationUp];

    // we're done with image now too
    CGImageRelease(image);

    return resultUIImage;
}


--------------------
Хајде Јано коло да играмо
iMac 27 mi 2010
Macbook air mi 2011
Mac Mini M1
Go to the top of the page
 
+Quote Post
SuperCed
posté 29 Jan 2021, 11:20
Message #4


Macbidouilleur d'Or !
*****

Groupe : Membres
Messages : 2 831
Inscrit : 19 Jul 2001
Lieu : Живим у Греноблу
Membre no 519



J'ai testé CoreImage pour faire mon traitement, mais les perfs sont horribles. Et le résultat est pas tout à fait bon.

C'est ça qui est super lent :

[UIImage imageWithCGImage:[context createCGImage:outputImage fromRect:outputImage.extent]];

Code
-(UIImage *) convertColorsWithForgroundColor:(UIColor*)foregroundColor backgroundColor:(UIColor*)backgroundColor strokeColor:(UIColor*)strokeColor
{
    
    CGFloat bgRed = 0.0, bgGreen = 0.0, bgBlue = 0.0, bgAlpha =0.0;
    [backgroundColor getRed:&bgRed green:&bgGreen blue:&bgBlue alpha:&bgAlpha];
    
    CGFloat fgRed = 0.0, fgGreen = 0.0, fgBlue = 0.0, fgAlpha =0.0;
    [foregroundColor getRed:&fgRed green:&fgGreen blue:&fgBlue alpha:&fgAlpha];
    
    CGFloat stRed = 0.0, stGreen = 0.0, stBlue = 0.0, stAlpha =0.0;
    [strokeColor getRed:&stRed green:&stGreen blue:&stBlue alpha:&stAlpha];
    
    // Make the input image recipe
    CIImage *inputImage = [CIImage imageWithCGImage:[self CGImage]]; // 1

    // Make the filter
    CIFilter *colorMatrixFilter = [CIFilter filterWithName:@"CIColorMatrix"]; // 2
    [colorMatrixFilter setDefaults]; // 3
    [colorMatrixFilter setValue:inputImage forKey:kCIInputImageKey]; // 4
    
    [colorMatrixFilter setValue:[CIVector vectorWithX:stRed Y:stGreen Z:stBlue W:0] forKey:@"inputRVector"]; // 6
    [colorMatrixFilter setValue:[CIVector vectorWithX:fgRed Y:fgGreen Z:fgBlue W:0] forKey:@"inputGVector"]; // 7
    [colorMatrixFilter setValue:[CIVector vectorWithX:bgRed Y:bgGreen Z:bgBlue W:0] forKey:@"inputBVector"]; // 8
    [colorMatrixFilter setValue:[CIVector vectorWithX:0 Y:0 Z:0 W:1] forKey:@"inputAVector"]; // 5

    // Get the output image recipe
    CIImage *outputImage = [colorMatrixFilter outputImage];  // 9
    
    CIContext *context = [CIContext contextWithOptions:nil];
    UIImage *returnImage = [UIImage imageWithCGImage:[context createCGImage:outputImage fromRect:outputImage.extent]];
        
    return returnImage;
        
}


--------------------
Хајде Јано коло да играмо
iMac 27 mi 2010
Macbook air mi 2011
Mac Mini M1
Go to the top of the page
 
+Quote Post
SuperCed
posté 29 Jan 2021, 15:55
Message #5


Macbidouilleur d'Or !
*****

Groupe : Membres
Messages : 2 831
Inscrit : 19 Jul 2001
Lieu : Живим у Греноблу
Membre no 519



Citation (audionuma @ 28 Jan 2021, 21:44) *
Peut-être avec Accelerate ?
Soit vImage, soit vDSP ?


J'ai regardé un peu vImage, j'ai bien trouvé quelques trucs qui avaient l'air de pouvoir fonctionner genre :

Code
vImageMatrixMultiply_ARGB8888ToPlanar8(&sourceBuffer,
                                       &destinationBuffer,
                                       &coefficientsMatrix,
                                       divisor,
                                       preBias,
                                       postBias,
                                       vImage_Flags(kvImageNoFlags))


Sauf que par rapport à mon code, le divisor semble fixé au début et indépendant de l'image source.
Dans mon algo, j'ai besoin des couleurs de pixel source pour faire une moyenne.
Donc ça ne semble pas adapté.

Par contre, j'ai réussi à utiliser les instruction vectorielles du processeur avec succès !

Pour la version avec CoreImage, j'ai l'impression que ce n'est pas adapté non plus car j'ai un traitement particulier pour le cannal alpha, c'est à dire le laisser tranquille...

Et là, dans le produit matriciel, il a l'air de compter.

D'autre par, la copie entre CGImage et UIImage prend pas mal de temps.
Dans la version avec les boucles, on ne fait pas de copie, on modifie directement les pixels.

Ce message a été modifié par SuperCed - 29 Jan 2021, 18:22.


--------------------
Хајде Јано коло да играмо
iMac 27 mi 2010
Macbook air mi 2011
Mac Mini M1
Go to the top of the page
 
+Quote Post

Reply to this topicStart new topic
1 utilisateur(s) sur ce sujet (1 invité(s) et 0 utilisateur(s) anonyme(s))
0 membre(s) :

 



Nous sommes le : 29th March 2024 - 15:35