實際項目場景:去除圖片的純白色背景圖,得到一張透明底圖片用於拼圖功能html
下面介紹兩種途徑的三種處理方式(不知道爲啥想起了孔乙己),具體性能鶸並未對比,若是有大佬能告知,不勝感激。bash
Core Image是一個很強大的框架。它可讓你簡單地應用各類濾鏡來處理圖像,好比修改鮮豔程度,色澤,或者曝光。 它利用GPU(或者CPU)來很是快速、甚至實時地處理圖像數據和視頻的幀。而且隱藏了底層圖形處理的全部細節,經過提供的API就能簡單的使用了,無須關心OpenGL或者OpenGL ES是如何充分利用GPU的能力的,也不須要你知道GCD在其中發揮了怎樣的做用,Core Image處理了所有的細節。app
在蘋果官方文檔Core Image Programming Guide中,提到了Chroma Key Filter Recipe對於處理背景的範例框架
其中使用了HSV顏色模型,由於HSV模型,對於顏色範圍的表示,相比RGB更加友好。ide
大體過程處理過程:工具
Alpha
置爲0.0f
CIColorCube
濾鏡和cubeMap對源圖像進行顏色處理CIColorCube
處理的Core Image
對象CIImage
,轉換爲Core Graphics
中的CGImageRef
對象,經過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初始化,獲得的並非一個經過相似CGImage的標準UIImage
* 因此若是不用context進行渲染處理,是沒辦法正常顯示的
*/
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); // 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;
}
複製代碼
接下來咱們試一下,去除綠色背景的效果如何 ui
咱們能夠經過使用HSV工具,肯定綠色HUE
值的大概範圍爲50-170spa
調用一下方法試一下
[[SPImageChromaFilterManager sharedManager] removeColorWithMinHueAngle:50 maxHueAngle:170 image:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"nb" ofType:@"jpeg"]]]
複製代碼
效果
效果還能夠的樣子。
若是認真觀察HSV模型的同窗也許會發現,咱們經過指定色調角度(Hue)的方式,對於指定灰白黑顯得無能爲力。咱們不得不去用飽和度(Saturation)和明度(Value)去共同判斷,感興趣的同窗能夠在代碼中判斷Alphafloat alpha = (hsv[0] > minHueAngle && hsv[0] < maxHueAngle) ? 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:視頻實時濾鏡
上文中提到的基於OpenGl
的Core Image
顯然功能十分強大,做爲視圖另外一基石的Core Graphics一樣強大。對他的探究,讓鶸筆者更多的瞭解到圖片的相關知識。因此在此處總結,供往後查閱。
若是對探究不感興趣的同窗,請直接跳到文章最後 Masking an Image with Color 部分
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{
// 分配內存
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);
// 建立context
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;
}else{
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;
}
複製代碼
還記得咱們在Core Image中提到的HSV模式的弊端嗎?那麼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];
}
複製代碼
看一下咱們對於白色背景的處理效果對比
看起來彷佛還不錯,可是對於紗質的衣服,就顯得很不友好。看一下筆者作的幾組圖片的測試
很顯然,若是不是白色背景,「衣衫襤褸」的效果很是明顯。這個問題,在筆者嘗試的三種方法中,無一倖免,若是哪位大佬知道好的處理方法,並且能告訴鶸,將不勝感激。(先放倆膝蓋在這兒)
除了上述問題外,這種對比每一個像素的方法,讀取出來的數值會同做圖時出現偏差。可是這種偏差肉眼基本不可見。
筆者嘗試過理解並使用上一種方法後,在重讀文檔時發現了這個方法,簡直就像是發現了Father 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模式而言,更利於咱們摳除圖片中的彩色,而RGB則正好相反。筆者由於項目中,只須要去除白色背景,因此最終採用了最後一種方式。