Swift中如何用VImage高效获取CGImage所有像素的RGBA值
问题原因
- 内存泄漏:你创建的4个
argbSourcePlanarBuffers数组中的vImage缓冲区没有调用free()释放,仅释放了原始的交错格式缓冲区,导致分配的平面内存没有被回收。 - 像素数据读取错误:调用
vImageConvert_ARGB8888toPlanar8后,RGBA四个通道的数据已经分别写入了四个平面缓冲区,你仍然读取原始转换前的buffer.data自然拿不到正确的拆分后数据,且计算的缓冲区长度参数n逻辑错误。
最优实现方案
实时视频处理场景下,如果你只需要按顺序读取每个像素的RGBA值,不需要单独拆分通道平面,不需要做ARGB到Planar的格式转换,直接使用vImage读取交错格式的CGImage数据即可,性能损耗最低。
无转换直接读取RGBA的实现代码
guard let cgImage = context.makeImage(), let format = vImage_CGImageFormat(cgImage: cgImage), var buffer = try? vImage_Buffer(cgImage: cgImage, format: format) else { return nil } defer { // 所有手动创建的vImage_Buffer都要在退出作用域前释放 buffer.free() } // 确认是8位每分量、4分量的ARGB/RGBA格式 assert(format.bitsPerComponent == 8 && format.componentCount == 4) gp.async { let pixelPtr = buffer.data.assumingMemoryBound(to: UInt8.self) let bytesPerRow = buffer.rowBytes let width = Int(buffer.width) let height = Int(buffer.height) for y in 0..<height { let rowBase = pixelPtr + y * bytesPerRow for x in 0..<width { let offset = x * 4 // 注意:ARGB8888顺序为 A R G B,如果你的CGImage是RGBA顺序调整下标即可 let a = rowBase[offset] let r = rowBase[offset + 1] let g = rowBase[offset + 2] let b = rowBase[offset + 3] // 处理你的像素逻辑,注意实时场景不要写print,会严重拖慢性能 // process(r: r, g: g, b: b, a: a) } } }
如果你确实需要拆分RGBA为单独平面的正确实现
guard let cgImage = context.makeImage(), let format = vImage_CGImageFormat(cgImage: cgImage), var interleaveBuffer = try? vImage_Buffer(cgImage: cgImage, format: format) else { return nil } defer { interleaveBuffer.free() } let width = Int(interleaveBuffer.width) let height = Int(interleaveBuffer.height) // 创建四个平面缓冲区 var aBuffer = try! vImage_Buffer(width: width, height: height, bitsPerPixel: 8) var rBuffer = try! vImage_Buffer(width: width, height: height, bitsPerPixel: 8) var gBuffer = try! vImage_Buffer(width: width, height: height, bitsPerPixel: 8) var bBuffer = try! vImage_Buffer(width: width, height: height, bitsPerPixel: 8) // 所有平面缓冲区都要释放 defer { aBuffer.free() rBuffer.free() gBuffer.free() bBuffer.free() } // 转换交错ARGB到四个平面 vImageConvert_ARGB8888toPlanar8( &interleaveBuffer, &aBuffer, &rBuffer, &gBuffer, &bBuffer, vImage_Flags(kvImageNoFlags) ) // 读取单独通道数据示例,比如读取R平面的(x,y)像素 let rPtr = rBuffer.data.assumingMemoryBound(to: UInt8.self) let rRowBytes = rBuffer.rowBytes let rValue = rPtr[y * rRowBytes + x]
注意事项
- 实时处理场景下请移除所有
print调试代码,IO操作会带来极大的性能损耗。 - vImage的缓冲区内存是手动管理的,所有通过
init(width:height:bitsPerPixel:)或者init(cgImage:format:)创建的缓冲区都必须调用free()释放,用defer在作用域结束时统一释放是最稳妥的方案。 - 注意CGImage的像素字节顺序,常见的格式有ARGB8888和RGBA8888两种,读取分量的时候要对应调整下标。
内容的提问来源于stack exchange,提问作者Parham Khamsepour
相关产品推荐
相关产品推荐

