Swift 4中获取UIImage指定像素RGB值的问题求助
解决Swift 4+中UIImage获取像素颜色的API适配与相机快照崩溃问题
我来帮你搞定这个问题——不管是Swift 4+的API适配,还是相机快照场景的崩溃,都有对应的解决方案:
一、Swift 4+ 适配版的UIImage像素颜色获取实现
你提到的CGImageGetDataProvider和CGDataProviderCopyData确实在Swift 4中被属性替代了,但Xcode的提示有点容易误导人。正确的做法是使用CGImage.dataProvider和CGDataProvider.data,同时要处理好可选值和安全的内存访问,避免崩溃。
这里给你一个安全可靠的Swift 4+扩展实现:
extension UIImage { func getPixelColor(at point: CGPoint) -> UIColor? { // 先校验点是否在图像范围内 guard point.x >= 0, point.x < size.width, point.y >= 0, point.y < size.height else { return nil } // 关键:确保cgImage存在,避免强制解包崩溃 guard let cgImage = self.cgImage else { return nil } // 获取数据提供者和像素数据 guard let dataProvider = cgImage.dataProvider, let pixelData = dataProvider.data as Data? else { return nil } // 根据图像实际的字节信息计算偏移,适配不同色彩空间 let bytesPerPixel = cgImage.bitsPerPixel / 8 let bytesPerRow = cgImage.bytesPerRow let pixelOffset = Int(point.y) * bytesPerRow + Int(point.x) * bytesPerPixel // 安全访问内存中的像素字节 let rgbValues = pixelData.withUnsafeBytes { buffer -> [UInt8] in guard let baseAddr = buffer.baseAddress?.assumingMemoryBound(to: UInt8.self) else { return [] } return [ baseAddr[pixelOffset], baseAddr[pixelOffset + 1], baseAddr[pixelOffset + 2], baseAddr[pixelOffset + 3] ] } guard rgbValues.count == 4 else { return nil } // 转换为UIColor需要的0-1范围的CGFloat let red = CGFloat(rgbValues[0]) / 255.0 let green = CGFloat(rgbValues[1]) / 255.0 let blue = CGFloat(rgbValues[2]) / 255.0 let alpha = CGFloat(rgbValues[3]) / 255.0 return UIColor(red: red, green: green, blue: blue, alpha: alpha) } }
这个实现做了这些优化:
- 全程使用可选绑定,杜绝强制解包引发的崩溃
- 用
Data.withUnsafeBytes安全访问内存(Swift 4+官方推荐方式) - 依赖图像自身的
bitsPerPixel和bytesPerRow计算偏移,适配不同色彩空间的图像(不止RGBA)
二、相机快照场景的崩溃原因与解决方案
你遇到的崩溃,核心问题是用UIImage(ciImage: ciImg)创建的UIImage没有cgImage属性!
当你从CVPixelBuffer生成CIImage再转成UIImage时,这个UIImage只是一个包装了CIImage的“壳子”,并没有生成对应的位图CGImage。此时调用self.cgImage会返回nil,后续的操作自然会崩溃。
这里有两种解决思路,你可以根据需求选择:
思路1:把CIImage转成带CGImage的真实UIImage
通过UIGraphicsImageRenderer生成真正的位图UIImage,这样就有可用的cgImage了:
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { let pixelBuffer: CVPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)! let ciImg = CIImage(cvPixelBuffer: pixelBuffer) // 生成带CGImage的UIImage let rendererFormat = UIGraphicsImageRendererFormat() rendererFormat.scale = UIScreen.main.scale let renderer = UIGraphicsImageRenderer(size: ciImg.extent.size, format: rendererFormat) let cameraImage = renderer.image { context in ciImg.draw(in: ciImg.extent) } // 现在调用getPixelColor就不会崩溃了 if let color = cameraImage.getPixelColor(at: CGPoint(x: 100, y: 100)) { print("获取到的颜色:\(color)") } }
思路2:直接从CVPixelBuffer读取像素(更高效)
既然我们拿到的是原始的CVPixelBuffer,完全可以跳过转UIImage的步骤,直接读取像素数据——这种方式性能更好,非常适合实时相机的场景:
func getPixelColor(from pixelBuffer: CVPixelBuffer, at point: CGPoint) -> UIColor? { // 锁定像素缓冲区,确保可以安全访问 CVPixelBufferLockBaseAddress(pixelBuffer, .readOnly) defer { // 延迟解锁,避免忘记释放资源 CVPixelBufferUnlockBaseAddress(pixelBuffer, .readOnly) } // 校验点是否在缓冲区范围内 let bufferWidth = CVPixelBufferGetWidth(pixelBuffer) let bufferHeight = CVPixelBufferGetHeight(pixelBuffer) guard point.x >= 0, point.x < CGFloat(bufferWidth), point.y >= 0, point.y < CGFloat(bufferHeight) else { return nil } // 获取缓冲区的内存地址和字节信息 guard let baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer) else { return nil } let bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer) let bytesPerPixel = CVPixelBufferGetBytesPerPixel(pixelBuffer) // 计算目标像素的内存偏移 let xPos = Int(point.x) let yPos = Int(point.y) let pixelOffset = yPos * bytesPerRow + xPos * bytesPerPixel let pixelData = baseAddress.advanced(by: pixelOffset).assumingMemoryBound(to: UInt8.self) // 注意:iOS相机的CVPixelBuffer默认是BGRA格式,所以红蓝通道顺序要调整 let blue = CGFloat(pixelData[0]) / 255.0 let green = CGFloat(pixelData[1]) / 255.0 let red = CGFloat(pixelData[2]) / 255.0 let alpha = CGFloat(pixelData[3]) / 255.0 return UIColor(red: red, green: green, blue: blue, alpha: alpha) } // 在捕获回调中使用这个方法 func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { let pixelBuffer: CVPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)! if let color = getPixelColor(from: pixelBuffer, at: CGPoint(x: 100, y: 100)) { print("获取到的颜色:\(color)") } }
注意:iOS相机输出的CVPixelBuffer默认是BGRA色彩格式,所以读取时要把红蓝通道的顺序调换,否则得到的颜色会和实际不符。
内容的提问来源于stack exchange,提问作者J.Treutlein
相关产品推荐
相关产品推荐

