English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Methode zur Entfernung der Hintergrundfarbe von Bildern in iOS

Praktische Anwendungsszenarien: Entfernen des rein weißen Hintergrundbildes aus dem Bild, um ein transparentes Hintergrundbild zu erhalten, das für die Bildzusammensetzungsfunktion verwendet werden kann

Es werden zwei Ansätze und drei Verarbeitungsmethoden vorgestellt (weiß nicht, warum mir der Konfuzius einfiel), die spezifischen Leistungsnachweise wurden nicht verglichen, wenn ein Meister uns darüber informieren könnte, wäre ich unendlich dankbar.

Core Image Core Graphics/Quarz 2D Core Image

Core Image ist ein sehr leistungsstarkes Framework. Es ermöglicht es Ihnen, verschiedene Filter einfach anzuwenden, um Bilder zu bearbeiten, z.B. um die Leuchtkraft, den Farbton oder die Belichtung zu ändern. Es nutzt das GPU (oder CPU) zur sehr schnellen, ja fast realzeitlichen Verarbeitung von Bild- und Videodaten. Es versteckt alle Details der unteren Grafikverarbeitung und kann über die bereitgestellten API einfach verwendet werden, ohne sich um OpenGL oder OpenGL ES kümmern zu müssen, wie sie die GPU-Leistung充分利用, und auch ohne zu wissen, wie GCD darin eine Rolle spielt, bearbeitet Core Image alle Details.

in der offiziellen Dokumentation von AppleCore Image Programming Guidewurde erwähntChroma Key Filter RezeptFür die Beispielanwendung zur Verarbeitung des Hintergrunds

Hier wird das HSV-Farbschema verwendet, da das HSV-Schema im Vergleich zum RGB-Schema für die Darstellung von Farbbereichen freundlicher ist.

Allgemeiner Prozess der Verarbeitung:

Erstellen Sie einen cubeMap-Kubus, um den Bereich der zu entfernenden Farben zu kartieren, stellen Sie den Alpha-Wert der Zielfarbe auf 0.0f ein. Verwenden Sie den CIColorCube-Filter und den cubeMap, um die Farben der Quellobjektbilder zu verarbeiten, um das Core Image-Objekt CIImage zu erhalten, das nach dem CIColorCube-Filter bearbeitet wurde, konvertieren Sie es in das CGImageRef-Objekt von Core Graphics und erhalten Sie das Ergebnisbild über imageWithCGImage:

Hinweis: Im dritten Schritt darf imageWithCIImage: nicht direkt verwendet werden, da das Ergebnis nicht eine standardmäßige UIImage ist. Wenn man es direkt verwendet, könnte es zu einem Nichtanzeigeproblem kommen.

- (UIImage *)removeColorWithMinHueAngle:(float)minHueAngle maxHueAngle:(float)maxHueAngle image:(UIImage *)originalImage{
 CIImage *image = [CIImage imageWithCGImage:originalImage.CGImage];
 CIContext *context = [CIContext contextWithOptions:nil];// kCIContextUseSoftwareRenderer : CPURender
 /** Note
 * The UIImage initialized through CIimage is not a standard UIImage like through CGImage
 * Therefore, it is not possible to display it normally without using the context for rendering processing
 */
 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"];
 // Setze Daten für den Würfel
 [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; // Blau-Wert
 for (int y = 0; y < size; y++{
 rgb[1] = ((double)y)/(size-1; // Grün-Wert
 for (int x = 0; x < size; x ++{
 rgb[0] = ((double)x)/(size-1; // R-Wert
 rgbToHSV(rgb,hsv);
 // Verwende den Farbtonwert, um zu bestimmen, was transparent gemacht werden soll
 // Der Mindest- und Maximalfarbtonwinkel hängt von
 // die Farbe, die du entfernen möchtest
 float alpha = (hsv[0] > minHueAngle && hsv[0] < maxHueAngle) &63; 0.0f: 1.0f;
 // Berechne die vorgezogenen Alpha-Werte für den Würfel
 c[0] = rgb[0] * alpha;
 c[1] = rgb[1] * alpha;
 c[2] = rgb[2] * alpha;
 c[3] = alpha;
 c += 4; // advance our pointer into memory for the next color value
 }
 }
 }
 map.data = cubeData;
 return map;
}

rgbToHSV wird im offiziellen Dokument nicht erwähnt. Der Autor fand in den Blogs der genannten Experten die entsprechenden Umwandlungsbehandlungen. Vielen Dank

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;
}

Versuchen wir, den Effekt der Entfernung des grünen Hintergrunds zu überprüfen

Wir können durch die VerwendungHSV-Werkzeug, um den ungefähren Bereich der Grün-HUE-Werte zu bestimmen50-170

Versuchen wir, die Methode aufzurufen

[[SPImageChromaFilterManager sharedManager] removeColorWithMinHueAngle:50 maxHueAngle:170 image:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"nb" ofType:@"jpeg"]]]

Das Ergebnis

Das Ergebnis sieht noch gut aus.

Wenn man sorgfältig das HSV-Modell beobachtet, könnte man vielleicht feststellen, dass wir durch die Festlegung des Farbtons (Hue) nicht in der Lage sind, Grau, Weiß und Schwarz zu spezifizieren. Wir müssen auf die Sättigung (Saturation) und Helligkeit (Value) zusammen zugreifen, um zu urteilen, interessierte Schüler können dies im Code überprüfen判断Alpha float alpha = (hsv[0] > minHueAngle && hsv[0] < maxHueAngle) &63; 0.0f: 1.0f;那里试一下效果。(至于代码中为啥RGB和HSV这么转换,请百度他们的转换,因为鶸笔者也不懂。哎,鶸不聊生)

对于Core Image感兴趣的同学,请移步大佬的系列文章

iOS8 Core Image In Swift:自动改善图像以及内置滤镜的使用

iOS8 Core Image In Swift:更复杂的滤镜

iOS8 Core Image In Swift:人脸检测以及马赛克

iOS8 Core Image In Swift:视频实时滤镜

Core Graphics/Quarz 2D

上文中提到的基于OpenGl的Core Image显然功能十分强大,作为视图另一基石的Core Graphics同样强大。对他的探究,让鶸笔者更多的了解到图片的相关知识。所以在此处总结,供日后查阅。

如果对探究不感兴趣的同学,请直接跳到文章最后 Masking an Image with Color 部分

Bitmap


在Quarz 2D官方文档中,对于BitMap有如下描述

A bitmap image (or sampled image) is an array of pixels (or samples). Each pixel represents a single point in the image. JPEG, TIFF, and PNG graphics files are examples of bitmap images.

32-bit and 16-bit pixel formats for CMYK and RGB color spaces in Quartz 2D

回到我们的需求,对于去除图片中的指定颜色,如果我们能够读取到每个像素上的RGBA信息,分别判断他们的值,如果符合目标范围,我们将他的Alpha值改为0,然后输出成新的图片,那么我们就实现了类似上文中cubeMap的处理方式。

强大的Quarz 2D为我们提供了实现这种操作的能力,下面请看代码示例:

- (UIImage *)removeColorWithMaxR:(float)maxR minR:(float)minR maxG:(float)maxG minG:(float)minG maxB:(float)maxB minB:(float)minB image:(UIImage *)image{
 // Speicher zuweisen}}
 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);
 // Erstelle context
 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();// Farbreich container
 CGContextRef context = CGBitmapContextCreate(rgbImageBuf, imageWidth, imageHeight, 8, bytesPerRow, colorSpace,kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipLast);
 CGContextDrawImage(context, CGRectMake(0, 0, imageWidth, imageHeight), image.CGImage);
 // durchsuche die Pixel
 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;
 } else {
 printf("\n---->ptr0:%d ptr1:%d ptr2:%d ptr3:%d<----\n", ptr[0], ptr[123]
 }
 }
 // den Speicher in ein Bild umwandeln
 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]; 
 // Freigabe
 CGImageRelease(imageRef);
 CGContextRelease(context);
 CGColorSpaceRelease(colorSpace);
 return resultUIImage;
}

Denken Sie noch an die Nachteile des HSV-Modus in Core Image? Dann Quartz 2D nutzt direkt die Informationen von RGBA zur Verarbeitung und umgeht das Problem, dass Schwarz und Weiß nicht freundlich sind. Wir müssen nur den Bereich von RGB einstellen (da Schwarz und Weiß im RGB-Farbraum gut bestimmt werden können), und wir können ihn grob verpacken. Hier ist ein Beispiel

- (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];
}

Schauen Sie sich die Vergleichsergebnisse unserer Verarbeitung auf weißem Hintergrund an

Es scheint recht gut zu funktionieren, aber für Seidenkleidung ist es nicht so freundlich. Schauen Sie sich die Tests mit einigen von mir erstellten Bildern an

Es ist offensichtlich, dass der Effekt von "kleidungslos" sehr auffällig wäre, wenn nicht auf weißem Hintergrund. Bei den drei Methoden, die ich versucht habe, war dies bei keinem Fall der Fall. Wenn jemand eine gute Lösung kennt und mir, einem kleinen Vogel, mitteilen kann, werde ich ihm/ihm mehr als dankbar sein. (Zwei Knie hierherstellen)

Abgesehen von den oben genannten Problemen, können die Werte, die durch das Vergleichen jedes Pixels gewonnen werden, Fehler bei der Zeichnung aufweisen. Aber dieser Fehler ist für das Auge fast unsichtbar.


Wie in dem folgenden Bild gezeigt, sind die RGB-Werte, die wir beim Zeichnen festgelegt haben100/240/220 Aber die Werte, die beim Lesen durch CG gelesen werden, sind92/241/220. Im Vergleich zu den "neuen" und "aktuellen" Bildern in der Abbildung ist der Farbunterschied nicht sehr auffällig. Dieses kleine Problem wissen Sie schon, es hat nicht viel Einfluss auf die tatsächliche Entfernung von Farben.

Ein Bild mit Farbe maskieren

Ich habe versucht, die obige Methode zu verstehen und zu verwenden, und habe sie beim erneuten Lesen der Dokumentation entdeckt, was so ist, als hätte ich die göttliche Gabe von Vater Apple entdeckt. Hier ist der Code

- (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];
}

Offizielle Dokumentation hier

Zusammenfassung

Die HSV-Farbmuster sind im Vergleich zum RGB-Muster besser geeignet, um Farbbilder zu entfernen, während RGB genau das Gegenteil ist. Da ich im Projekt nur den weißen Hintergrund entfernen muss, habe ich letztendlich die letzte Methode gewählt.

Gefällt mir