實際項目場景:去除圖片的純白色背景圖,獲得一張透明底圖片用于拼圖功能
介紹兩種途徑的三種處理方式(不知道為啥想起了孔乙己),具體性能鶸并未對比,如果有大佬能告知,不勝感激。
core image core graphics/quarz 2d core image
core image是一個很強大的框架。它可以讓你簡單地應用各種濾鏡來處理圖像,比如修改鮮艷程度,色澤,或者曝光。 它利用gpu(或者cpu)來非常快速、甚至實時地處理圖像數據和視頻的幀。并且隱藏了底層圖形處理的所有細節,通過提供的api就能簡單的使用了,無須關心opengl或者opengl es是如何充分利用gpu的能力的,也不需要你知道gcd在其中發揮了怎樣的作用,core image處理了全部的細節。
在蘋果官方文檔core image programming guide中,提到了chroma key filter recipe對于處理背景的范例
其中使用了hsv顏色模型,因為hsv模型,對于顏色范圍的表示,相比rgb更加友好。
大致過程處理過程:
創建一個映射希望移除顏色值范圍的立方體貼圖cubemap,將目標顏色的alpha
置為0.0f
使用cicolorcube
濾鏡和cubemap對源圖像進行顏色處理獲取到經過cicolorcube
處理的core image
對象ciimage
,轉換為core graphics
中的cgimageref
對象,通過imagewithcgimage:
獲取結果圖片
注意:第三步中,不可以直接使用imagewithciimage:
,因為得到的并不是一個標準的uiimage
,如果直接拿來用,會出現不顯示的情況。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
- (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
在官方文檔中并沒有提及,筆者在下文中提到的大佬的博客中找到了相關轉換處理。感謝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
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
調用一下方法試一下
1
|
[[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這么轉換,請百度他們的轉換,因為鶸筆者也不懂。哎,鶸不聊生)
對于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同樣強大。對他的探究,讓鶸筆者更多的了解到圖片的相關知識。所以在此處總結,供日后查閱。
如果對探究不感興趣的同學,請直接跳到文章最后 masking an image with color 部分
bitmap
在quarz 2d官方文檔中,對于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.
1
|
32-bit and 16-bit pixel formats for cmyk and rgb color spaces in quartz 2d |
回到我們的需求,對于去除圖片中的指定顏色,如果我們能夠讀取到每個像素上的rgba信息,分別判斷他們的值,如果符合目標范圍,我們將他的alpha值改為0,然后輸出成新的圖片,那么我們就實現了類似上文中cubemap的處理方式。
強大的quarz 2d
為我們提供了實現這種操作的能力,下面請看代碼示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
- (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顏色模式中,很好確定),我們可以大致封裝一下。如下
1
2
3
|
- (uiimage *)removewhitecolorwithimage:(uiimage *)image{ return [self removecolorwithmaxr:255 minr:250 maxg:255 ming:240 maxb:255 minb:240 image:image]; } |
1
2
3
|
- (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。對比圖中的“新的”“當前”,基本看不出色差。這點小問題各位知道就好,對實際去色效果影響并不大
masking an image with color
筆者嘗試過理解并使用上一種方法后,在重讀文檔時發現了這個方法,簡直就像是發現了father apple的恩賜。直接上代碼
1
2
3
4
5
6
|
- (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則正好相反。筆者因為項目中,只需要去除白色背景,所以最終采用了最后一種方式。
原文鏈接:https://segmentfault.com/a/1190000012523188