Convert image to binary in fast

I want to convert an image to binary black and white, currently I am looping over the pixels (stored in UnsafeMutableBufferPointer) using normal nested loops, comparing each RGB to the average and setting it to black or white.

This seems to be very slow and I am sure there is a built-in way that gpu uses or is well optimized. If you could provide a sample code or link, that would be great.

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)
        }
    }
}

      

+3


source to share


2 answers


A few observations:

  • Make sure you run the test on a device with release (or optimization disabled). This in itself makes it much faster. On iPhone 7+, it reduced the conversion of a 1920 x 1080 pixel color image to grayscale from 1.7 seconds to less than 0.1 seconds.

  • You can use DispatchQueue.concurrentPerform

    to process pixels at the same time. On my iPhone 7+, this made it about twice as fast.

In my experience, Core Image filters weren't that much faster, but you might consider vImage or Metal if you need it much faster. But unless you're dealing with unusually large images, the response time with optimized (and possibly concurrent) simple Swift code may be sufficient.

Unrelated observation:



  1. Also, I'm not sure how the conversion to black and white works, but often you want to calculate the relative brightness of a color pixel (e.g. 0.2126 * red + 0.7152 * green + 0.0722 * blue). Of course, when converting a color image to grayscale, you would do something like this to get an image that more accurately reflects what the human eye can see, and I personally would do something similar if I converted it to black and white. white too.

FYI, my Swift 3/4 routine from color to grayscale looks like this:

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
    }
}

      

Obviously, if you want to convert it to absolute black and white, then adjust the algorithm accordingly, but this illustrates the procedure for manipulating a parallel image buffer.

+4


source


Convert vImage to 1 bit - vImageConvert_Planar8ToPlanar1. I recommend using one of the options. First you need to convert your RGB image to grayscale. Basically, this is vImageMatrixMultiply_ARGB8888ToPlanar8 (), although in reality this should probably involve a more complex color space conversion rather than a simple matrix one.



If this all sounds too complicated, just use vImageConvert_AnyToAny and it should do the right thing.

0


source







All Articles