簡體   English   中英

在swift中將圖像轉換為二進制

[英]converting image to binary in swift

我想將圖像轉換為二進制黑白,此時我正在使用常規嵌套循環遍歷像素(存儲在UnsafeMutableBufferPointer中),將每個RGB與平均值進行比較並將其設置為黑色或白色。

這似乎真的很慢,我確信有一種內置的方式使用gpu或經過優化。 如果您可以提供代碼示例或鏈接,那就太棒了。

for var y in 0..<height {
    for var x in 0..<width{
        //Pixel is small class i made for 8 bit access and comparison
        if (Buffer[x+y*width]  < AVRRGB) {
            Buffer[x+y*width] = Pixel(RGB: 0x000000FF)
        } else{
            Buffer[x+y*width] = Pixel(RGB: 0xFFFFFFFF)
        }
    }
}

幾點意見:

  1. 確保您在具有發布版本(或關閉優化)的設備上進行測試。 僅這一點就可以讓它更快。 在iPhone 7+上,它將1920 x 1080像素彩色圖像的轉換率從1.7秒降低到不到0.1秒。

  2. 您可能希望使用DispatchQueue.concurrentPerform同時處理像素。 在我的iPhone 7+上,它的速度提高了一倍。

根據我的經驗,Core Image濾鏡的速度並不快,但是如果您需要更快的速度,可以考慮使用vImage或Metal。 但除非您處理的是非常大的圖像,否則使用優化(可能是並發)的簡單Swift代碼的響應時間可能就足夠了。

一個不相關的觀察:

  1. 另外,我不確定你的黑白轉換是如何工作的,但通常你想要計算彩色像素的相對亮度 (例如0.2126 *紅+ 0.7152 *綠+ 0.0722 *藍)。 當然,在將彩色圖像轉換為灰度時,你會做類似的事情,以獲得更接近人眼可以看到的圖像,如果轉換為黑白,我會親自做類似的事情。

僅供參考,我的Swift 3/4顏色到灰度的例程看起來像:

func blackAndWhite(image: UIImage, completion: @escaping (UIImage?) -> Void) {
    DispatchQueue.global(qos: .userInitiated).async {
        // get information about image

        let imageref = image.cgImage!
        let width = imageref.width
        let height = imageref.height

        // create new bitmap context

        let bitsPerComponent = 8
        let bytesPerPixel = 4
        let bytesPerRow = width * bytesPerPixel
        let colorSpace = CGColorSpaceCreateDeviceRGB()
        let bitmapInfo = Pixel.bitmapInfo
        let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo)!

        // draw image to context

        let rect = CGRect(x: 0, y: 0, width: CGFloat(width), height: CGFloat(height))
        context.draw(imageref, in: rect)

        // manipulate binary data

        guard let buffer = context.data else {
            print("unable to get context data")
            completion(nil)
            return
        }

        let pixels = buffer.bindMemory(to: Pixel.self, capacity: width * height)

        DispatchQueue.concurrentPerform(iterations: height) { row in
            for col in 0 ..< width {
                let offset = Int(row * width + col)

                let red = Float(pixels[offset].red)
                let green = Float(pixels[offset].green)
                let blue = Float(pixels[offset].blue)
                let alpha = pixels[offset].alpha
                let luminance = UInt8(0.2126 * red + 0.7152 * green + 0.0722 * blue)
                pixels[offset] = Pixel(red: luminance, green: luminance, blue: luminance, alpha: alpha)
            }
        }

        // return the image

        let outputImage = context.makeImage()!
        completion(UIImage(cgImage: outputImage, scale: image.scale, orientation: image.imageOrientation))
    }
}

struct Pixel: Equatable {
    private var rgba: UInt32

    var red: UInt8 {
        return UInt8((rgba >> 24) & 255)
    }

    var green: UInt8 {
        return UInt8((rgba >> 16) & 255)
    }

    var blue: UInt8 {
        return UInt8((rgba >> 8) & 255)
    }

    var alpha: UInt8 {
        return UInt8((rgba >> 0) & 255)
    }

    init(red: UInt8, green: UInt8, blue: UInt8, alpha: UInt8) {
        rgba = (UInt32(red) << 24) | (UInt32(green) << 16) | (UInt32(blue) << 8) | (UInt32(alpha) << 0)
    }

    static let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Little.rawValue

    static func ==(lhs: Pixel, rhs: Pixel) -> Bool {
        return lhs.rgba == rhs.rgba
    }
}

顯然,如果要將其轉換為絕對黑白,則相應地調整算法,但這說明了並發圖像緩沖區操作例程。


雖然上述速度相當快(再次,在優化的發布版本中),但使用vImage的速度更快。 以下內容適用於將彩色圖像轉換為灰度

func grayscale(of image: UIImage) -> UIImage? {
    guard var source = sourceBuffer(for: image) else { return nil }

    defer { free(source.data) }

    var destination = destinationBuffer(for: source)

    // Declare the three coefficients that model the eye's sensitivity
    // to color.
    let redCoefficient: Float = 0.2126
    let greenCoefficient: Float = 0.7152
    let blueCoefficient: Float = 0.0722

    // Create a 1D matrix containing the three luma coefficients that
    // specify the color-to-grayscale conversion.
    let divisor: Int32 = 0x1000
    let fDivisor = Float(divisor)

    var coefficients = [
        Int16(redCoefficient * fDivisor),
        Int16(greenCoefficient * fDivisor),
        Int16(blueCoefficient * fDivisor)
    ]

    // Use the matrix of coefficients to compute the scalar luminance by
    // returning the dot product of each RGB pixel and the coefficients
    // matrix.
    let preBias: [Int16] = [0, 0, 0, 0]
    let postBias: Int32 = 0

    let result = vImageMatrixMultiply_ARGB8888ToPlanar8(
        &source,
        &destination,
        &coefficients,
        divisor,
        preBias,
        postBias,
        vImage_Flags(kvImageNoFlags))

    guard result == kvImageNoError else { return nil }

    defer { free(destination.data) }

    // Create a 1-channel, 8-bit grayscale format that's used to
    // generate a displayable image.
    var monoFormat = vImage_CGImageFormat(
        bitsPerComponent: 8,
        bitsPerPixel: 8,
        colorSpace: Unmanaged.passRetained(CGColorSpaceCreateDeviceGray()),
        bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue),
        version: 0,
        decode: nil,
        renderingIntent: .defaultIntent)

    // Create a Core Graphics image from the grayscale destination buffer.
    let cgImage = vImageCreateCGImageFromBuffer(&destination,
                                                &monoFormat,
                                                nil,
                                                nil,
                                               vImage_Flags(kvImageNoFlags),
                                               nil)?.takeRetainedValue()
    return cgImage.map { UIImage(cgImage: $0) }
}


func sourceBuffer(for image: UIImage) -> vImage_Buffer? {
    guard let cgImage = image.cgImage else { return nil }

    let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue).union(.byteOrder32Big)

    var format = vImage_CGImageFormat(bitsPerComponent: 8,
                                      bitsPerPixel: 32,
                                      colorSpace: Unmanaged.passRetained(CGColorSpaceCreateDeviceRGB()),
                                      bitmapInfo: bitmapInfo,
                                      version: 0,
                                      decode: nil,
                                      renderingIntent: .defaultIntent)

    var sourceImageBuffer = vImage_Buffer()
    vImageBuffer_InitWithCGImage(&sourceImageBuffer,
                                 &format,
                                 nil,
                                 cgImage,
                                 vImage_Flags(kvImageNoFlags))

    return sourceImageBuffer

func destinationBuffer(for sourceBuffer: vImage_Buffer) -> vImage_Buffer {
    var destinationBuffer = vImage_Buffer()

    vImageBuffer_Init(&destinationBuffer,
                      sourceBuffer.height,
                      sourceBuffer.width,
                      8,
                      vImage_Flags(kvImageNoFlags))

    return destinationBuffer
}

vImage轉換為1位是vImageConvert_Planar8ToPlanar1。 我建議使用其中一個抖動選項。 您需要先將RGB圖像轉換為灰度圖像。 原則上,這是vImageMatrixMultiply_ARGB8888ToPlanar8(),但實際上它可能應該涉及一些更復雜的顏色空間轉換而不是簡單的矩陣。

如果所有這些聽起來太復雜,只需使用vImageConvert_AnyToAny,它應該做正確的事情。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM