获取 CGImage Swift 的所有像素的 RGBA 值

Posted

技术标签:

【中文标题】获取 CGImage Swift 的所有像素的 RGBA 值【英文标题】:Getting RGBA values for all pixels of CGImage Swift 【发布时间】:2021-11-17 18:32:13 【问题描述】:

我正在尝试创建一个实时视频处理应用程序,我需要在其中获取每个帧的所有像素的 RGBA 值,并使用外部库处理它们,并显示它们。我正在尝试获取每个像素的 RGBA 值,但是我这样做的方式太慢了,我想知道是否有一种方法可以更快地使用VImage。这是我当前的代码,也是我获取所有像素的方式,因为我得到了当前帧:

        guard let cgImage = context.makeImage() else 
        return nil
    
    guard let data = cgImage.dataProvider?.data,
    let bytes = CFDataGetBytePtr(data) else 
    fatalError("Couldn't access image data")
    
    assert(cgImage.colorSpace?.model == .rgb)
    let bytesPerPixel = cgImage.bitsPerPixel / cgImage.bitsPerComponent
    gp.async 
        for y in 0 ..< cgImage.height 
            for x in 0 ..< cgImage.width 
                let offset = (y * cgImage.bytesPerRow) + (x * bytesPerPixel)
                let components = (r: bytes[offset], g: bytes[offset + 1], b: bytes[offset + 2])
                print("[x:\(x), y:\(y)] \(components)")
            
            print("---")
        

    

这是使用VImage的版本,但是我有一些内存泄漏,我无法访问像素

        guard
        let format = vImage_CGImageFormat(cgImage: cgImage),
        var buffer = try? vImage_Buffer(cgImage: cgImage,
                                        format: format) else 
            exit(-1)
        

    let rowStride = buffer.rowBytes / MemoryLayout<Pixel_8>.stride / format.componentCount
    do 
        
        let componentCount = format.componentCount
        var argbSourcePlanarBuffers: [vImage_Buffer] = (0 ..< componentCount).map  _ in
            guard let buffer1 = try? vImage_Buffer(width: Int(buffer.width),
                                                   height: Int(buffer.height),
                                                  bitsPerPixel: format.bitsPerComponent) else 
                                                    fatalError("Error creating source buffers.")
            
            return buffer1
        
        vImageConvert_ARGB8888toPlanar8(&buffer,
                                        &argbSourcePlanarBuffers[0],
                                        &argbSourcePlanarBuffers[1],
                                        &argbSourcePlanarBuffers[2],
                                        &argbSourcePlanarBuffers[3],
                                        vImage_Flags(kvImageNoFlags))

        let n = rowStride * Int(argbSourcePlanarBuffers[1].height) * format.componentCount
        let start = buffer.data.assumingMemoryBound(to: Pixel_8.self)
        var ptr = UnsafeBufferPointer(start: start, count: n)

        print(Array(argbSourcePlanarBuffers)[1]) // prints the first 15 interleaved values
        buffer.free()
    

【问题讨论】:

您对每像素字节数的计算实际上是在计算每像素的分量。每个像素的字节数是 bitsPerPixel/8。 非常感谢伊恩的回复。我仍在努力解决这个问题。您能否举例说明使用 VImages 获取每个像素的 R、G、B、A 值的正确方法。您在代码中提到的问题到底在哪里? @IanOllmann 【参考方案1】:

您可以访问 vImage 缓冲区中的底层像素来执行此操作。

例如,给定一个名为 cgImage 的图像,使用以下代码填充 vImage 缓冲区:

guard
    let format = vImage_CGImageFormat(cgImage: cgImage),
    let buffer = try? vImage_Buffer(cgImage: cgImage,
                                    format: format) else 
        exit(-1)
    

let rowStride = buffer.rowBytes / MemoryLayout<Pixel_8>.stride / format.componentCount

请注意,vImage 缓冲区的 data 可能比图像宽(参见:https://developer.apple.com/documentation/accelerate/finding_the_sharpest_image_in_a_sequence_of_captured_images),这就是我添加 rowStride 的原因。

要将像素作为交错值的单个缓冲区访问,请使用:

do 
    let n = rowStride * Int(buffer.height) * format.componentCount
    let start = buffer.data.assumingMemoryBound(to: Pixel_8.self)
    let ptr = UnsafeBufferPointer(start: start, count: n)
    
    print(Array(ptr)[ 0 ... 15]) // prints the first 15 interleaved values

要将像素作为Pixel_8888 值的缓冲区访问,请使用(确保format.componentCount4

do 
    let n = rowStride * Int(buffer.height)
    let start = buffer.data.assumingMemoryBound(to: Pixel_8888.self)
    let ptr = UnsafeBufferPointer(start: start, count: n)
    
    print(Array(ptr)[ 0 ... 3]) // prints the first 4 pixels

【讨论】:

使用 let ptr = UnsafeBufferPointer(start: start, count: n),我可以使用 vimage 将 r,g,b,a 的交错矩阵更改为值为 r 的平面矩阵吗,g,b,a 在自己的缓冲区中吗? 是的。 vImageConvert_ARGB8888toPlanar8 将从交错的 4 通道缓冲区中填充四个平面缓冲区。 根据您给出的答案,我正在使用let buffer = try? vImage_Buffer(cgImage: cgImage,format: format) 并将其传递给vImageConvert_ARGB8888toPlanar8 。如何从 vImageConvert_ARGB8888toPlanar8 访问缓冲区?例如,如果我想访问 R 的第 100x100 个像素。此外,这似乎有内存泄漏。有没有办法避免内存泄漏? 创建并填充 RGBA 缓冲区后,您需要创建四个平面 8 位缓冲区,作为目标传递给 vImageConvert_ARGB8888toPlanar8。对于每个平面缓冲区,使用buffer.data.assumingMemoryBound(to: Pixel_8.self) 示例 - 每个缓冲区都包含其相应颜色通道的像素值。每当您使用完一个 vImage 缓冲区时,您都需要释放它的内存。 谢谢。我编辑了我的原始帖子,并添加了 VImage 部分,我找不到我做错了什么。能否请您看一下使用 VImage 的代码,并帮助我减少内存泄漏并访问每个 RGBA 缓冲区?【参考方案2】:

这是最慢的方法。更快的方法是使用custom CoreImage filter。

比编写自己的 OpenGL 着色器更快(或者更确切地说,它在 Metal 中等效于当前设备)

我已经编写了 OpenGL 着色器,但还没有使用过 Metal。

两者都允许您编写直接在 GPU 上运行的图形代码。

【讨论】:

谢谢。由于我没有 OpenGL 或 Metal 的先验知识,我想知道您是否有代码 sn-p,或者可以帮助我转动图片以获取 UIImage 或 CGImage 的每个像素的 RGBA 值的教程 我不知道。我做过的 OpenGL 工作一直在 MacOS 上,ios 使用 OpenGL ES。着色器语言不同。我没有用 Metal 做过任何工作。新的 iOS 设备使用 Metal,所以你应该使用它,而不是 OpenGL。 是否可以使用金属获取每个像素的 r,g,b,a 值的矩阵,然后将其传递给 swift?因为我有一个外部 C++ 库,我将把这些 r,g,b, 矩阵提供给它,并得到一些结果。使用金属是最好的选择,只是为每一帧提取这些数据,甚至可能吗? 因为我需要每个 R、G、B、A 值作为矩阵(二维数组或缓冲区),我可以将它们传递给我的库。每个图像都是来自相机的一帧,需要快速处理。实现这一目标的最佳方法是什么?我希望现在更清楚了

以上是关于获取 CGImage Swift 的所有像素的 RGBA 值的主要内容,如果未能解决你的问题,请参考以下文章

Re: 快速从 UIImage/CGImage 获取像素数据作为数组

从像素数组问题中获取 NSImage (Swift)

从CGImage获取像素格式

将 UIImage 转换为 CGImage 以获取像素颜色

如何从 UIImage (Cocoa Touch) 或 CGImage (Core Graphics) 获取像素数据?

如何在 Swift iOS 中读取和记录图像的原始像素