繁体   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