在swift中将图像转换为二进制

时间:2017-07-17 22:05:40

标签: ios swift cocoa-touch uiimage core-graphics

我想将图像转换为二进制黑白,此时我正在使用普通的嵌套循环遍历像素(存储在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)
        }
    }
}

2 个答案:

答案 0 :(得分:6)

有几点意见:

  1. 确保您在具有发布版本(或已关闭优化)的设备上进行测试。仅这一点就可以让它更快。在iPhone 7+上,它将1920 x 1080像素彩色图像的转换率从1.7秒减少到不到0.1秒。

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

  3. 根据我的经验,Core Image滤镜的速度要快得多,但如果你需要它更快,你可以考虑使用vImage或Metal。但除非您处理非常大的图像,否则使用优化的(可能是并发的)简单Swift代码的响应时间可能就足够了。

    无关的观察:

    1. 此外,我不确定您转换为黑白的方式如何,但通常您想要计算彩色像素的relative luminance(例如0.2126 *红色+ 0.7152 *绿色+ 0.0722 *蓝色)。当然,在将彩色图像转换为灰度时,您可以做类似的事情,以获得更接近人眼可以看到的图像,如果转换为黑白,我个人会做类似的事情。
    2. 仅供参考,我的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的速度更快。以下内容改编自Converting Color Images to Grayscale

      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
      }
      

答案 1 :(得分:0)

vImage转换为1位是vImageConvert_Planar8ToPlanar1。我建议使用其中一个抖动选项。您需要先将RGB图像转换为灰度图像。原则上,这是vImageMatrixMultiply_ARGB8888ToPlanar8(),但实际上它可能应该涉及一些更复杂的颜色空间转换而不是简单的矩阵。

如果所有这些听起来太复杂,只需使用vImageConvert_AnyToAny就应该做正确的事情。