こちらのポストやQiitaの投稿を参考に、カメラからの映像にCIFilterを使ってエフェクトをかけようとしたのだが、手元のiPhone Xで試してみたところ映像に歪みが発生してしまった。公式ドキュメントetc.から正しい対処法を見つけられなかったのだが、普通にアスペクト比を揃えて中央でクロップすれば良さそうだったので、やり方を記しておく。
ARKit + CIFilterでカメラの映像にエフェクトをかけるのを試しているけれども、なんか歪む。どこかの変換の工程でアスペクト比がおかしくなってるのかな。https://t.co/50VQL6wC5V pic.twitter.com/TEn31D6HL1
— Sousai🐳👀⏰ (@croquette0212) 2019年5月25日
歪んでしまう理由を調べてみると、カメラからの入力画像(1920 x 1440)とiPhone Xの画面のサイズ(812 x 375)のアスペクト比が違うことが問題のようだった。
private let ciContext = CIContext()
private let gammaAdjustFilter = CIFilter(name: "CIGammaAdjust")!
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
guard let currentFrame = sceneView.session.currentFrame else { return }
let ciImage = CIImage(cvPixelBuffer: currentFrame.capturedImage).oriented(.right)
gammaAdjustFilter.setValue(ciImage, forKey: kCIInputImageKey)
gammaAdjustFilter.setValue(3, forKey: "inputPower")
let screenSize = UIScreen.main.bounds.size
let scaleW = ciImage.extent.width / screenSize.width
let scaleH = ciImage.extent.height / screenSize.height
let scale = min(scaleW, scaleH)
let cropWidth = screenSize.width * scale
let cropHeight = screenSize.height * scale
if let gammaAdjusted = gammaAdjustFilter.outputImage,
let cgImage = ciContext.createCGImage(gammaAdjusted, from: CGRect(x: (ciImage.extent.width - cropWidth) * 0.5, y: (ciImage.extent.height - cropHeight) * 0.5, width: cropWidth, height: cropHeight)) {
sceneView.scene.background.contents = cgImage
}
}
処理後の画像からCGImageを生成する箇所で、画面のアスペクト比にあわせて中央部分のみをクロップする(Aspect Fill)ようにしてからbackground.contentsにセットするように変更したところ、歪まず正しいアスペクト比で表示されるようになった。
全画面表示ではない場合はUIScreen.main.boundsではなくsceneView.boundsを使う。ただその場合、sceneView.boundsをレンダリングスレッドから参照するとエラーになってしまうので、viewDidLayoutSubviewsなどでsceneView.boundsをどこかにコピーしておく必要がある。
画面のアスペクト比にあわせてクロップするようにしたら正しいアスペクト比で表示されるようになった。CIImage生成する前にアスペクト比を変更しておいた方が無駄がなさそうだけどやり方あるのかな。 pic.twitter.com/j7sbPYcmYg
— Sousai🐳👀⏰ (@croquette0212) 2019年6月2日
単純に中央のクロップで問題ないのか(Hit Testとかでずれたりしないか)若干心配ではあるが、今のところ問題なく動いていそう。