【问题标题】:How to add black and white filter on arkit (swift4)如何在arkit(swift4)上添加黑白滤镜
【发布时间】:2018-09-29 10:49:59
【问题描述】:

我要做的就是将基本的 arkit 视图转换为黑白视图。现在基本视图很正常,我不知道如何添加过滤器。理想情况下,在截取屏幕截图时,会将黑白滤镜添加到屏幕截图中。

import UIKit
import SceneKit
import ARKit

class ViewController: UIViewController, ARSCNViewDelegate {

    @IBOutlet var sceneView: ARSCNView!

    override func viewDidLoad() {
        super.viewDidLoad()
        sceneView.delegate = self
        sceneView.showsStatistics = true
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        let configuration = ARWorldTrackingConfiguration()
        sceneView.session.run(configuration)
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        sceneView.session.pause()
    }

    @IBAction func changeTextColour(){
        let snapShot = self.augmentedRealityView.snapshot()
        UIImageWriteToSavedPhotosAlbum(snapShot, self, #selector(image(_:didFinishSavingWithError:contextInfo:)), nil)
    }
}

【问题讨论】:

  • 你的意思是把同一个课程发两次吗?
  • @user1118321 不,我只想将常规图像变成黑白。
  • 你能添加你的快照代码吗?
  • @ArunB 好的,我在最底部添加了代码。
  • 更新了我的答案以包括黑白实时渲染。

标签: ios filter swift4 scenekit arkit


【解决方案1】:

如果您想实时应用过滤器,最好的方法是使用SCNTechnique。技术用于后处理,允许我们在多个通道中渲染 SCNView 内容——这正是我们所需要的(首先渲染场景,然后对其应用效果)。

这是example project


Plist 设置

首先,我们需要在.plist 文件中描述一种技术。

这是我想出的plist 的屏幕截图(为了更好地可视化):

这是它的来源:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>sequence</key>
    <array>
        <string>apply_filter</string>
    </array>
    <key>passes</key>
    <dict>
        <key>apply_filter</key>
        <dict>
            <key>metalVertexShader</key>
            <string>scene_filter_vertex</string>
            <key>metalFragmentShader</key>
            <string>scene_filter_fragment</string>
            <key>draw</key>
            <string>DRAW_QUAD</string>
            <key>inputs</key>
            <dict>
                <key>scene</key>
                <string>COLOR</string>
            </dict>
            <key>outputs</key>
            <dict>
                <key>color</key>
                <string>COLOR</string>
            </dict>
        </dict>
    </dict>
</dict>

SCNTechniques 的主题范围很广,我只会快速介绍我们手头的案例所需的内容。为了真正了解他们的能力,我建议阅读Apple's comprehensive documentation 的技术。

技术说明

passes 是一个字典,其中包含您希望 SCNTechnique 执行的传递的描述。

sequence 是一个数组,它指定了使用它们的键执行这些传递的顺序。

您没有在此处指定主渲染通道(意味着在没有应用 SCNTechniques 的情况下渲染的任何内容)——这是隐含的,并且可以使用 COLOR 常量访问其结果颜色(稍后会详细介绍)。

因此,我们要做的唯一“额外”通道(除了主要通道)将是apply_filter,它将颜色转换为黑白(可以任意命名,只要确保它具有相同的名称)键入passessequence)。

现在来描述 apply_filter 传递本身。

渲染过程描述

metalVertexShadermetalFragmentShader – 将用于绘图的 Metal 着色器函数的名称。

draw 定义 pass 将要渲染的内容。 DRAW_QUAD 代表:

只渲染一个覆盖视图整个边界的矩形。采用 用于绘图的此选项传递处理图像缓冲区输出的过程 较早的通行证。

这意味着,粗略地说,我们将在没有渲染通道的情况下渲染一个普通的“图像”。

inputs 指定我们将能够在着色器中使用的输入资源。正如我之前所说,COLOR 指的是由主渲染通道提供的颜色数据。

outputs 指定输出。可以是colordepthstencil,但我们只需要color 输出。 COLOR 值意味着,简单地说,我们将“直接”渲染到屏幕上(例如,与渲染到中间目标相反)。


金属着色器

创建一个.metal 文件,内容如下:

#include <metal_stdlib>
using namespace metal;
#include <SceneKit/scn_metal>

struct VertexInput {
    float4 position [[ attribute(SCNVertexSemanticPosition) ]];
    float2 texcoord [[ attribute(SCNVertexSemanticTexcoord0) ]];
};

struct VertexOut {
    float4 position [[position]];
    float2 texcoord;
};

// metalVertexShader
vertex VertexOut scene_filter_vertex(VertexInput in [[stage_in]])
{
    VertexOut out;
    out.position = in.position;
    out.texcoord = float2((in.position.x + 1.0) * 0.5 , (in.position.y + 1.0) * -0.5);
    return out;
}

// metalFragmentShader
fragment half4 scene_filter_fragment(VertexOut vert [[stage_in]],
                                    texture2d<half, access::sample> scene [[texture(0)]])
{
    constexpr sampler samp = sampler(coord::normalized, address::repeat, filter::nearest);
    constexpr half3 weights = half3(0.2126, 0.7152, 0.0722);

    half4 color = scene.sample(samp, vert.texcoord);
    color.rgb = half3(dot(color.rgb, weights));

    return color;
}

请注意,片段着色器和顶点着色器的函数名称应与传递描述符中plist 文件中指定的名称相同。

要更好地了解VertexInputVertexOut 结构的含义,请参阅SCNProgram documentation

给定的顶点函数几乎可以在任何DRAW_QUAD 渲染过程中使用。它基本上为我们提供了屏幕空间的标准化坐标(在片段着色器中使用vert.texcoord 访问)。

片段函数是所有“魔法”发生的地方。在那里,您可以操纵从主通道获得的纹理。使用此设置,您可能会实现大量过滤器/效果等等。

在我们的例子中,我使用了一个基本的去饱和度(零饱和度)公式来获得黑白颜色。


快速设置

现在,我们终于可以在ARKit/SceneKit 中使用所有这些了。

let plistName = "SceneFilterTechnique" // the name of the plist you've created

guard let url = Bundle.main.url(forResource: plistName, withExtension: "plist") else {
    fatalError("\(plistName).plist does not exist in the main bundle")
}

guard let dictionary = NSDictionary(contentsOf: url) as? [String: Any] else {
    fatalError("Failed to parse \(plistName).plist as a dictionary")
}

guard let technique = SCNTechnique(dictionary: dictionary) else {
    fatalError("Failed to initialize a technique using \(plistName).plist")
}

只需将其设置为ARSCNView 中的technique

sceneView.technique = technique

就是这样。现在整个场景将在拍摄快照时以灰度渲染包括

【讨论】:

  • 一个有趣且经过深思熟虑的答案:)
  • 希望你不介意我借用你的球体想法:S
  • 解释得很好。我正在尝试使用 meta 使皮肤光滑。你能给我提供任何参考或任何提示来实现这一目标吗?
【解决方案2】:

过滤 ARSCNView 快照: 如果您想为您的 ARSCNView 创建一个黑白屏幕截图,您可以执行类似的操作,它会在灰度中返回 UIImage,而 augmentedRealityView 指的是ARSCNView:

/// Converts A UIImage To A High Contrast GrayScaleImage
///
/// - Returns: UIImage
func highContrastBlackAndWhiteFilter() -> UIImage?
{
    //1. Convert It To A CIIamge
    guard let convertedImage = CIImage(image: self) else { return nil }

    //2. Set The Filter Parameters
    let filterParameters = [kCIInputBrightnessKey: 0.0,
                            kCIInputContrastKey:   1.1,
                            kCIInputSaturationKey: 0.0]

    //3. Apply The Basic Filter To The Image
    let imageToFilter = convertedImage.applyingFilter("CIColorControls", parameters: filterParameters)

    //4. Set The Exposure
    let exposure =  [kCIInputEVKey: NSNumber(value: 0.7)]

    //5. Process The Image With The Exposure Setting
    let processedImage = imageToFilter.applyingFilter("CIExposureAdjust", parameters: exposure)

    //6. Create A CG GrayScale Image
    guard let grayScaleImage = CIContext().createCGImage(processedImage, from: processedImage.extent) else { return nil }

    return UIImage(cgImage: grayScaleImage, scale: self.scale, orientation: self.imageOrientation)
}

因此使用它的一个例子可能是这样的:

 override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

    //1. Create A UIImageView Dynamically
    let imageViewResult = UIImageView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.height))
    self.view.addSubview(imageViewResult)

    //2. Create The Snapshot & Get The Black & White Image
    guard let snapShotImage = self.augmentedRealityView.snapshot().highContrastBlackAndWhiteFilter() else { return }
    imageViewResult.image = snapShotImage

    //3. Remove The ImageView After A Delay Of 5 Seconds
    DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
        imageViewResult.removeFromSuperview()
    }

}

这将产生如下结果:

为了使您的代码可重用,您还可以创建一个 extension of `UIImage:

//------------------------
//MARK: UIImage Extensions
//------------------------

extension UIImage
{

    /// Converts A UIImage To A High Contrast GrayScaleImage
    ///
    /// - Returns: UIImage
    func highContrastBlackAndWhiteFilter() -> UIImage?
    {
        //1. Convert It To A CIIamge
        guard let convertedImage = CIImage(image: self) else { return nil }

        //2. Set The Filter Parameters
        let filterParameters = [kCIInputBrightnessKey: 0.0,
                                kCIInputContrastKey:   1.1,
                                kCIInputSaturationKey: 0.0]

        //3. Apply The Basic Filter To The Image
        let imageToFilter = convertedImage.applyingFilter("CIColorControls", parameters: filterParameters)

        //4. Set The Exposure
        let exposure =  [kCIInputEVKey: NSNumber(value: 0.7)]

        //5. Process The Image With The Exposure Setting
        let processedImage = imageToFilter.applyingFilter("CIExposureAdjust", parameters: exposure)

        //6. Create A CG GrayScale Image
        guard let grayScaleImage = CIContext().createCGImage(processedImage, from: processedImage.extent) else { return nil }

        return UIImage(cgImage: grayScaleImage, scale: self.scale, orientation: self.imageOrientation)
    }

}

然后您可以像这样轻松使用它:

guard let snapShotImage = self.augmentedRealityView.snapshot().highContrastBlackAndWhiteFilter() else { return }

记住你应该把你的扩展放在你的class declaration上面,例如:

extension UIImage{

}

class ViewController: UIViewController, ARSCNViewDelegate {

}

因此,根据您问题中提供的代码,您将得到如下内容:

/// Creates A Black & White ScreenShot & Saves It To The Photo Album
@IBAction func changeTextColour(){

    //1. Create A Snapshot
    guard let snapShotImage = self.augmentedRealityView.snapshot().highContrastBlackAndWhiteFilter() else { return }

    //2. Save It The Photos Album
    UIImageWriteToSavedPhotosAlbum(snapShotImage, self, #selector(image(_:didFinishSavingWithError:contextInfo:)), nil)

}

///Calback To Check Whether The Image Has Been Saved
@objc func image(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) {

    if let error = error {
        print("Error Saving ARKit Scene \(error)")
    } else {
        print("ARKit Scene Successfully Saved")
    }
}

黑白实时渲染: 使用diviaki 的这个绝妙答案,我还能够使用以下方法让整个相机画面以黑白方式呈现:

第一。像这样注册ARSessionDelegate

 augmentedRealitySession.delegate = self

第二。然后在以下委托回调中添加以下内容:

 //-----------------------
 //MARK: ARSessionDelegate
 //-----------------------

 extension ViewController: ARSessionDelegate{

 func session(_ session: ARSession, didUpdate frame: ARFrame) {

        /*
        Full Credit To https://stackoverflow.com/questions/45919745/reliable-access-and-modify-captured-camera-frames-under-scenekit
        */

        //1. Convert The Current Frame To Black & White
        guard let currentBackgroundFrameImage = augmentedRealityView.session.currentFrame?.capturedImage,
              let pixelBufferAddressOfPlane = CVPixelBufferGetBaseAddressOfPlane(currentBackgroundFrameImage, 1) else { return }

        let x: size_t = CVPixelBufferGetWidthOfPlane(currentBackgroundFrameImage, 1)
        let y: size_t = CVPixelBufferGetHeightOfPlane(currentBackgroundFrameImage, 1)
        memset(pixelBufferAddressOfPlane, 128, Int(x * y) * 2)

      }

 }

这成功地呈现了黑白照片:

过滤黑白 SCNScene 的元素:

正如@Confused 所说,如果您决定希望cameraFeed 是彩色的,但AR Experience 的内容是黑白的,您可以使用过滤器直接对SCNNode 应用过滤器filters 属性很简单:

要应用于渲染内容的核心图像过滤器数组 节点。

例如,假设我们使用Sphere Geometry 动态创建3 个SCNNodes,我们可以像这样直接将CoreImageFilter 应用于这些:

/// Creates 3 Objects And Adds Them To The Scene (Rendering Them In GrayScale)
func createObjects(){

    //1. Create An Array Of UIColors To Set As The Geometry Colours
    let colours = [UIColor.red, UIColor.green, UIColor.yellow]

    //2. Create An Array Of The X Positions Of The Nodes
    let xPositions: [CGFloat] = [-0.3, 0, 0.3]

    //3. Create The Nodes & Add Them To The Scene
    for i in 0 ..< 3{

        let sphereNode = SCNNode()
        let sphereGeometry = SCNSphere(radius: 0.1)
        sphereGeometry.firstMaterial?.diffuse.contents = colours[i]
        sphereNode.geometry = sphereGeometry
        sphereNode.position = SCNVector3( xPositions[i], 0, -1.5)
        augmentedRealityView.scene.rootNode.addChildNode(sphereNode)

        //a. Create A Black & White Filter
        guard let blackAndWhiteFilter = CIFilter(name: "CIColorControls", withInputParameters: [kCIInputSaturationKey:0.0]) else { return }
        blackAndWhiteFilter.name = "bw"
        sphereNode.filters = [blackAndWhiteFilter]
        sphereNode.setValue(CIFilter(), forKeyPath: "bw")
    }

}

这将产生如下结果:

有关这些过滤器的完整列表,您可以参考以下内容:CoreImage Filter Reference

示例项目:这是一个完整的Example Project,您可以自己下载和探索。

希望对你有帮助...

【讨论】:

  • 我无法保存图像。从您的示例项目中,即使所有代码看起来都正确,图像也没有保存。
  • 我现在已经更新了代码和示例项目。也请把我的答案标记为正确:)
  • 伟大的编码。如果我想在拍照时保留 3 个对象的原始颜色。我该怎么做?黑白背景,上面有 3 个彩色球。
  • 实时渲染技术对我不起作用,因为session(_:didUpdate:) 每隔几秒才会调用一次
  • @BlackMirrorz 您好,我想使用 CIFilter 将颜色过滤器集成到 ARKit 中,我已经在网站上发布了我的问题,请看一下,stackoverflow.com/questions/58501761/… 如果您有任何集成它的想法请分享给我,谢谢。
【解决方案3】:

snapshot 对象应该是UIImage。通过导入CoreImage 框架在此UIImage 对象上应用过滤器,然后在其上应用Core Image 过滤器。您应该调整图像上的曝光和控制值。有关更多实施细节,请查看answer。从iOS6开始,也可以使用CIColorMonochromefilter达到同样的效果。

这是所有可用过滤器的苹果documentation。单击每个过滤器,以了解应用过滤器后图像的视觉效果。

这里是 swift 4 代码。

 func imageBlackAndWhite() -> UIImage?
    {
        if let beginImage = CoreImage.CIImage(image: self)
        {
            let paramsColor: [String : Double] = [kCIInputBrightnessKey: 0.0,
                                                  kCIInputContrastKey:   1.1,
                                                  kCIInputSaturationKey: 0.0]
            let blackAndWhite = beginImage.applyingFilter("CIColorControls", parameters: paramsColor)

            let paramsExposure: [String : AnyObject] = [kCIInputEVKey: NSNumber(value: 0.7)]
            let output = blackAndWhite.applyingFilter("CIExposureAdjust", parameters: paramsExposure)

            guard let processedCGImage = CIContext().createCGImage(output, from: output.extent) else {
                return nil
            }

            return UIImage(cgImage: processedCGImage, scale: self.scale, orientation: self.imageOrientation)
        }
        return nil
    }

【讨论】:

  • 这是从 swift 3 和早期你知道 swift 4 的任何东西。
  • 如何将此功能添加到快照中。
  • 将此作为 UIImage 的扩展,您将能够执行类似 snapshot.imageBlackAndWhite() 的操作
【解决方案4】:

这可能是最简单、最快的方法:

对场景应用 CoreImage 滤镜:

https://developer.apple.com/documentation/scenekit/scnnode/1407949-filters

这个滤镜给黑白照片留下了很好的印象,通过灰色的过渡效果很好:https://developer.apple.com/library/content/documentation/GraphicsImaging/Reference/CoreImageFilterReference/index.html#//apple_ref/doc/filter/ci/CIPhotoEffectMono

你也可以使用这个,而且得到的结果也很容易改变色调:

https://developer.apple.com/library/content/documentation/GraphicsImaging/Reference/CoreImageFilterReference/index.html#//apple_ref/doc/filter/ci/CIColorMonochrome

这里是过滤器和 SceneKit ARKit 一起工作的日语证明:http://appleengine.hatenablog.com/entry/advent20171215

【讨论】:

  • node.filters 是一个非常有趣的概念 :)
猜你喜欢
  • 2015-11-23
  • 2014-07-27
  • 2018-03-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-21
  • 2017-05-21
相关资源
最近更新 更多