本文将演示如何给相机添加实时的滤镜效果。
首先打开项目的配置文件【Info.plist】,在空白区域点击鼠标右键,弹出右键菜单。
选择【Add Row】添加行命令,添加一行配置选项。
在【Key】键输入框输入相机的访问标识:【Application Category】
在【Value】值输入框输入当应用程序访问相机设备时的提示语:
【Requires access to the camera】
在左侧的项目导航区,打开视图控制器的代码文件【ViewController.swift】
现在开始编写代码,在应用程序中使用相机设备,并给相机添加实时滤镜。
需要使用真机进行调试。
1 import UIKit 2 //引入需要使用到的类库,用来添加滤镜效果。 3 import CoreImage 4 //引入需要使用到的类库,用来对视频的采样进行处理 5 import AVFoundation 6 7 //给当前的类添加协议,使用该协议,可以获得相机设备中的实时输出的数据流。 8 class ViewController: UIViewController, AVCaptureVideoDataOutputSampleBufferDelegate{ 9 10 //添加一个滤镜,它将在代理协议中的方法中被使用, 11 //从而对视频流实时添加滤镜效果。 12 var filter: CIFilter! 13 //该属性用来存储在协议方法中获得的图像。 14 //当用户点击截图按钮时,从而获得当前视频流中的截图。 15 var cgImage: CGImage! 16 //该属性用来展示应用滤镜后的视频流截图 17 var videoLayer: CALayer! 18 //当用户点击截图按钮时,展示视频流的截图 19 var imageView : UIImageView! 20 //使用该属性获得相机设备的数据流 21 var avCaptureSession: AVCaptureSession! 22 //该属性可以将应用滤镜后的图像,转换成CGImage格式的图像, 23 //并提交给视频层进行展示。 24 var context: CIContext = { 25 //返回一个指定接口的上下文对象 26 return CIContext(eaglContext: EAGLContext(api: EAGLRenderingAPI.openGLES2)!, options: nil) 27 }() 28 29 //在视图加载完成的方法中,对部分属性进行初始化操作。 30 override func viewDidLoad() { 31 super.viewDidLoad() 32 33 //初始化滤镜对象,该滤镜可以使用图像显示复古、暖色调的艺术风格。 34 filter = CIFilter(name: "CIPhotoEffectTransfer") 35 //调用生成界面的方法,对程序的界面进行初始化操作。 36 buildUI() 37 //对用于捕捉视频流的对象进行初始化操作 38 buildSession() 39 } 40 41 //添加一个方法,用来创建应用程序的界面 42 func buildUI() 43 { 44 //对视图层进行初始化操作 45 videoLayer = CALayer() 46 //设置视图层的锚点点位置在原点 47 videoLayer.anchorPoint = CGPoint.zero 48 //保持视图层的尺寸和骗你干嘛的尺寸相同 49 videoLayer.bounds = view.bounds 50 //将视图层添加到根视图的层中 51 self.view.layer.insertSublayer(videoLayer, at: 0) 52 53 //创建一个图像视图对象,该图像视图将用来展示从视频流中, 54 //获得应用滤镜后的截图,它的尺寸也跟屏幕尺寸相同。 55 imageView = UIImageView(frame: view.bounds) 56 //将图像视图添加到根视图中。 57 self.view.addSubview(imageView) 58 59 //添加一个按钮,当用户点击该按钮时,获得视频流中的应用滤镜后的截图。 60 let button = UIButton(frame: CGRect(x: 0, y: 420, width: 320, height: 60)) 61 //设置按钮在正常状态下的标题文字 62 button.setTitle("Capture", for: .normal) 63 //同时设置按钮的背景颜色为黑色。 64 button.backgroundColor = UIColor.black 65 //给按钮控件绑定点击事件 66 button.addTarget(self, action: #selector(ViewController.captureScreen), for: .touchUpInside) 67 //将按钮添加到根视图 68 self.view.addSubview(button) 69 } 70 71 //添加一个方法,用来响应按钮的点击事件 72 func buildSession() 73 { 74 //对获得数据流的对象进行初始化操作 75 avCaptureSession = AVCaptureSession() 76 //通过调用对象的开始配置方法,开始对各种参数进行配置。 77 avCaptureSession.beginConfiguration() 78 //设置获得质量较高的视频流和音频流。 79 avCaptureSession.sessionPreset = AVCaptureSession.Preset.high 80 81 //获得当前的相机设备 82 let captureDevice = AVCaptureDevice.default(for: .video) 83 //初始化一个视频捕捉设备输入对象 84 let deviceInput = try! AVCaptureDeviceInput(device: captureDevice!) 85 //当相机设备处于可用状态时, 86 if avCaptureSession.canAddInput(deviceInput) 87 { 88 //设置视频流的输入设备为相机设备 89 avCaptureSession.addInput(deviceInput) 90 } 91 92 //获得视频捕捉数据输出对象, 93 //该对象用于从视频流中,获取未经压缩的帧 94 let dataOutput = AVCaptureVideoDataOutput() 95 //设置视频帧的格式为32位的RGBA格式 96 dataOutput.videoSettings = ([kCVPixelBufferPixelFormatTypeKey as AnyHashable : Int(kCVPixelFormatType_32BGRA)] as! [String : Any]) 97 //设置自动丢弃由于视频延迟等因素,而造成延迟等的视频帧。 98 dataOutput.alwaysDiscardsLateVideoFrames = true 99 100 //将数据输出对象,添加到视频捕捉对象的数据输出端口 101 if avCaptureSession.canAddOutput(dataOutput) 102 { 103 avCaptureSession.addOutput(dataOutput) 104 } 105 106 //创建一个串行的任务队列 107 let queue = DispatchQueue(label: "VideoQueue", attributes: .concurrent) 108 //设置数据输出对象的采样换成代理,位当前的视图控制器对象, 109 //并使用穿好的任务队列 110 dataOutput.setSampleBufferDelegate(self, queue: queue) 111 112 //通过调用视频捕捉对象的提交配置方法,结束对各种参数的配置。 113 avCaptureSession.commitConfiguration() 114 //调用开始运行方法,开始使用相机设备捕捉视频。 115 avCaptureSession.startRunning() 116 } 117 118 //实现协议中的代理方法,以实时检测视频流, 119 //并给视频流实时添加滤镜。 120 func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) 121 { 122 //添加一个自动释放池 123 autoreleasepool 124 { 125 //将采样的流数据,转换成图像缓存对象 126 let imgBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)! 127 //将图像缓存对象进行格式的转换, 128 //以便给图像添加滤镜。 129 var ciImage = CIImage(cvPixelBuffer: imgBuffer) 130 131 //将数据流转换格式后,就可以应用框架中的众多滤镜。 132 //首先设置滤镜的输入图像,为流数据转换格式后的对象。 133 self.filter.setValue(ciImage, forKey: kCIInputImageKey) 134 //获得应用滤镜后所输出的图像。 135 ciImage = self.filter.outputImage! 136 137 //获得当前设备的朝向 138 let orientation = UIDevice.current.orientation 139 //因为两种坐标系统的原点不同,所以需要对视频流中的应用滤镜后的截图,进行旋转操作。 140 if orientation == UIDeviceOrientation.portrait 141 { 142 ciImage = ciImage.transformed(by: CGAffineTransform(rotationAngle: CGFloat(Double.pi / -2.0))) 143 } 144 //处理设备在竖立状态,主键在上的情况。 145 else if orientation == UIDeviceOrientation.portraitUpsideDown 146 { 147 ciImage = ciImage.transformed(by: CGAffineTransform(rotationAngle: CGFloat(Double.pi / 2.0))) 148 } 149 //处理设备在横向状态,主键在右的情况。 150 else if (orientation == UIDeviceOrientation.landscapeRight) 151 { 152 ciImage = ciImage.transformed(by: CGAffineTransform(rotationAngle: CGFloat(Double.pi))) 153 } 154 //将调整方向后的图像,赋予应用的属性,供按钮控件的点击事件使用。 155 self.cgImage = self.context.createCGImage(ciImage, from: ciImage.extent) 156 157 //返回主线程,在主线程中刷新界面上的内容, 158 DispatchQueue.main.sync(execute: 159 { 160 //将视频层的内容属性,设置为应用滤镜后的图像。 161 self.videoLayer.contents = self.cgImage 162 }) 163 } 164 } 165 166 //添加一个方法,用来响应按钮的点击事件 167 @objc func captureScreen(_ sender: UIButton) 168 { 169 //当按钮被点击时,首先中止视频流的传递。 170 avCaptureSession.stopRunning() 171 //将用来显示时流的图层,从父层中移除。 172 videoLayer.removeFromSuperlayer() 173 //隐藏当前的按钮控件 174 sender.isHidden = true 175 176 //设置图像视图的内容模式,图片按一定比例缩放, 177 //直到在长度或者宽度达到图像视图的边界为止。 178 imageView.contentMode = .scaleAspectFit 179 //将应用滤镜后的截图,赋予当前根视图中的图像视图, 180 //在屏幕上显示来自视频流的截图。 181 imageView.image = UIImage(cgImage: self.cgImage) 182 } 183 184 override func didReceiveMemoryWarning() { 185 super.didReceiveMemoryWarning() 186 // Dispose of any resources that can be recreated. 187 } 188 }
原文地址:https://www.cnblogs.com/strengthen/p/10353871.html
时间: 2024-11-05 21:48:47