Примените фильтр цветовой матрицы к UIImage, используя vImageMatrixMultiply_ARGB8888, как это делает CIColorMatrix - PullRequest
0 голосов
/ 28 апреля 2020

Я пытаюсь создать быструю функцию для применения фильтра цветовой матрицы к изображению, используя vImageMatrixMultiply_ARGB8888 фреймворка Accellerate. Ранее я мог применять цветовую матрицу к изображению, используя CIFilter CIColorMatrix.

     let ciimage = CIImage(image: image)!
     let colorMatrix = CIFilter(name: "CIColorMatrix")!
     colorMatrix.setDefaults()
     colorMatrix.setValue(ciimage, forKey: "inputImage")
     colorMatrix.setValue(CIVector(x: 1.9692307692307693, y: 0, z: 0, w: 0), forKey: "inputRVector")
     colorMatrix.setValue(CIVector(x: 0, y: 2.226086956521739, z: 0, w: 0), forKey: "inputGVector")
     colorMatrix.setValue(CIVector(x: 0, y: 0, z: 2.585858585858586, w: 0), forKey: "inputBVector")
     colorMatrix.setValue(CIVector(x: 0, y: 0, z: 0, w: 1), forKey: "inputAVector")
     return UIImage(ciImage: colorMatrix.outputImage!)

RotatioMatrix , используемый в vImageMatrixMultiply_ARGB8888 использование те же значения входных векторов CIFilter .

rotationMatrix = [1.9692307692307693, 0, 0, 0
                  0, 2.226086956521739, 0, 0,
                  0, 0, 2.585858585858586, 0,
                  0, 0, 0, 1].map {
                     return Int16(Float($0) * Float(divisor)) // divisor = 256
                  }

Следующий код не дает того же результата, что и в CIFilter выше.

private func rgbAdjustmentWithMatrix(image sourceImage: UIImage, rotationMatrix: [Int16]) -> UIImage? {

    guard
        let cgImage = sourceImage.cgImage,
        let sourceImageFormat = vImage_CGImageFormat(cgImage: cgImage),
        let rgbDestinationImageFormat = vImage_CGImageFormat(
            bitsPerComponent: 8,
            bitsPerPixel: 32,
            colorSpace: CGColorSpaceCreateDeviceRGB(),
            bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.first.rawValue),
            renderingIntent: .defaultIntent) else {
                print("Unable to initialize cgImage or colorSpace.")
                return nil
    }

    guard
        let sourceBuffer = try? vImage_Buffer(cgImage: cgImage),
        var rgbDestinationBuffer = try? vImage_Buffer(width: Int(sourceBuffer.width),
            height: Int(sourceBuffer.height),
            bitsPerPixel: rgbDestinationImageFormat.bitsPerPixel) else {
                print("Unable to initialize source buffer or destination buffer.")
                return nil
    }

    defer {
        sourceBuffer.free()
        rgbDestinationBuffer.free()
    }

    do {
        let toRgbConverter = try vImageConverter.make(sourceFormat: sourceImageFormat, destinationFormat: rgbDestinationImageFormat)
            try toRgbConverter.convert(source: sourceBuffer, destination: &rgbDestinationBuffer)
    } catch {
        print("Unable to initialize converter or unable to convert.")
        return nil
    }

    guard var resultBuffer = try? vImage_Buffer(width: Int(sourceBuffer.width),
            height: Int(sourceBuffer.height),
            bitsPerPixel: rgbDestinationImageFormat.bitsPerPixel) else {
                print("Unable to initialize result buffer.")
                return nil
    }

    defer {
        resultBuffer.free()
    }
    var error: vImage_Error

    let divisor: Int32 = 256

    let preBias = [Int16](repeating: -256, count: 4)
    let postBias = [Int32](repeating: 256 * divisor, count: 4)

    error = vImageMatrixMultiply_ARGB8888(&rgbDestinationBuffer,
                                  &resultBuffer,
                                  rotationMatrix,
                                  divisor,
                                  preBias,
                                  postBias,
                                  0)
    guard error == kvImageNoError else { return nil }

    if let cgImage = try? resultBuffer.createCGImage(format: rgbDestinationImageFormat) {
        return UIImage(cgImage: cgImage)
    } else {
        return nil
    } 

}

Может быть проблема с цветовым пространством? Или матрица ввода vImageMatrixMultiply_ARGB8888 просто отличается от матрицы ввода CIFilter?

1 Ответ

1 голос
/ 28 апреля 2020

Обновление

Итак, похоже, что vImageMatrixMultiply_ARGB8888 ожидает значения в следующем порядке:

let rotationMatrix = [
    1, 0,                  0,                 0,                 // A
    0, 1.9692307692307693, 0,                 0,                 // R
    0, 0,                  2.226086956521739, 0,                 // G
    0, 0,                  0,                 2.585858585858586  // B
    ].map { return Int16($0 * Float(divisor)) } // divisor = 0x1000

Это не соответствует примеру кода Apple, который подразумевает BGRA. Возможно, эта матрица изначально предназначалась для использования с другим форматом pixelBuffer - или какой-то другой проблемой.

Я считаю, что другая проблема, с которой вы сталкиваетесь, заключается в том, что CIFilter использует CIContext по умолчанию. Когда я запускаю это, я получаю linearSRGB workingColorSpace.

let context = CIContext()
print(context.workingColorSpace ?? "")

Вывод:

<CGColorSpace 0x7fe4abd06d30> (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; sRGB Linear)

Таким образом, вы можете либо указать CGColorSpaceCreateDeviceRGB () в качестве CIContext workingColorSpace для вашего CIFilter, либо установить rgbDestinationImageFormat colorSpace для .linearSRGB в вашем использовании vImage. Я полагаю, что между этим и исправленным порядком матриц ваши выходные изображения теперь будут совпадать.

// error handling removed for brevity
let ciImage = CIImage(cgImage: cgImage)
let colorMatrix = CIFilter(name: "CIColorMatrix")!
colorMatrix.setDefaults()
colorMatrix.setValue(ciImage, forKey: "inputImage")
colorMatrix.setValue(CIVector(x: 1.9692307692307693, y: 0, z: 0, w: 0), forKey: "inputRVector")
colorMatrix.setValue(CIVector(x: 0, y: 2.226086956521739, z: 0, w: 0), forKey: "inputGVector")
colorMatrix.setValue(CIVector(x: 0, y: 0, z: 2.585858585858586, w: 0), forKey: "inputBVector")
colorMatrix.setValue(CIVector(x: 0, y: 0, z: 0, w: 1), forKey: "inputAVector")
let context = CIContext(options: [.workingColorSpace : CGColorSpaceCreateDeviceRGB()]) // without this, colorSpace defaults to linearSRGB
return context.createCGImage(colorMatrix.outputImage!, from: rect)! 

В этом сценарии, вероятно, нет необходимости в смещении до и после, если у вас нет другой причины для этого.

Наконец, вы можете извлечь выгоду из большего делителя. В то время как данные пикселей составляют 8 бит на канал, аккумулятор 32-битный, а значения матрицы 16-битные. Большая часть ссылочного кода использует делитель 0x1000 (4096).

func rgbAdjustmentWithMatrix(image sourceImage: NSImage, rotationMatrix: [Int16]) -> CGImage?  {

    var rect = CGRect(x: 0, y: 0, width: sourceImage.size.width, height: sourceImage.size.height)

    guard
        let cgImage = sourceImage.cgImage(forProposedRect: &rect, context: nil, hints: nil),
        let sourceImageFormat = vImage_CGImageFormat(cgImage: cgImage),
        let rgbDestinationImageFormat = vImage_CGImageFormat(
            bitsPerComponent: 8,
            bitsPerPixel: 32,
            colorSpace: CGColorSpaceCreateDeviceRGB(),
            // can match CIFilter default with this:
            // colorSpace: CGColorSpace(name: CGColorSpace.linearSRGB)!,
            bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.first.rawValue),
            renderingIntent: .defaultIntent)
        else {
                print("Unable to initialize cgImage or colorSpace.")
                return nil
    }

    guard
        var sourceBuffer = try? vImage_Buffer(cgImage: cgImage),
        var rgbDestinationBuffer = try? vImage_Buffer(width: Int(sourceBuffer.width),
                                                      height: Int(sourceBuffer.height),
                                                    bitsPerPixel: rgbDestinationImageFormat.bitsPerPixel)
        else {
            fatalError("Error initializing source and destination buffers.")
    }

    defer {
        sourceBuffer.free()
        rgbDestinationBuffer.free()
    }

    do {
        let toRgbConverter = try vImageConverter.make(sourceFormat: sourceImageFormat, destinationFormat: rgbDestinationImageFormat)
        try toRgbConverter.convert(source: sourceBuffer, destination: &rgbDestinationBuffer)
    } catch {
        fatalError(error.localizedDescription)
    }

    let divisor: Int32 = 0x1000 // matrix values are 16 bit and accumulator is 32 bit
                                // 4096 gives us decent overhead for the matrix operation
                                // 12-bit color

    let preBias: [Int16] = [0, 0, 0, 0]  // or simply pass nil
    let postBias: [Int32] = [0, 0, 0, 0] // or simply pass nil

    let error = vImageMatrixMultiply_ARGB8888(&rgbDestinationBuffer,
                                              &rgbDestinationBuffer,
                                              rotationMatrix,
                                              divisor,
                                              preBias,
                                              postBias,
                                              vImage_Flags(kvImageNoFlags))

    if error != kvImageNoError { print("Error: \(error)") }

    let result = try? rgbDestinationBuffer.createCGImage(format: rgbDestinationImageFormat)
    return result

}


Предыдущий ответ

Глядя на ваш код и выходные изображения, я Я полагаю, что у вас поменялись коэффициенты красного и синего. Если это так, вы бы заполнили матрицу vImage следующим образом:

rotationMatrix = [2.585858585858586, 0, 0, 0   // b
                  0, 2.226086956521739, 0, 0,  // g
                  0, 0, 1.9692307692307693, 0, // r
                  0, 0, 0, 1].map {            // a
                     return Int16(Float($0) * Float(divisor)) // divisor = 256
                  }

Это будет соответствовать образцу кода Apple vImage для обесцвечивания выбранной части изображения .

    let desaturationMatrix = [
        0.0722, 0.0722, 0.0722, 0,
        0.7152, 0.7152, 0.7152, 0,
        0.2126, 0.2126, 0.2126, 0,
        0,      0,      0,      1
        ].map {
            return Int16($0 * Float(divisor))
    }

Обратите внимание, что коэффициенты преобразования для Rec709 в Luma имеют вид:

Y = R * 0.2126 + G * 0.7152 + B * 0.0722 
...