English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Escena de proyecto real: Eliminar el fondo blanco puro de la imagen para obtener una imagen de fondo transparente para la función de composición de imágenes
Se presentan tres métodos de procesamiento de dos vías (no sé por qué me acordé de Kong Yiji), no se compararon las especificaciones específicas, si algún maestro puede informar, estaré muy agradecido.
Core Image Core Graphics/Quarz 2D Core Image
Core Image es un marco muy potente. Te permite aplicar fácilmente varios filtros para procesar imágenes, como modificar la intensidad, el color o la exposición. Utiliza GPU (o CPU) para procesar datos de imágenes y cuadros de video muy rápidamente, incluso en tiempo real. Y oculta todos los detalles de procesamiento gráfico subyacente, permitiendo su uso simple a través de la API proporcionada, sin necesidad de preocuparse por cómo OpenGL o OpenGL ES utilizan al máximo las capacidades de GPU, ni de saber cómo GCD juega su papel, Core Image maneja todos los detalles.
en la documentación oficial de AppleGuía de Programación de Core Imagese mencionóReceta de Filtro de Chroma KeyPara el ejemplo de procesamiento de fondo
Se utiliza el modelo de color HSV, ya que el modelo HSV es más amigable para la representación del rango de color en comparación con RGB.
Proceso general del proceso:
Se crea un mapeo cúbico de cuboMap para eliminar el rango de valores de color, se establece el Alpha del color objetivo en 0.0f, se utiliza el filtro CIColorCube y el cubeMap para procesar el color de la imagen de origen, se obtiene el objeto CIImage procesado por CIColorCube y se convierte en el objeto CGImageRef de Core Graphics a través de imageWithCGImage: para obtener la imagen de resultado
Atención: En el tercer paso, no se puede usar directamente imageWithCIImage:, porque lo obtenido no es un UIImage estándar, y si se utiliza directamente, puede aparecer el problema de que no se muestra.
- (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 CGImage * So if you don't use context for rendering processing, it is impossible to display normally */ 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 tamaño = 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"]; // Establecer datos para el cubo [colorCube setValue:data forKey:@"inputCubeData"]; [colorCube setValue:originalImage forKey:kCIInputImageKey]; CIImage *resultado = [colorCube valueForKey:kCIOutputImageKey]; return resultado; } struct CubeMap createCubeMap(float minHueAngle, float maxHueAngle) { const unsigned int tamaño = 64; struct CubeMap map; longitudDeMapa = tamaño; * tamaño * tamaño * tamañoDeByteDeFloat * 4; dimensiónDeMapa = tamaño; float *cubeData = (float *)malloc (longitudDeMapa); float rgb[3], hsv[3], *c = cubeData; for (int z = 0; z < tamaño; z++{ rgb[2] = ((double)z)/(tamaño-1); // Valor azul for (int y = 0; y < tamaño; y++{ rgb[1] = ((double)y)/(tamaño-1); // Valor verde for (int x = 0; x < tamaño; x ++{ rgb[0] = ((double)x)/(tamaño-1); // Valor rojo rgbToHSV(rgb,hsv); // Usa el valor de tono para determinar cuál hacer transparente // El ángulo de tono mínimo y máximo depende de // el color que deseas eliminar float alpha = (hsv[0] > minHueAngle && hsv[0] < maxHueAngle) &63; 0.0f: 1.0f; // Calcular valores de alfa premultiplicados para el cubo c[0] = rgb[0] * alpha; c[1] = rgb[1] * alpha; c[2] = rgb[2] * alpha; c[3] = alpha; c += 4; // avanzar nuestro puntero en la memoria para el siguiente valor de color } } } map.data = cubeData; return map; }
rgbToHSV no se menciona en el documento oficial, el autor encontró el proceso de conversión relacionado en el blog de los expertos mencionados a continuación. Gracias
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; }
A continuación, probemos el efecto de eliminar el fondo verde
Podemos usarHerramienta HSV, determine el rango aproximado del valor de HUE verde50-170
Llame a este método para probar
[[SPImageChromaFilterManager sharedManager] removeColorWithMinHueAngle:50 maxHueAngle:170 image:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"nb" ofType:@"jpeg"]]]
El efecto
El efecto parece bastante bueno.
Si observan atentamente el modelo HSV, tal vez se darán cuenta de que mediante la especificación del ángulo de tono (Hue), no podemos actuar sobre el gris, blanco y negro. Tenemos que usar la saturación (Saturation) y la luminosidad (Value) para juzgar conjuntamente, los estudiantes interesados pueden encontrarlo en el códigojuzgar Alpha float alpha = (hsv[0] > minHueAngle && hsv[0] < maxHueAngle) &63; 0.0f: 1.0f;pruebe el efecto allí. (Respecto a por qué RGB y HSV se convierten así en el código, por favor, busque en Baidu sus conversiones, ya que el autor novato tampoco lo entiende. ¡Ah, el autor novato no tiene vida!)
Si están interesados en Core Image, por favor, diríjase a la serie de artículos del experto
iOS8 Core Image In Swift: mejora automática de imágenes y uso de filtros integrados
iOS8 Core Image In Swift: filtros más complejos
iOS8 Core Image In Swift: detección de rostros y máscara
iOS8 Core Image In Swift: filtros en tiempo real de video
Core Graphics/Quarz 2D
Como se mencionó anteriormente, Core Image basado en OpenGL es muy potente, como otro pilar de la vistaCore Graphicstambién muy poderoso. Su exploración ha permitido que el autor novato tenga una mayor comprensión de los conocimientos relacionados con las imágenes. Por lo tanto, aquí se realiza un resumen para futuras consultas.
Si no están interesados en la exploración, por favor, salten directamente a la parte final del artículo Masking an Image with Color
Bitmap
en Quarz 2En el documento oficial de D, paraBitMap tiene la siguiente descripción:
Una imagen de mapa de bits (o imagen muestreada) es un array de píxeles (o muestras). Cada píxel representa un único punto en la imagen. Los archivos gráficos JPEG, TIFF y PNG son ejemplos de imágenes de mapa de bits.
32-y 16-formatos de pixel de 32 bits para los espacios de color CMYK y RGB en Quartz 2D
Volviendo a nuestras necesidades, para eliminar el color específico de la imagen, si podemos leer la información RGBA en cada píxel, determinar sus valores, si coinciden con el rango objetivo, cambiaremos su valor Alpha a 0 y lo saldremos como una nueva imagen, de esta manera hemos implementado una forma similar al tratamiento mencionado en el texto anterior cubeMap.
fuerte Quarz 2D nos proporciona la capacidad de realizar esta operación, a continuación, por favor, vea el ejemplo de código:
- (UIImage *)removeColorWithMaxR:(float)maxR minR:(float)minR maxG:(float)maxG minG:(float)minG maxB:(float)maxB minB:(float)minB image:(UIImage *)image{ // Asignar 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); // Crear context CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();// Contenedor del rango de colores CGContextRef context = CGBitmapContextCreate(rgbImageBuf, imageWidth, imageHeight, 8, bytesPerRow, colorSpace,kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipLast); CGContextDrawImage(context, CGRectMake(0, 0, imageWidth, imageHeight), image.CGImage); // Recorrer los píxeles 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[1], ptr[2], ptr[3]); } } // Convertir la memoria en imagen 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]; // liberar CGImageRelease(imageRef); CGContextRelease(context); CGColorSpaceRelease(colorSpace); return resultUIImage; }
¿Recuerda los defectos del modo HSV mencionados en Core Image? Entonces, Quarz 2D es directamente utilizar la información de RGBA para procesar, evitando muy bien el problema de que no es amigable con el negro y el blanco, solo necesitamos configurar el rango de RGB (porque el negro y el blanco en el modo de color RGB se pueden determinar muy bien), podemos encapsularlo aproximadamente. Así
- (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]; }
Mire la comparación del efecto de procesamiento de fondo blanco que hemos hecho
Parece que está bien, pero para la ropa de gasa, no es muy amigable. Mire las pruebas de varios grupos de imágenes que hice
Claramente, si no es de fondo blanco, el efecto de 'vestido desgastado' es muy evidente. Este problema, en los tres métodos que intenté, no escapó a la ruleta, si algún gran maestro conoce un buen método y puede decirle a la pato, estaré muy agradecido. (Pongo aquí dos rodillas primero)
Además de los problemas mencionados anteriormente, el método de comparación de cada píxel, los valores leídos coinciden con el error que aparece al dibujar. Sin embargo, este error es prácticamente invisible a simple vista.
Como se muestra en la imagen siguiente, los valores RGB establecidos al dibujar son100/240/220 Pero al leer los valores mediante el procesamiento de CG anterior, los valores leídos son92/241/220. Comparando las imágenes "nuevas" y "actuales", prácticamente no se nota la diferencia en el color. Este pequeño problema, todos lo sabemos, no afecta mucho el efecto real de la eliminación de colores
Enmascarar una Imagen con Color
Después de intentar entender y usar el método anterior, al volver a leer el documento, descubrí este método, que es como encontrar un regalo de la mano de Father Apple. Aquí va el código
- (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]; }
Pulse aquí para la documentación oficial
Resumen
El modo de color HSV es más beneficioso que el modo RGB para eliminar colores de las imágenes, mientras que RGB es exactamente lo contrario. Dado que solo necesito eliminar el fondo blanco en mi proyecto, finalmente opté por la última manera.