[英]How to normalize pixel values of an UIImage in Swift?
我們正在嘗試規范化UIImage
以便可以將其正確傳遞到CoreML模型中。
我們從每個像素中檢索RGB值的方法是,首先為每個像素初始化一個名為rawData
的[CGFloat]
數組,以獲取每個像素的值,以便為紅色,綠色,藍色和alpha值定位。 在bitmapInfo
,我們從原始UIimage本身獲取原始像素值並進行操作。 這用於填充context
的CGContext
變量中的bitmapInfo
參數。 稍后,我們將使用context
變量來draw
CGImage
,然后將其標准化后的CGImage
轉換回UIImage
。
使用通過x
和y
坐標進行嵌套的for循環迭代,可以找到所有像素中所有顏色(通過CGFloat
的原始數據數組找到)中的最小和最大像素顏色值。 將綁定變量設置為終止for循環,否則它將出現超出范圍的錯誤。
range
指示可能的RGB值的范圍(即最大顏色值和最小顏色值之間的差)。
使用公式對每個像素值進行歸一化:
A = Image
curPixel = current pixel (R,G, B or Alpha)
NormalizedPixel = (curPixel-minPixel(A))/range
以及從上方嵌套的for循環類似設計,可通過rawData
數組進行解析,並根據此歸一化修改每個像素的顏色。
我們的大多數代碼來自:
我們使用CGFloat
代替UInt8
因為歸一化的像素值應該是介於0和1之間的實數,而不是0或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!)
}
歸一化之前,我們希望像素值范圍為0-255,歸一化之后,我們希望像素值范圍為0-1。
歸一化公式能夠將像素值歸一化為介於0和1之間的值。但是,當我們嘗試打印(在遍歷像素值時只需添加打印語句)歸一化之前的像素值,以驗證我們獲得的原始像素值正確無誤,我們發現這些值的范圍不正確。 例如,一個像素值的值為3.506e + 305(大於255)。我們認為我們在一開始就弄錯了原始像素值。
我們不熟悉Swift中的圖像處理,並且不確定整個規范化過程是否正確。 任何幫助,將不勝感激!
一些觀察:
您的rawData
是浮點數CGFloat
數組,但是您的上下文中沒有填充浮點數據,而是UInt8
數據。 如果需要浮點緩沖區,請使用CGBitmapInfo.floatComponents
構建浮點上下文,並相應地調整上下文參數。 例如:
func normalize() -> UIImage? { let colorSpace = CGColorSpaceCreateDeviceRGB() guard let cgImage = cgImage else { return nil } let width = cgImage.width let height = cgImage.height var rawData = [Float](repeating: 0, count: width * height * 4) let bytesPerPixel = 16 let bytesPerRow = bytesPerPixel * width let bitsPerComponent = 32 let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.floatComponents.rawValue | CGBitmapInfo.byteOrder32Little.rawValue guard let context = CGContext(data: &rawData, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo) else { return nil } let drawingRect = CGRect(origin: .zero, size: CGSize(width: width, height: height)) context.draw(cgImage, in: drawingRect) var maxValue: Float = 0 var minValue: Float = 1 for pixel in 0 ..< width * height { let baseOffset = pixel * 4 for offset in baseOffset ..< baseOffset + 3 { let value = rawData[offset] if value > maxValue { maxValue = value } if value < minValue { minValue = value } } } let range = maxValue - minValue guard range > 0 else { return nil } for pixel in 0 ..< width * height { let baseOffset = pixel * 4 for offset in baseOffset ..< baseOffset + 3 { rawData[offset] = (rawData[offset] - minValue) / range } } return context.makeImage().map { UIImage(cgImage: $0, scale: scale, orientation: imageOrientation) } }
但這引出了一個問題,為什么您要打擾浮點數據。 如果將這個浮點數據返回到ML模型,那么我可以想象它可能會有用,但是您只是在創建一個新圖像。 因此,您還必須有機會僅檢索UInt8
數據,進行浮點運算,然后更新UInt8
緩沖區並從中創建圖像。 從而:
func normalize() -> UIImage? { let colorSpace = CGColorSpaceCreateDeviceRGB() guard let cgImage = cgImage else { return nil } let width = cgImage.width let height = cgImage.height var rawData = [UInt8](repeating: 0, count: width * height * 4) let bytesPerPixel = 4 let bytesPerRow = bytesPerPixel * width let bitsPerComponent = 8 let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue guard let context = CGContext(data: &rawData, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo) else { return nil } let drawingRect = CGRect(origin: .zero, size: CGSize(width: width, height: height)) context.draw(cgImage, in: drawingRect) var maxValue: UInt8 = 0 var minValue: UInt8 = 255 for pixel in 0 ..< width * height { let baseOffset = pixel * 4 for offset in baseOffset ..< baseOffset + 3 { let value = rawData[offset] if value > maxValue { maxValue = value } if value < minValue { minValue = value } } } let range = Float(maxValue - minValue) guard range > 0 else { return nil } for pixel in 0 ..< width * height { let baseOffset = pixel * 4 for offset in baseOffset ..< baseOffset + 3 { rawData[offset] = UInt8(Float(rawData[offset] - minValue) / range * 255) } } return context.makeImage().map { UIImage(cgImage: $0, scale: scale, orientation: imageOrientation) } }
我僅取決於您是否真的為ML模型需要此浮點緩沖區(在這種情況下,您可能在第一個示例中返回浮點數組,而不是創建新圖像)還是目標只是創建規范化對象? UIImage
。
我對此進行了基准測試,它在iPhone XS Max上比浮點渲染快了一點,但占用了四分之一的內存(例如,使用UInt8拍攝2000×2000px圖像需要UInt8
,而使用Float
需要64mb)。
最后,我應該提到vImage具有高度優化的功能,即vImageContrastStretch_ARGB8888
,其功能與我們之前所做的非常相似。 只需import Accelerate
,然后您可以執行以下操作:
func normalize3() -> UIImage? { let colorSpace = CGColorSpaceCreateDeviceRGB() guard let cgImage = cgImage else { return nil } var format = vImage_CGImageFormat(bitsPerComponent: UInt32(cgImage.bitsPerComponent), bitsPerPixel: UInt32(cgImage.bitsPerPixel), colorSpace: Unmanaged.passRetained(colorSpace), bitmapInfo: cgImage.bitmapInfo, version: 0, decode: nil, renderingIntent: cgImage.renderingIntent) var source = vImage_Buffer() var result = vImageBuffer_InitWithCGImage( &source, &format, nil, cgImage, vImage_Flags(kvImageNoFlags)) guard result == kvImageNoError else { return nil } defer { free(source.data) } var destination = vImage_Buffer() result = vImageBuffer_Init( &destination, vImagePixelCount(cgImage.height), vImagePixelCount(cgImage.width), 32, vImage_Flags(kvImageNoFlags)) guard result == kvImageNoError else { return nil } result = vImageContrastStretch_ARGB8888(&source, &destination, vImage_Flags(kvImageNoFlags)) guard result == kvImageNoError else { return nil } defer { free(destination.data) } return vImageCreateCGImageFromBuffer(&destination, &format, nil, nil, vImage_Flags(kvImageNoFlags), nil).map { UIImage(cgImage: $0.takeRetainedValue(), scale: scale, orientation: imageOrientation) } }
盡管此算法使用的算法略有不同,但值得考慮,因為在我的基准測試中,在我的iPhone XS Max上,它的速度是浮點表示法的5倍以上。
一些無關的發現:
您的代碼段也在規范化alpha通道。 我不確定您是否要這樣做。 通常,顏色和Alpha通道是獨立的。 在上面,我假設您確實只想對顏色通道進行標准化。 如果您也想規范化Alpha通道,那么您可能對Alpha通道有一個單獨的min-max值范圍,並分別進行處理。 但是使用與色彩通道相同的值范圍來標准化Alpha通道並沒有多大意義(反之亦然)。
我不是使用UIImage
寬度和高度,而是使用CGImage
的值。 如果圖像的比例尺可能不為1,這是重要的區別。
例如,如果范圍已經為0-255(即無需標准化),則可能要考慮提前退出。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.