Я пытаюсь создать изображение в среднем из нескольких изображений. Я делаю так, чтобы l oop через пиксельное значение 2 фотографий, сложить их вместе и разделить на два. Простая математика. Однако, хотя это работает, это очень медленно (около 23 секунд для усреднения 2x 10-мегапиксельных фотографий на MacBook Pro 15 "2016 года с максимальной спецификацией, по сравнению с гораздо меньшим временем использования API Apples CIFilter для аналогичных алгоритмов). в настоящее время используется это, основываясь на другом вопросе StackOverflow здесь :
static func averageImages(primary: CGImage, secondary: CGImage) -> CGImage? {
guard (primary.width == secondary.width && primary.height == secondary.height) else {
return nil
}
let colorSpace = CGColorSpaceCreateDeviceRGB()
let width = primary.width
let height = primary.height
let bytesPerPixel = 4
let bitsPerComponent = 8
let bytesPerRow = bytesPerPixel * width
let bitmapInfo = RGBA32.bitmapInfo
guard let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo) else {
print("unable to create context")
return nil
}
guard let context2 = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo) else {
print("unable to create context 2")
return nil
}
context.draw(primary, in: CGRect(x: 0, y: 0, width: width, height: height))
context2.draw(secondary, in: CGRect(x: 0, y: 0, width: width, height: height))
guard let buffer = context.data else {
print("Unable to get context data")
return nil
}
guard let buffer2 = context2.data else {
print("Unable to get context 2 data")
return nil
}
let pixelBuffer = buffer.bindMemory(to: RGBA32.self, capacity: width * height)
let pixelBuffer2 = buffer2.bindMemory(to: RGBA32.self, capacity: width * height)
for row in 0 ..< Int(height) {
if row % 10 == 0 {
print("Row: \(row)")
}
for column in 0 ..< Int(width) {
let offset = row * width + column
let picture1 = pixelBuffer[offset]
let picture2 = pixelBuffer2[offset]
let minR = min(255,(UInt32(picture1.redComponent)+UInt32(picture2.redComponent))/2)
let minG = min(255,(UInt32(picture1.greenComponent)+UInt32(picture2.greenComponent))/2)
let minB = min(255,(UInt32(picture1.blueComponent)+UInt32(picture2.blueComponent))/2)
let minA = min(255,(UInt32(picture1.alphaComponent)+UInt32(picture2.alphaComponent))/2)
pixelBuffer[offset] = RGBA32(red: UInt8(minR), green: UInt8(minG), blue: UInt8(minB), alpha: UInt8(minA))
}
}
let outputImage = context.makeImage()
return outputImage
}
struct RGBA32: Equatable {
//private var color: UInt32
var color: UInt32
var redComponent: UInt8 {
return UInt8((color >> 24) & 255)
}
var greenComponent: UInt8 {
return UInt8((color >> 16) & 255)
}
var blueComponent: UInt8 {
return UInt8((color >> 8) & 255)
}
var alphaComponent: UInt8 {
return UInt8((color >> 0) & 255)
}
init(red: UInt8, green: UInt8, blue: UInt8, alpha: UInt8) {
let red = UInt32(red)
let green = UInt32(green)
let blue = UInt32(blue)
let alpha = UInt32(alpha)
color = (red << 24) | (green << 16) | (blue << 8) | (alpha << 0)
}
init(color: UInt32) {
self.color = color
}
static let red = RGBA32(red: 255, green: 0, blue: 0, alpha: 255)
static let green = RGBA32(red: 0, green: 255, blue: 0, alpha: 255)
static let blue = RGBA32(red: 0, green: 0, blue: 255, alpha: 255)
static let white = RGBA32(red: 255, green: 255, blue: 255, alpha: 255)
static let black = RGBA32(red: 0, green: 0, blue: 0, alpha: 255)
static let magenta = RGBA32(red: 255, green: 0, blue: 255, alpha: 255)
static let yellow = RGBA32(red: 255, green: 255, blue: 0, alpha: 255)
static let cyan = RGBA32(red: 0, green: 255, blue: 255, alpha: 255)
static let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Little.rawValue
static func ==(lhs: RGBA32, rhs: RGBA32) -> Bool {
return lhs.color == rhs.color
}
}
Я не очень опытен, когда дело доходит до работы со значениями пикселей RAW, и, вероятно, есть место для большой оптимизации Объявление RGBA32
может не потребоваться, но опять же я не уверен, как бы я go упростил код. Однако я попытался просто заменить эту структуру на UInt32, так как делю на 2 разделение между четырьмя каналами нарушается, и я получаю неправильный результат (на положительной ноте это сокращает время вычислений примерно до 6 секунд).
Я пытался сбросить альфа-канал (просто жестко закодировать его до 255), а также отбросить проверки безопасности, чтобы никакие значения не превышали 255. Это уменьшило время вычисления t o 19 секунд Тем не менее, это далеко от 6 секунд, к которым я надеялся приблизиться, и было бы также неплохо усреднить альфа-канал.
Примечание: я знаю о CIFilters; однако сначала затемнение изображения, а затем использование фильтра CIAdditionCompositing
не работает, поскольку API, предоставляемый Apple, на самом деле использует более сложный алгоритм, чем прямое добавление. Подробнее об этом см. здесь для моего предыдущего кода на эту тему и аналогичный вопрос здесь с тестированием, доказывающим, что API Apple не является прямым добавлением значений пикселей.
** Редактировать: ** Благодаря всем отзывам я теперь смог сделать огромные улучшения. Самая большая разница заключалась в том, чтобы перейти от отладки к выпуску, что значительно сократило время. Затем я смог написать более быстрый код для изменения значений RGBA, исключив для этого необходимость в отдельной структуре. Это изменило время с 23 секунд до 10 (плюс отладка для выпуска улучшений). Код теперь выглядит следующим образом, его также немного переписывают, чтобы он выглядел более читабельным:
static func averageImages(primary: CGImage, secondary: CGImage) -> CGImage? {
guard (primary.width == secondary.width && primary.height == secondary.height) else {
return nil
}
let colorSpace = CGColorSpaceCreateDeviceRGB()
let width = primary.width
let height = primary.height
let bytesPerPixel = 4
let bitsPerComponent = 8
let bytesPerRow = bytesPerPixel * width
let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Little.rawValue
guard let primaryContext = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo),
let secondaryContext = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo) else {
print("unable to create context")
return nil
}
primaryContext.draw(primary, in: CGRect(x: 0, y: 0, width: width, height: height))
secondaryContext.draw(secondary, in: CGRect(x: 0, y: 0, width: width, height: height))
guard let primaryBuffer = primaryContext.data, let secondaryBuffer = secondaryContext.data else {
print("Unable to get context data")
return nil
}
let primaryPixelBuffer = primaryBuffer.bindMemory(to: UInt32.self, capacity: width * height)
let secondaryPixelBuffer = secondaryBuffer.bindMemory(to: UInt32.self, capacity: width * height)
for row in 0 ..< Int(height) {
if row % 10 == 0 {
print("Row: \(row)")
}
for column in 0 ..< Int(width) {
let offset = row * width + column
let primaryPixel = primaryPixelBuffer[offset]
let secondaryPixel = secondaryPixelBuffer[offset]
let red = (((primaryPixel >> 24) & 255)/2 + ((secondaryPixel >> 24) & 255)/2) << 24
let green = (((primaryPixel >> 16) & 255)/2 + ((secondaryPixel >> 16) & 255)/2) << 16
let blue = (((primaryPixel >> 8) & 255)/2 + ((secondaryPixel >> 8) & 255)/2) << 8
let alpha = ((primaryPixel & 255)/2 + (secondaryPixel & 255)/2)
primaryPixelBuffer[offset] = red | green | blue | alpha
}
}
print("Done looping")
let outputImage = primaryContext.makeImage()
return outputImage
}
Что касается многопоточности, я собираюсь запустить эту функцию несколько раз и поэтому буду реализовывать многопоточность на итерациях функция, а не внутри самой функции. Я рассчитываю получить еще большее повышение производительности от этого, но оно также должно быть сбалансировано с увеличенным распределением памяти с одновременным увеличением количества изображений в памяти.
Спасибо всем, кто способствовал этому. Поскольку все отзывы были через комментарии, я не могу пометить ни одного из них как правильный ответ. Я также не хочу публиковать свой обновленный код как ответ, поскольку я не был тем, кто действительно сделал ответ. Любые предложения о том, как поступить?