LoginSignup
4
4

More than 3 years have passed since last update.

Create ML で Video Style Transfer モデルが追加された(WWDC2020)ということもあり、出力画像を動画にしたい場面もあるのではないでしょうか。

AVFoundation フレームワークで可能です。

ぼくはカメラの出力を一旦 Pixel Buffer コピーの配列にして保存してから、順次処理しました。
VideoCaptureOutputのフレームを参照し続けると処理が止まるので、PixelBuffer をディープコピーしてから使用した方がいいと思ってます。

手順

1,AVAssetWriterとを準備

サイズを Core ML の出力に合わせておきました。

func assetWriterSetting(){
    guard let url = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent(fileName + ".mp4") else { print("nil"); return}
    print(url)
    let videoSettings = [
      AVVideoWidthKey: 256,
      AVVideoHeightKey: 256,
      AVVideoCodecKey: AVVideoCodecType.h264
    ] as [String: Any]

    videoAssetInput = AVAssetWriterInput(mediaType: .video, outputSettings: videoSettings)
    pixelBufferAdaptor = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: videoAssetInput, sourcePixelBufferAttributes: [kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_32BGRA)])
    frameNumber = 0
    do {
        try assetWriter = AVAssetWriter(outputURL: url, fileType: .mp4)
       assetWriter.add(videoAssetInput)
       assetWriter.startWriting()
       assetWriter.startSession(atSourceTime: CMTime.zero)
       } catch {
          print("could not start video recording ", error)
      }
}

2,PixelBufferを追加していく

VNCoreMLRequest の Completion Handler 内で Pixel Buffer を AVAssetWriterInputPixelBufferAdaptor に追加していきます。
ぼくは Core ML Helpers をつかって Multi Array から CGImage に変換したので、 CGImage を Pixel Buffer に変換しています。

上記 AssetWriter のセッティングで sourcePixelBufferAttributes を BGRA にしているため、 RGB から BGR 色空間への変換もおこなっています。 AssetWriter のセッティングで RGB を設定する方法を知っている方がいたら、教えてください。

Pixel Buffer Poolがnilで返ってくる時は、AssetWriterの設定ミス(書き込み先URLが既に存在している。sourcePixelBufferAttributesで取扱不可のものを設定している)が原因のことが多いです。

let result = coreMLRequest.results?.first as! VNCoreMLFeatureValueObservation
let multiArray = result.featureValue.multiArrayValue
guard let cgimage = multiArray?.cgImage(min: -1, max: 1, channel: nil)?.toBGR() else {print("drop"); return}
guard let pixelBufferPool = pixelBufferAdaptor.pixelBufferPool else {
    fatalError("Failed to allocate the PixelBufferPool")
}
var pixelBufferOut: CVPixelBuffer? = nil
CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pixelBufferPool, &pixelBufferOut)

guard let pixelBuffer = pixelBufferOut else {
    fatalError("Failed to create the PixelBuffer")
}

CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: CVOptionFlags(0)))

let context = CGContext(
    data: CVPixelBufferGetBaseAddress(pixelBuffer),
    width: cgimage.width,
    height: cgimage.height,
    bitsPerComponent: cgimage.bitsPerComponent,
    bytesPerRow: cgimage.bytesPerRow,
    space: CGColorSpaceCreateDeviceRGB(),
    bitmapInfo: cgimage.bitmapInfo.rawValue)
context?.draw(cgimage, in: cgimage.frame)

CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: CVOptionFlags(0)))
if videoAssetInput.isReadyForMoreMediaData {

//処理速度が速すぎると、 isReadyForMoreMediaData が追いつかずに PixelBufferを追加できないことがあります。 適宜調整してください。

frameTime = CMTimeMake(value: Int64(Double(frameCount * fps) * durationForEachImage), timescale: Int32(fps))
pixelBufferAdaptor.append(pixelBuffer, withPresentationTime: frameTime)
frameCount += 1
print(frameTime)

CGImage のサイズ取得とBGR変換のextension

extension CGImage {
    var frame: CGRect {
        return CGRect(x: 0, y: 0, width: self.width, height: self.height)
    }

    func toBGR()->CGImage{
        let ciImage = CIImage(cgImage: self)
        let ctx = CIContext(options: nil)
        let swapKernel = CIColorKernel( source:
                                            "kernel vec4 swapRedAndGreenAmount(__sample s) {" +
                                            "return s.bgra;" +
                                            "}"
        )
        let ciOutput = swapKernel?.apply(extent: (ciImage.extent), arguments: [ciImage as Any])
        let cgOut:CGImage = ctx.createCGImage(ciOutput!, from: ciOutput!.extent)!
        return cgOut
    }
}

3,書き込み

if mlRequestsEnded {
   videoAssetInput.markAsFinished()
   assetWriter.endSession(atSourceTime: frameTime)    
   assetWriter.finishWriting(completionHandler: { [self] in
   print("comp")
}
4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4