11
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ユニークビジョン株式会社Advent Calendar 2018

Day 23

Metal 入門したので画像をぼかしてみる

Posted at

この記事はユニークビジョン株式会社 Advent Calendar 2018の23日目の記事です。

はじめに

最近Metalの勉強をはじめました。
Metalを使っていろいろとやろうと思うことはじめです。
Metalって響きがなんかかっこいい。。。

Metal入門・・・の前の基礎知識を一読するとこの記事は読みやすいと思います。

Metalとは

MetalはグラフィックスAPIのことでGPUにアクセスできるAPIです。
有名なものだとOpenGL、OpenCLなどがありますがそれらは汎用的に設計されているため、Apple製品本来のパフォーマンスが出せないってことでMetalがつくられました。
グラフィックスAPIと聞くと、ゲーム作るわけでもないから関係ないとか思う人も多いと思います(私はそうでした)。ですがCPUが不得手なこと、つまり並列演算をGPUに任せられるんですね。Metalは機械学習(CNN)向けのAPIもありますしメインはそこになってくると思います。(CoreMLも内部ではMetalを使用しています)

Metalを使う準備をする

まずはMetalを使うためにいろいろ初期化します。

import MetalKit
import UIKit

var mtkView: MTKView!
let device = MTLCreateSystemDefaultDevice()!
var commandQueue: MTLCommandQueue!

func setupMetal() {
    commandQueue = device.makeCommandQueue()
    mtkView.device = self.device
    mtkView.delegate = self
}

extension ViewController: MTKViewDelegate {
    func draw(in view: MTKView) {}
    func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {}
}

draw(in view: MTKView)は毎フレームごと呼ばれます。
今回は60fpsに設定しています

mtkView.preferredFramesPerSecond = 60

CIFilterでやってみる

CIFilterは優秀な子です。こちらによると201種類ものフィルターが公式で用意されていて、普通に画像処理をしたいだけなら十分だと思われます。
今回はブラーをかけてぼかしたいのでCIGaussianBlurを使ってみます。

let filter = CIFilter(name: "CIGaussianBlur")!
filter.setValue(ciImage, forKey: kCIInputImageKey)

func processImage(_ image: CIImage, scale: Double) {
    filter.setValue(scale, forKey: kCIInputScaleKey)
}

ブラーをかける準備が整いました。
これをMetalで描画します。

let context = CIContext()
let colorSpace = CGColorSpaceCreateDeviceRGB()

extension ViewController: MTKViewDelegate {
	func draw(in view: MTKView) {
	    guard let drawable = view.currentDrawable else { return }
        guard let commandBuffer = commandQueue.makeCommandBuffer() else { return }
        guard let outputImage = self.filter.outputImage else { return }
        defer {
            commandBuffer.present(drawable)
            commandBuffer.commit()
            commandBuffer.waitUntilCompleted()
        }
        self.processImage(image, scale: scale)
        self.context.render(
            outputImage,
            to: drawable.texture,
            commandBuffer: commandBuffer,
            bounds: outputImage.extent,
            colorSpace: self.colorSpace
        )
      }
}

scaleは時間経過で増えていくように設定してあります

let startTime = Date().timeIntervalSinceReferenceDate
var scale: Double {
    return Date().timeIntervalSinceReferenceDate - self.startTime
}

これだと実行時に
failed assertion `frameBufferOnly texture not supported for compute.'
とかいわれて死んだので以下を設定しておきます。(readonlyなのに何書き込もうとしてるんだよ的な理由だと思います)

mtkView.framebufferOnly = false

結果

ezgif.com-video-to-gif (1).gif

ピクセルが散らばるので縁が黒くなっていきます。
トリミングすれば問題にはなりませんが手間がかかります。
(右下にずれていくのはリサイズ処理が甘いせいです...)

MPSでもっと簡単にできる

はい。MPSというとても便利なものがありました。
MetalPerformanceShadersの略です。

Optimize graphics and compute performance with kernels that are fine-tuned for the unique characteristics of each Metal GPU family.

とApple様はおっしゃっていて、各GPUに最適化され簡単かつ効率的に優れたパフォーマンスを発揮するそうです。
画像フィルター、ニューラルネットワーク、行列とベクトル、光線追跡といった機能があります。詳しくはこちら

MPS対応デバイス

MPSに対応しているデバイスはドキュメントによるとGPUファミリー2以降(iPhone6以降)をサポートしています。
GPUファミリーってなんぞやなのですがこちらの記事がわかりやすいです。

MPSSupportsMTLDevice(_ device: MTLDevice?) -> Boolという関数も用意されているので実行時に判定することもできます。

MPSでやってみる

画像フィルターの中からMPSImageGaussianBlurを使います。

まずは画像を読み込んでtextureを作成します。

func loadImage() {
    let textureLoader = MTKTextureLoader(device: self.device)
    self.texture = try! textureLoader.newTexture(
        name: "imageName",
        scaleFactor: view.contentScaleFactor,
        bundle: nil
    )
    mtkView.colorPixelFormat = texture.pixelFormat
}

CIFilterでレンダリングしていた箇所を以下に書き換えます

import MetalPerformanceShaders

extension ImageProcessViewController: MTKViewDelegate {
    func draw(in view: MTKView) {
        // 中略
        let blur = MPSImageGaussianBlur(device: self.device, sigma: Float(scale))
        blur.encode(
            commandBuffer: commandBuffer,
            sourceTexture: self.texture,
            destinationTexture: drawable.texture
        )
    }
}

これだけでOKです。

結果

とてもいい感じです(gifなので荒く見えてしまってます...)
ezgif.com-video-to-gif.gif

まとめ

今回はMetalことはじめということで簡単な画像処理をやってみました。
本格的にやるのであればシェーダを書く必要があるのでC++もといMetalShadingLanguageを使いこなす必要があります。

日本語の記事はあまり多くないのですが、ドキュメントはけっこう豊富なので基本の動きを抑えておけば理解はしやすいです。

アプリにうまくMetalを組み込めたらもっと幅が広がりそうですね。

次は計算処理をやってみようと思います。

参考

Metal入門・・・の前の基礎知識
Metal入門 ←とてもわかりやすいです
MetalKitでGPUを使いこなす

11
6
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
11
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?