2

我有一个包含数百万个元素(7201 x 7201 数据点)的数组,我将数据转换为灰度图像。

var imagePixels = heights.map { terrainToColorScale($0.height, gradient: .Linear) }
let _ = writeImageToFile(imagePixels, height: latStrides, width: lonStrides, imgColorSpace: .BW, to: imageURL, as: .png)

这段代码大约需要 11 秒才能完成(CPU=2.3Ghz 8-Core i9),但如果可能的话,我希望获得更好的性能。代码当前在单个线程中运行。

简单地将我的heights数组分成块(比如 100 个块)并为每个块运行一个任务组并获得一个不错的改进吗?或者我是否正在考虑进入金属和着色器(我对金属的了解为零!!)以获得更好的结果?

只是为了感兴趣,生成的典型图像是......(图像被下采样,因为太大而无法在此处上传。) SRTM 1 arc-sec 数据的灰度图像


更新:添加与terrainToColorScale 基本上相关的代码,对于线性转换,它将采用地形高度(通常为 0...9000)并将其缩放以返回 0...255 之间的值我也有非线性实现(未在下面显示) 这将为主要是低/高地形海拔的数据集显示更多细节。

let terrainLowerCutOff: Double = 0.0     // Mean Sea Level
let terrainUpperCutOff: Double = 9000.0  // Value in meters, just higher that Everest

func terrainToColorScale(_ elev: Double, lowerCutOff: Double = terrainLowerCutOff, upperCutOff: Double = terrainUpperCutOff, gradient: ImageColorGradient = .Linear) -> UInt8 {
  switch gradient {
  case .Linear:
    return linearColorScale(elev, lowerCutOff: lowerCutOff, upperCutOff: upperCutOff)
  case .LinearInverse:
    return linearInverseColorScale(elev, lowerCutOff: lowerCutOff, upperCutOff: upperCutOff)
  case .CurveEmphasiseLows:
    return reciprocalPowerColorScale(elev, lowerCutOff: lowerCutOff, upperCutOff: upperCutOff)
  case .CurveEmphasiseLowsInverse:
    return reciprocalInversePowerColorScale(elev, lowerCutOff: lowerCutOff, upperCutOff: upperCutOff)
  case .CurveEmphasiseHighs:
    return powerColorScale(elev, lowerCutOff: lowerCutOff, upperCutOff: upperCutOff)
  case .CurveEmphasiseHighsInverse:
    return powerInverseColorScale(elev, lowerCutOff: lowerCutOff, upperCutOff: upperCutOff)
  }
}

fileprivate func linearColorScale(_ value: Double, lowerCutOff: Double, upperCutOff: Double) -> UInt8 {
    return UInt8( 255 * normaliseColorScale(value, lowerCutOff: lowerCutOff, upperCutOff: upperCutOff) )
}

fileprivate func normaliseColorScale(_ value: Double, lowerCutOff: Double, upperCutOff: Double) -> Double {
  switch value {
  case _ where value <= lowerCutOff :
    return 0.0
  case _ where value >= upperCutOff :
    return 1.0
  default :
    return (value - lowerCutOff) / (upperCutOff - lowerCutOff)
  }
}
4

1 回答 1

1

这不是你问题的完整答案,但我认为它应该让你开始去哪里。vDSP是 的一部分Accelerate,它旨在加快对数组的数学运算。此代码使用多个步骤,因此可能会更加优化,并且它没有考虑线性以外的任何其他过滤器,但我没有足够的知识来使这些步骤更有效。但是,在我的机器上,对于以下处理,vDSP 比 map 快 4 倍:

import Foundation
import Accelerate

let count = 7200 * 7200
var date = Date()
print("Generating")
let test: [CGPoint] = (0..<count).map {
    CGPoint(x: $0, y: Int.random(in: -2000...10000))
}
print("Generating took \(Date().timeIntervalSince(date))")
date = Date()
print("Mapping")
let heights: [Float] = test.map { Float($0.y) }
print("Mapping took \(Date().timeIntervalSince(date))")
date = Date()
print("Converting via vDSP")
let clipped = vDSP.clip(heights, to: 0...9000)
let scaled = vDSP.divide(clipped, 9000)
let multiplied = vDSP.multiply(255, scaled)
let integers = vDSP.floatingPointToInteger(multiplied, integerType: UInt8.self, rounding: .towardNearestInteger)
print("Converting via DSP took \(Date().timeIntervalSince(date))")
date = Date()

print("Converting via map")
let mappedIntegers = heights.map { height -> UInt8 in
    let clipped: Float
    if height < 0 {
        clipped = 0
    } else if height > 9000 {
        clipped = 9000
    } else {
        clipped = height
    }
    let scaled = clipped / 9000
    let multiplied = scaled * 255
    return UInt8(multiplied.rounded())
}
print("Converting via map took \(Date().timeIntervalSince(date))")
于 2022-01-26T17:25:00.520 回答