iOS去除圖片背景顏色的方法
實(shí)際項(xiàng)目場(chǎng)景:去除圖片的純白色背景圖,獲得一張透明底圖片用于拼圖功能
介紹兩種途徑的三種處理方式(不知道為啥想起了孔乙己),具體性能鶸并未對(duì)比,如果有大佬能告知,不勝感激。
Core Image Core Graphics/Quarz 2D Core Image
Core Image是一個(gè)很強(qiáng)大的框架。它可以讓你簡(jiǎn)單地應(yīng)用各種濾鏡來(lái)處理圖像,比如修改鮮艷程度,色澤,或者曝光。 它利用GPU(或者CPU)來(lái)非常快速、甚至實(shí)時(shí)地處理圖像數(shù)據(jù)和視頻的幀。并且隱藏了底層圖形處理的所有細(xì)節(jié),通過(guò)提供的API就能簡(jiǎn)單的使用了,無(wú)須關(guān)心OpenGL或者OpenGL ES是如何充分利用GPU的能力的,也不需要你知道GCD在其中發(fā)揮了怎樣的作用,Core Image處理了全部的細(xì)節(jié)。

在蘋(píng)果官方文檔Core Image Programming Guide中,提到了Chroma Key Filter Recipe對(duì)于處理背景的范例
其中使用了HSV顏色模型,因?yàn)镠SV模型,對(duì)于顏色范圍的表示,相比RGB更加友好。
大致過(guò)程處理過(guò)程:
創(chuàng)建一個(gè)映射希望移除顏色值范圍的立方體貼圖cubeMap,將目標(biāo)顏色的Alpha置為0.0f 使用CIColorCube濾鏡和cubeMap對(duì)源圖像進(jìn)行顏色處理獲取到經(jīng)過(guò)CIColorCube處理的Core Image對(duì)象CIImage,轉(zhuǎn)換為Core Graphics中的CGImageRef對(duì)象,通過(guò)imageWithCGImage:獲取結(jié)果圖片
注意:第三步中,不可以直接使用imageWithCIImage:,因?yàn)榈玫降牟⒉皇且粋€(gè)標(biāo)準(zhǔn)的UIImage,如果直接拿來(lái)用,會(huì)出現(xiàn)不顯示的情況。
- (UIImage *)removeColorWithMinHueAngle:(float)minHueAngle maxHueAngle:(float)maxHueAngle image:(UIImage *)originalImage{
CIImage *image = [CIImage imageWithCGImage:originalImage.CGImage];
CIContext *context = [CIContext contextWithOptions:nil];// kCIContextUseSoftwareRenderer : CPURender
/** 注意
* UIImage 通過(guò)CIimage初始化,得到的并不是一個(gè)通過(guò)類似CGImage的標(biāo)準(zhǔn)UIImage
* 所以如果不用context進(jìn)行渲染處理,是沒(méi)辦法正常顯示的
*/
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在官方文檔中并沒(méi)有提及,筆者在下文中提到的大佬的博客中找到了相關(guān)轉(zhuǎn)換處理。感謝
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;
}
接下來(lái)我們?cè)囈幌拢コG色背景的效果如何

我們可以通過(guò)使用HSV工具,確定綠色HUE值的大概范圍為50-170
調(diào)用一下方法試一下
[[SPImageChromaFilterManager sharedManager] removeColorWithMinHueAngle:50 maxHueAngle:170 image:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"nb" ofType:@"jpeg"]]]
效果

效果還可以的樣子。
如果認(rèn)真觀察HSV模型的同學(xué)也許會(huì)發(fā)現(xiàn),我們通過(guò)指定色調(diào)角度(Hue)的方式,對(duì)于指定灰白黑顯得無(wú)能為力。我們不得不去用飽和度(Saturation)和明度(Value)去共同判斷,感興趣的同學(xué)可以在代碼中判斷Alpha float alpha = (hsv[0] > minHueAngle && hsv[0] < maxHueAngle) ? 0.0f: 1.0f;那里試一下效果。(至于代碼中為啥RGB和HSV這么轉(zhuǎn)換,請(qǐng)百度他們的轉(zhuǎn)換,因?yàn)辁U筆者也不懂。哎,鶸不聊生)
對(duì)于Core Image感興趣的同學(xué),請(qǐng)移步大佬的系列文章
iOS8 Core Image In Swift:自動(dòng)改善圖像以及內(nèi)置濾鏡的使用
iOS8 Core Image In Swift:更復(fù)雜的濾鏡
iOS8 Core Image In Swift:人臉檢測(cè)以及馬賽克
iOS8 Core Image In Swift:視頻實(shí)時(shí)濾鏡
Core Graphics/Quarz 2D
上文中提到的基于OpenGl的Core Image顯然功能十分強(qiáng)大,作為視圖另一基石的Core Graphics同樣強(qiáng)大。對(duì)他的探究,讓鶸筆者更多的了解到圖片的相關(guān)知識(shí)。所以在此處總結(jié),供日后查閱。
如果對(duì)探究不感興趣的同學(xué),請(qǐng)直接跳到文章最后 Masking an Image with Color 部分
Bitmap

在Quarz 2D官方文檔中,對(duì)于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
回到我們的需求,對(duì)于去除圖片中的指定顏色,如果我們能夠讀取到每個(gè)像素上的RGBA信息,分別判斷他們的值,如果符合目標(biāo)范圍,我們將他的Alpha值改為0,然后輸出成新的圖片,那么我們就實(shí)現(xiàn)了類似上文中cubeMap的處理方式。
強(qiáng)大的Quarz 2D為我們提供了實(shí)現(xiàn)這種操作的能力,下面請(qǐng)看代碼示例:
- (UIImage *)removeColorWithMaxR:(float)maxR minR:(float)minR maxG:(float)maxG minG:(float)minG maxB:(float)maxB minB:(float)minB image:(UIImage *)image{
// 分配內(nèi)存
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);
// 創(chuàng)建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]);
}
}
// 將內(nèi)存轉(zhuǎn)成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;
}
還記得我們?cè)贑ore Image中提到的HSV模式的弊端嗎?那么Quarz 2D則是直接利用RGBA的信息進(jìn)行處理,很好的規(guī)避了對(duì)黑白色不友好的問(wèn)題,我們只需要設(shè)置一下RGB的范圍即可(因?yàn)楹诎咨赗GB顏色模式中,很好確定),我們可以大致封裝一下。如下
- (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];
}
看一下我們對(duì)于白色背景的處理效果對(duì)比

看起來(lái)似乎還不錯(cuò),但是對(duì)于紗質(zhì)的衣服,就顯得很不友好??匆幌鹿P者做的幾組圖片的測(cè)試

很顯然,如果不是白色背景,“衣衫襤褸”的效果非常明顯。這個(gè)問(wèn)題,在筆者嘗試的三種方法中,無(wú)一幸免,如果哪位大佬知道好的處理方法,而且能告訴鶸,將不勝感激。(先放倆膝蓋在這兒)
除了上述問(wèn)題外,這種對(duì)比每個(gè)像素的方法,讀取出來(lái)的數(shù)值會(huì)同作圖時(shí)出現(xiàn)誤差。但是這種誤差肉眼基本不可見(jiàn)。

如下圖中,我們作圖時(shí),設(shè)置的RGB值分別為100/240/220 但是通過(guò)CG上述處理時(shí),讀取出來(lái)的值則為92/241/220。對(duì)比圖中的“新的”“當(dāng)前”,基本看不出色差。這點(diǎn)小問(wèn)題各位知道就好,對(duì)實(shí)際去色效果影響并不大

Masking an Image with Color
筆者嘗試過(guò)理解并使用上一種方法后,在重讀文檔時(shí)發(fā)現(xiàn)了這個(gè)方法,簡(jiǎn)直就像是發(fā)現(xiàn)了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];
}
總結(jié)
HSV顏色模式相對(duì)于RGB模式而言,更利于我們摳除圖片中的彩色,而RGB則正好相反。筆者因?yàn)轫?xiàng)目中,只需要去除白色背景,所以最終采用了最后一種方式。
相關(guān)文章
iOS遠(yuǎn)程推送Push開(kāi)發(fā)教程
這篇文章主要為大家詳細(xì)介紹了iOS遠(yuǎn)程推送Push開(kāi)發(fā)教程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09
使用Reachability類判斷iOS設(shè)備的當(dāng)前網(wǎng)絡(luò)連接類型
這篇文章主要介紹了使用Reachability類判斷iOS設(shè)備的當(dāng)前網(wǎng)絡(luò)連接類型,這里開(kāi)發(fā)語(yǔ)言為傳統(tǒng)的Objectice-C,需要的朋友可以參考下2016-02-02
iOS中的應(yīng)用啟動(dòng)原理以及嵌套模型開(kāi)發(fā)示例詳解
這篇文章主要介紹了iOS中的應(yīng)用啟動(dòng)原理以及嵌套模型開(kāi)發(fā)示例詳解,代碼基于傳統(tǒng)的Objective-C,需要的朋友可以參考下2015-12-12
iOS App設(shè)計(jì)模式開(kāi)發(fā)中對(duì)建造者模式的運(yùn)用實(shí)例
這篇文章主要介紹了iOS App設(shè)計(jì)模式開(kāi)發(fā)中對(duì)建造者模式的運(yùn)用實(shí)例,示例代碼為傳統(tǒng)的Objective-C,需要的朋友可以參考下2016-04-04
IOS開(kāi)發(fā)環(huán)境windows化攻略
本人主要介紹了IOS開(kāi)發(fā)環(huán)境windows化攻略,需要的朋友可以參考下2013-06-06
iOS App開(kāi)發(fā)中使用設(shè)計(jì)模式中的單例模式的實(shí)例解析
單例模式是最簡(jiǎn)單和基本的一種設(shè)計(jì)模式,下面我們就簡(jiǎn)單解讀一下iOS中單例設(shè)計(jì)模式的用法,示例代碼還是為傳統(tǒng)的Objective-C,主要為了體現(xiàn)單例模式的思想,需要的朋友可以參考下2016-05-05
iOS開(kāi)發(fā)網(wǎng)絡(luò)篇—實(shí)現(xiàn)大文件的多線程斷點(diǎn)下載
iOS開(kāi)發(fā)中經(jīng)常會(huì)用到文件的下載功能,這篇文章主要介紹了iOS開(kāi)發(fā)網(wǎng)絡(luò)篇—實(shí)現(xiàn)大文件的多線程斷點(diǎn)下載,今天咱們來(lái)分享一下思路。2016-11-11
設(shè)計(jì)模式開(kāi)發(fā)中的備忘錄模式在iOS應(yīng)用開(kāi)發(fā)中的運(yùn)用實(shí)例
這篇文章主要介紹了設(shè)計(jì)模式開(kāi)發(fā)中的備忘錄模式在iOS應(yīng)用開(kāi)發(fā)中的實(shí)例,代碼為傳統(tǒng)的Objective-C,需要的朋友可以參考下2016-03-03

