MILLEN BOX

音楽好きの組み込みソフトエンジニアによるプログラミング(主にiOSアプリ開発)の勉強の記録

CVPixelBufferを回転させたい時の話

あんまり役に立つ人はいないかもしれないけど、折角分かった事だから残しておこうと思います。

CVPixelBuffer型の何か(以後pixelBufferとします)を回転させたい場合、これを直接回転させることはできません。
CVPixelBufferをUIImage、CGImage、CIImageの何れかに変換して回転させる必要があります。

調べた限りCIImageに変換して回転させるのが省エネだし一番簡単です。

CIImageへの変換

まずCIImageへの変換

let ciImage: CIImage = CIImage(cvPixelBuffer: pixelBuffer)

上記で変換できます。

回転

お次は回転。
回転については色々やり方がありますが…
今回はiOS11で追加されたメソッド oriented を使います。
理由はとてもシンプルな使い勝手だから。

(参考) oriented(_:) - CIImage | Apple Developer Documentation

let orientation :CGImagePropertyOrientation = CGImagePropertyOrientation.right
let orientedImage = ciImage.oriented(orientation)

上記だとorientedImageが元画像は右に90°回転したCIImageになります。

CIImageからCVPixelBufferへの戻し変換

最後にCIImage→CVPixelBufferへの戻し変換作業です。
実はここが一番引っかかったところ。
ciImageには元々CVPixelBuffer型のpixelBufferというプロパティがあり、
これを参照すれば楽勝で回転したnewPixelBufferを取得できると考えていました。
しかし…
これ、うまくいきません!!!
正確な理由は調査していません(失礼!)が、ある決まった作成方法で作られたCIImageのみ情報が保存されているでしょうね。
(証拠にpixelBufferをCIImageに変換し、回転させずにそのままnewPixelBufferに戻したものは問題なく取り出せました。)

ではどうするのか?(CIImageからCVPixelBufferへの戻し変換)

  1. CVPixelBuffer型の変数を新たに作成する。
  2. CIContext型のコンテキストを用意して、CIImageを元にCVPixelBuffer型へrenderする。

と、してあげるのが一番シンプルな書き方っぽいです。(この書き方であってるんだろうか..??)

今回いっちばん引っかかったのは「CVPixelBuffer型の変数を新たに作成する」ところ。
CVPixelBufferCreateで新たにBuffer変数を作成する時、作成Optionを指定してあげなければなりません。
実は今回は前回の記事 AVPlayerItemVideoOutputを使ってムービーファイルから取得したPixelBufferからムービーの再生させてみた で再生されているムービーを回転させたかったことが出発点になっているのですが、その場合、正確にoptionを指定してあげなければ再生可能なCVPixelBufferにならないのです。
具体的には「iosurface」が存在するCVPixelBufferを作成するために kCVPixelBufferIOSurfacePropertiesKey as String: [:]] as [String : Any] を指定してあげなければいけないのです。ここ大事です。

コードは以下。convertFromCIImageToCVPixelBuffer というメソッドを作りました。

 private func convertFromCIImageToCVPixelBuffer (ciImage:CIImage, pxbuffer:CVPixelBuffer) -> CVPixelBuffer? {
        let size:CGSize = ciImage.extent.size
        var pixelBuffer:CVPixelBuffer?
        let options = [
            kCVPixelBufferCGImageCompatibilityKey as String: true,
            kCVPixelBufferCGBitmapContextCompatibilityKey as String: true,
            kCVPixelBufferIOSurfacePropertiesKey as String: [:]
            ] as [String : Any]
        
        let status:CVReturn = CVPixelBufferCreate(kCFAllocatorDefault,
                                                  Int(size.width),
                                                  Int(size.height),
                                                  kCVPixelFormatType_32BGRA,
                                                  options as CFDictionary,
                                                  &pixelBuffer)
        
        
        let ciContext = CIContext()
        
        if (status == kCVReturnSuccess && pixelBuffer != nil) {
            ciContext.render(ciImage, to: pixelBuffer!)
        }
        
        return pixelBuffer
    }

はじめに書いた通り、役に立つ方がおられるかは不明ですが、折角調べたことを忘れちゃうのもアレなんでブログに残しときました。
それでは。