ARKit / SpriteKit - установите pixelBufferAttributes на SKVideoNode или сделайте прозрачные пиксели в видео (эффект хроматического ключа) другим способом - PullRequest
0 голосов
/ 02 мая 2018

Моя цель - представить 2D анимированных персонажей в реальной среде, используя ARKit. Анимированные персонажи являются частью видео, представленного в следующем снимке из видео:

Snapshot from the video

Отображение самого видео было достигнуто без проблем при использовании кода:

func view(_ view: ARSKView, nodeFor anchor: ARAnchor) -> SKNode? {
    guard let urlString = Bundle.main.path(forResource: "resourceName", ofType: "mp4") else { return nil }

    let url = URL(fileURLWithPath: urlString)
    let asset = AVAsset(url: url)
    let item = AVPlayerItem(asset: asset)
    let player = AVPlayer(playerItem: item)

    let videoNode = SKVideoNode(avPlayer: player)
    videoNode.size = CGSize(width: 200.0, height: 150.0)
    videoNode.anchorPoint = CGPoint(x: 0.5, y: 0.0)

    return videoNode
}

Результат этого кода представлен на снимке экрана нижеприведенного приложения:

App screenshot #1

Но, как вы можете видеть, фон персонажей не очень приятный, поэтому мне нужно сделать так, чтобы он исчез, чтобы создать иллюзию персонажей, фактически стоящих на поверхности горизонтальной плоскости. Я пытаюсь добиться этого, используя эффект хроматического ключа для видео.

  • Для тех, кто не знаком с хроматическим ключом, это название «эффекта зеленого экрана», которое иногда можно увидеть на телевизоре, чтобы сделать цвет прозрачным.

Мой подход к эффекту хроматического ключа - создать пользовательский фильтр на основе "CIColorCube" CIFilter, а затем применить фильтр к видео, используя AVVideoComposition.

Во-первых, это код для создания фильтра:

func RGBtoHSV(r : Float, g : Float, b : Float) -> (h : Float, s : Float, v : Float) {
    var h : CGFloat = 0
    var s : CGFloat = 0
    var v : CGFloat = 0
    let col = UIColor(red: CGFloat(r), green: CGFloat(g), blue: CGFloat(b), alpha: 1.0)
    col.getHue(&h, saturation: &s, brightness: &v, alpha: nil)
    return (Float(h), Float(s), Float(v))
}

func colorCubeFilterForChromaKey(hueAngle: Float) -> CIFilter {

    let hueRange: Float = 20 // degrees size pie shape that we want to replace
    let minHueAngle: Float = (hueAngle - hueRange/2.0) / 360
    let maxHueAngle: Float = (hueAngle + hueRange/2.0) / 360

    let size = 64
    var cubeData = [Float](repeating: 0, count: size * size * size * 4)
    var rgb: [Float] = [0, 0, 0]
    var hsv: (h : Float, s : Float, v : Float)
    var offset = 0

    for z in 0 ..< size {
        rgb[2] = Float(z) / Float(size) // blue value
        for y in 0 ..< size {
            rgb[1] = Float(y) / Float(size) // green value
            for x in 0 ..< size {

                rgb[0] = Float(x) / Float(size) // red value
                hsv = RGBtoHSV(r: rgb[0], g: rgb[1], b: rgb[2])
                // TODO: Check if hsv.s > 0.5 is really nesseccary
                let alpha: Float = (hsv.h > minHueAngle && hsv.h < maxHueAngle && hsv.s > 0.5) ? 0 : 1.0

                cubeData[offset] = rgb[0] * alpha
                cubeData[offset + 1] = rgb[1] * alpha
                cubeData[offset + 2] = rgb[2] * alpha
                cubeData[offset + 3] = alpha
                offset += 4
            }
        }
    }
    let b = cubeData.withUnsafeBufferPointer { Data(buffer: $0) }
    let data = b as NSData

    let colorCube = CIFilter(name: "CIColorCube", withInputParameters: [
        "inputCubeDimension": size,
        "inputCubeData": data
        ])
    return colorCube!
}

А потом код для применения фильтра к видео путем изменения функции func view(_ view: ARSKView, nodeFor anchor: ARAnchor) -> SKNode?, которую я написал ранее:

func view(_ view: ARSKView, nodeFor anchor: ARAnchor) -> SKNode? {
    guard let urlString = Bundle.main.path(forResource: "resourceName", ofType: "mp4") else { return nil }

    let url = URL(fileURLWithPath: urlString)
    let asset = AVAsset(url: url)

    let filter = colorCubeFilterForChromaKey(hueAngle: 38)
    let composition = AVVideoComposition(asset: asset, applyingCIFiltersWithHandler: { request in
        let source = request.sourceImage
        filter.setValue(source, forKey: kCIInputImageKey)
        let output = filter.outputImage

        request.finish(with: output!, context: nil)
    })

    let item = AVPlayerItem(asset: asset)
    item.videoComposition = composition
    let player = AVPlayer(playerItem: item)

    let videoNode = SKVideoNode(avPlayer: player)
    videoNode.size = CGSize(width: 200.0, height: 150.0)
    videoNode.anchorPoint = CGPoint(x: 0.5, y: 0.0)

    return videoNode
}

Код должен заменить все пиксели каждого кадра видео на alpha = 0.0, если цвет пикселя соответствует диапазону оттенка фона. Но вместо того, чтобы получать прозрачные пиксели, я получаю эти пиксели черными, как видно на рисунке ниже:

App screenshot #2

Теперь, несмотря на то, что это не нужный эффект, меня это не удивляет, так как я знал, что именно так iOS отображает видео с альфа-каналом. Но вот реальная проблема - при отображении обычного видео в AVPlayer, есть возможность добавить AVPlayerLayer к представлению и установить pixelBufferAttributes, чтобы слой игрока знал, что мы используем прозрачный пиксельный буфер, вот так:

let playerLayer = AVPlayerLayer(player: player)
playerLayer.bounds = view.bounds
playerLayer.position = view.center
playerLayer.pixelBufferAttributes = [(kCVPixelBufferPixelFormatTypeKey as String): kCVPixelFormatType_32BGRA]
view.layer.addSublayer(playerLayer)

Этот код дает нам видео с прозрачным фоном ( ХОРОШО! ), но с фиксированным размером и положением ( НЕ ХОРОШО ... ), как вы можете видеть на этом скриншоте:

App screenshot #3

Я хочу добиться того же эффекта, но на SKVideoNode, а не на AVPlayerLayer. Тем не менее, я не могу найти способ установить pixelBufferAttributes на SKVideoNode, и установка слоя игрока не дает желаемого эффекта ARKit, поскольку он зафиксирован в позиции.

Есть ли какое-нибудь решение моей проблемы, или, может быть, есть другой метод для достижения того же желаемого эффекта?

Ответы [ 2 ]

0 голосов
/ 21 июня 2019

Спасибо! Была такая же проблема + смешивание [AR / Scene / Sprite] Kit. Но я бы порекомендовал использовать этот алгоритм вместо. Это дает лучший результат:

...
var r: [Float] = removeChromaKeyColor(r: rgb[0], g: rgb[1], b: rgb[2])
                cubeData[offset] = r[0]
                cubeData[offset + 1] = r[1]
                cubeData[offset + 2] = r[2]
                cubeData[offset + 3] = r[3]
                offset += 4
...

func removeChromaKeyColor(r: Float, g: Float, b: Float) -> [Float] {
    let threshold: Float = 0.1
    let refColor: [Float] = [0, 1.0, 0, 1.0]    // chroma key color

    //http://www.shaderslab.com/demo-40---video-in-video-with-green-chromakey.html
    let val = ceil(saturate(g - r - threshold)) * ceil(saturate(g - b - threshold))
    var result = lerp(a: [r, g, b, 0.0], b: refColor, w: val)
    result[3] = fabs(1.0 - result[3])

    return result
}

func saturate(_ x: Float) -> Float {
    return max(0, min(1, x));
}

func ceil(_ v: Float) -> Float {
    return -floor(-v);
}

func lerp(a: [Float], b: [Float], w: Float) -> [Float] {
    return [a[0]+w*(b[0]-a[0]), a[1]+w*(b[1]-a[1]), a[2]+w*(b[2]-a[2]), a[3]+w*(b[3]-a[3])];
}
0 голосов
/ 21 мая 2018

Решение довольно простое! Все, что нужно сделать, это добавить видео в качестве дочернего элемента SKEffectNode и применить фильтр к SKEffectNode вместо самого видео (AVVideoComposition не обязательно). Вот код, который я использовал:

func view(_ view: ARSKView, nodeFor anchor: ARAnchor) -> SKNode? {
    // Create and configure a node for the anchor added to the view's session.
    let bialikVideoNode = videoNodeWith(resourceName: "Tsina_05", ofType: "mp4")
    bialikVideoNode.size = CGSize(width: kDizengofVideoWidth, height: kDizengofVideoHeight)
    bialikVideoNode.anchorPoint = CGPoint(x: 0.5, y: 0.0)

    // Make the video background transparent using an SKEffectNode, since chroma-key doesn't work on video
    let effectNode = SKEffectNode()
    effectNode.addChild(bialikVideoNode)
    effectNode.filter = colorCubeFilterForChromaKey(hueAngle: 120)

    return effectNode
}

И вот результат по мере необходимости: enter image description here

...