gpt4 book ai didi

swift - 如何在 Swift 中规范化 UIImage 的像素值?

转载 作者:行者123 更新时间:2023-11-28 07:29:18 38 4
gpt4 key购买 nike

我们正在尝试规范化 UIImage这样它就可以正确地传递到 CoreML 模型中。

我们从每个像素中检索 RGB 值的方法是首先初始化一个 [CGFloat]名为 rawData 的数组每个像素的值,使得红色、绿色、蓝色和 alpha 值都有一个位置。在 bitmapInfo ,我们从原始 UIimage 本身获取原始像素值并进行处理。这用于填充 bitmapInfo context 中的参数, 一个 CGContext多变的。我们稍后会用到 context变量为 draw一个CGImage稍后将转换标准化的 CGImage回到UIImage .

使用嵌套的 for 循环遍历 xy坐标,找到所有像素的所有颜色(通过 CGFloat 的原始数据数组找到)中的最小和最大像素颜色值。设置一个绑定(bind)变量来终止 for 循环,否则会出现超出范围的错误。

range指示可能的 RGB 值的范围(即最大颜色值和最小颜色值之间的差异)。

使用等式对每个像素值进行归一化:

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

和一个类似设计的嵌套 for 循环从上面解析 rawData 的数组并根据此归一化修改每个像素的颜色。

我们的大部分代码来自:

  1. UIImage to UIColor array of pixel colors
  2. Change color of certain pixels in a 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 是 float ,CGFloat,数组,但是你的上下文没有用 float 据填充它,而是用 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. 但这引出了一个问题,即您为什么要为 float 据烦恼。如果您将此 float 据返回到您的 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 模型使用这个浮点缓冲区(在这种情况下,您可能会在第一个示例中返回 float 组,而不是创建新图像)或者目标是否只是创建规范化的 UIImage

    我对此进行了基准测试,它在 iPhone XS Max 上比浮点再现快一点,但占用内存的四分之一(例如,一张 2000×2000 像素的图像使用 UInt8 占用 16mb,但使用 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 channel 。我不确定你是否愿意这样做。通常颜色和 alpha channel 是独立的。上面我假设你真的只想标准化颜色 channel 。如果你也想标准化 alpha channel ,那么你可能有一个单独的 alpha channel 值的最小-最大范围并单独处理。但是,使用与颜色 channel 相同的值范围对 alpha channel 进行归一化(反之亦然)没有多大意义。

  2. 我没有使用 UIImage 宽度和高度,而是使用 CGImage 中的值。这是重要的区别,以防您的图片的比例不是 1。

  3. 例如,如果范围已经是 0-255(即不需要标准化),您可能需要考虑提前退出。

关于swift - 如何在 Swift 中规范化 UIImage 的像素值?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55433107/

38 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com