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)
}
}
}
source to share
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:
- 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.
source to share
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.
source to share