你如何使用Core Image滤镜有效地对图像进行色彩平衡?

25

我正在尝试使用Core Image过滤器在我的Mac应用程序中为图像添加一些颜色校正。首先,我正在考虑允许自定义的白平衡,以消除图像上的色彩偏差。看起来CIWhitePointAdjust 就是我需要的,但在尝试后,我不确定是否使用错误或它是否没有达到我想要的效果。

从这张黄色老化的图片开始,右侧有一条薄白条:

原始图片

我这样应用这个滤镜:

NSColor *whiteSample = // Chosen from the speech bubble's background
CIColor *whiteInputColor = [[CIColor alloc] initWithColor:whiteSample];

layer.filters = @[
    [CIFilter filterWithName:@"CIWhitePointAdjust"
         withInputParameters:@{kCIInputColorKey: whiteInputColor}]
];

并获得以下图片:

应用CIFilter后的图像

请注意,它看起来比原始图像更暗和更黄(与我期望的效果相反)。我一直期望的效果更像在Photoshop中执行“自动颜色”操作,如下所示:

Photoshopped color balance

我是在错误地使用 CIWhitePointAdjust,还是这个工具不适用于此任务?如果有其他滤镜或滤镜组合可以更好地完成这项工作,我很想知道。

由于我正在处理已经在CALayer对象中的图像,所以Core Image滤镜似乎是正确的选择,但如果只能通过其他方式实现,我也可以考虑。

更新

信号处理网站上有一个有用的答案给了我我试图实现的东西的名字。广义上讲,它被称为直方图均衡化。我正试图弄清楚是否有一种方法可以使用Core Image滤镜来执行该过程,到目前为止,这看起来不太有希望(除非我自己写一个)。


这可能很有用:https://dev59.com/UnDYa4cB1Zd3GeqPD8Nj#15972314 - matt
你尝试使用 CIColorControls 降低图像的饱和度了吗? - joelg
@joelg 谢谢,但去饱和度并不能解决问题。我想让用户纠正颜色,而不是去除颜色(任何程度)。 - Dov
你能分别查看 R G B 吗?重新规范化每个 R G B。你可能会发现其中一个的范围很小(0到100),因此将(0到100)规范化为(0到255)或某个值,而不是255。 - jdl
@jdl 谢谢,我已经了解到这通常是用于自动颜色平衡的方法 - 诀窍在于知道每个通道直方图的下降点在哪里。您有使用Cocoa(最好是Core Image滤镜)实现此功能的示例吗? - Dov
显示剩余2条评论
3个回答

6

好的,我认为我已经成功了!以下是我从 https://dev59.com/_Ijca4cB1Zd3GeqPtjzl#30447041 和几个连接部分中整理出来的:

@import Accelerator;

// I haven't yet found a way to do it within Core Image

// use this when you need to CIImage* -> CGImageRef
CIImage *ciImage = [CIImage imageWithCGImage:cgImage];

...

// use this when you need to CGImageRef -> CIImage*
CIContext* context = [[CIContext alloc] init];
CGImageRef cgImage = [context createCGImage:ciImage fromRect:ciImage.extent];

...

// the algorithm itself, which uses vImage and has to convert to/from it via CGImage
CGImageRef CreateEqualisedCGImageFromCGImage(CGImageRef original)
{
    vImage_Error err;
    vImage_Buffer _img;
    vImage_CGImageFormat format = {
        .bitsPerComponent = 8,
        .bitsPerPixel = 32,
        .colorSpace = NULL,
        .bitmapInfo = (CGBitmapInfo)kCGImageAlphaFirst,
        .version = 0,
        .decode = NULL,
        .renderingIntent = kCGRenderingIntentDefault,
    };

    CGFloat width = CGImageGetWidth(original);
    CGFloat height = CGImageGetHeight(original);

    vImage_Buffer _dstA, _dstR, _dstG, _dstB;

    err = vImageBuffer_InitWithCGImage(&_img, &format, NULL, original, kvImageNoFlags);
    if (err != kvImageNoError)
        NSLog(@"vImageBuffer_InitWithCGImage error: %ld", err);

    err = vImageBuffer_Init( &_dstA, height, width, 8 * sizeof( uint8_t ), kvImageNoFlags);
    if (err != kvImageNoError)
        NSLog(@"vImageBuffer_Init (alpha) error: %ld", err);

    err = vImageBuffer_Init( &_dstR, height, width, 8 * sizeof( uint8_t ), kvImageNoFlags);
    if (err != kvImageNoError)
        NSLog(@"vImageBuffer_Init (red) error: %ld", err);

    err = vImageBuffer_Init( &_dstG, height, width, 8 * sizeof( uint8_t ), kvImageNoFlags);
    if (err != kvImageNoError)
        NSLog(@"vImageBuffer_Init (green) error: %ld", err);

    err = vImageBuffer_Init( &_dstB, height, width, 8 * sizeof( uint8_t ), kvImageNoFlags);
    if (err != kvImageNoError)
        NSLog(@"vImageBuffer_Init (blue) error: %ld", err);

    err = vImageConvert_ARGB8888toPlanar8(&_img, &_dstA, &_dstR, &_dstG, &_dstB, kvImageNoFlags);
    if (err != kvImageNoError)
        NSLog(@"vImageConvert_ARGB8888toPlanar8 error: %ld", err);

    err = vImageEqualization_Planar8(&_dstR, &_dstR, kvImageNoFlags);
    if (err != kvImageNoError)
        NSLog(@"vImageEqualization_Planar8 (red) error: %ld", err);

    err = vImageEqualization_Planar8(&_dstG, &_dstG, kvImageNoFlags);
    if (err != kvImageNoError)
        NSLog(@"vImageEqualization_Planar8 (green) error: %ld", err);

    err = vImageEqualization_Planar8(&_dstB, &_dstB, kvImageNoFlags);
    if (err != kvImageNoError)
        NSLog(@"vImageEqualization_Planar8 (blue) error: %ld", err);

    err = vImageConvert_Planar8toARGB8888(&_dstA, &_dstR, &_dstG, &_dstB, &_img, kvImageNoFlags);
    if (err != kvImageNoError)
        NSLog(@"vImageConvert_Planar8toARGB8888 error: %ld", err);

    err = vImageContrastStretch_ARGB8888( &_img, &_img, kvImageNoError );
    if (err != kvImageNoError)
        NSLog(@"vImageContrastStretch_ARGB8888 error: %ld", err);

    free(_dstA.data);
    free(_dstR.data);
    free(_dstG.data);
    free(_dstB.data);

    CGImageRef result = vImageCreateCGImageFromBuffer(&_img, &format, NULL, NULL, kvImageNoFlags, &err);
    if (err != kvImageNoError)
        NSLog(@"vImageCreateCGImageFromBuffer error: %ld", err);

    free(_img.data);

    return result;
}

这个解决方案的优点归功于https://stackoverflow.com/users/4735340/james-bush,但是我为寻找一种以图像为导向的解决方案(与该问题中讨论的视频处理不同)而毫无成果而苦苦寻找,我认为这样一个现成的答复是相关的。 在OS X或iOS上查找“AutoLevels”并没有任何效果,我希望这对其他人有用。

请分享Swift版本。 - Chanchal Warde

1

Swift 5

import Accelerate
import Metal

extension UIImage {

func whiteBalance() -> UIImage? {
    
    // Create a vImage_Buffer from the CGImage
    
    guard let sourceRef = cgImage else { return nil }
    
    var srcBuffer = vImage_Buffer()
    
    var format = vImage_CGImageFormat(bitsPerComponent: 8,
                                      bitsPerPixel: 32,
                                      colorSpace: nil,
                                      bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.first.rawValue),
                                      version: 0,
                                      decode: nil,
                                      renderingIntent: .defaultIntent)
    
    // init cgImage from sourceBuffer
    
    var err = vImageBuffer_InitWithCGImage(&srcBuffer, &format, nil, sourceRef, vImage_Flags(kvImageNoFlags))
    
    guard err == kvImageNoError else {
        free(srcBuffer.data)
        return nil
    }
    
    // Create dest buffers
            
    let dstWidth = sourceRef.width
    let dstHeight = sourceRef.height
            
    let pixelBits : UInt32 = UInt32(8*MemoryLayout<__uint8_t>.size)

    var dstA : vImage_Buffer = vImage_Buffer.init()
    var dstR : vImage_Buffer = vImage_Buffer.init()
    var dstG : vImage_Buffer = vImage_Buffer.init()
    var dstB : vImage_Buffer = vImage_Buffer.init()
                    
    // dstA - Alpha
                    
    err = vImageBuffer_Init(&dstA, UInt(dstHeight), UInt(dstWidth), pixelBits, vImage_Flags(kvImageNoFlags))
    
    guard err == kvImageNoError else {
        free(srcBuffer.data)
        print("vImageBuffer_Init (alpha) error: \(String(describing: err))")
        return nil
    }
    
    // dstR - Red
    
    err = vImageBuffer_Init(&dstR, UInt(dstHeight), UInt(dstWidth), pixelBits, vImage_Flags(kvImageNoFlags))
    
    guard err == kvImageNoError else {
        free(srcBuffer.data)
        print("vImageBuffer_Init (red) error: \(String(describing: err))")
        return nil
    }
    
    // dstG - Green
    
    err = vImageBuffer_Init(&dstG, UInt(dstHeight), UInt(dstWidth), pixelBits, vImage_Flags(kvImageNoFlags))

    guard err == kvImageNoError else {
        free(srcBuffer.data)
        print("vImageBuffer_Init (green) error: \(String(describing: err))")
        return nil
    }
    
    // _dstB - Blue
    
    err = vImageBuffer_Init(&dstB, UInt(dstHeight), UInt(dstWidth), pixelBits, vImage_Flags(kvImageNoFlags))

    guard err == kvImageNoError else {
        free(srcBuffer.data)
        print("vImageBuffer_Init (blue) error: \(String(describing: err))")
        return nil
    }

    // convert to planar8
    
    err = vImageConvert_ARGB8888toPlanar8(&srcBuffer, &dstA, &dstR, &dstG, &dstB, vImage_Flags(kvImageNoFlags))
    
    guard err == kvImageNoError else {
        free(srcBuffer.data)
        print("vImageConvert_ARGB8888toPlanar8 error: \(String(describing: err))")
        return nil
    }
    
    // equalize red

    err = vImageEqualization_Planar8(&dstR, &dstR, vImage_Flags(kvImageNoFlags))
    
    guard err == kvImageNoError else {
        free(srcBuffer.data)
        print("vImageEqualization_Planar8 (red) error: \(String(describing: err))")
        return nil
    }

    // equalize green

    err = vImageEqualization_Planar8(&dstG, &dstG, vImage_Flags(kvImageNoFlags))
    
    guard err == kvImageNoError else {
        free(srcBuffer.data)
        print("vImageEqualization_Planar8 (green) error: \(String(describing: err))")
        return nil
    }

    // equalize blue

    err = vImageEqualization_Planar8(&dstB, &dstB, vImage_Flags(kvImageNoFlags))
    
    guard err == kvImageNoError else {
        free(srcBuffer.data)
        print("vImageEqualization_Planar8 (blue) error: \(String(describing: err))")
        return nil
    }

    // planar8 to ARGB8888

    err = vImageConvert_Planar8toARGB8888(&dstA, &dstR, &dstG, &dstB, &srcBuffer, vImage_Flags(kvImageNoFlags))
    
    guard err == kvImageNoError else {
        free(srcBuffer.data)
        print("vImageConvert_Planar8toARGB8888 error: \(String(describing: err))")
        return nil
    }

    // contrast stretch

    err = vImageContrastStretch_ARGB8888( &srcBuffer, &srcBuffer, vImage_Flags(kvImageNoFlags))
    
    guard err == kvImageNoError else {
        free(srcBuffer.data)
        print("vImageContrastStretch_ARGB8888 error: \(String(describing: err))")
        return nil
    }

    // free buffers

    free(dstA.data)
    free(dstR.data)
    free(dstG.data)
    free(dstB.data)
    
    // Create CGImage from vImage_Buffer
    
    guard let result = vImageCreateCGImageFromBuffer(&srcBuffer, &format, nil, nil, vImage_Flags(kvImageNoAllocate), &err)?.takeRetainedValue() else {
        
        print("vImageCreateCGImageFromBuffer error: \(err))")

        return nil
    }
    
    //free(srcBuffer.data)

    // Create UIImage
    
    let destImage = UIImage(cgImage: result, scale: 0.0, orientation: imageOrientation)
    
    return destImage
    
}
}

0

1
这是一个很好的想法(我真的希望它能够奏效),但是我在我正在使用的测试图像上尝试了一下,看起来它只调整了整体水平,但没有返回任何过滤器来改变颜色平衡。具体来说,它返回这些过滤器:CIVibranceCIToneCurveCIHighlightShadowAdjust。这令人失望 :-( - Dov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接