簡體   English   中英

如何在Swift中標准化UIImage的像素值?

[英]How to normalize pixel values of an UIImage in Swift?

我們正在嘗試規范化UIImage以便可以將其正確傳遞到CoreML模型中。

我們從每個像素中檢索RGB值的方法是,首先為每個像素初始化一個名為rawData[CGFloat]數組,以獲取每個像素的值,以便為紅色,綠色,藍色和alpha值定位。 bitmapInfo ,我們從原始UIimage本身獲取原始像素值並進行操作。 這用於填充contextCGContext變量中的bitmapInfo參數。 稍后,我們將使用context變量來draw CGImage ,然后將其標准化后的CGImage轉換回UIImage

使用通過xy坐標進行嵌套的for循環迭代,可以找到所有像素中所有顏色(通過CGFloat的原始數據數組找到)中的最小和最大像素顏色值。 將綁定變量設置為終止for循環,否則它將出現超出范圍的錯誤。

range指示可能的RGB值的范圍(即最大顏色值和最小顏色值之間的差)。

使用公式對每個像素值進行歸一化:

A = Image
curPixel = current pixel (R,G, B or Alpha) 
NormalizedPixel = (curPixel-minPixel(A))/range

以及從上方嵌套的for循環類似設計,可通過rawData數組進行解析,並根據此歸一化修改每個像素的顏色。

我們的大多數代碼來自:

  1. UIImage到像素顏色的UIColor數組
  2. 更改UIImage中某些像素的顏色
  3. https://gist.github.com/pimpapare/e8187d82a3976b851fc12fe4f8965789

我們使用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中的圖像處理,並且不確定整個規范化過程是否正確。 任何幫助,將不勝感激!

一些觀察:

  1. 您的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) } } 
  2. 但這引出了一個問題,為什么您要打擾浮點數據。 如果將這個浮點數據返回到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)。

  3. 最后,我應該提到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倍以上。


一些無關的發現:

  1. 您的代碼段也在規范化alpha通道。 我不確定您是否要這樣做。 通常,顏色和Alpha通道是獨立的。 在上面,我假設您確實只想對顏色通道進行標准化。 如果您也想規范化Alpha通道,那么您可能對Alpha通道有一個單獨的min-max值范圍,並分別進行處理。 但是使用與色彩通道相同的值范圍來標准化Alpha通道並沒有多大意義(反之亦然)。

  2. 我不是使用UIImage寬度和高度,而是使用CGImage的值。 如果圖像的比例尺可能不為1,這是重要的區別。

  3. 例如,如果范圍已經為0-255(即無需標准化),則可能要考慮提前退出。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM