【问题标题】:CI Filter to create Black & White image?CI过滤器创建黑白图像?
【发布时间】:2011-03-08 18:25:42
【问题描述】:

我有一个 CIImage,我需要在我的 Cocoa/Objective C 程序中将颜色转换为黑白。 Peter H. 之前向我指出了这个链接http://www.codingadventures.com/2008/06/threshold-filter-in-glsl/ 作为一个潜在的解决方案......但是我在那里编译内核例程时遇到了麻烦(如果有兴趣,请参阅单独的线程)。

所以我想知道其他内置 CIFilter 之一是否会完成我想要做的事情。我不想要灰度图像...我希望结果图像中的每个像素都是黑色或白色——我只需要能够告诉过滤器如何确定哪些像素应该变成黑色,哪些应该变成白色. Photoshop 中的“阈值”过滤器正是这样做的——它让我指定“阈值”,然后它使用该值来决定哪些像素变为白色,哪些变为黑色。这就是我试图通过 Xcode 项目中的代码“复制”的内容。

是否可以为此使用其他内置过滤器之一?谢谢。

【问题讨论】:

  • 关于内核例程的单独线程的链接怎么样?
  • 这是另一个线程的链接:stackoverflow.com/questions/2963218/… 跟随“阈值过滤器”后面的超链接
  • Quartz Composer 是试验 Core Image 滤镜的好地方。我用它来测试链接给出的阈值过滤器。有一个编译错误,由看起来是减号但实际上是破折号字符引起的。一旦我解决了这个问题,它就起作用了。
  • 我也无法从其他线程获取内核例程进行编译。请参阅我寻求帮助的线程:stackoverflow.com/questions/3107274/… 编译器错误基本上是我首先问“是否有其他方法可以做到这一点”的原因!当我第一次粘贴内核代码时,我遇到了与您描述的相同的“破折号”问题。我发现并修复了这个问题,但也有一个我无法弄清楚的不同编译器错误。 :(

标签: objective-c cocoa


【解决方案1】:

您可以使用CIColorMap filter。 给它一个 20(w) x 1(h) jpg,其中左半部分是白色,右半部分是黑色(或相反),并将其用作您的颜色图渐变。这似乎很好地降低了颜色。我最初尝试使用 1 个白色像素和一个黑色像素的 2x1 图像,但看起来它被插值了一点。我提高到 20x1 并且效果很好。

提示:我使用 Core Image Funhouse(不是 Quartz Composer)进行实验。

【讨论】:

  • 嗯。我一直在玩你建议的技术,但它没有产生我期望的结果。我试过 1x2 黑白和黑白(交换顺序)--- 和 1x20 黑白和黑白。是否有任何可用的在线文档描述梯度图应该如何工作?还是例子?
  • 发布您用来设置所有这些的代码。在 Core Image Fun House 中运行良好,它只是一个用于试用现有 CI 过滤器的基本 GUI 包装器。
  • 经过额外的实验,我认为问题可能出在我的源图像渐变滤镜上,而不是我的代码上。使用 FunHouse,如果我将渐变滤镜应用于包含的 Copenhagen.jpg 图像……它完全符合我的要求。但是,如果我将相同的过滤器应用于我自己的图像(我需要在我的程序中修改的那个)......它的行为方式就不一样了。转换使我的图像要么全白要么全黑(取决于我的 1x20 渐变的左侧是白色还是黑色)。待续...
  • 我使用 Photoshop 创建了渐变滤镜。它是一个 8 位 RGB 文件,白色背景,72 ppi 分辨率,1hx20w 大小。颜色配置文件默认为 sRGB IEC61966-2.1。我通过选择左边的 10 个像素然后使用编辑/填充来创建数据;黑色(或白色),混合模式正常,不透明度 100%。然后在右侧像素上使用另一种颜色。然后将文件保存为 JPG,图像质量 = 8(默认)和基线“标准”。这会导致问题吗?如果它适用于哥本哈根,想知道为什么它不适用于我的图像(来自我的 iphone)?感谢您的任何想法!
  • 哎呀......在倒数第二条评论中,我的意思是在第一句话中说“......使用我的源图像或我的渐变过滤器......”。但是当我发布评论 2 时,我编辑评论 1 的能力显然消失了。
【解决方案2】:

花了一些时间弄清楚 CIColorMap 的代码,所以我想发布这个。约书亚有上面的答案。这只是一个实现的例子......

CIImage *beginImage = [CIImage imageWithContentsOfURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"wedding" ofType:@"jpg"]]];
CIImage *inputGradientImage = [CIImage imageWithContentsOfURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"grad" ofType:@"png"]]];
CIContext *context = [CIContext contextWithOptions:nil];
CIFilter *filter = [CIFilter filterWithName:@"CIColorMap" keysAndValues:kCIInputImageKey, beginImage, @"inputGradientImage",inputGradientImage, nil];
CIImage *outputImage = [filter outputImage];
CGImageRef cgimg = [context createCGImage:outputImage fromRect:[outputImage extent]];
UIImage *newImage = [UIImage imageWithCGImage:cgimg];
self.imageView.image = newImage;
CGImageRelease(cgimg);

【讨论】:

    【解决方案3】:

    互联网上的许多解决方案都会创建灰度值。不需要过滤器。如果您需要这样的代码而不是使用代码,例如

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
    CGImageRef greyImage = CGImageCreateCopyWithColorSpace(backingImage, colorSpace);
    CGColorSpaceRelease(colorSpace);
    

    适当的阈值过滤器如下: inputThreshold 可以是从 0.0f 到 0.5f 的浮点值

    https://gist.github.com/xhruso00/a3f8a9c8ae7e33b8b23d

    详细食谱可以在https://developer.apple.com/library/ios/documentation/graphicsimaging/Conceptual/CoreImaging/ci_custom_filters/ci_custom_filters.html#//apple_ref/doc/uid/TP30001185-CH6-CJBEDHHH找到

    多个金属库取决于目标Compile each .metal file into a separate .metallib

    金属文件(快速数学iOS12+)

    #include <metal_stdlib>
    using namespace metal;
    
    //https://en.wikipedia.org/wiki/List_of_monochrome_and_RGB_palettes
    
    //https://en.wikipedia.org/wiki/Relative_luminance
    //https://en.wikipedia.org/wiki/Grayscale
    constant half3 kRec709Luma  = half3(0.2126, 0.7152, 0.0722);
    constant half3 kRec601Luma  = half3(0.299 , 0.587 , 0.114);
    //constant float3 kRec2100Luma = float3(0.2627, 0.6780, 0.0593);
    
    #include <CoreImage/CoreImage.h>
    
    extern "C" { namespace coreimage {
    
        half lumin601(half3 p)
        {
            return dot(p.rgb, kRec601Luma);
        }
    
        half lumin709(half3 p)
        {
            return dot(p.rgb, kRec709Luma);
        }
    
        half4 thresholdFilter(sample_h image, half threshold)
        {
            half4 pix = unpremultiply(image);
            half luma = lumin601(pix.rgb);
            pix.rgb = half3(step(threshold, luma));
            return premultiply(pix);
        }
    }}
    

    Objective-C 文件

    #import "CIFilter+BlackAndWhiteThresholdFilter.h"
    #include <objc/runtime.h>
    #import <CoreImage/CoreImageDefines.h>
    #import <Metal/Metal.h>
    
    @class BlackAndWhiteThresholdFilter;
    
    static NSString *const kCIInputThreshold = @"inputThreshold";
    NSString *const kBlackAndWhiteThresholdFilterName = @"BlackAndWhiteThreshold";
    static NSString *const kBlackAndWhiteThresholdFilterDisplayName = @"Black & White Threshold";
    
    @implementation CIFilter(BlackAndWhiteThresholdFilter)
    
    @dynamic inputImage;
    @dynamic threshold;
    
    + (CIFilter<BlackAndWhiteThreshold>*) blackAndWhiteThresholdFilter
    {
        [BlackAndWhiteThresholdFilter class]; //kick off initialize to register filter
        CIFilter<BlackAndWhiteThreshold>*filter = (CIFilter<BlackAndWhiteThreshold>*)[CIFilter filterWithName:kBlackAndWhiteThresholdFilterName];
        static dispatch_once_t oncePredicate;
        dispatch_once(&oncePredicate, ^{
            /// convenience removing input keyword
            class_addMethod([self class], @selector(threshold), (IMP)floatGetter, "f@:");
            class_addMethod([self class], @selector(setThreshold:), (IMP)floatSetter, "v@:f");
        });
        return filter;
    }
    
    static float floatGetter(id self, SEL _cmd) {
    
        NSString *selector = NSStringFromSelector(_cmd);
        ///capitalize first letter
        NSString *firstLetter = [[selector substringWithRange:NSMakeRange(0, 1)] uppercaseString];
        NSString *key = [@"input" stringByAppendingString:[selector stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstLetter]];
        id value = [self valueForKey:key];
        float number = NAN;
        if (value && [value isKindOfClass:[NSNumber class]]) {
            number = [value floatValue];
        }
        return number;
    }
    
    static void floatSetter(id self, SEL _cmd, float value) {
        NSString *selector = NSStringFromSelector(_cmd);
        NSString *aaa = [selector stringByReplacingCharactersInRange:NSMakeRange(0, 3) withString:@"input"];
        [self setValue:@(value) forKey:[aaa substringWithRange:NSMakeRange(0, [aaa length] - 1)]];
    }
    
    
    @end
    
    @interface BlackAndWhiteThresholdFilter()
    {
        CIColorKernel *_kernel;
    }
    @end
    
    @implementation BlackAndWhiteThresholdFilter
    
    //more https://developer.apple.com/library/ios/documentation/graphicsimaging/Conceptual/CoreImaging/ci_image_units/ci_image_units.html#//apple_ref/doc/uid/TP30001185-CH7-SW8
    + (void)initialize
    {
        //verify registration with [CIFilter filterNamesInCategories:@[kCICategoryVideo]]
        //registering class responsible for CIFilter execution
        [CIFilter registerFilterName:kBlackAndWhiteThresholdFilterName
                         constructor:(id <CIFilterConstructor>)self //self means class BlackAndWhiteThresholdFilter
                     classAttributes:@{
                         kCIAttributeFilterCategories: @[
                                 kCICategoryVideo,
                                 kCICategoryStillImage,
                                 kCICategoryCompositeOperation,
                                 kCICategoryInterlaced,
                                 kCICategoryNonSquarePixels
                         ],
                         kCIAttributeFilterDisplayName: kBlackAndWhiteThresholdFilterDisplayName,
    
                     }];
    }
    
    
    + (CIFilter *)filterWithName:(NSString *)aName
    {
        return [[self alloc] init];
    }
    
    - (instancetype)init {
        self = [super init];
        if (self) {
    
            BOOL supportsMetal;
    
    #if TARGET_OS_IOS
            supportsMetal = MTLCreateSystemDefaultDevice() != nil; //this forces GPU on macbook to switch immediatelly
    #else
            supportsMetal = [MTLCopyAllDevices() count] >= 1;
    #endif
    
            if (@available(macOS 10.13, *)) {
    
                //10.13 there are macbooks without metal + other forced installations
                if (supportsMetal) {
                    _kernel = [self metalKernel];
                } else {
                    _kernel = [self GLSLKernel];
                }
    
            } else {
                _kernel = [self GLSLKernel];
            }
    
            if (_kernel == nil) return nil;
        }
    
        return self;
    }
    
    - (CIColorKernel *)metalKernel
    {
    
        NSURL *URL = [[NSBundle mainBundle] URLForResource:@"default" withExtension:@"metallib"]; //default is
        NSData *data = [NSData dataWithContentsOfURL:URL];
        NSError *error;
        if (error) {
            NSLog(@"%@", error);
        }
        return [CIColorKernel kernelWithFunctionName:@"thresholdFilter" fromMetalLibraryData:data error:&error];
    }
    
    - (CIColorKernel *)GLSLKernel //OpenGL Shading Language
    {
        // WWDC 2017 510 - disadvanage is that this needs to be compiled on first run (performance penalty)
        NSString *kernelString = [self colorKernelText];
        return [CIColorKernel kernelWithString:kernelString]; // to suppress warning define CI_SILENCE_GL_DEPRECATION in pre-processor macros
    }
    
    - (NSString *)colorKernelText
    {
        return
        @""
        "float lumin601(vec3 p)"
        "{"
        "  return dot(p, vec3(0.299 , 0.587 , 0.114));"
        "}"
        ""
        "kernel vec4 thresholdFilter(__sample image, float inputThreshold)"
        "{"
        "  vec4 src = unpremultiply( image) );"
        "  float luma = lumin601( src.rgb );"
        "  src.rgb = vec3( step( inputThreshold, luma));"
        "  return premultiply(src);"
        "}";
    }
    
    //kept for reference purpose 
    - (NSString *)oldNonColorKernelText
    {
        return
        @""
        "float lumin601(vec3 p)"
        "{"
        "  return dot(p, vec3(0.299 , 0.587 , 0.114));"
        "}"
        ""
        "kernel vec4 thresholdFilter(sampler image, float inputThreshold)"
        "{"
        "  vec4 src = unpremultiply( sample(image, samplerCoord(image)) );"
        "  float luma = lumin601( src.rgb );"
        "  src.rgb = vec3( step( inputThreshold, luma));"
        "  return premultiply(src);"
        "}";
    }
    
    - (NSArray *)inputKeys {
        return @[kCIInputImageKey, kCIInputThreshold];
    }
    
    - (NSArray *)outputKeys {
        return @[kCIOutputImageKey];
    }
    
    // ------------  ------------  ------------  ------------  ------------  ------------
    #pragma mark - CIFilter Protocol
    
    + (NSDictionary *)customAttributes
    {
        NSDictionary *inputThreshold = @{
                                             kCIAttributeType: kCIAttributeTypeScalar,
                                             kCIAttributeMin: @0.0f,
                                             kCIAttributeMax: @1.0f,
                                             kCIAttributeIdentity : @0.00,
                                             kCIAttributeDefault: @0.5f,
                                             };
    
        return @{
                 kCIInputThreshold : inputThreshold,
                 // This is needed because the filter is registered under a different name than the class.
                 kCIAttributeFilterName : kBlackAndWhiteThresholdFilterName
                 };
    }
    
    - (CIImage *)outputImage {
    
        CIImage *inputImage = [self inputImage];
    
        if ([self inputImage] == nil) {
            return nil;
        }
    
        CIImage *outputImage;
    
        outputImage = [_kernel applyWithExtent:[inputImage extent]
                                   roiCallback:^CGRect(int index, CGRect destRect) { return destRect; }
                                     arguments:@[inputImage, [self inputThreshold]]];
    
        return outputImage;
    }
    
    @end
    

    更新了黑白(左)和灰度(右)之间的视觉差异

    【讨论】:

    • "不需要过滤器。"您的解决方案使用过滤器。我错过了什么?
    • 你是对的。我表达自己有点错误。 “CGImageCreateCopyWithColorSpace”的前三行是创建灰度图像,这是互联网上许多解决方案所做的。下面的代码是 CIFilter 根据阈值创建适当的黑白图像。
    猜你喜欢
    • 1970-01-01
    • 2014-04-06
    • 1970-01-01
    • 2013-01-09
    • 2017-03-19
    • 1970-01-01
    • 2011-10-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多