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

2024-01-20

我们正在尝试使UIImage以便它可以正确传递到 CoreML 模型中。

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

使用嵌套的 for 循环迭代x and y坐标,所有颜色中的最小和最大像素颜色值(通过CGFloat的原始数据数组)跨所有像素被发现。 设置一个绑定变量来终止for循环,否则会出现超出范围的错误。

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

使用方程标准化每个像素值:

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

以及上面类似设计的嵌套 for 循环来解析数组rawData并根据此标准化修改每个像素的颜色。

我们的大部分代码来自:

  1. UIImage 到 UIColor 像素颜色数组 https://stackoverflow.com/questions/38163523/uiimage-to-uicolor-array-of-pixel-colors
  2. 更改 UIImage 中某些像素的颜色 https://stackoverflow.com/questions/31661023/change-color-of-certain-pixels-in-a-uiimage
  3. https://gist.github.com/pimpapare/e8187d82a3976b851fc12fe4f8965789 https://gist.github.com/pimpapare/e8187d82a3976b851fc12fe4f8965789

We use 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. Your 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 上它比浮点渲染要快一点,但占用了四分之一的内存(例如,一张 2000×2000px 的图像需要 16mb,UInt8,但 64mbFloat).

  3. 最后,我要提一下vImage https://developer.apple.com/documentation/accelerate/vimage具有高度优化的功能,vImageContrastStretch_ARGB8888 https://developer.apple.com/documentation/accelerate/1546811-vimagecontraststretch_argb8888?language=occ这与我们上面所做的非常相似。只是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 通道值的最小-最大范围,并单独处理它。但使用与颜色通道相同的值范围来标准化 Alpha 通道并没有多大意义(反之亦然)。

  2. 而不是使用UIImage宽度和高度,我使用的值CGImage。如果您的图像的比例可能不是 1,这是一个重要的区别。

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在 Swift 中规范化 UIImage 的像素值? 的相关文章

随机推荐

  • 我如何近似“你的意思是?”不使用谷歌?

    我知道这个问题重复 谷歌 你是说吗 是怎么回事 算法工作 https stackoverflow com questions 307291 how does the google did you mean algorithm work 如何
  • 错误“virtualenv:找不到命令”,但安装位置位于 PYTHONPATH 中

    在过去的两天里 这让我发疯 我在 Macbook 上安装了 virtualenvpip install virtualenv 但是当我尝试使用创建一个新的 virtualenv 时virtualenv venv 我收到错误消息 virtua
  • 如何使用 Java/Swing 旋转图像,然后将其原点设置为 0,0?

    我能够旋转已添加到 JLabel 的图像 唯一的问题是 如果高度和宽度不相等 旋转后的图像将不再出现在 JLabel 的原点 0 0 处 这就是我正在做的事情 我还尝试使用 AffineTransform 并旋转图像本身 但结果相同 Gra
  • 在 WPF DataGrid 中使用 Enter 键作为 Tab

    我有一个DataGrid in WPF I want to move to the NextCell when i hit Enter and when the LastColumn is reached it should have th
  • Android Studio - 恐慌:无法打开 AVD

    经过几个小时修复 Gradle 问题后 我能够在 Android Studio 中构建我的测试应用程序 但是当我尝试在 AVD 中运行它时 它就是打不开 这是日志 Waiting for device C Users Rahaman App
  • 如何在不使用 len 的情况下知道列表是否仅包含 1 个元素

    我想知道列表是否只包含一个元素 而不使用len 在这两种解决方案之间 最Pythonic的方法是什么 或者也许这些都不是Pythonic的 如果是的话那又是什么 解决方案a 删除位置1处的项目 除了IndexError所以我知道只有 1 件
  • Python 求解一个变量的方程

    我正在尝试使用 SymPy 求解 python 中的方程 我有一个生成的方程 类似于function y 8 0 y 3 0 我将其与 SymPy 一起使用来创建一个如下所示的新方程 eq sympy Eq function 2 哪个输出y
  • 如何反序列化动态Json对象?

    我目前从我的 api 收到以下 JSON 响应 Lastname ERRLASTNAMEEMPTY Firstname ERRFIRSTNAMEEMPTY 请注意 上述响应是动态的 即有时我可以有名字 有时可以有姓氏 有时两者都有 此响应基
  • 如何从 Kafka 主题获取最近的消息

    我们是否有任何选项 例如从 Kafka 主题获取最近 10 20 等消息 我可以看到 from beginning 选项从主题中获取所有消息 但如果我只想获取第一个 最后一个 中间或最新的几条消息 10 我们有一些选择吗 前 N 条消息 您
  • 在哪里可以找到张量流预训练模型(列表或下载链接)

    我开始使用英特尔 movidius 神经计算棒 就我而言 要开始工作 有必要下载预训练的模型 在他们提到的教程中http download tensorflow org models http download tensorflow org
  • 整数对的唯一哈希公式

    我想我可以使用 Cantor 创建一个独特的哈希 n x y x y x y 2 但我可以反转这个哈希值吗 如果没有 有人可以为可逆哈希提供类似的公式对吗 Thanks 如果 x y 和 n 都是相同的数据类型 n x y x y x y
  • Python 与 matplotlib - 重用绘图函数

    我对此有一个后续问题question https stackoverflow com questions 1401102 python with matplotlib drawing multiple figures in parallel
  • 本地化组名称

    我想通过向 Everyone 组分配权限来配置互斥访问规则 当我创建规则时 它看起来类似于以下内容 new MutexAccessRule Everyone MutexRights Modify MutexRights Synchroniz
  • 使用 CUPS 进行 Zebra 打印,不打印 ZPL 或 EPL

    我有一台 Zebra GK420d 通过 CUPS 连接到 OS X 但是 当我向其发送以 ZPL 或 EPL 编写的文件时 它们仅以纯文本形式打印 我需要更改打印机模式吗 与其他人所说的相反 您不需要专门添加原始队列 相反 您可以使用以下
  • 理解 SwiftUI 中的 @Binding

    我观看了一些关于数据绑定的WWDC视频和Apple文档 根据我目前的理解 State作为属性委托将提供视图和带注释的属性之间的绑定连接 例如 State var myText String var body some View VStack
  • PyGame 使 Linux 陷入困境?

    当我运行 pygame 代码时 它会使系统陷入困境 PyGame 变得无响应 并且它使 Ubuntu 的速度大大减慢 以至于我不得不强制关闭两次 我在这里发布了一个非常相似的问题 为什么我的基本 PyGame 模块这么慢 https sta
  • 如何内嵌文本旋转 90 度

    如何在不使用样式表的情况下将文本旋转 90 度 我已将以下说明放置在页面的标题区域中 然后我在相关段落周围放置了以下内容 div p My paragraph p div 但它不起作用 因此我的问题 这是一个小的视觉示例 rotate te
  • 谷歌地图不显示

    我需要在我的 php 页面地图上实现 我有容器 div div 它位于其他 div 内 我将这段代码放入其中标签 但根本不显示 有谁能够帮助我
  • 如何将表转换为 Spark Dataframe

    在 Spark SQL 中 可以使用以下命令将数据帧作为表进行查询 sqlContext registerDataFrameAsTable df mytable 假设我有的是mytable 我如何获取或访问它作为 DataFrame 最干净
  • 如何在 Swift 中规范化 UIImage 的像素值?

    我们正在尝试使UIImage以便它可以正确传递到 CoreML 模型中 我们从每个像素检索 RGB 值的方法是首先初始化一个 CGFloat 数组称为rawData每个像素的值 这样就有一个红色 绿色 蓝色和 alpha 值的位置 在bit