English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

iOS에서 이미지 배경 색상 제거하는 방법

실제 프로젝트 시나리오: 이미지의 흰색 배경을 제거하여 투명 배경 이미지를 얻어 펼치기 기능에 사용

두 가지 접근 방식의 세 가지 처리 방법을 소개합니다(왜 그런지 고민했습니다). 구체적인 성능 비교는 하지 않았습니다. 어떤 분이 알려주시면 감사하겠습니다.

Core Image Core Graphics/Quarz 2D Core Image

Core Image는 매우 강력한 프레임워크입니다. 여러 가지 필터를 쉽게 적용하여 이미지를 처리할 수 있으며, 예를 들어 밝기, 색상, 또는 노출을 수정할 수 있습니다. GPU(또는 CPU)를 사용하여 매우 빠르고 심지어 실시간으로 이미지 데이터와 비디오 프레임을 처리할 수 있으며,OpenGL 또는 OpenGL ES가 GPU의 능력을 최대한 활용하는 방법을 알지 않아도 됩니다. 또한 GCD가 어떤 역할을 하는지 알지 않아도 됩니다. Core Image는 모든 세부 사항을 처리합니다.

애플 공식 문서Core Image Programming Guide에서 언급되었습니다.Chroma Key Filter Recipe배경 처리 예제

HSV 색상 모델이 사용되었습니다. 왜냐하면 HSV 모델이 RGB보다 색상 범위를 표현하는 데 더 친숙하기 때문입니다.

대략적인 과정 처리 과정:

색상 값 범위를 제거하고자 하는 맵핑을 생성하여, 목표 색상의 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 *이미지 = [CIImage imageWithCGImage:originalImage.CGImage];
 CIContext *context = [CIContext contextWithOptions:nil];// kCIContextUseSoftwareRenderer : CPURender
 /** Note
 * The UIImage initialized through CIimage is not a standard UIImage like CGImage
 * so if it is not rendered with context, it cannot be displayed normally
 */
 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"];
 // キューブのデータを設定します
 [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;
 // キューブの前乗算されたアルファ値を計算します
 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 최대 색상 각도:170 이미지:[UIImage imageNamed:@"nb"]

효과

좋은 효과를 보이는 것 같습니다.

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]);
 }
 }
 // 메모리를 이미지로 변환
 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;
}

HSV 모드의 단점을 Core Image에서 언급한 것을 기억하십니까? 그런 다음 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];
}

흰색 배경에 대한 처리 결과를 비교해 보세요

보이는 것처럼 괜찮아 보이지만, 천의 옷에 대해서는 친절하지 않습니다. 저의 몇 가지 이미지 테스트를 보세요

명백하게, 흰색 배경이 아니라면 '이빨린 옷' 효과가 매우 두드러집니다. 저가 시도한 세 가지 방법 모두 이 문제를 피하지 못했습니다. 누가 이 문제를 잘 해결할 수 있고 '똥친'에게 알려줄 수 있다면 감사하겠습니다. (먼저 두膝를 여기 두고)

위의 문제 외에도, 각 픽셀을 비교하는 이 방법은 그림을 그릴 때와 같은 오차가 발생합니다. 하지만 이 오차는 눈에 띄지 않습니다.


아래 그림에서, 그림을 그릴 때 설정한 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는 반대입니다. 저는 프로젝트에서 백그라운드를 제거하는 데 필요했기 때문에 마지막 방식을 선택했습니다.

좋아하는 것