如何在arkit上添加黑白过滤器(swift4)

时间:2018-04-19 04:01:18

标签: ios filter swift4 scenekit arkit

我想要做的就是采用基本的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)
    }
}

4 个答案:

答案 0 :(得分:8)

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

这是example project

Plist设置

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

以下是我提出的plist的屏幕截图(为了更好的可视化):

plist describing an SCNTechnique

这是它的来源:

<?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>

SCNTechnique的主题是广泛的,我只会快速介绍我们手头的案例所需要的内容。为了真正了解他们的能力,我建议您阅读Apple's comprehensive documentation技巧。

技术说明

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

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

你没有在这里指定主渲染通道(意思是在不应用SCNTechnique的情况下渲染的任何东西) - 它是隐含的,它的结果颜色可以使用COLOR常量访问(更多内容在位)。

所以我们要做的唯一“额外”传递(除了主要的传递)将是apply_filter将颜色转换为黑白(它可以命名为任何你想要的,只要确保它有passessequence)中的相同密钥。

现在来看apply_filter传球本身的描述。

渲染传递说明

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

draw定义了要传递的内容。 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访问)。

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

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

Swift设置

现在,我们最终可以在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")
}

并将其设置为technique的{​​{1}}。

ARSCNView

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

enter image description here

答案 1 :(得分:6)

过滤ARSCNView快照:如果您要为ARSCNView创建黑白屏幕截图,可以执行以下操作:在GrayScale中返回UIImage,并{ {1}}指的是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)
}

这会产生如下结果:

enter image description here

为了使您的代码可重用,您还可以创建一个 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() } } 的`UIImage:

extension

然后您可以轻松使用:

//------------------------
//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 {

}

黑色和现场渲染白: 通过diviaki在这里使用这个精彩的答案我还能够使用以下方法将整个相机输入渲染为黑白:

第一。像这样注册/// 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") } }

ARSessionDelegate

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

 augmentedRealitySession.delegate = self

成功渲染相机Feed Black&amp;白:

enter image description here

过滤SCNScene的元素在Black&amp;白:

正如@Confused正确地说的那样,如果你决定要 //----------------------- //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) } } 为彩色,而你cameraFeed的内容是黑色&amp;白色,您可以使用AR Experience属性直接将过滤器应用于SCNNode,这只是:

  

要应用于渲染内容的Core Image过滤器数组   节点。

比方说我们动态创建3 filters SCNNodes我们可以直接将Sphere Geometry应用于这些:{/ p>

CoreImageFilter

将产生如下结果:

enter image description here

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

示例项目:这是一个完整的Example Project,您可以自行下载和浏览。

希望它有所帮助...

答案 2 :(得分:0)

snapshot对象应为UIImage。通过导入UIImage框架在此CoreImage对象上应用过滤器,然后在其上应用Core Image过滤器。您应该调整图像上的曝光和控制值。有关更多实施详细信息,请查看此answer。从iOS6开始,您还可以使用CIColorMonochrome过滤器来实现相同的效果。

以下是所有可用过滤器的苹果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
    }

答案 3 :(得分:-1)

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

将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