画像をグレースケールへ変換する(Metal編)

RGBのMTLTextureをグレースケールのMTLTextureに変換する方法

UIImageを変換する方法はこちらを参照

Metal上でこういった画像処理をしたい場合は、MetalPerformanceShadersを利用する。 今回のような色空間の変更だけでなく、各種フィルタ処理なども高速に処理できる(どれくらい高速かは未確認)

ただし、iOS10(iOS_GPUFamily2_v3)以降でしか使えない

使い方

色空間の変更にはMPSImageConversionを利用する

色空間の定義はこちらAccessing System-Defined Color Spacesを参照

  1. 変換元と変換後の色空間を定義する
    sRGBからグレースケールの場合は以下のとおり

    let srcColorSpace = CGColorSpace(name: CGColorSpace.sRGB)!
    let dstColorSpace = CGColorSpace(name: CGColorSpace.linearGray)!
    let conversionInfo = CGColorConversionInfo(src: srcColorSpace,
                                               dst: dstColorSpace)
    
  2. フィルタ(カーネル)を生成する

    let device = MTLCreateSystemDefaultDevice()!
    let conversion = MPSImageConversion(device: device,
                                        srcAlpha: .alphaIsOne,
                                        destAlpha: .alphaIsOne,
                                        backgroundColor: nil,
                                        conversionInfo: conversionInfo)
    

    αは特に関係ないのでデフォルトの1に、背景色も指定しないのでnilにしておく

  3. 実行する

    let commandQueue = device.makeCommandQueue()
    let commandBuffer = commandQueue.makeCommandBuffer()
    
    conversion.encode(commandBuffer: commandBuffer,
                      sourceTexture: colorTexture,
                      destinationTexture: grayTexture)
    // 必要に応じて他のMetalの処理
    commandBuffer.commit()
    

    これでgrayTextureにグレースケール化された画像が入る

小ネタ

色空間の調べ方

変換先の色空間はともかく、変換元の色空間がなんなのか判らない場合、 UIImageCGImage)であれば以下で取得できる

let image: UIImage = ...
let colorSpace = image.cgImage!.colorSpace

ちなみにnilになる場合は、マスク画像が入っているらしい

MPSImageの場合

MPSをCNNで使っている場合などで、MTLTextureではなくMPSImageがソースの場合は、

conversion.encode(commandBuffer: commandBuffer,
                  sourceTexture: colorImage.texture,
                  destinationTexture: grayImage.texture)

というように指定する

なお、この場合は、必ずMPSImageのチャンネル数を合わせないと実行時にエラーとなるので注意 (RGBなら3、グレースケールなら1)

開発環境