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处理了全部的细节。
在苹果官方文档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,如果直接拿来用,会出现不显示的情况。
- (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; // メモリをキューブデータで作成 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]; 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); // 蓝色值 for (int y = 0; y < size; y++{ rgb[1] = ((double)y)/(size-1); // 绿色值 for (int x = 0; x < size; x ++{ rgb[0] = ((double)x)/(size-1); // 红色值 rgbToHSV(rgb,hsv); // 使用色调值来确定要使其透明的部分 // 最小和最大色调角度取决于 // 您想要移除的颜色 float alpha = (hsv[0] > minHueAngle && hsv[0] < maxHueAngle)63; 0.0f: 1.0f; // 立方体的预乘alpha值进行计算 c[0] = rgb[0] * alpha; c[1] = rgb[1] * alpha; c[2] = rgb[2] * alpha; c[3] = alpha; c += 4; // メモリ内の次の色値にポインタを進めます } } } 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) &63; 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同様に強力です。その探求は、鶸の筆者が画像に関する多くの知識を深めることになりました。したがって、今後の参照のためにここで要約します。
興味がない方には、最後に「画像を色でマスクする」部分に飛んでください
Bitmap
Quarz 2Dの公式ドキュメントでは、BitMapは以下のように説明されています:
ビットマップ画像(またはサンプリング画像)はピクセル(またはサンプル)の配列です。各ピクセルは画像の単一のポイントを表します。JPEG、TIFF、PNGグラフィックファイルはビットマップ画像の例です。
32-ビットおよび 16-QuartzのCMYKおよびRGB色空間のためのビットピクセル形式 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]; }
白色背景の処理効果の比較を見てみましょう
見た目は良いかもしれませんが、織物の衣服に対してはあまり良い効果ではありません。筆者が作成したいくつかの画像のテストを見てみましょう
明らかに、白い背景でない場合、「衣衫褴褛」の効果は非常に明瞭です。この問題は、筆者が試した3つの方法すべてで避けられませんでした。もし、良い処理方法を知っている方で、鶸に教えてくれるのであれば、大変感謝します。(まずは膝を2つここに置きます)
上記の問題に加えて、各ピクセルを比較するこの方法で読み取られた値は、描画時と誤差が生じます。しかし、この誤差は肉眼ではほとんど見えません。
以下の図のように、描画時に設定したRGB値は100/240/220 しかし、CGで上記の処理を行った場合、読み取られた値は92/241/220。画像の「新しい」「現在」の比較では、色の違いはほとんど見られません。この小さな問題は、皆さんに知っておくだけで十分です。実際の去色効果に大きな影響はありません。
Colorで画像をマスクする
前述の方法を理解し、使用しようと試みた後、ドキュメントを再読み込み中にこの方法を見つけ、まるで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 はその逆です。このプロジェクトでは、白色の背景を取り除く必要があるので、最終的には最後の方法を選択しました。