English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Реальные проектные сцены: удаление чисто белого фона изображения, получение изображения с прозрачным фоном для функции составления пазлов
Представляю два способа обработки трех методов (не знаю, почему вспомнилось имя Кун Цзы-цзюнь), конкретные характеристики не сравнивали, если кто-то может сообщить, буду очень благодарен.
Core Image Core Graphics/Quarz 2D Core Image
Core Image - это очень мощный фреймворк. Он позволяет вам легко применять различные фильтры для обработки изображений, например, изменять насыщенность, цвет и экспозицию. Он использует GPU (или CPU) для очень быстрого, даже реального времени, обработки данных изображений и кадров видео. И скрыл все детали низкоуровневой графической обработки, предоставляя API, с помощью которого его можно легко использовать, не заботясь о том, как OpenGL или OpenGL ES充分利用 возможности GPU, также не нужно знать, как GCD играет свою роль, Core Image обрабатывает все детали.
в официальной документации AppleCore Image Programming GuideупомянутоРецепт Chroma Key FilterДля примера обработки фона
Здесь используется цветовая модель HSV, так как модель HSV, по сравнению с RGB, более удобна для представления диапазона цветов.
Общий процесс обработки:
Создание карты цветов cubeMap, которую нужно удалить из диапазона цветов, устанавливает Alpha целевого цвета в 0.0f, использует фильтр CIColorCube и cubeMap для обработки цвета исходного изображения, получая после обработки Core Image объект CIImage, преобразует его в CGImageRef объект в Core Graphics, используя imageWithCGImage:, чтобы получить результат изображения
Внимание: в третьем шаге не可以直接 использовать imageWithCIImage:, так как полученная не является стандартной UIImage, и если использовать её напрямую, может не отображаться.
- (UIImage *)removeColorWithMinHueAngle:(float)minHueAngle maxHueAngle:(float)maxHueAngle image:(UIImage *)originalImage{ CIImage *image = [CIImage imageWithCGImage:originalImage.CGImage]; CIContext *context = [CIContext contextWithOptions:nil]; // kCIContextUseSoftwareRenderer : CPURender /** Внимание * UIImage, инициированный через CIimage, не является стандартной UIImage, полученной через подобие CGImage * Поэтому если не использовать контекст для обработки рендеринга, нормальное отображение не возможно */ CIImage *renderBgImage = [self outputImageWithOriginalCIImage:image minHueAngle:minHueAngle maxHueAngle:maxHueAngle]; CGImageRef renderImg = [context createCGImage:renderBgImage fromRect:image.extent]; UIImage *renderImage = [UIImage imageWithCGImage:renderImg]; возврат 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; // Создать память с данными куба NSData *data = [NSData dataWithBytesNoCopy:map.data length:map.length freeWhenDone:YES]; CIFilter *colorCube = [CIFilter filterWithName:@"CIColorCube"]; [colorCube setValue:@(size) forKey:@"inputCubeDimension"]; // Установить данные для куба [colorCube setValue:data forKey:@"inputCubeData"]; [colorCube setValue:originalImage forKey:kCIInputImageKey]; CIImage *result = [colorCube valueForKey:kCIOutputImageKey]; возврат 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); // Blue value for (int y = 0; y < size; y++){ rgb[1] = ((double)y)/(size-1); // Green value for (int x = 0; x < size; x ++){ rgb[0] = ((double)x)/(size-1); // Red value rgbToHSV(rgb,hsv); // Use the hue value to determine which to make transparent // The minimum and maximum hue angle depends on // the color you want to remove float alpha = (hsv[0] > minHueAngle && hsv[0] < maxHueAngle) ? 0.0f: 1.0f; // Calculate premultiplied alpha values for the cube 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 не упоминается в официальной документации, автор нашел соответствующие преобразования в блоге упомянутых экспертов. Спасибо.
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; }
Давайте попробуем удалить зеленый фон и看看, как это выглядит
Мы можем использоватьИнструмент HSV, чтобы определить приблизительный диапазон значений HUE для зеленого цвета 50-170
попробуйте вызвать этот метод
[[SPImageChromaFilterManager sharedManager] removeColorWithMinHueAngle:50 maxHueAngle:170 image:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"nb" ofType:@"jpeg"]]]
Эффект
Эффект все еще неплохой.
Если внимательно изучить модель HSV, можно заметить, что мы не можем определить серый, белый и черный цвет, используя только углы тона (Hue). Мы должны использовать насыщенность (Saturation) и亮度 (Value) для的共同 определения, интересующиеся студенты могут найти это в кодеОпределение Alpha float alpha = (hsv[0] > minHueAngle && hsv[0] < maxHueAngle) ? 0.0f: 1.0f;пробуйте эффекты. (Что касается того, почему в коде RGB и HSV так преобразуются, посмотрите их преобразование в Бaidu, потому что автор не знает. Эй, автор не имеет настроения)
Если вас интересует 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Он также силен. Исследование его позволило автору лучше понять相关知识 о изображениях. Поэтому в данном разделе мы summarize, чтобы их можно было проверить в будущем.
Если вы не интересуетесь исследованием, пропустите до конца статьи в разделе Masking an Image with Color
Bitmap
В официальной документации Quarz 2D дляBitMap имеет следующее описание:
Растровое изображение (или采样ное изображение) представляет собой массив пикселей (или образцов). Каждый пиксель представляет собой единственную точку в изображении. JPEG, TIFF и PNG графические файлы являются примерами растровых изображений.
32-битные и 16-битные форматы пикселей для цветовых пространств CMYK и RGB в Quarz 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{ // Назначение памяти 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); // Создание контекста CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); // Контейнер для цветового диапазона CGContextRef context = CGBitmapContextCreate(rgbImageBuf, imageWidth, imageHeight, 8, bytesPerRow, colorSpace,kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipLast); CGContextDrawImage(context, CGRectMake(0, 0, imageWidth, imageHeight), image.CGImage); // Пробег по пикселям 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]); } } // Преобразование памяти в 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]; // Освобождение CGImageRelease(imageRef); CGContextRelease(context); CGColorSpaceRelease(colorSpace); return resultUIImage; }
Помните, мы говорили о недостатках режима HSV в Core Image? Quarz 2D использует информацию RGBA напрямую для обработки, что хорошо решает проблему несовместимости с черным и белым. Достаточно настроить диапазон RGB (поскольку черное и белое легко определить в цветовой модели RGB), и мы можем примерно упаковать это. Вот так
- (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]; }
Давайте сравним результаты обработки белого фона
Кажется, что это выглядит неплохо, но для ткани, такая одежда выглядит не очень приятной. Давайте посмотрим на несколько тестовых изображений, которые я сделал
Конечно, если бы не белый фон, эффект 'одет в лохмотья' был бы очень очевиден. Эта проблема не избежала в трёх методах, которые я попробовал. Если кто-то из гуру знает хорошее решение и может рассказать ляму, буду очень благодарен. (Поставлю колени на место)
Кроме указанных проблем, при сравнении каждого像素 методом, чтение числовых значений может давать ошибки, но эти ошибки не видны глазом.
Как показано на рисунке, при создании изображения мы установили значения RGB 100/240/220, но при обработке с помощью CG, читаемые значения составляют 92/241/220. По сравнению с «новыми» и «текущими» значениями в графике, различия в цвете практически не видны. Это мелкое упущение вы знаете, и оно не сильно влияет на реальный эффект удаления цвета
Маскирование изображения цветом
Автор пытался понять и использовать предыдущий метод, но при чтении документа обнаружил этот метод, словно открыл благословение отца Apple. Давайте сразу перейдем к коду
- (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]; }
Официальная документация вот здесь
Резюме
Модель цветов HSV более удобна для удаления цветов из изображений по сравнению с моделью RGB, которая делает это наоборот. Автор, поскольку в проекте нужно только удалить белый фон, в конечном итоге выбрал последний способ.