Изображение с порогом работает в Swift и Matlab, но не в ядре Core Image

tl;dr: Когда я порождаю изображение с определенным порогом в Swift, я получаю чистую сегментацию (и двойная проверка в Matlab полностью совпадает), но когда я делаю это в ядре Core Image, он не сегментируется чисто. У меня есть ошибка в моем ядре?

Я пытаюсь порог с ядром Core Image. Мой код кажется достаточно простым:

class ThresholdFilter: CIFilter
{
    var inputImage : CIImage?
    var threshold: Float = 0.554688 // This is set to a good value via Otsu's method

    var thresholdKernel =  CIColorKernel(source:
        "kernel vec4 thresholdKernel(sampler image, float threshold) {" +
        "  vec4 pixel = sample(image, samplerCoord(image));" +
        "  const vec3 rgbToIntensity = vec3(0.114, 0.587, 0.299);" +
        "  float intensity = dot(pixel.rgb, rgbToIntensity);" +
        "  return intensity < threshold ? vec4(0, 0, 0, 1) : vec4(1, 1, 1, 1);" +
        "}")

    override var outputImage: CIImage! {
        guard let inputImage = inputImage,
            let thresholdKernel = thresholdKernel else {
                return nil
        }

        let extent = inputImage.extent
        let arguments : [Any] = [inputImage, threshold]
        return thresholdKernel.apply(extent: extent, arguments: arguments)
    }
}

И изображения, как этот простой лист: получить должный порог:

Но некоторые изображения, как это (с более грязным фоном): Стать мусором

Я не думаю, что это просто вопрос выбора плохого порога, так как я могу использовать точно такой же порог в Matlab и получить чистую сегментацию:

Чтобы перепроверить, я "переделал" ядро ​​в outputImage в чистом Swift, просто печать на консоль:

let img: CGImage = inputImage.cgImage!
let imgProvider: CGDataProvider = img.dataProvider!
let imgBitmapData: CFData = imgProvider.data!
var imgBuffer = vImage_Buffer(data: UnsafeMutableRawPointer(mutating: CFDataGetBytePtr(imgBitmapData)), height: vImagePixelCount(img.height), width: vImagePixelCount(img.width), rowBytes: img.bytesPerRow)

for i in 0...img.height {
    for j in 0...img.width {
        let test = imgBuffer.data.load(fromByteOffset: (i * img.width + j) * 4, as: UInt32.self)

        let r = Float((test >> 16) & 255) / 256
        let g = Float((test >> 8) & 255) / 256
        let b = Float(test & 255) / 256
        let intensity = 0.114 * r + 0.587 * g + 0.299 * b

        print(intensity > threshold ? "1" : "0", terminator: "")
    }
    print("")
}

И это печатает чисто сегментированное изображение в 0 и 1. Я не могу уменьшить масштаб настолько, чтобы сразу увидеть его на экране, но вы можете увидеть дыру в листе четко сегментированной:

Я беспокоился о том, что интенсивность пикселей может быть разной в Matlab и ядре (поскольку RGB для интенсивности можно сделать разными способами), поэтому я использовал этот метод печати на консоли, чтобы проверить точную интенсивность разных пикселей, и все они соответствовали интенсивностям Я вижу для того же изображения в Matlab. Поскольку я использую один и тот же точечный продукт между Swift и ядром, я не понимаю, почему этот порог будет работать в Swift и Matlab, но не в ядре.

Есть идеи, что происходит?

1 ответ

Решил это.

Базовое изображение "услужливо" переводит все в светлое линейное цветовое пространство, потому что это помогает определенным фильтрам, и вы должны явно отключить это, если вы хотите истинные цвета. https://developer.apple.com/library/content/documentation/GraphicsImaging/Conceptual/CoreImaging/ci_performance/ci_performance.html

Вы можете сделать это при инициализации CIImage, который вы передаете фильтру:

filter.inputImage = CIImage(image: image!, options: [kCIImageColorSpace: NSNull()])

Я понятия не имею, почему это делается только в CIFilters, а не везде в приложении или во всех других типах обработки изображений; это кажется очень противоречивой и скрытой "особенностью".

Другие вопросы по тегам