GPUImage是一個基於OpenGL ES 2.0的開源的圖像處理庫,作者是Brad Larson。GPUImage將OpenGL ES封裝為簡潔的Objective-C或Swift接口,可以用來給圖像、實時相機視頻、電影等添加濾鏡。對於諸如處理圖像或實況視頻幀的大規模並行操作,GPU相對於CPU具有一些顯着的性能優點。在iPhone 4上,簡單的圖像濾鏡在GPU上的執行速度比等效的基於CPU的濾鏡快100多倍,與Core Image (ios5.0的一部分)相比,GPUImage允許您編寫自己的自定義濾鏡,支持部署到ios4.0,並且有一個更簡單的界面。然而,它目前缺乏核心圖像的一些更高級的特征,比如面部檢測等功能。
我發現在創建和使用OpenGLES過程中需要編寫大量的樣板代碼。因此,GPUImage封裝了處理圖像和視頻時會遇到的許多常見任務,這樣就不需要關心OpenGL ES 2.0的基礎了。
GPUImage有三個版本,GPUImage是基於OpenGL ES 使用OC語言寫的,對於OC的項目可以集成並使用它,GPUImage2是用Swift語言寫的基於OpenGL ES2.0,GPUImage3是基於蘋果的圖像渲染框架Metal封裝的,語言也是swift,可以根據自己的需求集成不同版本的GPUImage,本篇介紹GPUImage2使用,語言為Swift。
一、處理靜態圖片使用濾鏡
class StillImageViewController: UIViewController { lazy var imageView: UIImageView = { let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)) imageView.image = UIImage(contentsOfFile: Bundle.main.path(forResource: "hulu", ofType: "jpg")!) imageView.contentMode = .scaleAspectFit return imageView }() var slider: UISlider = { let slider = UISlider(frame: CGRect(x: 20, y: SCREEN_HEIGHT - 30, width: SCREEN_WIDTH - 40, height: 20)) return slider }() var filter:BasicOperation! var pictureInput : PictureInput! var filterModel:FilterModel = FilterModel(name: "BrightnessAdjustment 亮度", filterType: .basicOperation, range: (-1.0, 1.0, 0.0), initCallback: {BrightnessAdjustment()}, valueChangedCallback: nil) let renderView = RenderView(frame:CGRect(x: 0, y: 0, width: SCREEN_WIDTH, height: SCREEN_HEIGHT - 85)) override func viewDidLoad() { super.viewDidLoad() title = "圖片濾鏡" view.backgroundColor = .white view.addSubview(imageView) view.addSubview(renderView) renderView.backgroundColor = .white renderView.isHidden = true view.addSubview(slider) slider.isHidden = true slider.addTarget(self, action: #selector(sliderValueChanged(slider:)), for: .valueChanged) pictureInput = PictureInput(image: imageView.image!) let filterButton = UIButton(frame: CGRect(x: 90, y: UIScreen.main.bounds.height - 80, width: 150, height: 40)) filterButton.setTitle("選擇濾鏡", for: UIControl.State.normal) filterButton.center.x = view.center.x filterButton.backgroundColor = .gray filterButton.addTarget(self, action: #selector(ChoseFilters(btn:)), for: .touchUpInside) view.addSubview(filterButton) } @objc func ChoseFilters(btn:UIButton) { imageView.isHidden = true renderView.isHidden = false let fvc = FilterListTableViewController() fvc.filterBlock = { [weak self] filterModel in guard let `self` = self else { return } self.filterModel = filterModel self.setupFilterChain(filterModel: filterModel) } self.navigationController?.pushViewController(fvc, animated: true) } func setupFilterChain(filterModel:FilterModel) { title = filterModel.name // pictureInput = PictureInput(image: MaYuImage) slider.minimumValue = filterModel.range?.0 ?? 0 slider.maximumValue = filterModel.range?.1 ?? 0 slider.value = filterModel.range?.2 ?? 0 let filterObject = filterModel.initCallback() pictureInput.removeAllTargets() self.filter?.removeAllTargets() switch filterModel.filterType! { case .imageGenerators: imageView.image = imageView.image case .basicOperation: if let actualFilter = filterObject as? BasicOperation { self.filter = actualFilter pictureInput --> filter --> renderView } case .operationGroup: if let actualFilter = filterObject as? OperationGroup { pictureInput --> actualFilter --> renderView } case .blend: if let actualFilter = filterObject as? BasicOperation { self.filter = actualFilter let blendImgae = PictureInput(image: flowerImage) blendImgae --> actualFilter pictureInput --> filter --> renderView blendImgae.processImage() } case .custom: filterModel.customCallback!(pictureInput, filterObject, renderView) filter = filterObject as? BasicOperation } pictureInput.processImage() self.sliderValueChanged(slider: slider) } @objc func sliderValueChanged(slider: UISlider) { if let actualCallback = filterModel.valueChangedCallback { actualCallback(filter, slider.value) slider.isHidden = false } else { slider.isHidden = true } if filterModel.filterType! != .imageGenerators { pictureInput.processImage() } } func filteringImage() { // 創建一個BrightnessAdjustment顏色處理濾鏡 let brightnessAdjustment = BrightnessAdjustment() brightnessAdjustment.brightness = 0.2 // 創建一個ExposureAdjustment顏色處理濾鏡 let exposureAdjustment = ExposureAdjustment() exposureAdjustment.exposure = 0.5 // 1.使用GPUImage對UIImage的擴展方法進行濾鏡處理 var filteredImage: UIImage // 1.1單一濾鏡 filteredImage = imageView.image!.filterWithOperation(brightnessAdjustment) // 1.2多個濾鏡疊加 filteredImage = imageView.image!.filterWithPipeline { (input, output) in input --> brightnessAdjustment --> exposureAdjustment --> output } // 不建議的 imageView.image = filteredImage // 2.使用管道處理 // 創建圖片輸入 let pictureInput = PictureInput(image: imageView.image!) // 創建圖片輸出 let pictureOutput = PictureOutput() // 給閉包賦值 pictureOutput.imageAvailableCallback = { image in // 這里的image是處理完的數據,UIImage類型 } // 綁定處理鏈 pictureInput --> brightnessAdjustment --> exposureAdjustment --> pictureOutput // 開始處理 synchronously: true 同步執行 false 異步執行,處理完畢后會調用imageAvailableCallback這個閉包 pictureInput.processImage(synchronously: true) } }
二、拍攝照片使用濾鏡
class TakePhotoViewController: UIViewController { var filterModel:FilterModel = FilterModel(name: "BrightnessAdjustment 亮度", filterType: .basicOperation, range: (-1.0, 1.0, 0.5), initCallback: {BrightnessAdjustment()}, valueChangedCallback: { (filter, value) in (filter as! BrightnessAdjustment).brightness = value }) var picture:PictureInput! var filter:BasicOperation! var camera: Camera! var movieOutput:MovieOutput? = nil var movie: MovieInput! var renderView: RenderView! var takeButton : UIButton! var filterButton : UIButton! var reTakeButton : UIButton! var slider: UISlider = { let slider = UISlider(frame: CGRect(x: 20, y: SCREEN_HEIGHT - 30, width: SCREEN_WIDTH - 40, height: 20)) return slider }() func creaatRenderView() -> RenderView{ let renderView = RenderView(frame:CGRect(x: 0, y: 0, width: SCREEN_WIDTH, height: SCREEN_HEIGHT - 100)) return renderView } lazy var imageView: UIImageView = { let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: SCREEN_WIDTH, height: SCREEN_HEIGHT - 80)) imageView.image = UIImage(contentsOfFile: Bundle.main.path(forResource: "hulu", ofType: "jpg")!) imageView.contentMode = .scaleAspectFit imageView.backgroundColor = .purple imageView.isHidden = true return imageView }() @objc func ChoseFilters(btn:UIButton) { let fvc = FilterListTableViewController() fvc.filterBlock = { [weak self] filterModel in guard let `self` = self else { return } self.filterModel = filterModel self.setupFilterChain(filterModel: filterModel) } self.navigationController?.pushViewController(fvc, animated: true) } func setupFilterChain(filterModel:FilterModel) { title = filterModel.name // pictureInput = PictureInput(image: MaYuImage) slider.minimumValue = filterModel.range?.0 ?? 0 slider.maximumValue = filterModel.range?.1 ?? 0 slider.value = filterModel.range?.2 ?? 0 let filterObject = filterModel.initCallback() camera.removeAllTargets() filter.removeAllTargets() renderView.sources.removeAtIndex(0) switch filterModel.filterType! { case .imageGenerators: filterObject as! ImageSource --> renderView case .basicOperation: if let actualFilter = filterObject as? BasicOperation { filter = actualFilter camera --> actualFilter --> renderView // pictureInput.processImage() } case .operationGroup: if let actualFilter = filterObject as? OperationGroup { camera --> actualFilter --> renderView } case .blend: if let actualFilter = filterObject as? BasicOperation { filter = actualFilter let blendImgae = PictureInput(image: flowerImage) blendImgae --> actualFilter camera --> actualFilter --> renderView blendImgae.processImage() } case .custom: filterModel.customCallback!(camera, filterObject, renderView) filter = filterObject as? BasicOperation } self.sliderValueChanged(slider: slider) } @objc func sliderValueChanged(slider: UISlider) { // print("slider value: \(slider.value)") if let actualCallback = filterModel.valueChangedCallback { actualCallback(filter, slider.value) slider.isHidden = false } else { slider.isHidden = true } if filterModel.filterType! != .imageGenerators { } } //拍攝 @objc func takePhoto() { takeButton.isHidden = true reTakeButton.isHidden = false // 設置保存路徑 guard let outputPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first else { return } let originalPath = outputPath + "/originalImage.png" print("path: \(originalPath)") let originalURL = URL(fileURLWithPath: originalPath) let filteredPath = outputPath + "/filteredImage.png" print("path: \(filteredPath)") let filteredlURL = URL(fileURLWithPath: filteredPath) // 保存相機捕捉到的圖片 self.camera.saveNextFrameToURL(originalURL, format: .png) // 保存濾鏡后的圖片 self.filter.saveNextFrameToURL(filteredlURL, format: .png) DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .milliseconds(100)) { self.renderView.isHidden = true self.imageView.isHidden = false self.imageView.image = UIImage(contentsOfFile: filteredPath) } // // 如果需要處理回調,有下面兩種寫法 // let dataOutput = PictureOutput() // dataOutput.encodedImageFormat = .png // dataOutput.encodedImageAvailableCallback = {imageData in // // 這里的imageData是截取到的數據,Data類型 // } // self.camera --> dataOutput // // let imageOutput = PictureOutput() // imageOutput.encodedImageFormat = .png // imageOutput.imageAvailableCallback = {image in // // 這里的image是截取到的數據,UIImage類型 // self.imageView.image = image // } // // self.camera --> imageOutput } override func viewDidLoad() { super.viewDidLoad() title = "拍照濾鏡" view.backgroundColor = .white slider.addTarget(self, action: #selector(sliderValueChanged(slider:)), for: .valueChanged) self.renderView = creaatRenderView() view.addSubview(renderView) view.addSubview(imageView) view.addSubview(slider) slider.isHidden = true if let fi = filterModel.initCallback() as? BasicOperation{ filter = fi }else{ filter = BrightnessAdjustment() } takeButton = UIButton(frame: CGRect(x: 20, y: UIScreen.main.bounds.height - 100, width:60, height: 60)) takeButton.setTitle("拍攝", for: UIControl.State.normal) takeButton.backgroundColor = .gray takeButton.center.x = self.view.center.x takeButton.layer.cornerRadius = 30 takeButton.addTarget(self, action: #selector(takePhoto), for: UIControl.Event.touchUpInside) view.addSubview(takeButton) filterButton = UIButton(frame: CGRect(x: UIScreen.main.bounds.width - 100, y: UIScreen.main.bounds.height - 90, width: 80, height: 40)) filterButton.setTitle("選擇濾鏡", for: UIControl.State.normal) filterButton.backgroundColor = .gray filterButton.addTarget(self, action: #selector(ChoseFilters), for: .touchUpInside) view.addSubview(filterButton) reTakeButton = UIButton(frame: CGRect(x: UIScreen.main.bounds.width - 100, y: UIScreen.main.bounds.height - 90, width: 80, height: 40)) reTakeButton.setTitle("重新拍攝", for: UIControl.State.normal) reTakeButton.backgroundColor = .gray reTakeButton.center.x = self.view.center.x reTakeButton.addTarget(self, action: #selector(retake), for: UIControl.Event.touchUpInside) view.addSubview(reTakeButton) reTakeButton.isHidden = true cameraFiltering() } @objc func retake() { takeButton.isHidden = false reTakeButton.isHidden = true renderView.isHidden = false imageView.isHidden = true } func cameraFiltering() { // Camera的構造函數是可拋出錯誤的 do { camera = try Camera(sessionPreset: AVCaptureSession.Preset.hd1280x720, cameraDevice: nil, location: .backFacing, captureAsYUV: true) } catch { print(error) return } // 綁定處理鏈 camera --> renderView // 開始捕捉數據 self.camera.startCapture() // 結束捕捉數據 // camera.stopCapture() } }
三、播放視頻時添加濾鏡
class PlayMoviewViewController: UIViewController { var filter:BasicOperation = BrightnessAdjustment() var renderView: RenderView! var filterModel:FilterModel = FilterModel(name: "BrightnessAdjustment 亮度", filterType: .basicOperation, range: (-1.0, 1.0, 0.0), initCallback: {BrightnessAdjustment()}, valueChangedCallback: { (filter, value) in (filter as! BrightnessAdjustment).brightness = value }) var movie: MovieInput! = { let documentsDir = try! FileManager.default.url(for:.documentDirectory, in:.userDomainMask, appropriateFor:nil, create:true) let fileURL = URL(string:"test.mp4", relativeTo:documentsDir)! let movie = try? MovieInput(url:fileURL, playAtActualSpeed:true) return movie }() var slider: UISlider = { let slider = UISlider(frame: CGRect(x: 8, y: SCREEN_HEIGHT - 30, width: SCREEN_WIDTH - 18, height: 20)) return slider }() override func viewDidLoad() { super.viewDidLoad() title = "播放視頻添加濾鏡" view.backgroundColor = .white addbutton() slider.addTarget(self, action: #selector(sliderValueChanged(slider:)), for: .valueChanged) view.addSubview(slider) slider.isHidden = true } @objc func buttonClick(btn:UIButton){ if btn.tag == 101 { }else if btn.tag == 102{ playMovie(btn: btn) }else if btn.tag == 103{ let fvc = FilterListTableViewController() fvc.filterBlock = { [weak self] filterModel in guard let `self` = self else { return } self.filterModel = filterModel self.setupFilterChain(filterModel: filterModel) } self.navigationController?.pushViewController(fvc, animated: true) } } func setupFilterChain(filterModel:FilterModel) { title = filterModel.name // pictureInput = PictureInput(image: MaYuImage) slider.minimumValue = filterModel.range?.0 ?? 0 slider.maximumValue = filterModel.range?.1 ?? 0 slider.value = filterModel.range?.2 ?? 0 let filterObject = filterModel.initCallback() movie.removeAllTargets() filter.removeAllTargets() renderView.sources.removeAtIndex(0) switch filterModel.filterType! { case .imageGenerators: filterObject as! ImageSource --> renderView case .basicOperation: if let actualFilter = filterObject as? BasicOperation { filter = actualFilter movie --> actualFilter --> renderView // pictureInput.processImage() } case .operationGroup: if let actualFilter = filterObject as? OperationGroup { movie --> actualFilter --> renderView } case .blend: if let actualFilter = filterObject as? BasicOperation { filter = actualFilter let blendImgae = PictureInput(image: flowerImage) blendImgae --> actualFilter movie --> actualFilter --> renderView blendImgae.processImage() } case .custom: filterModel.customCallback!(movie, filterObject, renderView) filter = (filterObject as? BasicOperation)! } self.sliderValueChanged(slider: slider) } @objc func sliderValueChanged(slider: UISlider) { // print("slider value: \(slider.value)") if let actualCallback = filterModel.valueChangedCallback { actualCallback(filter, slider.value) } else { slider.isHidden = true } if filterModel.filterType! != .imageGenerators { } } func addbutton() { let buttonX = UIButton(frame: CGRect.zero) buttonX.tag = 101 buttonX.setTitle("選視頻", for: UIControl.State.normal) buttonX.addTarget(self, action: #selector(buttonClick(btn:)), for: UIControl.Event.touchUpInside) buttonX.backgroundColor = UIColor.gray let buttonY = UIButton(frame: CGRect.zero) buttonY.tag = 102 buttonY.setTitle("播放", for: UIControl.State.normal) buttonY.addTarget(self, action: #selector(buttonClick(btn:)), for: UIControl.Event.touchUpInside) buttonY.backgroundColor = UIColor.gray let buttonZ = UIButton(frame: CGRect.zero) buttonZ.tag = 103 buttonZ.setTitle("選濾鏡", for: UIControl.State.normal) buttonZ.addTarget(self, action: #selector(buttonClick(btn:)), for: UIControl.Event.touchUpInside) buttonZ.backgroundColor = UIColor.gray view.addSubview(buttonX) view.addSubview(buttonY) view.addSubview(buttonZ) buttonX.translatesAutoresizingMaskIntoConstraints = false buttonY.translatesAutoresizingMaskIntoConstraints = false buttonZ.translatesAutoresizingMaskIntoConstraints = false buttonY.widthAnchor.constraint(equalTo: buttonX.widthAnchor).isActive = true buttonZ.widthAnchor.constraint(equalTo: buttonX.widthAnchor).isActive = true buttonX.leftAnchor.constraint(equalTo: view.leftAnchor,constant: 20).isActive = true buttonX.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -5).isActive = true buttonX.heightAnchor.constraint(equalToConstant: 60).isActive = true buttonY.leftAnchor.constraint(equalTo: buttonX.rightAnchor,constant: 10).isActive = true buttonY.topAnchor.constraint(equalTo: buttonX.topAnchor).isActive = true buttonY.bottomAnchor.constraint(equalTo: buttonX.bottomAnchor).isActive = true buttonZ.leftAnchor.constraint(equalTo: buttonY.rightAnchor,constant: 10).isActive = true buttonZ.topAnchor.constraint(equalTo: buttonX.topAnchor).isActive = true buttonZ.bottomAnchor.constraint(equalTo: buttonX.bottomAnchor).isActive = true buttonZ.rightAnchor.constraint(equalTo: view.rightAnchor,constant: -20).isActive = true } //播放 @objc func playMovie(btn:UIButton){ btn.isSelected = !btn.isSelected if btn.isSelected { btn.setTitle("stop", for: UIControl.State.normal) if (movie == nil) { filter = SaturationAdjustment() movie --> filter --> renderView movie.start() }else{ movie --> filter } // movie.runBenchmark = true // }else{ btn.setTitle("play", for: UIControl.State.normal) // movie.cancel() movie.removeAllTargets() // filter.removeAllTargets() } } }
四、錄制視頻添加實時濾鏡
class VideoViewController: UIViewController { var filterModel:FilterModel = FilterModel(name: "BrightnessAdjustment 亮度", filterType: .basicOperation, range: (-1.0, 1.0, 0.0), initCallback: {BrightnessAdjustment()}, valueChangedCallback: { (filter, value) in (filter as! BrightnessAdjustment).brightness = value }) var picture:PictureInput! var filter:BasicOperation! var camera: Camera! var movieOutput:MovieOutput? = nil var movie: MovieInput! var renderView: RenderView! var slider: UISlider = { let slider = UISlider(frame: CGRect(x: 8, y: SCREEN_HEIGHT - 30, width: SCREEN_WIDTH - 18, height: 20)) return slider }() //RenderView func creaatRenderView() -> RenderView{ let renderView = RenderView(frame:CGRect(x: 0, y: 0, width: SCREEN_WIDTH, height: SCREEN_HEIGHT - 50)) let button = UIButton(frame: CGRect(x: 20, y: UIScreen.main.bounds.height - 100, width:80, height: 40)) button.setTitle("開始錄制", for: UIControl.State.normal) button.backgroundColor = .gray button.addTarget(self, action: #selector(startVideo(btn:)), for: UIControl.Event.touchUpInside) renderView.addSubview(button) let filterButton = UIButton(frame: CGRect(x: 130, y: UIScreen.main.bounds.height - 100, width: 80, height: 40)) filterButton.setTitle("選擇濾鏡", for: UIControl.State.normal) filterButton.backgroundColor = .gray filterButton.center.x = self.view.center.x filterButton.addTarget(self, action: #selector(ChoseFilters), for: .touchUpInside) renderView.addSubview(filterButton) let playButton = UIButton(frame: CGRect(x: UIScreen.main.bounds.width - 100, y: UIScreen.main.bounds.height - 100, width: 80, height: 40)) playButton.setTitle("播放視頻", for: UIControl.State.normal) playButton.backgroundColor = .gray playButton.addTarget(self, action: #selector(playMovie(btn:)), for: UIControl.Event.touchUpInside) renderView.addSubview(playButton) return renderView } @objc func ChoseFilters(btn:UIButton) { let fvc = FilterListTableViewController() fvc.filterBlock = { [weak self] filterModel in guard let `self` = self else { return } self.filterModel = filterModel self.setupFilterChain(filterModel: filterModel) } self.navigationController?.pushViewController(fvc, animated: true) } func setupFilterChain(filterModel:FilterModel) { title = filterModel.name // pictureInput = PictureInput(image: MaYuImage) slider.minimumValue = filterModel.range?.0 ?? 0 slider.maximumValue = filterModel.range?.1 ?? 0 slider.value = filterModel.range?.2 ?? 0 let filterObject = filterModel.initCallback() camera.removeAllTargets() filter.removeAllTargets() renderView.sources.removeAtIndex(0) switch filterModel.filterType! { case .imageGenerators: filterObject as! ImageSource --> renderView case .basicOperation: if let actualFilter = filterObject as? BasicOperation { filter = actualFilter camera --> actualFilter --> renderView // pictureInput.processImage() } case .operationGroup: if let actualFilter = filterObject as? OperationGroup { camera --> actualFilter --> renderView } case .blend: if let actualFilter = filterObject as? BasicOperation { filter = actualFilter let blendImgae = PictureInput(image: flowerImage) blendImgae --> actualFilter camera --> actualFilter --> renderView blendImgae.processImage() } case .custom: filterModel.customCallback!(camera, filterObject, renderView) filter = filterObject as? BasicOperation } self.sliderValueChanged(slider: slider) } @objc func sliderValueChanged(slider: UISlider) { // print("slider value: \(slider.value)") if let actualCallback = filterModel.valueChangedCallback { actualCallback(filter, slider.value) } else { slider.isHidden = true } if filterModel.filterType! != .imageGenerators { } } //播放 @objc func playMovie(btn:UIButton){ let playVc = PlayMoviewViewController() self.navigationController?.pushViewController(playVc, animated: true) } //拍攝 @objc func startVideo(btn:UIButton){ btn.isSelected = !btn.isSelected if btn.isSelected { btn.setTitle("stop", for: UIControl.State.normal) do { let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first! as NSString print(documentsPath) let documentsDir = try FileManager.default.url(for:.documentDirectory, in:.userDomainMask, appropriateFor:nil, create:true) let fileURL = URL(string:"test.mp4", relativeTo:documentsDir)! do { try FileManager.default.removeItem(at:fileURL) } catch { print("error") } movieOutput = try MovieOutput(URL:fileURL, size:Size(width:480, height:640), liveVideo:true) camera.audioEncodingTarget = movieOutput camera.removeAllTargets() camera --> filter --> movieOutput! movieOutput!.startRecording() } catch { fatalError("Couldn't initialize movie, error: \(error)") } }else{ btn.setTitle("start", for: UIControl.State.normal) movieOutput?.finishRecording{ self.camera.audioEncodingTarget = nil self.movieOutput = nil } } } override func viewDidLoad() { super.viewDidLoad() title = "拍攝視頻" view.backgroundColor = .white slider.addTarget(self, action: #selector(sliderValueChanged(slider:)), for: .valueChanged) self.renderView = creaatRenderView() view.addSubview(renderView) view.addSubview(slider) slider.isHidden = true if let fi = filterModel.initCallback() as? BasicOperation{ filter = fi }else{ filter = BrightnessAdjustment() } cameraFiltering() } func cameraFiltering() { // Camera的構造函數是可拋出錯誤的 do { camera = try Camera(sessionPreset: AVCaptureSession.Preset.hd1280x720, cameraDevice: nil, location: .backFacing, captureAsYUV: true) } catch { print(error) return } // 綁定處理鏈 camera --> renderView // 開始捕捉數據 self.camera.startCapture() // 結束捕捉數據 // camera.stopCapture() } }
五、自定義濾鏡
框架使用一系列協議來定義可以輸出要處理的圖像、接收要處理的圖像或同時執行這兩種操作的類型。它們分別是ImageSource、ImageConsumer和ImageProcessingOperation協議。任何類型都可以遵循這些協議,但通常使用類。許多常見的濾鏡和其他圖像處理操作可以被描述為BasicOperation類的子類。BasicOperation提供了從一個或多個圖像輸入中獲取圖像所需的大量內部代碼,使用指定的着色程序從這些輸入中繪制圖像,並將該圖像提供給所有目標。在基本操作上的變化,如紋理放大操作或兩個階段操作,提供額外的信息給着色程序,可能需要某些類型的操作。要構建一個簡單的單自定義濾鏡,甚至可能不需要創建自己的子類。當實例化一個基本操作時,你所需要做的就是提供一個片段着色器和輸入的數量:
let myFilter = BasicOperation(fragmentShaderFile:MyFilterFragmentShaderURL, numberOfInputs:1)
一個着色器程序是由匹配的頂點着色器和片段着色器組成的,它們被編譯並鏈接到一個程序中。默認情況下,框架根據輸入到操作中的圖像的數量使用一系列頂點着色器。通常,你所需要做的就是提供用於執行過濾或其他處理的自定義片段着色器。
func customFilter() { // 獲取文件路徑 let url = URL(fileURLWithPath: Bundle.main.path(forResource: "Custom", ofType: "fsh")!) var customFilter: BasicOperation do { // 從文件中創建自定義濾鏡 customFilter = try BasicOperation(fragmentShaderFile: url) } catch { print(error) return } // 進行濾鏡處理 imageView.image = imageView.image!.filterWithOperation(customFilter) } /* 自定義片元着色器代碼 precision highp float; varying vec2 TextureCoordsVarying; uniform sampler2D Texture; void main() { vec2 sampleDivisor = vec2(1.0 / 200.0, 1.0 / 320.0); //highp vec4 colorDivisor = vec4(colorDepth); vec2 samplePos = TextureCoordsVarying - mod(TextureCoordsVarying, sampleDivisor); vec4 color = texture2D(Texture, samplePos ); //gl_FragColor = texture2D(Texture, samplePos ); vec4 colorCyan = vec4(85.0 / 255.0, 1.0, 1.0, 1.0); vec4 colorMagenta = vec4(1.0, 85.0 / 255.0, 1.0, 1.0); vec4 colorWhite = vec4(1.0, 1.0, 1.0, 1.0); vec4 colorBlack = vec4(0.0, 0.0, 0.0, 1.0); vec4 endColor; float blackDistance = distance(color, colorBlack); float whiteDistance = distance(color, colorWhite); float magentaDistance = distance(color, colorMagenta); float cyanDistance = distance(color, colorCyan); vec4 finalColor; float colorDistance = min(magentaDistance, cyanDistance); colorDistance = min(colorDistance, whiteDistance); colorDistance = min(colorDistance, blackDistance); if (colorDistance == blackDistance) { finalColor = colorBlack; } else if (colorDistance == whiteDistance) { finalColor = colorWhite; } else if (colorDistance == cyanDistance) { finalColor = colorCyan; } else { finalColor = colorMagenta; } gl_FragColor = finalColor; } */
六、濾鏡操作組
如果希望將一系列操作分組到單個單元中以便傳遞,可以創建一個新的OperationGroup實例。OperationGroup提供了一個configureGroup屬性,它帶有一個閉包,該閉包指定了該組應該如何配置
unc operationGroup() { // 創建一個BrightnessAdjustment顏色處理濾鏡 let brightnessAdjustment = BrightnessAdjustment() brightnessAdjustment.brightness = 0.2 // 創建一個ExposureAdjustment顏色處理濾鏡 let exposureAdjustment = ExposureAdjustment() exposureAdjustment.exposure = 0.5 // 創建一個操作組 let operationGroup = OperationGroup() // 給閉包賦值,綁定處理鏈 operationGroup.configureGroup{input, output in input --> brightnessAdjustment --> exposureAdjustment --> output } // 進行濾鏡處理 imageView.image = imageView.image!.filterWithOperation(operationGroup) }
Core Image是iOS內置的圖像處理框架,兩者相比各有優點:
GPUImage 優勢
最低支持 iOS 4.0,iOS 5.0 之后就支持自定義濾鏡。
在低端機型上,GPUImage 有更好的表現。(這個我沒用真正的設備對比過,GPUImage 的主頁上是這么說的)
GPUImage 在視頻處理上有更好的表現。
GPUImage 的代碼完成公開,實現透明。
可以根據自己的業務需求,定制更加復雜的管線操作。可定制程度高。
Core Image 優勢
官方框架,使用放心,維護方便。
支持 CPU 渲染,可以在后台繼續處理和保存圖片。
一些濾鏡的性能更強勁。例如由 Metal Performance Shaders 支持的模糊濾鏡等。
支持使用 Metal 渲染圖像。而 Metal 在 iOS 平台上有更好的表現。
與 Metal,SpriteKit,SceneKit,Core Animation 等更完美的配合。
支持圖像識別功能。包括人臉識別、條形碼識別、文本識別等。
支持自動增強圖像效果,會分析圖像的直方圖,圖像屬性,臉部區域,然后通過一組濾鏡來改善圖像效果。
支持對原生 RAW 格式圖片的處理。
濾鏡鏈的性能比 GPUImage 高。(沒有驗證過,GPUImage 的主頁上是這么說的)。
支持對大圖進行處理,超過 GPU 紋理限制 (4096 * 4096)的時候,會自動拆分成幾個小塊處理(Automatic tiling)。GPUImage 當處理超過紋理限制的圖像時候,會先做判斷,壓縮成最大紋理限制的圖像,導致圖像質量損失。
本文所有示例代碼github地址:https://github.com/duzhaoquan/UseGPUImage2
參考資料:GPUImage集成與使用:https://www.jianshu.com/p/1bcf38960dbb
GPUImage2:https://github.com/BradLarson/GPUImage2