美文网首页
深度图转伪色彩图源码阅读(swift、metal)

深度图转伪色彩图源码阅读(swift、metal)

作者: 梁间 | 来源:发表于2018-10-17 10:54 被阅读0次

    Metal shader中核心转化流程如下:

    直方图的作用是确保颜色都分配给正在使用的值,最终输出的颜色不代表真实深度,它是当前帧内部相对深度的体现。

    首先创建一个Metal文件,建立一个 Compute Shaders

    kernel void depthToJET(texture2d<float, access::read>  inputTexture      [[ texture(0) ]],
                           texture2d<float, access::write> outputTexture     [[ texture(1) ]],
                           constant JETParams& params [[ buffer(0) ]],
                           constant float* histogram        [[ buffer(1) ]],
                           constant BGRAPixel *colorTable [[ buffer(2) ]],
                           uint2 gid [[ thread_position_in_grid ]])
    

    其中 inputTexture 为输入,outputTexture为输出,params 为转换参数,histogram为直方图,colorTable为转换颜色对照表。

    struct BGRAPixel {
        uchar b;
        uchar g;
        uchar r;
        uchar a;
    };
    
    struct JETParams {
        int histogramSize;
        int binningFactor;
    };
    
    kernel void depthToJET(texture2d<float, access::read>  inputTexture      [[ texture(0) ]],
                           texture2d<float, access::write> outputTexture     [[ texture(1) ]],
                           constant JETParams& params [[ buffer(0) ]],
                           constant float* histogram        [[ buffer(1) ]],
                           constant BGRAPixel *colorTable [[ buffer(2) ]],
                           uint2 gid [[ thread_position_in_grid ]])
    {
        // Ensure we don't read or write outside of the texture
        if ((gid.x >= inputTexture.get_width()) || (gid.y >= inputTexture.get_height())) {
            return;
        }
    
        // depthDataType is kCVPixelFormatType_DepthFloat16
        float depth = inputTexture.read(gid).x;
        ushort histIndex = (ushort)(depth * params.binningFactor);
    
        // make sure the value is part of the histogram
        if (histIndex >= params.histogramSize) {
            return;
        }
        
        float colorIndex = histogram[histIndex];
        BGRAPixel outputColor = colorTable[(int)colorInd;
        outputTexture.write(float4(outputColor.r / 255.0, outputColor.g / 255.0, outputColor.b / 255.0, 1.0), gid);
    }
    

    在swift文件中声明和Metal文件一致的数据结构

    struct BGRAPixel {
        var blue: UInt8 = 0
        var green: UInt8 = 0
        var red: UInt8 = 0
        var alpha: UInt8 = 0
    }
    
    struct JETParams {
        var histogramSize: Int32 = 1 << 16
        var binningFactor: Int32 = 8000
    }
    

    颜色对照表

    class ColorTable: NSObject {
        private var tableBuf: MTLBuffer?
        
        required init (metalDevice: MTLDevice, size: Int) {
            self.tableBuf = metalDevice.makeBuffer(length: MemoryLayout<BGRAPixel>.size * size, options: .storageModeShared)
            super.init()
            self.fillJetTable(size: size)
        }
        
        deinit {
        }
        
        // second order curve (from HueWave 2010) -- increases saturation at cyan, magenta, and yellow
        private func wave(_ pos: Double, phase: Double) -> Double {
            let piVal: Double = 4.0 * atan(1.0)
            let sinShift: Double = -1.0 / 4.0
            // Phase shift sine wave such that sin(2pi * (x+sinShift)) == -1
            let xVal: Double = 2.0 * piVal * (pos + sinShift + phase)
            let sVal: Double = (sin(xVal) + 1.0) / 2.0
            // Normalized sin function
            let s2Val: Double = sin(piVal / 2.0 * sVal)
            // Flatten top
            return s2Val * s2Val
            // Symmetrically flattened botton and top
        }
        
        private func fillJetTable(size: Int) {
            let piVal: Double = 4.0 * atan(1.0)
            let rPhase: Double = -1.0 / 4.0
            let gPhase: Double = 0.0
            let bPhase: Double = +1.0 / 4.0
            
            let table = tableBuf?.contents().bindMemory(to: BGRAPixel.self, capacity: size)
            
            table![0].blue = 0
            table![0].green = table![0].blue
            table![0].red = table![0].green
            // Get pixel info
            for idx in 1..<size {
                // Get the normalized position
                let pos = (Double)(idx) / ((Double)(size) - 1.0)
                // Get the current hue value
                var red: Double = wave(pos, phase: rPhase)
                let green: Double = wave(pos, phase: gPhase)
                var blue: Double = wave(pos, phase: bPhase)
                // Preserve the jet color table attenuation of red near the start, and blue near the end
                // Except instead of making them zero, causing a discontinuity, use an 8th order 1-cos function
                if pos < 1.0 / 8.0 {
                    // Attenuate red  channel for 0 < x < 1/8
                    let xVal: Double = pos * 8.0 * piVal
                    var attenuation: Double = (cos(xVal) + 1.0) / 2.0
                    attenuation = 1.0 - pow(attenuation, 0.125)
                    red *= attenuation
                } else if pos > 7.0 / 8.0 {
                    // Attenuate blue channel for 7/8 < x < 1
                    let xVal: Double = (1.0 - pos) * 8.0 * piVal
                    var attenuation: Double = (cos(xVal) + 1.0) / 2.0
                    attenuation = 1.0 - pow(attenuation, 0.125)
                    blue *= attenuation
                }
                
                table![idx].alpha = (UInt8)(255)
                table![idx].red = (UInt8)(255 * red)
                table![idx].green = (UInt8)(255 * green)
                table![idx].blue = (UInt8)(255 * blue)
            }
        }
        
        func getColorTable() -> MTLBuffer {
            return tableBuf!
        }
    }
    

    按步骤设置metal
    a、在声明中创建Device、CommandQueue

    private let metalDevice = MTLCreateSystemDefaultDevice()!
    private lazy var commandQueue: MTLCommandQueue? = {
            return self.metalDevice.makeCommandQueue()
        }()
    

    创建jetParams

    private let jetParams = JETParams()
    

    声明metal参数空间及PipelineState

    private let jetParamsBuffer: MTLBuffer
    private let histogramBuffer: MTLBuffer
    private var computePipelineState: MTLComputePipelineState?
    

    声明其他metal所需参数

    private(set) var inputFormatDescription: CMFormatDescription?    
    private(set) var outputFormatDescription: CMFormatDescription?
    private var inputTextureFormat: MTLPixelFormat = .invalid
    private var outputPixelBufferPool: CVPixelBufferPool!
    private let colors = 512
    private var textureCache: CVMetalTextureCache!
    private var colorBuf: MTLBuffer?
    

    b、
    在init()中加载metal文件,创建computePipelineState对象

            let defaultLibrary = metalDevice.makeDefaultLibrary()!
            let kernelFunction = defaultLibrary.makeFunction(name: "depthToJET")
            do {
                computePipelineState = try metalDevice.makeComputePipelineState(function: kernelFunction!)
            } catch {
                fatalError("Unable to create depth converter pipeline state. (\(error))")
            }
            
    

    根据jetParams初始化histBuffer空间

           guard let histBuffer = metalDevice.makeBuffer(
                length: MemoryLayout<Float>.size * Int(jetParams.histogramSize),
                options: .storageModeShared) else {
                    fatalError("Failed to allocate buffer for histogram")
            }
            
            self.histogramBuffer = histBuffer
    

    初始化jetBuffer空间,并写入参数内容

           guard let jetBuffer = metalDevice.makeBuffer(length: MemoryLayout<JETParams>.size, options: .storageModeShared) else {
                fatalError("Failed to allocate buffer for histogram size")
            }
            
            jetBuffer.contents().bindMemory(to: JETParams.self, capacity: 1)
                .assign(repeating: self.jetParams, count: 1)
            
            self.jetParamsBuffer = jetBuffer
    

    c、创建allocateOutputBufferPool函数为metal输出分配空间

    static private func allocateOutputBufferPool(with formatDescription: CMFormatDescription,
                                                     outputRetainedBufferCountHint: Int) -> CVPixelBufferPool? {
            let inputDimensions = CMVideoFormatDescriptionGetDimensions(formatDescription)
            let outputBufferAttributes: [String: Any] = [
                kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA,
                kCVPixelBufferWidthKey as String: Int(inputDimensions.width),
                kCVPixelBufferHeightKey as String: Int(inputDimensions.height),
                kCVPixelBufferIOSurfacePropertiesKey as String: [:]
            ]
            
            let poolAttributes = [kCVPixelBufferPoolMinimumBufferCountKey as String: outputRetainedBufferCountHint]
            var cvPixelBufferPool: CVPixelBufferPool?
            // Create a pixel buffer pool with the same pixel attributes as the input format description
            CVPixelBufferPoolCreate(kCFAllocatorDefault, poolAttributes as NSDictionary?, outputBufferAttributes as NSDictionary?, &cvPixelBufferPool)
            guard let pixelBufferPool = cvPixelBufferPool else {
                assertionFailure("Allocation failure: Could not create pixel buffer pool")
                return nil
            }
            return pixelBufferPool
        }
    

    d、创建prepare函数

     func prepare(with formatDescription: CMFormatDescription, outputRetainedBufferCountHint: Int) {
      ...
    }
    

    在prepare函数中调用allocateOutputBufferPool

            outputPixelBufferPool = DepthToJETConverter.allocateOutputBufferPool(with: formatDescription, outputRetainedBufferCountHint: outputRetainedBufferCountHint)
            if outputPixelBufferPool == nil {
                  return
            }
    

    设置inputFormatDescription、outputFormatDescription

            var pixelBuffer: CVPixelBuffer?
            var pixelBufferFormatDescription: CMFormatDescription?
            _ = CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, outputPixelBufferPool!, &pixelBuffer)
            if pixelBuffer != nil {
                CMVideoFormatDescriptionCreateForImageBuffer(allocator: kCFAllocatorDefault,
                                                             imageBuffer: pixelBuffer!,
                                                             formatDescriptionOut: &pixelBufferFormatDescription)
            }
            pixelBuffer = nil
            
            inputFormatDescription = formatDescription
            outputFormatDescription = pixelBufferFormatDescription
    

    设置inputTextureFormat

            let inputMediaSubType = CMFormatDescriptionGetMediaSubType(formatDescription)
            if inputMediaSubType == kCVPixelFormatType_DepthFloat16 {
                inputTextureFormat = .r16Float
            } else {
                assertionFailure("Input format not supported")
            }
    

    设置textureCache

            var metalTextureCache: CVMetalTextureCache?
            if CVMetalTextureCacheCreate(kCFAllocatorDefault, nil, metalDevice, nil, &metalTextureCache) != kCVReturnSuccess {
                assertionFailure("Unable to allocate depth converter texture cache")
            } else {
                textureCache = metalTextureCache
            }
    

    设置colorBuf

            let colorTable = ColorTable(metalDevice: metalDevice, size: self.colors)
            colorBuf = colorTable.getColorTable()
    

    e、在render中进行转换操作

    func render(pixelBuffer: CVPixelBuffer) -> CVPixelBuffer? {
    ... ...
    }
    

    建立一个CVPixelBuffer

             var newPixelBuffer: CVPixelBuffer?
            CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, outputPixelBufferPool!, &newPixelBuffer)
            guard let outputPixelBuffer = newPixelBuffer else {
                print("Allocation failure: Could not get pixel buffer from pool (\(self.description))")
                return nil
            }
    

    建立histogramBuffer

    let hist = histogramBuffer.contents().bindMemory(to: Float.self, capacity: Int(self.jetParams.histogramSize))
            
            HistogramCalculator.calcHistogram(for: pixelBuffer,
                                              toBuffer: hist,
                                              withSize: self.jetParams.histogramSize,
                                              forColors: Int32(colors),
                                              minDepth: 0.0,
                                              maxDepth: 5.0,
                                              binningFactor: self.jetParams.binningFactor)
    

    建立outputTexture、inputTexture

          guard let outputTexture = makeTextureFromCVPixelBuffer(pixelBuffer: outputPixelBuffer, textureFormat: .bgra8Unorm),
                let inputTexture = makeTextureFromCVPixelBuffer(pixelBuffer: pixelBuffer, textureFormat: inputTextureFormat) else {
                    return nil
            }
    

    建立并执行commandQueue

          guard let commandQueue = commandQueue,
                let commandBuffer = commandQueue.makeCommandBuffer(),
                let commandEncoder = commandBuffer.makeComputeCommandEncoder() else {
                    print("Failed to create Metal command queue")
                    CVMetalTextureCacheFlush(textureCache!, 0)
                    return nil
            }
            
            commandEncoder.label = "Depth to JET"
            commandEncoder.setComputePipelineState(computePipelineState!)
            commandEncoder.setTexture(inputTexture, index: 0)
            commandEncoder.setTexture(outputTexture, index: 1)
            commandEncoder.setBuffer(self.jetParamsBuffer, offset: 0, index: 0)
            commandEncoder.setBuffer(self.histogramBuffer, offset: 0, index: 1)
            commandEncoder.setBuffer(colorBuf, offset: 0, index: 2)
            
            // Set up thread groups as described in https://developer.apple.com/reference/metal/mtlcomputecommandencoder
            let width = computePipelineState!.threadExecutionWidth
            let height = computePipelineState!.maxTotalThreadsPerThreadgroup / width
            let threadsPerThreadgroup = MTLSizeMake(width, height, 1)
            let threadgroupsPerGrid = MTLSize(width: (inputTexture.width + width - 1) / width,
                                              height: (inputTexture.height + height - 1) / height,
                                              depth: 1)
            commandEncoder.dispatchThreadgroups(threadgroupsPerGrid, threadsPerThreadgroup: threadsPerThreadgroup)
            
            commandEncoder.endEncoding()
            
            commandBuffer.commit()
    

    相关文章

      网友评论

          本文标题:深度图转伪色彩图源码阅读(swift、metal)

          本文链接:https://www.haomeiwen.com/subject/dsphzftx.html