我们正试图将UIImage
规范化,以便将其正确传递到CoreML模型中。
我们从每个像素检索RGB值的方法是,首先初始化一个称为每个像素值的[CGFloat]
数组rawData
,这样就有了红色、绿色、蓝色和alpha值的位置。在bitmapInfo中,我们从原始UIimage本身获取原始像素值,并执行以下操作。这用于填充context
中的bitmapInfo
参数,这是一个CGContext
变量。我们稍后将使用context
变量来draw
aCGImage
,这将在稍后将规范化的CGImage
转换回UIImage
。
使用嵌套的for循环在x
和y
坐标中迭代,可以找到所有像素中所有颜色(通过CGFloat
的原始数据数组找到)的最小和最大像素颜色值。绑定变量被设置为终止for循环,否则,它将出现超出范围的错误。
range
表示可能的RGB值的范围(即最大颜色值和最小颜色值之间的差异)。
使用公式对每个像素值进行规格化:
A = Image
curPixel = current pixel (R,G, B or Alpha)
NormalizedPixel = (curPixel-minPixel(A))/range
和一个类似的设计嵌套的循环从上面解析通过数组的rawData
和修改每个像素的颜色根据这个归一化。
我们的大多数代码来自:
我们使用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中的图像处理,我们不确定整个归一化过程是否正确。
以下是几点观察:
>
您的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) }
}
但这就引出了一个问题:为什么要处理浮点数据。如果你把这个浮点数据返回到你的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
。
我对此进行了基准测试,它在苹果XS Max上比浮点渲染快一点,但占用了四分之一的内存(例如,一个2000×2000px的图像在UInt8
下需要16mb,但在Float
下需要64mb)。
最后,我应该提到vImage有一个高度优化的函数,VimageControlStretch_argb888
,它的功能与我们上面所做的非常相似。只需导入加速
,然后您就可以执行以下操作:
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)
}
}
虽然这使用了一个稍微不同的算法,但值得考虑,因为在我的基准测试中,在我的苹果XS Max上,它的速度是浮点再现的5倍多。
一些不相关的观察结果:
>
我使用的不是UIImage
宽度和高度,而是CGImage
中的值。这是一个重要的区别,以防你的图像可能没有1的比例。
例如,如果范围已经是0到255(即不需要标准化),您可能需要考虑早期退出。
问题内容: 我正在尝试使用Swift来获取UIImage中像素的颜色,但它似乎总是返回0。这是从@Minas 对此线程的回答中转换的代码: 提前致谢! 问题答案: 由于遇到了类似的问题,一些搜索将我引到了这里。您的代码工作正常。该问题可能是由您的图像引起的。 码: 发生的是此方法将从图像的CGImage中选择像素颜色。因此,请确保您选择的是正确的图像。例如,如果您的UIImage是200x200,
问题内容: 我一直在尝试找出如何在Swift中将rgb像素数据数组转换为UIImage。 我将每个像素的rgb数据保持在一个简单的结构中: 我已经实现了以下功能,但是生成的图像不正确: 关于如何正确地将rgb数组转换为UIImage的任何提示或指针? 问题答案: 注意: 这是iOS创建的解决方案。有关macOS和的解决方案,请参见此答案。 唯一的问题是结构中的数据类型需要为。我在Playgroun
问题内容: 我在Swift中编程。我想使用CALayer和UIImage遮罩图像。我正在以编程方式创建蒙版图像。创建的蒙版图像是UIImage,当我单独查看它时可以正常工作。但是,当我将其用作遮罩时,整个屏幕会变成白色。我怀疑我的问题出在配置CALayer对象。多谢您的协助。谢谢! 问题答案: 不幸的是,您提出的问题非常糟糕- 您尚未说出您实际上在试图做什么!但是,看起来好像您正在尝试使用蒙版在图
问题内容: 对于给定的多色PNG (具有透明度),什么是最好的/快速惯用的方法: 创建一个副本 在副本中找到所有黑色像素并将其更改为红色 (返回修改后的副本) 关于SO有一些相关的问题,但我一直无法找到可行的方法。 问题答案: 您必须提取图像的像素缓冲区,然后可以循环浏览,并根据需要更改像素。最后,从缓冲区创建一个新图像。 在Swift 3中,这看起来像:
问题内容: 我正在尝试保存到,然后在Swift中读回新的内容。要转换到我使用下面的代码: 如何将(即)转换回新的? 问题答案: 假设图像的比例为1。 在Swift 4.2中,将以下代码用于get Data()。
问题内容: 在ObjectiveC中,我会这样做 但是我在Swift中尝试了所有这样的选择,但都没有成功 它显示一个错误: 使用未解析的标识符“ AlwaysOriginal” 我怎么做? 问题答案: 那将是正确的语法: (对于 Swift 3.x 或 Swift 4 ) (对于 Swift 2.x ) 但您也可以使用此“快捷方式”: