English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Scena di progetto reale: rimuovere lo sfondo bianco puro dell'immagine per ottenere un'immagine con sfondo trasparente da utilizzare per la funzione di collage
Introduco due metodi di tre modi di elaborazione (non so perché mi viene in mente Kong Yiji), la prestazione specifica non è stata confrontata, se qualcuno può informarmi, sarò grato.
Core Image Core Graphics/Quarz 2D Core Image
Core Image è una struttura molto potente. Ti permette di applicare semplicemente vari filtri per elaborare immagini, come modificare la vivacità, il colore o l'esposizione. Utilizza GPU (o CPU) per elaborare dati di immagini e fotogrammi di video molto rapidamente, persino in tempo reale. E nasconde tutti i dettagli di elaborazione grafica di livello inferiore, che possono essere utilizzati semplicemente tramite l'API fornito, senza preoccuparsi di come OpenGL o OpenGL ES utilizzano al meglio le capacità della GPU, né di sapere come GCD agisce in questo contesto, Core Image gestisce tutti i dettagli.
nei documenti ufficiali di AppleGuida alla programmazione di Core Imageè menzionatoRicetta del filtro di Chroma KeyPer esempio, nel caso di elaborazione dello sfondo
Viene utilizzato il modello di colore HSV, poiché il modello HSV è più amichevole per la rappresentazione dell'intervallo di colori rispetto al modello RGB.
Procedura generale di elaborazione:
Creare una mappa cubica per rimuovere l'intervallo di valori dei colori, impostare l'Alpha del colore di destinazione a 0.0f, utilizzare il filtro CIColorCube e la mappa cubica per elaborare l'immagine sorgente, ottenere l'oggetto CIImage trattato con CIColorCube e convertirlo in un oggetto CGImageRef nel contesto di Core Graphics, quindi ottenere l'immagine di risultato tramite imageWithCGImage:
Attenzione: nel passo terzo, non è possibile utilizzare imageWithCIImage:, poiché non si ottiene una UIImage standard, e se utilizzata direttamente, potrebbe non essere visibile.
- (UIImage *)removeColorWithMinHueAngle:(float)minHueAngle maxHueAngle:(float)maxHueAngle image:(UIImage *)originalImage{ CIImage *image = [CIImage imageWithCGImage:originalImage.CGImage]; CIContext *context = [CIContext contextWithOptions:nil]; // kCIContextUseSoftwareRenderer : CPURender /** Attenzione * UIImage inizializzato con CIimage non è una UIImage standard ottenuta tramite standard come CGImage * Pertanto, se non si utilizza il contesto per la rendering, non è possibile visualizzare correttamente. */ CIImage *renderBgImage = [self outputImageWithOriginalCIImage:image minHueAngle:minHueAngle maxHueAngle:maxHueAngle]; CGImageRef renderImg = [context createCGImage:renderBgImage fromRect:image.extent]; UIImage *renderImage = [UIImage imageWithCGImage:renderImg]; return renderImage; } struct CubeMap { int length; float dimension; float *data; }; - (CIImage *)outputImageWithOriginalCIImage:(CIImage *)originalImage minHueAngle:(float)minHueAngle maxHueAngle:(float)maxHueAngle{ struct CubeMap map = createCubeMap(minHueAngle, maxHueAngle); const unsigned int size = 64; // Create memory with the cube data NSData *data = [NSData dataWithBytesNoCopy:map.data length:map.length freeWhenDone:YES]; CIFilter *colorCube = [CIFilter filterWithName:@"CIColorCube"]; [colorCube setValue:@(size) forKey:@"inputCubeDimension"]; // Set data for cube [colorCube setValue:data forKey:@"inputCubeData"]; [colorCube setValue:originalImage forKey:kCIInputImageKey]; CIImage *result = [colorCube valueForKey:kCIOutputImageKey]; return result; } struct CubeMap createCubeMap(float minHueAngle, float maxHueAngle) { const unsigned int size = 64; struct CubeMap map; map.length = size * size * size * sizeof (float) * 4; map.dimension = size; float *cubeData = (float *)malloc (map.length); float rgb[3], hsv[3], *c = cubeData; for (int z = 0; z < size; z++){ rgb[2] = ((double)z)/(size-1); // valore blu for (int y = 0; y < size; y++){ rgb[1] = ((double)y)/(size-1); // valore verde for (int x = 0; x < size; x ++){ rgb[0] = ((double)x)/(size-1); // valore rosso rgbToHSV(rgb,hsv); // Usa il valore di tonalità per determinare quale rendere trasparente // L'angolo di tonalità minimo e massimo dipende da // il colore che desideri rimuovere float alpha = (hsv[0] > minHueAngle && hsv[0] < maxHueAngle) ? 0.0f: 1.0f; // Calcola i valori alpha pre-moltiplicati per il cubo c[0] = rgb[0] * alpha; c[1] = rgb[1] * alpha; c[2] = rgb[2] * alpha; c[3] = alpha; c += 4; // avanziamo il puntatore nella memoria per il prossimo valore del colore } } } map.data = cubeData; return map; }
La conversione rgbToHSV non è menzionata nel documento ufficiale, l'autore ha trovato il trattamento di conversione relativo nel blog del signore menzionato nel testo successivo. Grazie.
void rgbToHSV(float *rgb, float *hsv) { float min, max, delta; float r = rgb[0], g = rgb[1], b = rgb[2]; float *h = hsv, *s = hsv + 1, *v = hsv + 2; min = fmin(fmin(r, g), b ); max = fmax(fmax(r, g), b ); *v = max; delta = max - min; if( max != 0 ) *s = delta / max; else { *s = 0; *h = -1; return; } if( r == max ) *h = ( g - b ) / delta; else if( g == max ) *h = 2 + ( b - r ) / delta; else *h = 4 + ( r - g ) / delta; *h *= 60; if( *h < 0 ) *h += 360; }
Proveremo ora a rimuovere l'effetto di sfondo verde, come si vede
Possiamo utilizzareStrumento HSV, determinare approssimativamente l'intervallo di valore HUE verde tra 50-170
Chiamiamo il metodo per provare
[[SPImageChromaFilterManager sharedManager] removeColorWithMinHueAngle:50 maxHueAngle:170 image:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"nb" ofType:@"jpeg"]]]
L'effetto
L'effetto sembra buono.
Se si osserva attentamente il modello HSV, forse si noterà che, attraverso la specifica dell'angolo del tono (Hue), non possiamo fare molto per il grigio, bianco e nero specificato. Dobbiamo usare la saturazione (Saturation) e la luminosità (Value) per giudicare insieme, gli studenti interessati possono trovarlo nel codiceValutare Alpha float alpha = (hsv[0] > minHueAngle && hsv[0] < maxHueAngle) ? 0.0f: 1.0f;Prova l'effetto lì. (Riguardo alla ragione del trasformazione RGB e HSV nel codice, cercate su Baidu la loro trasformazione, perché anche l'autore inesperto non lo sa. Ah, è una vita di disperazione)
Se siete interessati a Core Image, passate al blog series del signore
iOS8 Core Image In Swift: miglioramento automatico dell'immagine e utilizzo dei filtri integrati
iOS8 Core Image In Swift: filtri più complessi
iOS8 Core Image In Swift: rilevamento del viso e pixelazione
iOS8 Core Image In Swift: filtri in tempo reale dei video
Core Graphics/Quarz 2D
Come menzionato nell'articolo, Core Image basato su OpenGl è molto potente, come un altro fondamento delCore GraphicsAnche molto potente. L'esplorazione di questo argomento ha permesso all'autore inesperto di apprendere molte conoscenze relative alle immagini. Quindi, in questo punto, facciamo una sintesi per il futuro riferimento.
Se non siete interessati all'esplorazione, saltate direttamente alla parte finale dell'articolo Masking an Image with Color
Bitmap
Nel documento ufficiale di Quarz 2D, perBitMap ha la seguente descrizione:
Un'immagine bitmap (o immagine campionata) è un array di pixel (o campioni). Ogni pixel rappresenta un singolo punto nell'immagine. File grafici JPEG, TIFF e PNG sono esempi di immagini bitmap.
Formati di pixel 32-bit e 16-bit per gli spazi di colore CMYK e RGB in Quarz 2D
Torniamo alle nostre esigenze, per rimuovere il colore specifico dall'immagine, se possiamo leggere le informazioni RGBA di ogni pixel, giudicare i loro valori, se corrispondono al range di destinazione, cambiamo il valore Alpha in 0 e ne esportiamo un'immagine nuova, allora abbiamo implementato un metodo simile a quello descritto nell'articolo cubeMap.
Il potente Quarz 2D ci fornisce la capacità di implementare questa operazione, ecco un esempio di codice:
- (UIImage *)removeColorWithMaxR:(float)maxR minR:(float)minR maxG:(float)maxG minG:(float)minG maxB:(float)maxB minB:(float)minB image:(UIImage *)image{ // Assegnazione della memoria const int imageWidth = image.size.width; const int imageHeight = image.size.height; size_t bytesPerRow = imageWidth * 4; uint32_t* rgbImageBuf = (uint32_t*)malloc(bytesPerRow * imageHeight); // Create context CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); // Container for color range CGContextRef context = CGBitmapContextCreate(rgbImageBuf, imageWidth, imageHeight, 8, bytesPerRow, colorSpace,kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipLast); CGContextDrawImage(context, CGRectMake(0, 0, imageWidth, imageHeight), image.CGImage); // Traverse pixels int pixelNum = imageWidth * imageHeight; uint32_t* pCurPtr = rgbImageBuf; for (int i = 0; i < pixelNum; i++, pCurPtr++) { uint8_t* ptr = (uint8_t*)pCurPtr; if (ptr[3] >= minR && ptr[3] <= maxR && ptr[2] >= minG && ptr[2] <= maxG && ptr[1] >= minB && ptr[1] <= maxB) { ptr[0] = 0; } printf("\n---->ptr0:%d ptr1:%d ptr2:%d ptr3:%d<----\n",ptr[0],ptr[1],ptr[2],ptr[3]); } } // Convert memory to image CGDataProviderRef dataProvider = CGDataProviderCreateWithData(NULL, rgbImageBuf, bytesPerRow * imageHeight, nil); CGImageRef imageRef = CGImageCreate(imageWidth, imageHeight,8, 32, bytesPerRow, colorSpace,kCGImageAlphaLast |kCGBitmapByteOrder32Little, dataProvider,NULL,true,kCGRenderingIntentDefault); CGDataProviderRelease(dataProvider); UIImage* resultUIImage = [UIImage imageWithCGImage:imageRef]; // Liberare CGImageRelease(imageRef); CGContextRelease(context); CGColorSpaceRelease(colorSpace); return resultUIImage; }
Ricordi i difetti del modello HSV menzionati in Core Image? Quindi Quartz 2D utilizza direttamente le informazioni RGBA per elaborare, evitando il problema di non essere amichevole con il nero e il bianco, dobbiamo solo impostare l'intervallo RGB (poiché il nero e il bianco nel modello RGB di colore sono molto facili da determinare), possiamo fare un pacchetto di base. Ecco
- (UIImage *)removeWhiteColorWithImage:(UIImage *)image{ return [self removeColorWithMaxR:255 minR:250 maxG:255 minG:240 maxB:255 minB:240 image:image]; }
- (UIImage *)removeBlackColorWithImage:(UIImage *)image{ return [self removeColorWithMaxR:15 minR:0 maxG:15 minG:0 maxB:15 minB:0 image:image]; }
Guarda il confronto dell'effetto di elaborazione sullo sfondo bianco che abbiamo fatto
Sembra abbastanza buono, ma per i vestiti di seta, non sembra molto amichevole. Guarda i vari test di immagini che ho fatto
Evidentemente, se non fosse sfondo bianco, l'effetto 'abbandonato' sarebbe molto evidente. Questo problema, nei tre metodi che ho tentato, non è sfuggito a nessuno, se qualcuno dei grandi maestri sa di un ottimo metodo e può dirlo a 'stupidità', sarei grato (metto due ginocchia qui)
Oltre al problema menzionato sopra, il metodo di confronto per ogni pixel può generare valori con errore rispetto al disegno originale. Tuttavia, questo errore è praticamente invisibile agli occhi.}
Nelle immagini seguenti, quando disegnamo, le impostazioni RGB impostate sono 100/240/220, ma durante il trattamento con CG, i valori letti sono 92/241/220. Comparando le immagini "nuove" e "attuali", non si notano differenze di colore. Questo piccolo problema, se conosciuto, non influisce significativamente sull'effetto di rimozione dei colori.
Mascheramento di un'immagine con un colore
Dopo aver tentato di comprendere e utilizzare il metodo precedente, ho trovato questo metodo leggendo di nuovo la documentazione, come se avessi ricevuto un dono da parte del Padre Apple. Ecco il codice
- (UIImage *)removeColorWithMaxR:(float)maxR minR:(float)minR maxG:(float)maxG minG:(float)minG maxB:(float)maxB minB:(float)minB image:(UIImage *)image{ const CGFloat myMaskingColors[6] = {minR, maxR, minG, maxG, minB, maxB}; CGImageRef ref = CGImageCreateWithMaskingColors(image.CGImage, myMaskingColors); return [UIImage imageWithCGImage:ref]; }
Sommario
Il modello di colore HSV è più utile per eliminare i colori dalle immagini rispetto al modello RGB, che è esattamente l'opposto. Poiché nel mio progetto devo solo rimuovere lo sfondo bianco, ho scelto l'ultima opzione.