【问题标题】:Swift: Apply LUT (Lookup Table) to an image using CIColorCube slow performanceSwift:使用 CIColorCube 将 LUT(查找表)应用于图像性能缓慢
【发布时间】:2021-06-17 23:08:48
【问题描述】:

我正在使用 CIColorCube 将 LUT(来自 .png - Example LUT Image)应用于图像。它运作良好。我面临的唯一问题是当我创建按钮缩略图时,应用会停止几秒钟。

按钮看起来像这样 -> Buttons Example Image

这是我的代码:

    @IBOutlet weak var filtersScrollView: UIScrollView!
    var filters = ["-", "Filter1", "Filter2", "Filter3", "Filter4"]
    
    override func viewDidLoad() {
        createFilters()
    }
    
    func createFilters() {
        var x: CGFloat = 10
        let y: CGFloat = 0
        let width: CGFloat = 60
        let height: CGFloat = 83
        let gap: CGFloat = 2
    
        for i in 0..<filters.count {
            let filterButton = UIButton(type: .custom)
            filterButton.frame = CGRect(x: x, y: y, width: width, height: height)
            filterButton.imageView?.contentMode = .scaleAspectFill
            filterButton.setTitleColor(#colorLiteral(red: 1, green: 1, blue: 1, alpha: 0), for: .normal)
            
            let text = UILabel()
            text.frame = CGRect(x: 0, y: height - 21, width: filterButton.frame.width, height: 21)
            text.textAlignment = .center
            text.backgroundColor = #colorLiteral(red: 0.9372549057, green: 0.3490196168, blue: 0.1921568662, alpha: 1)
            text.textColor = .white
            text.font = .systemFont(ofSize: 8.5, weight: .medium)
            
            filterButton.addSubview(text)
            filtersScrollView.insertSubview(filterButton, at: 1)
            
            x += width + gap
    
            if i == 0 {
                filterButton.setImage(originalImage, for: .normal)
                text.text = "-"
                text.backgroundColor = #colorLiteral(red: 0.1215686275, green: 0.1215686275, blue: 0.1215686275, alpha: 1)
            }
            else {
                // THIS LINE MAKES THE APP STOP FOR A FEW SECONDS
                let filteredImage = filterFromLUT(inputImage: originalCIImage, lut: "\(filters[i])")?.outputImage
                filterButton.setImage(UIImage(ciImage: filteredImage!), for: .normal)
                text.text = "\(filters[i])"
            }
        }
        
        filtersScrollView.contentSize = CGSize(width: x, height: height)
    }
    
    func filterFromLUT(inputImage: CIImage, lut: String) -> CIFilter? {
        let dimension = 64
        
        let lutImage = UIImage(named: lut)!.cgImage
        let width = lutImage!.width
        let height = lutImage!.height
        let rowNum = width / dimension
        let columnNum = height / dimension
        
        let bitmap = createBitmap(image: lutImage!)
    
        let dataSize = dimension * dimension * dimension * MemoryLayout<Float>.size * 4
        var array = Array<Float>(repeating: 0, count: dataSize)
    
        var bitmapOffest: Int = 0
        var z: Int = 0
    
        for _ in stride(from: 0, to: rowNum, by: 1) {
          for y in stride(from: 0, to: dimension, by: 1) {
            let tmp = z
            for _ in stride(from: 0, to: columnNum, by: 1) {
              for x in stride(from: 0, to: dimension, by: 1) {
    
                let dataOffset = (z * dimension * dimension + y * dimension + x) * 4
    
                let position = bitmap!
                  .advanced(by: bitmapOffest)
    
                array[dataOffset + 0] = Float(position
                  .advanced(by: 0)
                  .pointee) / 255
    
                array[dataOffset + 1] = Float(position
                  .advanced(by: 1)
                  .pointee) / 255
    
                array[dataOffset + 2] = Float(position
                  .advanced(by: 2)
                  .pointee) / 255
    
                array[dataOffset + 3] = Float(position
                  .advanced(by: 3)
                  .pointee) / 255
    
                bitmapOffest += 4
              }
              z += 1
            }
            z = tmp
          }
          z += columnNum
        }
    
        free(bitmap)
    
        let data = Data.init(bytes: array, count: dataSize)
        
        // Create CIColorCube filter
        let filter = CIFilter.colorCube()
        filter.inputImage = inputImage
        filter.cubeData = data
        filter.cubeDimension = Float(dimension)
    
        return filter
    }
    
    func createBitmap(image: CGImage) -> UnsafeMutablePointer<UInt8>? {
        let width = image.width
        let height = image.height
    
        let bitsPerComponent = 8
        let bytesPerRow = width * 4
        
        let bitmapSize = bytesPerRow * height
    
        guard let data = malloc(bitmapSize) else {
            return nil
        }
        
        let context = CGContext(
            data: data,
            width: width,
            height: height,
            bitsPerComponent: bitsPerComponent,
            bytesPerRow: bytesPerRow,
            space: CGColorSpaceCreateDeviceRGB(),
            bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue,
            releaseCallback: nil,
            releaseInfo: nil)
    
        context!.draw(image, in: CGRect(x: 0, y: 0, width: width, height: height))
    
        return data.bindMemory(to: UInt8.self, capacity: bitmapSize)
    }

我认为可能是 createBitmap() 函数中的 CGContext 导致了这种情况。有谁知道如何解决这个问题?

【问题讨论】:

    标签: swift thumbnails core-image cifilter


    【解决方案1】:

    您可以采取一些措施来提高性能:

    • 目前,您正在处理原始输入图像(我假设它非常大),只是为了在 60 x 83 按钮中显示结果。考虑在通过过滤器之前首先缩小图像。
    • 您可以通过使图像处理代码异步来避免阻塞 UI。只需创建适当大小的按钮并 DispatchQueue.global().async { ... } 进行图像处理即可。
    • 不要使用.setImage(UIImage(ciImage: filteredImage)。根据我的经验,以这种方式从 CIImage 创建 UIImage 非常不可预测。而是使用CIContext 将过滤后的图像渲染为CGImage,然后将其转换为UIImage。还可以尝试重复使用单个 CIContext,而不是为每个图像重新创建它。
    • 使用 vDSP 可以加快将 LUT 图像转换为浮点数据数组的代码(见下文)。

    使用 vDSP 创建 LUT 数据:

    let lutImage = UIImage(named: lut)!.cgImage
    let dimension = lutImage.height
    
    // get data from image
    let lutImageData = lutImage.dataProvider?.data
    let lutImageDataPtr = CFDataGetBytePtr(lutImageData)!
    
    // convert to float and divide by 255
    let numElements = dimension * dimension * dimension * 4
    let inDataFloat = UnsafeMutablePointer<Float>.allocate(capacity: numElements)
    vDSP_vfltu8(lutImageDataPtr, 1, inDataFloat, 1, vDSP_Length(numElements))
    var div: Float = 255.0
    vDSP_vsdiv(inDataFloat, 1, &div, inDataFloat, 1, vDSP_Length(numElements))
    
    // convert buffer pointer to data
    let lutData = NSData(bytesNoCopy: inDataFloat, length: numElements * MemoryLayout<Float>.size, freeWhenDone: true)
    

    【讨论】:

    • 嘿弗兰克!我正在使用您上面编写的代码来创建 LUT 数据,但是这一行:vDSP_vfltu8(lutImageDataPtr, 1, inDataFloat, 1, vDSP_Length(numElements)) 导致了这个错误:malloc: can't allocate region。我尝试使用let dimension = 64 并且错误消失了,但图像结果绝对不是预期的。我该怎么办?
    • 啊,对不起。在我的 LUT 中,所有切片都在一行中(而不是 8x8 网格)。我觉得这是有利的,因为颜色立方体过滤器需要相同布局中的数据。
    • 好的,我尝试使用中性的单行 LUT,性能明显更快。但是返回的图像有点紫色......
    • 您可能还需要指定 LUT 的色彩空间。使用CIFilter.colorCubeWithColorSpace 作为过滤器并设置filter.colorSpace = lutImage.colorSpace
    • 这是带有中性LUT imgur.com/a/rD4909p 的结果,第一张图片是原图,其他应该相等
    猜你喜欢
    • 2021-03-17
    • 2015-07-27
    • 1970-01-01
    • 2023-03-08
    • 1970-01-01
    • 1970-01-01
    • 2010-10-22
    • 2012-07-05
    • 1970-01-01
    相关资源
    最近更新 更多