EXC_BAD_ACCESS (code=1) 更新 UIImageView 的 UIImage 时
EXC_BAD_ACCESS (code=1) when updating UIImage of UIImageView
我是 iOS 和 CoreML 的新手。我有一个非常简单的 UI 和两个 UIImageView(一个应该是输入,第二个应该是输出)。点击第一个时,图像应由神经网络处理,输出应显示在第二个中。
但是,当我尝试从 MLMultiArray 输出对象下载图像并从中创建一个 UIImage 然后我可以上传到第二个 UIImageView 我得到一个 EXC_BAD_ACCESS(代码=1).
我已将问题减少为根本不调用神经网络处理,只是尝试从 MLMultiArray 创建新图像。结果是一样的。
之后,我尝试从一个空缓冲区生成一个 UI图像。图像创建正确,但如果我尝试更新 UIImageView 以使用它,我会得到同样的错误。
如果我尝试将第二个 UIImageView 更新为不同的图像(例如:输入图像)一切正常。
我认为这是关于我正在创建的 UIImage 对象的内存管理问题,但我无法弄清楚我做错了什么
class ViewController: UIViewController {
@IBOutlet weak var out: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func imageTapped(_ sender: UITapGestureRecognizer) {
let imageView = sender.view as? UIImageView
if let imageToAnalyse = imageView?.image {
if let outImg = process(forImage: imageToAnalyse) {
out.image = outImg
}
}
}
func process (forImage inImage:UIImage) -> UIImage? {
let size = CGSize(width: 512, height: 512)
let mlOut = try? MLMultiArray(shape: [1, size.height, size.width] as [NSNumber], dataType: .float32)
let newImage = getSinglePlaneImage(inBuffer: mlOut!, width: Int(size.width), height: Int(size.height))
return newImage
}
func getSinglePlaneImage(inBuffer: MLMultiArray, width: Int, height: Int) -> UIImage
{
var newImage: UIImage
// let floatPtr = inBuffer.dataPointer.bindMemory(to: Float32.self, capacity: inBuffer.count)
// let floatBuffer = UnsafeBufferPointer(start: floatPtr, count: inBuffer.count)
// let pixelValues : [UInt8]? = Array(floatBuffer).map({UInt8( ImageProcessor.clamp( (([=11=]) + 1.0) * 128.0, minValue: 0.0, maxValue: 255.0) ) })
//simulating pixels from MLMultiArray
let pixels : [UInt8]? = Array(repeating: 0, count: 512*512)
var imageRef: CGImage?
if var pixelValues = pixels {
let bitsPerComponent = 8
let bytesPerPixel = 1
let bitsPerPixel = bytesPerPixel * bitsPerComponent
let bytesPerRow = bytesPerPixel * width
let totalBytes = height * bytesPerRow
imageRef = withUnsafePointer(to: &pixelValues, {
ptr -> CGImage? in
var imageRef: CGImage?
let colorSpaceRef = CGColorSpaceCreateDeviceGray()
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue).union(CGBitmapInfo())
let data = UnsafeRawPointer(ptr.pointee).assumingMemoryBound(to: UInt8.self)
let releaseData: CGDataProviderReleaseDataCallback = {
(info: UnsafeMutableRawPointer?, data: UnsafeRawPointer, size: Int) -> () in
}
if let providerRef = CGDataProvider(dataInfo: nil, data: data, size: totalBytes, releaseData: releaseData) {
imageRef = CGImage(width: width,
height: height,
bitsPerComponent: bitsPerComponent,
bitsPerPixel: bitsPerPixel,
bytesPerRow: bytesPerRow,
space: colorSpaceRef,
bitmapInfo: bitmapInfo,
provider: providerRef,
decode: nil,
shouldInterpolate: false,
intent: CGColorRenderingIntent.defaultIntent)
}
return imageRef
})
}
newImage = UIImage(cgImage: imageRef!)
return newImage
}
}
您的代码似乎可以将 512x512-float32 数组成功转换为 512x512-UInt8 数组,因此我根据您代码的未注释版本编写了此答案。 (虽然,转换效率不够高,还有一定的改进空间。)
更新
以下描述不是 OP 问题的正确解决方案。只是留作记录。请跳至此答案底部的更新代码。
旧代码(不是正确的解决方案)
首先,代码中最严重的缺陷是以下两行:
imageRef = withUnsafePointer(to: &pixelValues, {
let data = UnsafeRawPointer(ptr.pointee).assumingMemoryBound(to: UInt8.self)
上面第一行传递了一个指向[UInt8]?
的指针,在Swift中,[UInt8]?
(又名Optional<Array<UInt8>>
)是一个8字节的结构,不是一个连续的区域像 C 数组。
第二种更危险。 ptr.pointee
是 [UInt8]?
,但不能保证通过指针访问 Swift 数组。将数组传递给 UnsafeRawPointer.init(_:)
可能会创建一个时间区域,该区域将在调用初始化程序后立即释放。
如您所知,访问悬垂指针偶尔在某些有限的条件下不会造成伤害,但随时可能产生意想不到的结果。
我会这样写:
func getSinglePlaneImage(inBuffer: MLMultiArray, width: Int, height: Int) -> UIImage {
//simulating pixels from MLMultiArray
//...
let pixelValues: [UInt8] = Array(repeating: 0, count: 1*512*512)
let bitsPerComponent = 8
let bytesPerPixel = 1
let bitsPerPixel = bytesPerPixel * 8
let bytesPerRow = bytesPerPixel * width
let totalBytes = height * bytesPerRow
let imageRef = pixelValues.withUnsafeBytes({bytes -> CGImage? in
var imageRef: CGImage?
let colorSpaceRef = CGColorSpaceCreateDeviceGray()
let bitmapInfo: CGBitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue)
let data = bytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
let releaseData: CGDataProviderReleaseDataCallback = {_,_,_ in }
if let providerRef = CGDataProvider(dataInfo: nil, data: data, size: totalBytes, releaseData: releaseData) {
imageRef = CGImage(width: width,
height: height,
bitsPerComponent: bitsPerComponent,
bitsPerPixel: bitsPerPixel,
bytesPerRow: bytesPerRow,
space: colorSpaceRef,
bitmapInfo: bitmapInfo,
provider: providerRef,
decode: nil,
shouldInterpolate: false,
intent: .defaultIntent)
}
return imageRef
})
let newImage = UIImage(cgImage: imageRef!)
return newImage
}
当你想要一个指向数组起始元素的指针时,使用withUnsafeBytes
并在闭包参数中使用指针(实际上它是一个UnsafeRawBufferPointer
)。
还有一点,您的 pixels
或 pixelValues
不需要是可选的。
否则,您可以创建每个像素 Float32
的灰度图像。
func getSinglePlaneImage(inBuffer: MLMultiArray, width: Int, height: Int) -> UIImage {
//simulating pixels from MLMultiArray
//...
let pixelValues: [Float32] = Array(repeating: 0, count: 1*512*512)
let bitsPerComponent = 32 //<-
let bytesPerPixel = 4 //<-
let bitsPerPixel = bytesPerPixel * 8
let bytesPerRow = bytesPerPixel * width
let totalBytes = height * bytesPerRow
let imageRef = pixelValues.withUnsafeBytes({bytes -> CGImage? in
var imageRef: CGImage?
let colorSpaceRef = CGColorSpaceCreateDeviceGray()
let bitmapInfo: CGBitmapInfo = [CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue),
.byteOrder32Little, .floatComponents] //<-
let data = bytes.baseAddress!.assumingMemoryBound(to: Float32.self)
let releaseData: CGDataProviderReleaseDataCallback = {_,_,_ in }
if let providerRef = CGDataProvider(dataInfo: nil, data: data, size: totalBytes, releaseData: releaseData) {
imageRef = CGImage(width: width,
height: height,
bitsPerComponent: bitsPerComponent,
bitsPerPixel: bitsPerPixel,
bytesPerRow: bytesPerRow,
space: colorSpaceRef,
bitmapInfo: bitmapInfo,
provider: providerRef,
decode: nil,
shouldInterpolate: false,
intent: CGColorRenderingIntent.defaultIntent)
}
return imageRef
})
let newImage = UIImage(cgImage: imageRef!)
return newImage
}
在我的测试项目中,两者都按预期工作,但如果您发现问题,请通知我。
更新代码(希望这是正确的解决方案)
我错过了一个事实,即 CGDataProvider
在使用 init(dataInfo:data:size:releaseData:)
创建时甚至在创建 CGImage
之后仍保留指针。因此,它可能在 withUnsafeBytes
的闭包结束后被引用,当它无效时。
在这种情况下你最好使用 CGDataProvider.init(data:)
。
func getSinglePlaneImage(inBuffer: MLMultiArray, width: Int, height: Int) -> UIImage {
var newImage: UIImage
//let floatPtr = inBuffer.dataPointer.bindMemory(to: Float32.self, capacity: inBuffer.count)
//let floatBuffer = UnsafeBufferPointer(start: floatPtr, count: inBuffer.count)
//let pixelValues: Data = Data((floatBuffer.lazy.map{
// UInt8(ImageProcessor.clamp((([=13=]) + 1.0) * 128.0, minValue: 0.0, maxValue: 255.0))
//})
//simulating pixels from MLMultiArray
//...
let pixelValues = Data(count: 1*512*512) // <- ###
var imageRef: CGImage?
let bitsPerComponent = 8
let bytesPerPixel = 1
let bitsPerPixel = bytesPerPixel * bitsPerComponent
let bytesPerRow = bytesPerPixel * width
let colorSpaceRef = CGColorSpaceCreateDeviceGray()
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue)
if let providerRef = CGDataProvider(data: pixelValues as CFData) { // <-###
imageRef = CGImage(width: width,
height: height,
bitsPerComponent: bitsPerComponent,
bitsPerPixel: bitsPerPixel,
bytesPerRow: bytesPerRow,
space: colorSpaceRef,
bitmapInfo: bitmapInfo,
provider: providerRef,
decode: nil,
shouldInterpolate: false,
intent: CGColorRenderingIntent.defaultIntent)
}
newImage = UIImage(cgImage: imageRef!)
return newImage
}
据我尝试,即使在具有重复触摸次数的实际设备中,这也不会崩溃。请试试。感谢您的耐心等待。
我是 iOS 和 CoreML 的新手。我有一个非常简单的 UI 和两个 UIImageView(一个应该是输入,第二个应该是输出)。点击第一个时,图像应由神经网络处理,输出应显示在第二个中。
但是,当我尝试从 MLMultiArray 输出对象下载图像并从中创建一个 UIImage 然后我可以上传到第二个 UIImageView 我得到一个 EXC_BAD_ACCESS(代码=1).
我已将问题减少为根本不调用神经网络处理,只是尝试从 MLMultiArray 创建新图像。结果是一样的。
之后,我尝试从一个空缓冲区生成一个 UI图像。图像创建正确,但如果我尝试更新 UIImageView 以使用它,我会得到同样的错误。
如果我尝试将第二个 UIImageView 更新为不同的图像(例如:输入图像)一切正常。
我认为这是关于我正在创建的 UIImage 对象的内存管理问题,但我无法弄清楚我做错了什么
class ViewController: UIViewController {
@IBOutlet weak var out: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func imageTapped(_ sender: UITapGestureRecognizer) {
let imageView = sender.view as? UIImageView
if let imageToAnalyse = imageView?.image {
if let outImg = process(forImage: imageToAnalyse) {
out.image = outImg
}
}
}
func process (forImage inImage:UIImage) -> UIImage? {
let size = CGSize(width: 512, height: 512)
let mlOut = try? MLMultiArray(shape: [1, size.height, size.width] as [NSNumber], dataType: .float32)
let newImage = getSinglePlaneImage(inBuffer: mlOut!, width: Int(size.width), height: Int(size.height))
return newImage
}
func getSinglePlaneImage(inBuffer: MLMultiArray, width: Int, height: Int) -> UIImage
{
var newImage: UIImage
// let floatPtr = inBuffer.dataPointer.bindMemory(to: Float32.self, capacity: inBuffer.count)
// let floatBuffer = UnsafeBufferPointer(start: floatPtr, count: inBuffer.count)
// let pixelValues : [UInt8]? = Array(floatBuffer).map({UInt8( ImageProcessor.clamp( (([=11=]) + 1.0) * 128.0, minValue: 0.0, maxValue: 255.0) ) })
//simulating pixels from MLMultiArray
let pixels : [UInt8]? = Array(repeating: 0, count: 512*512)
var imageRef: CGImage?
if var pixelValues = pixels {
let bitsPerComponent = 8
let bytesPerPixel = 1
let bitsPerPixel = bytesPerPixel * bitsPerComponent
let bytesPerRow = bytesPerPixel * width
let totalBytes = height * bytesPerRow
imageRef = withUnsafePointer(to: &pixelValues, {
ptr -> CGImage? in
var imageRef: CGImage?
let colorSpaceRef = CGColorSpaceCreateDeviceGray()
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue).union(CGBitmapInfo())
let data = UnsafeRawPointer(ptr.pointee).assumingMemoryBound(to: UInt8.self)
let releaseData: CGDataProviderReleaseDataCallback = {
(info: UnsafeMutableRawPointer?, data: UnsafeRawPointer, size: Int) -> () in
}
if let providerRef = CGDataProvider(dataInfo: nil, data: data, size: totalBytes, releaseData: releaseData) {
imageRef = CGImage(width: width,
height: height,
bitsPerComponent: bitsPerComponent,
bitsPerPixel: bitsPerPixel,
bytesPerRow: bytesPerRow,
space: colorSpaceRef,
bitmapInfo: bitmapInfo,
provider: providerRef,
decode: nil,
shouldInterpolate: false,
intent: CGColorRenderingIntent.defaultIntent)
}
return imageRef
})
}
newImage = UIImage(cgImage: imageRef!)
return newImage
}
}
您的代码似乎可以将 512x512-float32 数组成功转换为 512x512-UInt8 数组,因此我根据您代码的未注释版本编写了此答案。 (虽然,转换效率不够高,还有一定的改进空间。)
更新
以下描述不是 OP 问题的正确解决方案。只是留作记录。请跳至此答案底部的更新代码。
旧代码(不是正确的解决方案)
首先,代码中最严重的缺陷是以下两行:
imageRef = withUnsafePointer(to: &pixelValues, {
let data = UnsafeRawPointer(ptr.pointee).assumingMemoryBound(to: UInt8.self)
上面第一行传递了一个指向[UInt8]?
的指针,在Swift中,[UInt8]?
(又名Optional<Array<UInt8>>
)是一个8字节的结构,不是一个连续的区域像 C 数组。
第二种更危险。 ptr.pointee
是 [UInt8]?
,但不能保证通过指针访问 Swift 数组。将数组传递给 UnsafeRawPointer.init(_:)
可能会创建一个时间区域,该区域将在调用初始化程序后立即释放。
如您所知,访问悬垂指针偶尔在某些有限的条件下不会造成伤害,但随时可能产生意想不到的结果。
我会这样写:
func getSinglePlaneImage(inBuffer: MLMultiArray, width: Int, height: Int) -> UIImage {
//simulating pixels from MLMultiArray
//...
let pixelValues: [UInt8] = Array(repeating: 0, count: 1*512*512)
let bitsPerComponent = 8
let bytesPerPixel = 1
let bitsPerPixel = bytesPerPixel * 8
let bytesPerRow = bytesPerPixel * width
let totalBytes = height * bytesPerRow
let imageRef = pixelValues.withUnsafeBytes({bytes -> CGImage? in
var imageRef: CGImage?
let colorSpaceRef = CGColorSpaceCreateDeviceGray()
let bitmapInfo: CGBitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue)
let data = bytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
let releaseData: CGDataProviderReleaseDataCallback = {_,_,_ in }
if let providerRef = CGDataProvider(dataInfo: nil, data: data, size: totalBytes, releaseData: releaseData) {
imageRef = CGImage(width: width,
height: height,
bitsPerComponent: bitsPerComponent,
bitsPerPixel: bitsPerPixel,
bytesPerRow: bytesPerRow,
space: colorSpaceRef,
bitmapInfo: bitmapInfo,
provider: providerRef,
decode: nil,
shouldInterpolate: false,
intent: .defaultIntent)
}
return imageRef
})
let newImage = UIImage(cgImage: imageRef!)
return newImage
}
当你想要一个指向数组起始元素的指针时,使用withUnsafeBytes
并在闭包参数中使用指针(实际上它是一个UnsafeRawBufferPointer
)。
还有一点,您的 pixels
或 pixelValues
不需要是可选的。
否则,您可以创建每个像素 Float32
的灰度图像。
func getSinglePlaneImage(inBuffer: MLMultiArray, width: Int, height: Int) -> UIImage {
//simulating pixels from MLMultiArray
//...
let pixelValues: [Float32] = Array(repeating: 0, count: 1*512*512)
let bitsPerComponent = 32 //<-
let bytesPerPixel = 4 //<-
let bitsPerPixel = bytesPerPixel * 8
let bytesPerRow = bytesPerPixel * width
let totalBytes = height * bytesPerRow
let imageRef = pixelValues.withUnsafeBytes({bytes -> CGImage? in
var imageRef: CGImage?
let colorSpaceRef = CGColorSpaceCreateDeviceGray()
let bitmapInfo: CGBitmapInfo = [CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue),
.byteOrder32Little, .floatComponents] //<-
let data = bytes.baseAddress!.assumingMemoryBound(to: Float32.self)
let releaseData: CGDataProviderReleaseDataCallback = {_,_,_ in }
if let providerRef = CGDataProvider(dataInfo: nil, data: data, size: totalBytes, releaseData: releaseData) {
imageRef = CGImage(width: width,
height: height,
bitsPerComponent: bitsPerComponent,
bitsPerPixel: bitsPerPixel,
bytesPerRow: bytesPerRow,
space: colorSpaceRef,
bitmapInfo: bitmapInfo,
provider: providerRef,
decode: nil,
shouldInterpolate: false,
intent: CGColorRenderingIntent.defaultIntent)
}
return imageRef
})
let newImage = UIImage(cgImage: imageRef!)
return newImage
}
在我的测试项目中,两者都按预期工作,但如果您发现问题,请通知我。
更新代码(希望这是正确的解决方案)
我错过了一个事实,即 CGDataProvider
在使用 init(dataInfo:data:size:releaseData:)
创建时甚至在创建 CGImage
之后仍保留指针。因此,它可能在 withUnsafeBytes
的闭包结束后被引用,当它无效时。
在这种情况下你最好使用 CGDataProvider.init(data:)
。
func getSinglePlaneImage(inBuffer: MLMultiArray, width: Int, height: Int) -> UIImage {
var newImage: UIImage
//let floatPtr = inBuffer.dataPointer.bindMemory(to: Float32.self, capacity: inBuffer.count)
//let floatBuffer = UnsafeBufferPointer(start: floatPtr, count: inBuffer.count)
//let pixelValues: Data = Data((floatBuffer.lazy.map{
// UInt8(ImageProcessor.clamp((([=13=]) + 1.0) * 128.0, minValue: 0.0, maxValue: 255.0))
//})
//simulating pixels from MLMultiArray
//...
let pixelValues = Data(count: 1*512*512) // <- ###
var imageRef: CGImage?
let bitsPerComponent = 8
let bytesPerPixel = 1
let bitsPerPixel = bytesPerPixel * bitsPerComponent
let bytesPerRow = bytesPerPixel * width
let colorSpaceRef = CGColorSpaceCreateDeviceGray()
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue)
if let providerRef = CGDataProvider(data: pixelValues as CFData) { // <-###
imageRef = CGImage(width: width,
height: height,
bitsPerComponent: bitsPerComponent,
bitsPerPixel: bitsPerPixel,
bytesPerRow: bytesPerRow,
space: colorSpaceRef,
bitmapInfo: bitmapInfo,
provider: providerRef,
decode: nil,
shouldInterpolate: false,
intent: CGColorRenderingIntent.defaultIntent)
}
newImage = UIImage(cgImage: imageRef!)
return newImage
}
据我尝试,即使在具有重复触摸次数的实际设备中,这也不会崩溃。请试试。感谢您的耐心等待。