簡體   English   中英

如何在arkit上添加黑白過濾器(swift4)

[英]How to add black and white filter on arkit (swift4)

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

如果要實時應用過濾器,最好的方法是使用SCNTechnique 技術用於后處理,並允許我們在幾個過程中渲染SCNView內容 - 正是我們需要的(首先渲染場景,然后對其應用效果)。

這是示例項目


Plist設置

首先,我們需要在.plist文件中描述一種技術。

這是我提出的一個plist的截圖(為了更好的可視化):

plist描述了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關於技術的綜合文檔

技術說明

passes是一個字典,包含您希望SCNTechnique執行的過程的描述。

sequence是一個數組,指定使用其鍵執行這些傳遞的順序。

你沒有在這里指定主渲染過程(意思是在沒有應用SCNTechnique的情況下渲染的任何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文檔

給定的頂點函數可以在任何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")
}

並將其設置為ARSCNView technique

sceneView.technique = technique

而已。 現在整個場景將以灰度渲染, 包括拍攝快照時。

在此輸入圖像描述

過濾ARSCNView快照:如果你想創建一個黑白屏幕你的ARSCNView你可以做這樣的事情,返回GrayScale中的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()
    }

}

這會產生如下結果:

在此輸入圖像描述

為了使您的代碼可重用,您還可以創建`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 {

}

因此,基於您的問題中提供的代碼,您將擁有以下內容:

/// 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使用這個精彩的答案,我還可以使用以下方法將整個相機輸入渲染為黑白:

1。 像這樣注冊ARSessionDelegate

 augmentedRealitySession.delegate = self

第2位。 然后在以下委托回調中添加以下內容:

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

      }

 }

哪個成功渲染相機Feed Black&White:

在此輸入圖像描述

過濾SCNScene在黑與白中的元素:

正如@Confused正確地說的那樣,如果你決定想讓cameraFeed變成彩色,但你的AR Experience內容是黑白的,你可以使用它的filters屬性將過濾器直接應用到SCNNode ,這只是:

要應用於節點的渲染內容的Core Image過濾器數組。

讓我們假設我們使用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過濾器參考

示例項目:這是一個完整的示例項目 ,您可以自己下載和探索。

希望能幫助到你...

snapshot對象應該是UIImage 通過導入CoreImage框架在此UIImage對象上應用過濾器,然后在其上應用Core Image過濾器。 您應該調整圖像上的曝光和控制值。 有關更多實施細節,請查看此答案 從iOS6開始,您還可以使用CIColorMonochrome過濾器來實現相同的效果。

以下是所有可用過濾器的Apple 文檔 單擊每個過濾器,以了解應用過濾器時圖像上的視覺效果。

這是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
    }

這可能是最簡單,最快速的方法:

將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 /濾波器/ 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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM