We are attempting to normalize an UIImage
so that it can be passed correctly into a CoreML model.
The way we are retrieving the RGB values from each pixel is by first initializing a [CGFloat]
array called rawData
of values for each pixel such that there is a position for the colors Red, Green, Blue and the alpha value. In bitmapInfo
, we get the raw pixel values from the original UIimage itself and conduct. This is used to fill the bitmapInfo
paramter in context
, a CGContext
variable. We will later used the context
variable to draw
a CGImage
that will later convert the normalized CGImage
back into a UIImage
.
Using a nested for-loop iterating through x
and y
coordinates, the minimum and maximum pixel color values among all colors (found through the CGFloat
's raw data array) across all the pixels are found.
A bound variable is set to terminate the for loop, otherwise, it will has out of range error.
range
indicates the range of possible RGB values (ie. the difference between the maximum color value and the minimum).
Using the equation to normalize each pixel value:
A = Image
curPixel = current pixel (R,G, B or Alpha)
NormalizedPixel = (curPixel-minPixel(A))/range
and a similar designed nested for loop from above to parse through the array of rawData
and modify each pixel's colors according to this normalization.
Most of our codes are from:
- UIImage to UIColor array of pixel colors
- Change color of certain pixels in a UIImage
- https://gist.github.com/pimpapare/e8187d82a3976b851fc12fe4f8965789
We use CGFloat
instead of UInt8
because the normalized pixel values should be real numbers that between 0 and 1, not either 0 or 1.
func normalize() -> UIImage?{
let colorSpace = CGColorSpaceCreateDeviceRGB()
guard let cgImage = cgImage else {
return nil
}
let width = Int(size.width)
let height = Int(size.height)
var rawData = [CGFloat](repeating: 0, count: width * height * 4)
let bytesPerPixel = 4
let bytesPerRow = bytesPerPixel * width
let bytesPerComponent = 8
let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue & CGBitmapInfo.alphaInfoMask.rawValue
let context = CGContext(data: &rawData,
width: width,
height: height,
bitsPerComponent: bytesPerComponent,
bytesPerRow: bytesPerRow,
space: colorSpace,
bitmapInfo: bitmapInfo)
let drawingRect = CGRect(origin: .zero, size: CGSize(width: width, height: height))
context?.draw(cgImage, in: drawingRect)
let bound = rawData.count
//find minimum and maximum
var minPixel: CGFloat = 1.0
var maxPixel: CGFloat = 0.0
for x in 0..<width {
for y in 0..<height {
let byteIndex = (bytesPerRow * x) + y * bytesPerPixel
if(byteIndex > bound - 4){
break
}
minPixel = min(CGFloat(rawData[byteIndex]), minPixel)
minPixel = min(CGFloat(rawData[byteIndex + 1]), minPixel)
minPixel = min(CGFloat(rawData[byteIndex + 2]), minPixel)
minPixel = min(CGFloat(rawData[byteIndex + 3]), minPixel)
maxPixel = max(CGFloat(rawData[byteIndex]), maxPixel)
maxPixel = max(CGFloat(rawData[byteIndex + 1]), maxPixel)
maxPixel = max(CGFloat(rawData[byteIndex + 2]), maxPixel)
maxPixel = max(CGFloat(rawData[byteIndex + 3]), maxPixel)
}
}
let range = maxPixel - minPixel
print("minPixel: \(minPixel)")
print("maxPixel : \(maxPixel)")
print("range: \(range)")
for x in 0..<width {
for y in 0..<height {
let byteIndex = (bytesPerRow * x) + y * bytesPerPixel
if(byteIndex > bound - 4){
break
}
rawData[byteIndex] = (CGFloat(rawData[byteIndex]) - minPixel) / range
rawData[byteIndex+1] = (CGFloat(rawData[byteIndex+1]) - minPixel) / range
rawData[byteIndex+2] = (CGFloat(rawData[byteIndex+2]) - minPixel) / range
rawData[byteIndex+3] = (CGFloat(rawData[byteIndex+3]) - minPixel) / range
}
}
let cgImage0 = context!.makeImage()
return UIImage.init(cgImage: cgImage0!)
}
Before normalization, we expect the pixel values range is 0 - 255 and after normalization, the pixel values range is 0 - 1.
The normalization formula is able to normalize pixel values to values between 0 and 1. But when we try to print out (simply add print statements when we loop through pixel values) the pixel values before normalization to verify we get the raw pixel values correct, we found out that the range of those values are off. For example, a pixel value have value as 3.506e+305 (larger than 255.) We think we get the raw pixel value wrong at the beginning.
We are not familiar with image processing in Swift and we are not sure if the whole normalization process is right. any help would be appreciated!