核心图像:CIImage写入JPG时颜色偏移 [macOS]

4
使用CoreImage对照片进行滤镜处理时,保存为JPG文件会导致图片具有微妙但明显的蓝色色调。在这个例子中,使用一张黑白图片,直方图显示了颜色在保存后发生了偏移。
---输入 Input Histogram 输出 [Output Histogram] 直方图显示颜色层偏移
-- 问题在MacOS“预览”应用程序中得到证明
我可以使用“预览”应用程序展示类似的结果。
  1. 测试图片在此处:https://istack.dev59.com/Y3f03.webp
  2. 使用预览打开JPG图像。
  3. 以除默认值(85%?)以外的任何“质量”导出为JPEG。
  4. 打开导出的文件并查看直方图,可以看到与我在应用程序中遇到的相同的颜色偏移。

enter image description here

-- 在自定义的MacOS应用程序中展示的问题

这里的代码尽可能地简化,从照片创建一个CIImage,并立即保存它而不执行任何过滤。在这个例子中,我选择了0.61的压缩比,因为它的文件大小与原始大小相似。如果使用更高的压缩比,扭曲似乎会更广泛,但我找不到任何可以消除它的值。

if let img = CIImage(contentsOf: url) {
   let dest = procFolder.url(named: "InOut.jpg")
   img.jpgWrite(url: dest)
}

extension CIImage {
    func jpgWrite(url: URL) {

        let prop: [NSBitmapImageRep.PropertyKey: Any] = [
            .compressionFactor: 0.61
        ]

        let bitmap = NSBitmapImageRep(ciImage: self)
        let data = bitmap.representation(using: NSBitmapImageRep.FileType.jpeg, properties: prop)

        do {
            try data?.write(to: url, options: .atomic)
        } catch {
            log.error(error)
        }
    }
}

更新1:使用@Frank Schlegel的答案保存JPG文件

JPG现在带有颜色同步配置文件,我可以(不科学地)追踪肖像图像的约10%性能提升(对于横向图像来说则更少),这是一个很好的改进。但是,不幸的是,生成的文件仍然会以与上述直方图演示相同的方式偏斜颜色。

extension CIImage {
   static let writeContext = CIContext(mtlDevice: MTLCreateSystemDefaultDevice()!, options: [
        // using an extended working color space allows you to retain wide gamut information, e.g., if the input is in DisplayP3
        .workingColorSpace: CGColorSpace(name: CGColorSpace.extendedSRGB)!,
        .workingFormat: CIFormat.RGBAh // 16 bit color depth, needed in extended space
    ])

    func jpgWrite(url: URL) {
       // write the output in the same color space as the input; fallback to sRGB if it can't be determined
        let outputColorSpace = colorSpace ?? CGColorSpace(name: CGColorSpace.sRGB)!
        do {
            try CIImage.writeContext.writeJPEGRepresentation(of: self, to: url, colorSpace: outputColorSpace, options: [:])
        } catch {
        }
    }
}

问题:

如何将黑白JPG文件作为CIImage打开,并重新保存JPG文件以避免任何颜色偏移?


它们具有不同的ColorSync配置文件。 - Leo Dabus
@LeoDabus 我已经注意到了,并尝试设置colorSyncProfileData: NSBitmapImageRep.PropertyKey,但不确定如何使其与源文件的ColorSync配置文件匹配。 - gheclipse
2个回答

1
这看起来像是一个颜色同步问题(正如Leo所指出的)——更具体地说,是输入、处理和输出之间的颜色空间不匹配/误解。当你调用NSBitmapImageRep(ciImage:)时,实际上有很多事情在幕后发生。系统实际上需要渲染你提供的CIImage以获取结果的位图数据。它通过使用默认的(设备特定的)设置创建CIContext,使用它来处理你的图像(应用了所有的滤镜和变换),然后给你结果的原始位图数据。在此过程中,会发生多次颜色空间转换,使用这个API时你无法控制它们(似乎也不能得到你预期的结果)。因此,我不喜欢使用这些“方便”的API来渲染CIImage,而且我在SO上看到了很多与它们相关的问题。
我建议你改为使用CIContext将你的CIImage渲染成JPEG文件。这样可以直接控制颜色空间等内容。
let input = CIImage(contentsOf: url)

// ideally you create this context once and re-use it because it's an expensive object
let context = CIContext(mtlDevice: MTLCreateSystemDefaultDevice()!, options: [
    // using an extended working color space allows you to retain wide gamut information, e.g., if the input is in DisplayP3
    .workingColorSpace: CGColorSpace(name: CGColorSpace.extendedSRGB)!,
    .workingFormat: CIFormat.RGBAh // 16 bit color depth, needed in extended space
])

// write the output in the same color space as the input; fallback to sRGB if it can't be determined
let outputColorSpace = input.colorSpace ?? CGColorSpace(name: CGColorSpace.sRGB)!

context.writeJPEGRepresentation(of: input, to: dest, colorSpace: outputColorSpace, options: [kCGImageDestinationLossyCompressionQuality: 0.61])

如果您在使用此API时仍然看到差异,请告诉我。


这非常有趣。这似乎是苹果JPEG实现中的一个问题。当使用Affinity Photo导出相同的图像时,我没有看到这种“直方图出血”的情况。从颜色偏移的方式来看,我认为苹果使用某种基于感知的模型进行压缩,但我不了解JPEG的工作原理,无法推断更多......您只使用灰度图像吗?那么您可以尝试在灰度颜色空间中工作。 - Frank Rupprecht
这听起来像是colo sync在将图像“导入”上下文时错误地转换了它...非常奇怪。您能否尝试为我使用不同的工作色彩空间(.sRGB.linearSRGB)? - Frank Rupprecht
我尝试了几种颜色空间的组合,包括CIContext初始化和writeJPEGRepresentation()使用的指定outputColorSpace。但输出没有任何区别。 - gheclipse
writeContext.writePNGRepresentation(of: self, to: url, format: CIFormat.RGBAh, colorSpace: outputColorSpace, options: [:]) 看起来很准确。有没有一种方法可以利用它? - gheclipse
那么似乎是苹果的JPEG转换出了问题。也许你可以尝试使用核心图形来创建一个JPEG图像,看看是否有相同的问题。 - Frank Rupprecht
显示剩余4条评论

0
我从未找到此问题的根本原因,因此无法找到我所寻求的“真正”解决方案。在与@Frank Schlegel讨论之后,我开始相信这是Apple JPEG转换器的一个产物。当使用看似单色但实际上包含少量彩色信息的测试文件时,问题显然更为明显。
对于我的应用程序,最简单的修复方法是确保源图像中没有颜色,因此我在保存文件之前将饱和度降至0。
    let params = [
        "inputBrightness": brightness,  // -1...1, This filter calculates brightness by adding a bias value: color.rgb + vec3(brightness)
        "inputContrast": contrast,      // 0...2, this filter uses the following formula: (color.rgb - vec3(0.5)) * contrast + vec3(0.5)
        "inputSaturation": saturation   // 0...2
    ]
    image.applyingFilter("CIColorControls", parameters: params)

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