When you want to run a CoreML model whose input is TensorType
CoreML models with ImageType input can use CIImage, CGImage, and PixelBuffer as inputs, but CoreML models with TensorType input require MLMultiArray as input.
How to convert from UIImage to MLMultiArray when you want to use image input for such model.
Method
It can be converted with the following UIImageExtension.
// Usage:
// let mlMultiArray:MLMultiArray = uiImage.mlMultiArray()
//
// or if you need preprocess ...
// let preProcessedMlMultiArray:MLMultiArray = uiImage.mlMultiArray(scale: 127.5, rBias: -1, gBias: -1, bBias: -1)
//
// or if you have gray scale image ...
// let grayScaleMlMultiArray:MLMultiArray = uiImage.mlMultiArrayGrayScale()
extension UIImage {
func mlMultiArray(scale preprocessScale:Double=255, rBias preprocessRBias:Double=0, gBias preprocessGBias:Double=0, bBias preprocessBBias:Double=0) -> MLMultiArray {
let imagePixel = self.getPixelRgb(scale: preprocessScale, rBias: preprocessRBias, gBias: preprocessGBias, bBias: preprocessBBias)
let size = self.size
let imagePointer : UnsafePointer<Double> = UnsafePointer(imagePixel)
let mlArray = try! MLMultiArray(shape: [3, NSNumber(value: Float(size.width)), NSNumber(value: Float(size.height))], dataType: MLMultiArrayDataType.double)
mlArray.dataPointer.initializeMemory(as: Double.self, from: imagePointer, count: imagePixel.count)
return mlArray
}
func mlMultiArrayGrayScale(scale preprocessScale:Double=255,bias preprocessBias:Double=0) -> MLMultiArray {
let imagePixel = self.getPixelGrayScale(scale: preprocessScale, bias: preprocessBias)
let size = self.size
let imagePointer : UnsafePointer<Double> = UnsafePointer(imagePixel)
let mlArray = try! MLMultiArray(shape: [1, NSNumber(value: Float(size.width)), NSNumber(value: Float(size.height))], dataType: MLMultiArrayDataType.double)
mlArray.dataPointer.initializeMemory(as: Double.self, from: imagePointer, count: imagePixel.count)
return mlArray
}
func getPixelRgb(scale preprocessScale:Double=255, rBias preprocessRBias:Double=0, gBias preprocessGBias:Double=0, bBias preprocessBBias:Double=0) -> [Double]
{
guard let cgImage = self.cgImage else {
return []
}
let bytesPerRow = cgImage.bytesPerRow
let width = cgImage.width
let height = cgImage.height
let bytesPerPixel = 4
let pixelData = cgImage.dataProvider!.data! as Data
var r_buf : [Double] = []
var g_buf : [Double] = []
var b_buf : [Double] = []
for j in 0..<height {
for i in 0..<width {
let pixelInfo = bytesPerRow * j + i * bytesPerPixel
let r = Double(pixelData[pixelInfo])
let g = Double(pixelData[pixelInfo+1])
let b = Double(pixelData[pixelInfo+2])
r_buf.append(Double(r/preprocessScale)+preprocessRBias)
g_buf.append(Double(g/preprocessScale)+preprocessGBias)
b_buf.append(Double(b/preprocessScale)+preprocessBBias)
}
}
return ((b_buf + g_buf) + r_buf)
}
func getPixelGrayScale(scale preprocessScale:Double=255, bias preprocessBias:Double=0) -> [Double]
{
guard let cgImage = self.cgImage else {
return []
}
let bytesPerRow = cgImage.bytesPerRow
let width = cgImage.width
let height = cgImage.height
let bytesPerPixel = 2
let pixelData = cgImage.dataProvider!.data! as Data
var buf : [Double] = []
for j in 0..<height {
for i in 0..<width {
let pixelInfo = bytesPerRow * j + i * bytesPerPixel
let v = Double(pixelData[pixelInfo])
buf.append(Double(v/preprocessScale)+preprocessBias)
}
}
return buf
}
}
Check if the conversion is done properly
You can check if the conversion is properly done by inversely converting the MLMultiArray converted using CoreMLHelpers to UIImage.
// scale = 255
let resultUIImage = mlMultiArray.cgImage(min: 0, max: 1, channel: nil)
// scale = 255 bias = -1
let resultUIImage = preProcessedMlMultiArray.cgImage(min: -1, max: 1, channel: nil)
// min and max are range of preprocessed value
🐣
I’m a freelance engineer.
Work consultation
Please feel free to contact us with a brief development description.
rockyshikoku@gmail.com
I am making an app that uses Core ML and ARKit.
We send machine learning / AR related information.