質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.59%

マーカをスマホカメラで認識すると、マーカの大きさと同じ大きさのクロマキー処理された動画がマーカに重なるように再生される。

受付中

回答 0

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 466

score 4

前提・実現したいこと

タイトルにあるようなアプリケーションを制作しています。
二つの別々のプログラムを一つにまとめることができず困っています。

一つは、あらかじめ登録しておいたマーカ画像をカメラで認識すると、デバイスの画面上ではマーカの大きさと同じ大きさの(クロマキー処理されていない)動画がマーカの二次元平面上で覆いかぶさるように再生されます。
※ハリーポッター の中の人が動く肖像画を想像していただけるとわかりやすいかと思います。
二つ目は、アプリを立ち上げるとともに、(マーカを認識せずとも)クロマキー処理された動画が(マーカの大きさと異なる大きさで)再生されます。

この二つのプログラムを一つにまとめるとタイトルのようなアプリケーションが制作できると思うのですが、うまくいきません。

※MikaRika.MP4が再生したい動画
※ChromaKeyFilterという別ファイルで動画の緑色の部分をクロマキー処理しています

一つ目のコードViewController.swift

import UIKit
import SceneKit
import ARKit
import CoreMedia

class ViewController: UIViewController{
  @IBOutlet var sceneView: ARSCNView!
  @IBOutlet weak var instructionLabel: UILabel!

  private var imageConfiguration: ARImageTrackingConfiguration?
  private var worldConfiguration: ARWorldTrackingConfiguration?

  override func viewDidLoad() {
    super.viewDidLoad()
    sceneView.delegate = self

    let scene = SCNScene()
    sceneView.scene = scene

    setupObjectDetection()
  }

  override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    if let configuration = worldConfiguration {
      sceneView.debugOptions = .showFeaturePoints
      sceneView.session.run(configuration)
    }
  }

  override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    sceneView.session.pause()
  }

  private func setupImageDetection() {
    imageConfiguration = ARImageTrackingConfiguration()

    guard let referenceImages = ARReferenceImage.referenceImages(
      inGroupNamed: "AR Images", bundle: nil) else {
        fatalError("Missing expected asset catalog resources.")
    }
    imageConfiguration?.trackingImages = referenceImages
  }

  private func setupObjectDetection() {
    worldConfiguration = ARWorldTrackingConfiguration()
    worldConfiguration?.detectionObjects = referenceObjects

    guard let referenceImages = ARReferenceImage.referenceImages(
      inGroupNamed: "AR Images", bundle: nil) else {
        fatalError("Missing expected asset catalog resources.")
    }
    worldConfiguration?.detectionImages = referenceImages
  }
}

extension ViewController: ARSCNViewDelegate {
  func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
    DispatchQueue.main.async { self.instructionLabel.isHidden = true }
    if let imageAnchor = anchor as? ARImageAnchor {
      handleFoundImage(imageAnchor, node)
    } else if let objectAnchor = anchor as? ARObjectAnchor {
      handleFoundObject(objectAnchor, node)
    }
  }

  private func handleFoundImage(_ imageAnchor: ARImageAnchor, _ node: SCNNode) {
    let name = imageAnchor.referenceImage.name!
    print("you found a \(name) image")

    let size = imageAnchor.referenceImage.physicalSize
    if let videoNode = makeDinosaurVideo(size: size) {
      node.addChildNode(videoNode)
      node.opacity = 1
    }
  }

  private func makeDinosaurVideo(size: CGSize) -> SCNNode? {
    guard let videoURL = Bundle.main.url(forResource: "MikaRika",
                                         withExtension: "mp4") else {
                                          return nil
    }

    let avPlayerItem = AVPlayerItem(url: videoURL)
    let avPlayer = AVPlayer(playerItem: avPlayerItem)
    avPlayer.play()

    NotificationCenter.default.addObserver(
      forName: .AVPlayerItemDidPlayToEndTime,
      object: nil,
      queue: nil) { notification in
        avPlayer.seek(to: .zero)
        avPlayer.play()
    }

    let avMaterial = SCNMaterial()
    avMaterial.diffuse.contents = avPlayer

    let videoPlane = SCNPlane(width: size.width, height: size.height)
    videoPlane.materials = [avMaterial]

    let videoNode = SCNNode(geometry: videoPlane)
    videoNode.eulerAngles.x = -.pi / 2
    return videoNode
  }
  }

二つ目のコードViewController.swift

class VideoMetalView: MTKView {

    private let colorSpace = CGColorSpaceCreateDeviceRGB()
private let videoOutput = AVPlayerItemVideoOutput.init(pixelBufferAttributes: [
    kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,
    ] as [String: Any])

    private var ciContext: CIContext!
    private var player: AVPlayer?
    private var bufferMtkView: MTKView!

    private var pipelineState: MTLComputePipelineState!
    private var defaultLibrary: MTLLibrary!
    private var commandQueue: MTLCommandQueue!
    private var threadsPerThreadgroup: MTLSize!
    private var threadgroupsPerGrid: MTLSize!

    required init(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    required init(frame: CGRect, device: MTLDevice) {

        ciContext = CIContext(mtlDevice: device)

        super.init(frame: frame, device: device)

        framebufferOnly = false
        isOpaque = false
        backgroundColor = .clear

        commandQueue = device.makeCommandQueue()
        defaultLibrary = device.makeDefaultLibrary()!
        pipelineState = try! device.makeComputePipelineState(function: defaultLibrary.makeFunction(name: "ChromaKeyFilter")!)
        threadsPerThreadgroup = MTLSizeMake(16, 16, 1)

        bufferMtkView = MTKView(frame: frame, device: device)
        bufferMtkView.translatesAutoresizingMaskIntoConstraints = false
        bufferMtkView.framebufferOnly = false
        bufferMtkView.isHidden = true
        addSubview(bufferMtkView)
    }

func setupPlayer(url: URL) {
    player = AVPlayer(url: url)

    player!.actionAtItemEnd = AVPlayer.ActionAtItemEnd.none
    NotificationCenter.default.addObserver(self,
                                           selector: #selector(VideoMetalView.didPlayToEnd),
                                           name: NSNotification.Name("AVPlayerItemDidPlayToEndTimeNotification"),
                                           object: player!.currentItem)

    guard let player = player,
        let videoItem = player.currentItem else { return }

    videoItem.add(videoOutput)
}

    func play() {
        player?.play()
    }

    func pause() {
        player?.pause()
    }

    func updateThreadgroupsPerGrid() {
        threadgroupsPerGrid = MTLSizeMake(
            Int(ceilf(Float(frame.width) / Float(threadsPerThreadgroup.width))),
            Int(ceilf(Float(frame.height) / Float(threadsPerThreadgroup.height))),
            1)
    }

    func updateDrawableSize() {
        drawableSize = bounds.size
        bufferMtkView.drawableSize = bufferMtkView.bounds.size
    }

override func draw(_ dirtyRect: CGRect) {

    guard let device = device,
        let drawable = currentDrawable,
        let tempDrawable = bufferMtkView.currentDrawable,
        let image = makeCurrentVideoImage() else { return }

    ciContext.render(image, to: tempDrawable.texture, commandBuffer: nil, bounds: bounds, colorSpace: colorSpace)
    colorPixelFormat = tempDrawable.texture.pixelFormat

    let commandBuffer = commandQueue.makeCommandBuffer()!
    let commandEncoder = commandBuffer.makeComputeCommandEncoder()!

    commandEncoder.setComputePipelineState(pipelineState)

    commandEncoder.setTexture(tempDrawable.texture, index: 0)
    commandEncoder.setTexture(drawable.texture, index: 1)

    let factors: [Float] = [
        0,    // red
        1,    // green
        0,    // blue
        0.43, // threshold
        0.11  // smoothing
    ]
    for i in 0..<factors.count {
        var factor = factors[i]
        let size = max(MemoryLayout<Float>.size, 16)
        let buffer = device.makeBuffer(
            bytes: &factor,
            length: size,
            options: [.storageModeShared]
        )
        commandEncoder.setBuffer(buffer, offset: 0, index: i)
    }

    commandEncoder.dispatchThreadgroups(threadgroupsPerGrid,
                                        threadsPerThreadgroup: threadsPerThreadgroup)
    commandEncoder.endEncoding()

    commandBuffer.present(drawable)
    commandBuffer.commit()
}

    private func makeCurrentVideoImage() -> CIImage? {
        guard let player = player,
            let videoItem = player.currentItem
            else { return nil }
        let time = videoItem.currentTime()
        guard
            videoOutput.hasNewPixelBuffer(forItemTime: time),
            let pixelBuffer = videoOutput.copyPixelBuffer(forItemTime: time,
                                                          itemTimeForDisplay: nil)
            else { return nil }

        return CIImage(cvPixelBuffer: pixelBuffer)
    }

    @objc private func didPlayToEnd(notification: NSNotification) {
        let item: AVPlayerItem = notification.object as! AVPlayerItem
        item.seek(to: CMTime.zero, completionHandler: nil)
    }
}

試したこと

いろいろな箇所を追加したり省略したり試みたのですがどうもうまくいかず行き詰まっている状態です。
具体的には、レンダリングに不具合が生じておりアプリ立ち上げから動きません。原因として考えられるのは、レンダリングの命令文が重複して互いに干渉しているのかと考えております。

補足情報(FW/ツールのバージョンなど)

target ios13.1
Xcode 11

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正の依頼

  • t_obara

    2020/01/31 13:08

    「うまくいかない」をもう少し具体的に説明されると回答が得られやすくなるかもしれません。

    キャンセル

まだ回答がついていません

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.59%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • トップ
  • iOSに関する質問
  • マーカをスマホカメラで認識すると、マーカの大きさと同じ大きさのクロマキー処理された動画がマーカに重なるように再生される。