💨

iOS pixel format変換

2023/05/09に公開

最近、サードパーティが開発した美肌ライブラリを使用していて、特定のピクセルフォーマットのみに対応していることがわかり、変更する必要がありました。ピクセルフォーマットの変換方法を調べたところ、3つの方法があり、それぞれの違いをまとめました。

一般的には、YUV420から処理しやすいBGRAへの変換が行われます。

今回は、kCVPixelFormatType_420YpCbCr8BiPlanarFullRangeからkCVPixelFormatType_32BGRAへの変換パフォーマンスの違いを比較していきたいと思います。

vImage

vImageは、AppleのAccelerateフレームワークの一部であり、画像処理に関する高速で効率的な機能を提供しています。vImageは、低レベルの画像処理タスクを行うための最適化された関数群を提供し、iOS、macOS、tvOS、そしてwatchOSで利用できます。

特徴としては、CPUを使用しており、画像や動画編集アプリの作成に適しています。ただし、CPU使用により端末の熱がひどくなることがあり、実装が複雑で、一部のピクセルフォーマット変換のみに対応しています。

vImageのピクセルフォーマット変換の詳細は@noppefoxwolfのQiita記事を参照してください。

VTPixelTransferSession

VTPixelTransferSessionは、Video Toolboxフレームワークの一部で、AppleのCore Videoフレームワークと連携して画像処理ができます。ピクセルフォーマットや色空間、サイズの変換が可能です。

VTPixelTransferSessionは、GPUを使用していると思われます。このAPIでは、任意のピクセルフォーマット変換ができます。

サンプルコードは以下の通りです:
https://github.com/huiping192/PixelTransferKit

import VideoToolbox
import CoreVideo

var session: VTPixelTransferSession?
VTPixelTransferSessionCreate(allocator: kCFAllocatorDefault, pixelTransferSessionOut: &session)

let properties: NSDictionary = [
  kVTPixelTransferPropertyKey_RealTime: (kCFBooleanTrue) as Any
]
let setPropertyStatus = VTSessionSetProperties(pixelTransferSession, propertyDictionary: properties)

let pixelBufferAttributes: [CFString: Any] = [
  kCVPixelBufferPixelFormatTypeKey: destinationPixelFormat,
  kCVPixelBufferWidthKey: CVPixelBufferGetWidth(sourcePixelBuffer),
  kCVPixelBufferHeightKey: CVPixelBufferGetHeight(sourcePixelBuffer),
]

var destinationPixelBuffer: CVPixelBuffer?
let status = CVPixelBufferCreate(
  kCFAllocatorDefault,
  CVPixelBufferGetWidth(sourcePixelBuffer),
  CVPixelBufferGetHeight(sourcePixelBuffer),
  destinationPixelFormat,
  pixelBufferAttributes as CFDictionary,
  &destinationPixelBuffer
)

if status != kCVReturnSuccess {
  print("Error creating destination pixel buffer: \(status)")
  return
}

guard let outputPixelBuffer = destinationPixelBuffer else {
  print("[PixelTransferKit] Destination pixel buffer is not available")
  return
}

let transferStatus = VTPixelTransferSessionTransferImage(session, from: sourcePixelBuffer, to: outputPixelBuffer)

// outputPixelBuffer使う
    

Metal

Metalでは、GPUを使用して独自の変換実装が可能です。

GPUを使用して高速化が可能ですが、独自の実装が必要であり、またフォーマットごとに実装が必要であるため、コストがかかります。

Metalでのピクセルフォーマット変換の詳細は、@noppefoxwolfのQiita記事を参照してください。

パフォーマンス計測

それぞれvImage、VTPixelTransferSession、Metalで変換パフォーマンスの計測を行いました。

結果

M2 Pro Mac Xcode 14.3、1080pで1000回変換した結果、Metal処理が最も速く、vImageが最も遅かったです。

Test Case '-[PixelTransferKitTests.PixelTransferPerformanceTests testVImagePerformance]' passed (18.338 seconds).
Test Case '-[PixelTransferKitTests.PixelTransferPerformanceTests testPixelTransferKitPerformance]' passed (3.733 seconds).
Test Case '-[PixelTransferKitTests.PixelTransferPerformanceTests testBlueDressPerformance]' passed (0.477 seconds).

テスト用コード

PixelTransferPerformanceTests.swift

所感

  • vImageは最も重く、APIも複雑です。
  • VTPixelTransferSessionはフォーマットタイプに全対応しており、手軽に使えます。
  • Metal実装は独自の実装が必要ですが、パフォーマンスは最も優れています。

画像や動画の処理を行いたい場合、手軽に使えるVTPixelTransferSessionを最初に試して、さらなるパフォーマンスが求められる場合は、独自のMetal実装を行うのがいいと思います。

参考

https://qiita.com/noppefoxwolf/items/b12d56e052664a21d8b6
https://qiita.com/noppefoxwolf/items/2ae2eb5a5c30615c5dcc
https://developer.apple.com/documentation/accelerate/vimage
https://developer.apple.com/documentation/videotoolbox/vtpixeltransfersession-7cg

Discussion