iOS-摳圖:去除圖片中指定范圍內(nèi)顏色的三種方式

實(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é)窒升。

chroma_key

在蘋果官方文檔Core Image Programming Guide中缀遍,提到了Chroma Key Filter Recipe對(duì)于處理背景的范例

其中使用了HSV顏色模型,因?yàn)镠SV模型饱须,對(duì)于顏色范圍的表示域醇,相比RGB更加友好。

大致過(guò)程處理過(guò)程:

  1. 創(chuàng)建一個(gè)映射希望移除顏色值范圍的立方體貼圖cubeMap蓉媳,將目標(biāo)顏色的Alpha置為0.0f
  2. 使用CIColorCube濾鏡和cubeMap對(duì)源圖像進(jìn)行顏色處理
  3. 獲取到經(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)行渲染處理漆腌,是沒辦法正常顯示的
     */
    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在官方文檔中并沒有提及,筆者在下文中提到的大佬的博客中找到了相關(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é)可以在代碼中判斷Alphafloat 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

上文中提到的基于OpenGlCore Image顯然功能十分強(qiáng)大,作為視圖另一基石的Core Graphics同樣強(qiáng)大窿克。對(duì)他的探究骏庸,讓鶸筆者更多的了解到圖片的相關(guān)知識(shí)毛甲。所以在此處總結(jié),供日后查閱具被。

如果對(duì)探究不感興趣的同學(xué)玻募,請(qǐng)直接跳到文章最后 Masking an Image with Color 部分

Bitmap

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

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è)?strong>Core 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ì)的衣服苞笨,就顯得很不友好∽涌簦看一下筆者做的幾組圖片的測(cè)試

很顯然瀑凝,如果不是白色背景,“衣衫襤褸”的效果非常明顯臭杰。這個(gè)問(wèn)題粤咪,在筆者嘗試的三種方法中,無(wú)一幸免渴杆,如果哪位大佬知道好的處理方法寥枝,而且能告訴鶸,將不勝感激磁奖。(先放倆膝蓋在這兒)

除了上述問(wèn)題外囊拜,這種對(duì)比每個(gè)像素的方法,讀取出來(lái)的數(shù)值會(huì)同作圖時(shí)出現(xià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];
    
}

官方文檔點(diǎn)這兒

總結(jié)

HSV顏色模式相對(duì)于RGB模式而言,更利于我們摳除圖片中的彩色锅劝,而RGB則正好相反攒驰。筆者因?yàn)轫?xiàng)目中,只需要去除白色背景故爵,所以最終采用了最后一種方式玻粪。

掘金
博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市诬垂,隨后出現(xiàn)的幾起案子劲室,更是在濱河造成了極大的恐慌,老刑警劉巖结窘,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件很洋,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡隧枫,警方通過(guò)查閱死者的電腦和手機(jī)喉磁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)官脓,“玉大人协怒,你說(shuō)我怎么就攤上這事”氨浚” “怎么了孕暇?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)赤兴。 經(jīng)常有香客問(wèn)我妖滔,道長(zhǎng),這世上最難降的妖魔是什么桶良? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任座舍,我火速辦了婚禮,結(jié)果婚禮上艺普,老公的妹妹穿的比我還像新娘簸州。我一直安慰自己,他們只是感情好歧譬,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布岸浑。 她就那樣靜靜地躺著,像睡著了一般瑰步。 火紅的嫁衣襯著肌膚如雪矢洲。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天缩焦,我揣著相機(jī)與錄音读虏,去河邊找鬼。 笑死袁滥,一個(gè)胖子當(dāng)著我的面吹牛盖桥,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播题翻,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼揩徊,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了嵌赠?” 一聲冷哼從身側(cè)響起塑荒,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎姜挺,沒想到半個(gè)月后齿税,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡炊豪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年凌箕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片词渤。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡陌知,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出掖肋,到底是詐尸還是另有隱情仆葡,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布志笼,位于F島的核電站沿盅,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏纫溃。R本人自食惡果不足惜腰涧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望紊浩。 院中可真熱鬧窖铡,春花似錦疗锐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至箍铲,卻和暖如春雇卷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背颠猴。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工关划, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人翘瓮。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓贮折,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親资盅。 傳聞我的和親對(duì)象是個(gè)殘疾皇子脱货,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容