日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

iOS8 Core Image In Swift:视频实时滤镜

發(fā)布時間:2025/3/14 编程问答 45 豆豆
生活随笔 收集整理的這篇文章主要介紹了 iOS8 Core Image In Swift:视频实时滤镜 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

iOS8 Core Image In Swift:自動改善圖像以及內置濾鏡的使用

iOS8 Core Image In Swift:更復雜的濾鏡

iOS8 Core Image In Swift:人臉檢測以及馬賽克

iOS8 Core Image In Swift:視頻實時濾鏡

在Core Image之前,我們雖然也能在視頻錄制或照片拍攝中對圖像進行實時處理,但遠沒有Core Image使用起來方便,我們稍后會通過一個Demo回顧一下以前的做法,在此之前的例子都可以在模擬器和真機中測試,而這個例子因為會用到攝像頭,所以 只能在真機上測試。

視頻采集

我們要進行實時濾鏡的前提,就是對攝像頭以及UI操作的完全控制,那么我們將不能使用系統(tǒng)提供的Controller,需要自己去繪制一切。 先建立一個Single View Application工程(我命名名RealTimeFilter),還是在Storyboard里關掉Auto Layout和Size Classes,然后放一個Button進去,Button的事件連到VC的openCamera方法上,然后我們給VC加兩個屬性:

class?ViewController:?UIViewController?,?AVCaptureVideoDataOutputSampleBufferDelegate?{

? ??var?captureSession:?AVCaptureSession!

? ??var?previewLayer:?CALayer!

......

一個previewLayer用來做預覽窗口,還有一個AVCaptureSession則是重點。
除此之外,我還對VC實現(xiàn)了AVCaptureVideoDataOutputSampleBufferDelegate協(xié)議,這個會在后面說。
要使用AV框架,必須先引入庫:import AVFoundation
在viewDidLoad里實現(xiàn)如下:

override?func?viewDidLoad() {

? ??super.viewDidLoad()

?? ?

? ??previewLayer?=?CALayer()

? ??previewLayer.bounds?=?CGRectMake(0,?0,?self.view.frame.size.height,?self.view.frame.size.width);

? ??previewLayer.position?=?CGPointMake(self.view.frame.size.width?/?2.0,?self.view.frame.size.height?/?2.0);

? ??previewLayer.setAffineTransform(CGAffineTransformMakeRotation(CGFloat(M_PI?/?2.0)));

?? ?

? ??self.view.layer.insertSublayer(previewLayer, atIndex:?0)

?? ?

? ??setupCaptureSession()

}

這里先對previewLayer進行初始化,注意bounds的寬、高和設置的旋轉,這是因為AVFoundation產出的圖像是旋轉了90度的,所以這里預先調整過來,然后把layer插到最下面,全屏顯示,最后調用初始化captureSession的方法:

func?setupCaptureSession() {

? ??captureSession?=?AVCaptureSession()

? ? captureSession.beginConfiguration()

?

? ??captureSession.sessionPreset?=?AVCaptureSessionPresetLow

?? ?

? ??let?captureDevice =?AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)

?? ?

? ??let?deviceInput =?AVCaptureDeviceInput.deviceInputWithDevice(captureDevice, error:?nil)?as?AVCaptureDeviceInput

? ??if?captureSession.canAddInput(deviceInput) {

? ? ? ??captureSession.addInput(deviceInput)

? ? }

?? ?

? ??let?dataOutput =?AVCaptureVideoDataOutput()

? ? dataOutput.videoSettings?= [kCVPixelBufferPixelFormatTypeKey?:?kCVPixelFormatType_420YpCbCr8BiPlanarFullRange]

? ? dataOutput.alwaysDiscardsLateVideoFrames?=?true

?? ?

? ??if?captureSession.canAddOutput(dataOutput) {

? ? ? ??captureSession.addOutput(dataOutput)

? ? }

?? ?

? ??let?queue =?dispatch_queue_create("VideoQueue",?DISPATCH_QUEUE_SERIAL)

? ? dataOutput.setSampleBufferDelegate(self, queue: queue)

?

? ? captureSession.commitConfiguration()

}

從這個方法開始,就算正式開始了。

?

  • 首先實例化一個AVCaptureSession對象,AVFoundation基于會話的概念,會話(session)被用于控制輸入到輸出的過程
  • beginConfiguration與commitConfiguration總是成對調用,當后者調用的時候,會批量配置session,且是線程安全的,更重要的是,可以在session運行中執(zhí)行,總是使用這對方法是一個好的習慣
  • 然后設置它的采集質量,除了AVCaptureSessionPresetLow以外還有很多其他選項,感興趣可以自己看看。
  • 獲取采集設備,默認的攝像設備是后置攝像頭。
  • 把上一步獲取到的設備作為輸入設備添加到當前session中,先用canAddInput方法判斷一下是個好習慣。
  • 添加完輸入設備后再添加輸出設備到session中,我在這里添加的是AVCaptureVideoDataOutput, 表示視頻里的每一幀,除此之外,還有AVCaptureMovieFileOutput(完整的視頻)、 AVCaptureAudioDataOutput(音頻)、AVCaptureStillImageOutput(靜態(tài)圖)等。關于 videoSettings屬性設置,可以先看看文檔說明:

    后面有寫到雖然videoSettings是指定一個字典,但是目前只支持kCVPixelBufferPixelFormatTypeKey,我們用它指定像素的輸出格式,這個參數(shù)直接影響到生成圖像的成功與否,由于我打算先做一個實時灰度的效果,所以這里使用kCVPixelFormatType_420YpCbCr8BiPlanarFullRange的輸出格式,關于這個格式的詳細說明,可以看最后面的參數(shù)資料3(YUV的維基)。
  • 后面設置了alwaysDiscardsLateVideoFrames參數(shù),表示丟棄延遲的幀;同樣用canAddInput方法判斷并添加到session中。
  • 最后設置delegate回調(AVCaptureVideoDataOutputSampleBufferDelegate協(xié)議)和回調時所處的GCD隊列,并提交修改的配置。
  • ?

    我們現(xiàn)在完成一個session的建立過程,但這個session還沒有開始工作,就像我們訪問數(shù)據(jù)庫的時候,要先打開數(shù)據(jù)庫---然后建立連接---訪問數(shù)據(jù)---關閉連接---關閉數(shù)據(jù)庫一樣,我們在openCamera方法里啟動session:?

    ?

    @IBAction?func?openCamera(sender:?UIButton) {

    ? ? sender.enabled?=?false

    ? ??captureSession.startRunning()

    }

    session啟動之后,不出意外的話,回調就開始了,并且是實時回調(這也是為什么要把delegate回調放在一個GCD隊列中的原因),我們處理

    ?

    optional func captureOutput(captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, fromConnection connection: AVCaptureConnection!)

    這個回調就可以了:

    ?

    Core Image之前的方式

    func?captureOutput(captureOutput:?AVCaptureOutput!,

    ? ? ? ? ? ? ? ? ? ? didOutputSampleBuffer sampleBuffer:?CMSampleBuffer!,

    ? ? ? ? ? ? ? ? ? ? fromConnection connection:?AVCaptureConnection!) {

    ?

    ? ??let?imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)

    ?

    ? ??CVPixelBufferLockBaseAddress(imageBuffer,?0)

    ?

    ? ??let?width =?CVPixelBufferGetWidthOfPlane(imageBuffer,?0)

    ? ??let?height =?CVPixelBufferGetHeightOfPlane(imageBuffer,?0)

    ? ??let?bytesPerRow =?CVPixelBufferGetBytesPerRowOfPlane(imageBuffer,?0)

    ? ??let?lumaBuffer =?CVPixelBufferGetBaseAddressOfPlane(imageBuffer,?0)

    ?? ?

    ? ??let?grayColorSpace =?CGColorSpaceCreateDeviceGray()

    ? ??let?context =?CGBitmapContextCreate(lumaBuffer, width, height,?8, bytesPerRow, grayColorSpace,?CGBitmapInfo.allZeros)

    ? ??let?cgImage =?CGBitmapContextCreateImage(context)

    ?? ?

    ? ??dispatch_sync(dispatch_get_main_queue(), {

    ? ? ? ??self.previewLayer.contents = cgImage

    ? ? })

    }

    當數(shù)據(jù)緩沖區(qū)的內容更新的時候,AVFoundation就會馬上調這個回調,所以我們可以在這里收集視頻的每一幀,經過處理之后再渲染到layer上展示給用戶。

    ?

    ?

  • 首先這個回調給我們了一個CMSampleBufferRef類型的sampleBuffer,這是Core Media對象,我們可以通過CMSampleBufferGetImageBuffer方法把它轉成Core Video對象。
  • 然后我們把緩沖區(qū)的base地址給鎖住了,鎖住base地址是為了使緩沖區(qū)的內存地址變得可訪問,否則在后面就取不到必需的數(shù)據(jù),顯示在layer上就只有黑屏,更詳細的原因可以看這里:
    http://stackoverflow.com/questions/6468535/cvpixelbufferlockbaseaddress-why-capture-still-image-using-avfoundation
  • 接下來從緩沖區(qū)取圖像的信息,包括寬、高、每行的字節(jié)數(shù)等
  • 因為視頻的緩沖區(qū)是YUV格式的,我們要把它的luma部分提取出來
  • 我們?yōu)榱税丫彌_區(qū)的圖像渲染到layer上,需要用Core Graphics創(chuàng)建一個顏色空間和圖形上下文,然后通過創(chuàng)建的顏色空間把緩沖區(qū)的圖像渲染到上下文中
  • cgImage就是從緩沖區(qū)創(chuàng)建的Core Graphics圖像了(CGImage),最后我們在主線程把它賦值給layer的contents予以顯示
  • 現(xiàn)在在真機上編譯、運行,應該能看到如下的實時灰度效果: (這張圖是通過手機截屏獲取的,容易手抖,所以不是很清晰)

    用Core Image處理

    通過以上幾步可以看到,代碼不是很多,沒有Core Image也能處理,但是比較費勁,難以理解、不好維護,如果想多增加一些效果(這僅僅是一個灰度效果),代碼會變得非常臃腫,所以拓展性也不好。 事實上,我們想通過Core Image改造上面的代碼也很簡單,先從添加CIFilter和CIContext開始,這是Core Image的核心內容。 在VC上新增兩個屬性:

    var?filter:?CIFilter!

    lazy?var?context:?CIContext?= {

    ? ??let?eaglContext =?EAGLContext(API:?EAGLRenderingAPI.OpenGLES2)

    ? ??let?options = [kCIContextWorkingColorSpace?:?NSNull()]

    ? ??return?CIContext(EAGLContext: eaglContext, options: options)

    }()

    申明一個CIFilter對象,不用實例化;懶加載一個CIContext,這個CIContext的實例通過contextWithEAGLContext:方法構造,和我們之前所使用的不一樣,雖然通過contextWithOptions:方法也能構造一個GPU的CIContext,但前者的優(yōu)勢在于:渲染圖像的過程始終在GPU上進行,并且永遠不會復制回CPU存儲器上,這就保證了更快的渲染速度和更好的性能。
    實際上,通過contextWithOptions:創(chuàng)建的GPU的context,雖然渲染是在GPU上執(zhí)行,但是其輸出的image是不能顯示的,
    只有當其被復制回CPU存儲器上時,才會被轉成一個可被顯示的image類型,比如UIImage。
    我 們先創(chuàng)建了一個EAGLContext,再通過EAGLContext創(chuàng)建一個CIContext,并且通過把working color space設為nil來關閉顏色管理功能,顏色管理功能會降低性能,而且只有當對顏色保真度要求很高的時候才需要顏色管理功能,在其他情況下,特別是實時 處理中,顏色保真都不是特別重要(性能第一,視頻幀延遲很高的app大家都不會喜歡的)。 然后我們把session的配置過程稍微修改一下,只修改一處代碼即可:

    kCVPixelFormatType_420YpCbCr8BiPlanarFullRange

    替換為

    kCVPixelFormatType_32BGRA

    我們把上面那個難以理解的格式替換為BGRA像素格式,大多數(shù)情況下用此格式即可。

    再把session的回調進行一些修改,變成我們熟悉的方式,就像這樣:

    ?

    func?captureOutput(captureOutput:?AVCaptureOutput!,

    ? ? ? ? ? ? ? ? ? ? didOutputSampleBuffer sampleBuffer:?CMSampleBuffer!,

    ? ? ? ? ? ? ? ? ? ? fromConnection connection:?AVCaptureConnection!) {

    ? ??let?imageBuffer =?CMSampleBufferGetImageBuffer(sampleBuffer)

    ?? ? ? ? ? ? ? ? ? ? ? ?

    ? ??// CVPixelBufferLockBaseAddress(imageBuffer, 0)

    ? ??// let width = CVPixelBufferGetWidthOfPlane(imageBuffer, 0)

    ? ??// let height = CVPixelBufferGetHeightOfPlane(imageBuffer, 0)

    ? ??// let bytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 0)

    ? ??// let lumaBuffer = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0)

    ? ??//

    ? ??// let grayColorSpace = CGColorSpaceCreateDeviceGray()

    ? ??// let context = CGBitmapContextCreate(lumaBuffer, width, height, 8, bytesPerRow, grayColorSpace, CGBitmapInfo.allZeros)

    ? ??// let cgImage = CGBitmapContextCreateImage(context)

    ?? ?

    ? ??var?outputImage =?CIImage(CVPixelBuffer: imageBuffer)

    ?? ?

    ? ??if?filter?!=?nil?{

    ? ? ? ??filter.setValue(outputImage, forKey:?kCIInputImageKey)

    ? ? ? ? outputImage =?filter.outputImage

    ? ? }

    ?? ?

    ? ??let?cgImage =?context.createCGImage(outputImage, fromRect: outputImage.extent())

    ?? ?

    ? ??dispatch_sync(dispatch_get_main_queue(), {

    ? ? ? ??self.previewLayer.contents?= cgImage

    ? ? })

    }

    ?

    這是一段拓展性、維護性都比較好的代碼了:

  • 先拿到緩沖區(qū),看從緩沖區(qū)直接取到一張CIImage
  • 如果指定了濾鏡,就應用到圖像上;反之則顯示原圖
  • 通過context創(chuàng)建CGImage的實例
  • 在主隊列中顯示到layer上
  • 在此基礎上,我們只用添加一些濾鏡就可以了。 先在Storyboard上添加一個UIView,再以這個UIView作容器,往里面加四個button,從0到3設置button的tag,并把button們的事件全部連接到VC的applyFilter方法上,UI看起來像這樣: 把這個UIView(buttons的容器)連接到VC的filterButtonsContainer上,再添加一個字符串數(shù)組,存儲一些濾鏡的名字,最終VC的所有屬性如下:

    class?ViewController:?UIViewController?,?AVCaptureVideoDataOutputSampleBufferDelegate?{

    ? ??@IBOutlet?var?filterButtonsContainer:?UIView!

    ? ??var?captureSession:?AVCaptureSession!

    ? ??var?previewLayer:?CALayer!

    ? ??var?filter:?CIFilter!

    ? ??lazy?var?context:?CIContext?= {

    ? ? ? ??let?eaglContext =?EAGLContext(API:?EAGLRenderingAPI.OpenGLES2)

    ? ? ? ??let?options = [kCIContextWorkingColorSpace?:?NSNull()]

    ? ? ? ??return?CIContext(EAGLContext: eaglContext, options: options)

    ? ? }()

    ? ??lazy?var?filterNames: [String] = {

    ? ? ? ??return?["CIColorInvert","CIPhotoEffectMono","CIPhotoEffectInstant","CIPhotoEffectTransfer"]

    ? ? }()

    ......

    在viewDidLoad方法中先隱藏濾鏡按鈕們的容器:?

    ......

    filterButtonsContainer.hidden?=?true

    ?......

    修改openCamera方法,最終實現(xiàn)如下:

    @IBAction?func?openCamera(sender:?UIButton) {

    ? ? sender.enabled?=?false

    ? ??captureSession.startRunning()

    ? ??self.filterButtonsContainer.hidden?=?false

    }

    最后applyFilter方法的實現(xiàn):

    @IBAction?func?applyFilter(sender:?UIButton) {

    ? ??var?filterName =?filterNames[sender.tag]

    ? ??filter?=?CIFilter(name: filterName)

    }

    至此,我們就大功告成了,趕緊在真機上編譯、運行看看吧:

    保存到圖庫

    接下來我們添加拍照功能。 首先我們在VC上添加一個名為“拍照”的button,連接到VC的takePicture方法上,在實現(xiàn)方法之前,有幾步改造工作要先做完。 首 先就是圖像元數(shù)據(jù)的問題,一張圖像可能包含定位信息、圖像格式、方向等元數(shù)據(jù),而方向是我們最關心的部分,在上面的viewDidLoad方法中,我是通 過將previewLayer進行旋轉使我們看到正確的圖像,但是如果直接將圖像保存在圖庫或文件中,我們會得到一個方向不正確的圖像,為了最終獲取方向 正確的圖像,我把previewLayer的旋轉去掉:

    ?

    ......

    previewLayer?=?CALayer()

    // previewLayer.bounds = CGRectMake(0, 0, self.view.frame.size.height, self.view.frame.size.width);

    // previewLayer.position = CGPointMake(self.view.frame.size.width / 2.0, self.view.frame.size.height / 2.0);

    // previewLayer.setAffineTransform(CGAffineTransformMakeRotation(CGFloat(M_PI / 2.0)));

    previewLayer.anchorPoint?=?CGPointZero

    previewLayer.bounds?=?view.bounds

    ......

    設置layer的anchorPoint是為了把bounds的頂點從中心變?yōu)樽笊辖?#xff0c;這正是UIView的頂點。

    ?

    現(xiàn)在你運行的話看到的將是方向不正確的圖像。

    然后我們把方向統(tǒng)一放到captureSession的回調中處理,修改之前寫的實現(xiàn):

    ?

    ......

    var?outputImage =?CIImage(CVPixelBuffer: imageBuffer)

    ?? ? ? ? ? ? ? ? ? ?

    let?orientation =?UIDevice.currentDevice().orientation

    var?t:?CGAffineTransform!

    if?orientation?==?UIDeviceOrientation.Portrait?{

    ? ? t =?CGAffineTransformMakeRotation(CGFloat(-M_PI?/?2.0))

    }?else?if?orientation?==?UIDeviceOrientation.PortraitUpsideDown?{

    ? ? t =?CGAffineTransformMakeRotation(CGFloat(M_PI?/?2.0))

    }?else?if?(orientation?==?UIDeviceOrientation.LandscapeRight) {

    ? ? t =?CGAffineTransformMakeRotation(CGFloat(M_PI))

    }?else?{

    ? ? t =?CGAffineTransformMakeRotation(0)

    }

    outputImage = outputImage.imageByApplyingTransform(t)

    ?

    if?filter?!=?nil?{

    ? ??filter.setValue(outputImage, forKey:?kCIInputImageKey)

    ? ? outputImage =?filter.outputImage

    }

    ......

    在獲取outputImage之后并在使用濾鏡之前調整outputImage的方向,這樣一下,四個方向都處理了。

    ?

    運行之后看到的效果和之前就一樣了。

    方向處理完后我們還要用一個實例變量保存這個outputImage,因為這里面含有圖像的元數(shù)據(jù),我們不會丟棄它:

    給VC添加一個CIImage的屬性:?

    ?

    var?ciImage:?CIImage!

    在captureSession的回調里保存CIImage:

    ?

    ?

    ......

    if?filter?!=?nil?{

    ? ??filter.setValue(outputImage, forKey:?kCIInputImageKey)

    ? ? outputImage =?filter.outputImage

    }

    ?

    let?cgImage =?context.createCGImage(outputImage, fromRect: outputImage.extent())

    ciImage?= outputImage

    ......

    濾鏡處理完后,就將這個CIImage存起來,它可能被應用過濾鏡,也可能是干干凈凈的原圖。

    ?

    最后是takePicture的方法實現(xiàn):

    ?

    @IBAction?func?takePicture(sender:?UIButton) {

    ? ? sender.enabled?=?false

    ? ??captureSession.stopRunning()

    ?

    ? ??var?cgImage =?context.createCGImage(ciImage, fromRect:?ciImage.extent())

    ? ??ALAssetsLibrary().writeImageToSavedPhotosAlbum(cgImage, metadata:?ciImage.properties())

    ? ? ? ? { (url:?NSURL!, error :NSError!) -> Void?in

    ? ? ? ? ? ??if?error ==?nil?{

    ? ? ? ? ? ? ? ??println("保存成功")

    ? ? ? ? ? ? ? ??println(url)

    ? ? ? ? ? ? }?else?{

    ? ? ? ? ? ? ? ??let?alert =?UIAlertView(title:?"錯誤",?

    ? ? ??message: error.localizedDescription,?

    ? ? ?delegate:?nil,?

    ? ??cancelButtonTitle:?"確定")

    ? ? ? ? ? ? ? ? alert.show()

    ? ? ? ? ? ? }

    ? ? ? ? ? ??self.captureSession.startRunning()

    ? ? ? ? ? ? sender.enabled?=?true

    ? ? }

    }?

    先將按鈕禁用,session停止運行,再用實例變量ciImage繪制一張CGImage,最后連同元數(shù)據(jù)一同存進圖庫中。

    ?

    ?

    這里需要導入AssetsLibrary庫:import AssetsLibrary。writeImageToSavedPhotosAlbum方法的回調
    block用到了尾隨閉包語法。

    ?

    在真機上編譯、運行看看吧。

    注:由于我是用layer來做預覽容器的,它沒有autoresizingMask這樣的屬性,你會發(fā)現(xiàn)橫屏的時候就顯示不正常了,在iOS 8gh,你可以通過重寫VC的以下方法來兼容橫屏:

    ?

    override?func?viewWillTransitionToSize(size:?CGSize, withTransitionCoordinator?

    coordinator:?UIViewControllerTransitionCoordinator) {

    ? ??previewLayer.bounds.size?= size

    }

    ?

    ?

    ?

    錄制視頻


    前期配置

    這篇文章并不會詳解AVFoundation框架,但為了完成Core Image的功能,我們多多少少會說一些。 我們在VC上添加一個名為“開始錄制”的按鈕,把按鈕本身連接到VC的recordsButton屬性上,并把它的事件連接到record方法上,UI看起來像這樣: 為了愉快地進行下去,我先把為VC新增的所有屬性列出來:

    ......

    // Video Records

    @IBOutlet?var?recordsButton:?UIButton!

    var?assetWriter:?AVAssetWriter?

    var?assetWriterPixelBufferInput:?AVAssetWriterInputPixelBufferAdaptor?

    var?isWriting =?false

    var?currentSampleTime:?CMTime?

    var?currentVideoDimensions:?CMVideoDimensions?

    ......

    這些就是為了實現(xiàn)視頻錄制會用到的所有屬性,我們簡單說一下:
    • recordsButton,為了方便的獲取錄制按鈕的實例而增加的屬性
    • assetWriter,這是一個AVAssetWriter對象的實例,這個類的工作方式很像AVCaptureSession,也是為了控制輸入輸出的流程而存在的
    • assetWriterPixelBufferInput,一個AVAssetWriterInputPixelBufferAdaptor對象,這個屬性的作用如同它的名字,它允許我們不斷地增加像素緩沖區(qū)到assetWriter對象里
    • isWriting,如果我們當前正在錄制視頻,則會用這個實例變量記錄下來
    • currentSampleTime,這是一個時間戳,在AVFoundation框架里,每一塊添加的數(shù)據(jù)(視頻或音頻等)除了data部分外,還需要一個當前的時間,每一幀的時間都不同,這就形成了每一幀的持續(xù)時間(時間間隔)
    • currentVideoDimensions,這個屬性描述了視頻尺寸,雖然這個屬性并不重要,但是我更加懶得把尺寸寫死,它的單位是像素
    接下來我們先完成兩個工具方法:movieURLcheckForAndDeleteFile

    func?movieURL() ->?NSURL?{

    ? ??var?tempDir =?NSTemporaryDirectory()

    ? ??let?urlString = tempDir.stringByAppendingPathComponent("tmpMov.mov")

    ? ??return?NSURL(fileURLWithPath: urlString)

    }

    這個方法做的事情很簡單,只是構建一個臨時目錄里的文件URL。

    func?checkForAndDeleteFile() {

    ? ??let?fm =?NSFileManager.defaultManager()

    ? ??var?url =?movieURL()

    ? ??let?exist = fm.fileExistsAtPath(movieURL().path!)

    ?? ?

    ? ??var?error:?NSError?

    ? ??if?exist {

    ? ? ? ? fm.removeItemAtURL(movieURL(), error: &error)

    ? ? ? ??println("刪除之前的臨時文件")

    ? ? ? ??if?let?errorDescription = error?.localizedDescription?{

    ? ? ? ? ? ??println(errorDescription)

    ? ? ? ? }

    ? ? }

    }

    這個方法檢查了文件是否已存在,如果已存在就刪除舊文件,之所以要增加這個方法是因為AVAssetWriter不能在已有的文件URL上寫文件,如果文件已存在就會報錯。還有一點需要注意:我在iOS 7上判斷文件是否存在時用的是URL的absoluteString方法,結果導致AVAssetWriter沒報錯,但是后面的緩沖區(qū)出錯了,排查了很久,把absoluteString換成path就好了。。 二個工具方法完成后,我們就開始寫最主要的方法,即createWriter方法:

    func?createWriter() {

    ? ??self.checkForAndDeleteFile()

    ?? ?

    ? ??var?error:?NSError?

    ? ??assetWriter?=?AVAssetWriter(URL:?movieURL(), fileType:?AVFileTypeQuickTimeMovie, error: &error)

    ? ??if?let?errorDescription = error?.localizedDescription?{

    ? ? ? ??println("創(chuàng)建writer失敗")

    ? ? ? ??println(errorDescription)

    ? ? ? ??return

    ? ? }

    ?

    ? ??let?outputSettings = [

    ? ? ? ??AVVideoCodecKey?:?AVVideoCodecH264,

    ? ? ? ??AVVideoWidthKey?:?Int(currentVideoDimensions!.width),

    ? ? ? ??AVVideoHeightKey?:?Int(currentVideoDimensions!.height)

    ? ? ]

    ? ??let?assetWriterVideoInput =?AVAssetWriterInput(mediaType:?AVMediaTypeVideo, outputSettings: outputSettings)

    ?

    ? ? assetWriterVideoInput.expectsMediaDataInRealTime?=?true

    ? ? assetWriterVideoInput.transform?=?CGAffineTransformMakeRotation(CGFloat(M_PI?/?2.0))

    ?

    ? ??let?sourcePixelBufferAttributesDictionary = [

    ? ? ? ??kCVPixelBufferPixelFormatTypeKey?:?kCVPixelFormatType_32BGRA,

    ? ? ? ??kCVPixelBufferWidthKey?:?Int(currentVideoDimensions!.width),

    ? ? ? ??kCVPixelBufferHeightKey?:?Int(currentVideoDimensions!.height),

    ? ? ? ??kCVPixelFormatOpenGLESCompatibility?:?kCFBooleanTrue

    ? ? ]

    ? ??assetWriterPixelBufferInput?=?AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: assetWriterVideoInput,

    ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? sourcePixelBufferAttributes: sourcePixelBufferAttributesDictionary)

    ?? ?

    ? ??if?assetWriter!.canAddInput(assetWriterVideoInput) {

    ? ? ? ??assetWriter!.addInput(assetWriterVideoInput)

    ? ? }?else?{

    ? ? ? ??println("不能添加視頻writer的input?\(assetWriterVideoInput)")

    ? ? }

    }?

    這個方法主要是配置項很多。
    • 首先檢查了文件是否存在,如果存在的話就刪除舊的臨時文件,不然AVAssetWriter會因無法寫入文件而報錯
    • 實例化一個AVAssetWriter對象,把需要寫的文件URL和文件類型傳遞給它,再給它一個存儲錯誤信息的指針,方便在出錯的時候排查
    • 創(chuàng)建一個outputSettings的字典應用到AVAssetWriterInput對 象上,這個對象之前沒有提到,但也是相當重要的一個對象,它表示了一個輸入設備,比如視頻、音頻的輸入等,不同的設備擁有不同的參數(shù)和配置,并不復雜,我 們這里就不考慮音頻輸入了。在這個視頻的配置里,我們配置了視頻的編碼,以及用獲取到的當前視頻設備尺寸(單位像素)初始化了寬、高
    • 設置expectsMediaDataInRealTime為true,這是從攝像頭捕獲的源中進行實時編碼的必要參數(shù)
    • 設置了視頻的transform,主要也是為了解決方向問題
    • 創(chuàng)建另外一個屬性字典去實例化一個AVAssetWriterInputPixelBufferAdaptor對象,我們在視頻采集的過程中,會不斷地通過這個緩沖區(qū)往AVAssetWriter對象里添加內容,實例化的參數(shù)中還有AVAssetWriterInput對象,屬性字典標識了緩沖區(qū)的大小與格式。
    • 最后判斷一下能否添加這個輸入設備,雖然大多數(shù)情況下判斷一定為真,而且為假的情況我們也沒辦法考慮了,但預先判斷還是一個好的編碼習慣

    處理每一幀

    上 面這些基本性的配置工作完成后,在正式開始錄制視頻之前,我們還有最后一步要處理,那就是處理視頻的每一幀。其實在之前我們就已經嘗試過處理每一幀了,因 為我們做過拍照的實時濾鏡功能,現(xiàn)在我們只需要修改AVCaptureSession的回調就行了。由于之前在 captureOutput:didOutputSampleBuffer:這個回調方法中,我們是先對圖像的方向進行處理,然后再對其應用濾鏡,而錄制 視頻的時候我們不需要對方向進行處理,因為在配置AVAssetWriterInput對象的時候我們已經處理過了,所以我們先將應用濾鏡和方向調整的代 碼互換一下,變成先應用濾鏡,再處理方向,然后在他們中間插入處理錄制視頻的代碼:

    ......

    if?self.filter?!=?nil?{

    ? ??self.filter.setValue(outputImage, forKey:?kCIInputImageKey)

    ? ? outputImage =?self.filter.outputImage

    }

    ?

    //?處理錄制視頻

    let?formatDescription =?CMSampleBufferGetFormatDescription(sampleBuffer)

    self.currentVideoDimensions?=?CMVideoFormatDescriptionGetDimensions(formatDescription)

    self.currentSampleTime?=?CMSampleBufferGetOutputPresentationTimeStamp(sampleBuffer)

    if?self.isWriting?{

    ? ??if?self.assetWriterPixelBufferInput?.assetWriterInput.readyForMoreMediaData?==?true?{

    ? ? ? ??var?newPixelBuffer:?Unmanaged<CVPixelBuffer>? =?nil

    ? ? ? ??CVPixelBufferPoolCreatePixelBuffer(nil,?self.assetWriterPixelBufferInput?.pixelBufferPool, &newPixelBuffer)

    ?? ? ? ?

    ? ? ? ??self.context.render(outputImage,

    ? ? ? ? ? ? ? ? ? ? ? ? ? ? toCVPixelBuffer: newPixelBuffer?.takeUnretainedValue(),

    ? ? ? ? ? ? ? ? ? ? ? ? ? ? bounds: outputImage.extent(),

    ? ? ? ? ? ? ? ? ? ? ? ? ? ? colorSpace:?nil)

    ?? ? ? ?

    ? ? ? ??let?success =?self.assetWriterPixelBufferInput?.appendPixelBuffer(newPixelBuffer?.takeUnretainedValue(),

    ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? withPresentationTime:?self.currentSampleTime!)

    ?? ? ? ?

    ? ? ? ? newPixelBuffer?.autorelease()

    ?? ? ? ?

    ? ? ? ??if?success ==?false?{

    ? ? ? ? ? ??println("Pixel Buffer沒有append成功")

    ? ? ? ? }

    ? ? }

    }

    ?

    let?orientation =?UIDevice.currentDevice().orientation

    var?t:?CGAffineTransform!

    ......

    在對圖像應用完濾鏡之后,我們做了這些事情:
  • 獲 取尺寸和時間,這兩個值在后面會用到。強調一下,時間這個參數(shù)是很重要的,當你有一系列的幀的時候,assetWriter必須知道何時顯示他們,我們除 了通過CMSampleBufferGetOutputPresentationTimeStamp函數(shù)獲取之外,也可以手動創(chuàng)建一個時間,比如把每個緩 沖區(qū)的時間設置為比上一個緩沖區(qū)時間多1/30秒,這就相當于創(chuàng)建一個每秒30幀的視頻,但是這不能保證視頻時序的真實情況,因為某些濾鏡(或者其他操 作)可能會耗時過長
  • 當前是否需要錄制視頻,錄制視頻其實就是寫文件的一個過程
  • 判斷assetWriter是否已經準備好輸入數(shù)據(jù)了
  • 一切都準備好后,我們就先配置一個緩沖區(qū)。用CVPixelBufferPoolCreatePixelBuffer函 數(shù)能創(chuàng)建基于池的緩沖區(qū),它的好處是在創(chuàng)建緩沖區(qū)的時候會把之前對assetWriterPixelBufferInput對象的配置項應用到新的緩沖區(qū) 上,這樣就避免了你重新對新的緩沖區(qū)進行配置。有一點需要注意,如果我們的assetWriter還未開始工作,那么當我們調用 assetWriterPixelBufferInput的pixelBufferPool時候會得到一個空指針,緩沖區(qū)當然也就創(chuàng)建不了了
  • 我們把緩沖區(qū)準備好后,就利用context把圖像渲染到里面
  • 把緩沖區(qū)寫入到臨時文件中,同時得到是否寫入成功的返回值
  • 由于在Swift里CVPixelBufferPoolCreatePixelBuffer函數(shù)需要的是一個手動管理引用計數(shù)的對象(Unmanaged對象),所以需要自己把它處理一下
  • 如果第6步失敗的話就輸出一下
  • 之前的代碼還是保留,因為我們還是需要將每一幀繪制到屏幕上。 由于這個方法用到了很多對象,而且比較占用內存,所以我在進入這個方法的時候還手動增加了自動釋放池:

    autoreleasepool?{

    ? ??// ....

    }?

    保存視頻到圖庫

    我們之前就加入了recordsButton,并把它連接到了record方法上,現(xiàn)在來實現(xiàn)它:

    @IBAction?func?record() {

    ? ??if?isWriting?{

    ? ? ? ??self.isWriting?=?false

    ? ? ? ??assetWriterPixelBufferInput?=?nil

    ? ? ? ??recordsButton.enabled?=?false

    ? ? ? ??assetWriter?.finishWritingWithCompletionHandler({[unowned?self] () -> Void?in

    ? ? ? ? ? ??println("錄制完成")

    ? ? ? ? ? ??self.recordsButton.setTitle("處理中...", forState:?UIControlState.Normal)

    ? ? ? ? ? ??self.saveMovieToCameraRoll()

    ? ? ? ? })

    ? ? }?else?{

    ? ? ? ??createWriter()

    ? ? ? ??recordsButton.setTitle("停止錄制...", forState:?UIControlState.Normal)

    ? ? ? ??assetWriter?.startWriting()

    ? ? ? ??assetWriter?.startSessionAtSourceTime(currentSampleTime!)

    ? ? ? ??isWriting?=?true

    ? ? }

    }

    首先是不是在錄制,如果是的話就停止錄制、保存視頻,并清理資源。 如果還沒有開始錄制,就創(chuàng)建AVAssetWriter并配置好,然后調用startWriting方法使assetWriter開始工作,不然在回調里取pixelBufferPool的時候取不到,除此之外,還要調用startSessionAtSourceTime方法,調用后者是為了在回調中拿到最新的時間,即currentSampleTime。如果不調用這兩個方法,在appendPixelBuffer的時候就會有問題,就算最后能保存,也只能得到一個空的視頻文件。 當視頻錄制的過程開始后,就只有調用finishWriting方法才能停止,我們通過saveMovieToCameraRoll方法把視頻寫入到圖庫中,不然這視頻也就沒機會展示了:

    ?

    func?saveMovieToCameraRoll() {

    ? ??ALAssetsLibrary().writeVideoAtPathToSavedPhotosAlbum(movieURL(), completionBlock: { (url:?NSURL!, error:?NSError?) -> Void?in

    ? ? ? ??if?let?errorDescription = error?.localizedDescription?{

    ? ? ? ? ? ??println("寫入視頻錯誤:\(errorDescription)")

    ? ? ? ? }?else?{

    ? ? ? ? ? ??self.checkForAndDeleteFile()

    ? ? ? ? ? ??println("寫入視頻成功")

    ? ? ? ? }

    ? ? ? ??self.recordsButton.enabled?=?true

    ? ? ? ??self.recordsButton.setTitle("開始錄制", forState:?UIControlState.Normal)

    ? ? })

    }?

    之前在拍照并保存的時候,我們使用了尾隨閉包語法,這里使用的是完整語法的閉包。

    ?

    保存成功后就可以刪除臨時文件了。

    編譯、運行吧:

    ?

    ?

    局部濾鏡

    上面的濾鏡都是對整張圖像應用濾鏡,我們也可以只對部分區(qū)域應用濾鏡,例如把濾鏡應用到視頻中的面部上。不同于上一篇,AVFoundation框架內置了檢測人臉的功能,所以我們不需要使用CIDetector。

    標記人臉

    我們先簡單的用一個Layer把人臉的區(qū)域標記出來,給VC增加一個屬性:

    //?標記人臉

    var?faceLayer:?CALayer?

    修改setupCaptureSession方法,在captureSession調用commitConfiguration方法之前加入以下代碼:

    ......

    //?為了檢測人臉

    let?metadataOutput =?AVCaptureMetadataOutput()

    metadataOutput.setMetadataObjectsDelegate(self, queue:?dispatch_get_main_queue())

    ?

    if?captureSession.canAddOutput(metadataOutput) {

    ? ??captureSession.addOutput(metadataOutput)

    ? ??println(metadataOutput.availableMetadataObjectTypes)

    ? ? metadataOutput.metadataObjectTypes?= [AVMetadataObjectTypeFace]

    }

    ......

    這 里加入了一個元數(shù)據(jù)的output對象,添加到captureSession后我們就能在回調中得到圖像的元數(shù)據(jù),包括檢測到的人臉。給 metadataObjectTypes屬性賦值是為了申明要檢測的類型,這句要在增加到captureSession之后調用。因為我們要在回調中直接 操作Layer的顯示,所以我把回調放在主隊列中。 實現(xiàn)AVCaptureMetadataOutput的回調方法:

    // MARK: - AVCaptureMetadataOutputObjectsDelegate

    func?captureOutput(captureOutput:?AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection:?AVCaptureConnection!) {

    ? ??// println(metadataObjects)

    ? ??if?metadataObjects.count?>?0?{

    ? ? ? ??//識別到的第一張臉

    ? ? ? ??var?faceObject = metadataObjects.first?as?AVMetadataFaceObject

    ?? ? ? ?

    ? ? ? ??if?faceLayer?==?nil?{

    ? ? ? ? ? ??faceLayer?=?CALayer()

    ? ? ? ? ? ??faceLayer?.borderColor?=?UIColor.redColor().CGColor

    ? ? ? ? ? ??faceLayer?.borderWidth?=?1

    ? ? ? ? ? ??view.layer.addSublayer(faceLayer)

    ? ? ? ? }

    ? ? ? ??let?faceBounds = faceObject.bounds

    ? ? ? ??let?viewSize =?view.bounds.size

    ?

    ? ? ? ??faceLayer?.position?=?CGPoint(x: viewSize.width?*?(1 -?faceBounds.origin.y?-?faceBounds.size.height?/?2),

    ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? y: viewSize.height?*?(faceBounds.origin.x?+?faceBounds.size.width?/?2))

    ?? ? ? ?

    ? ? ? ??faceLayer?.bounds.size?=?CGSize(width: faceBounds.size.width?*?viewSize.height,

    ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? height: faceBounds.size.height?*?viewSize.width)

    ? ? ? ??print(faceBounds.origin)

    ? ? ? ??print("###")

    ? ? ? ??print(faceLayer!.position)

    ? ? ? ??print("###")

    ? ? ? ??print(faceLayer!.bounds)

    ? ? }

    }?

    簡單說明下上述代碼的作用:
  • 參數(shù)中的metadataObjects數(shù)組就是AVFoundation框架給我們的關于圖像的所有元數(shù)據(jù),由于我只設置了需要人臉檢測,所以簡單判斷是否為空后,取出其中的數(shù)據(jù)即可。在這里我只對第一張臉進行了處理
  • 接下來初始化Layer,并設置邊框
  • 取 到的faceObject對象雖然包含了bounds屬性,但并不能直接使用,因為從AVFoundation視頻中取到的bounds,是一個0~1之 間的數(shù),是相對于圖像的百分比,所以我們在設置position時,做了兩步:把x、y顛倒,修正方向等問題,我只是簡單地適配了Portrait方向, 此處能達到目的即可。再和view的寬、高相乘,其實是和Layer的父Layer的寬、高相乘。
  • 設置size也如上
  • 做的事情比較簡單,只是單純地初始化一個Layer,然后不停地修改它的postion和size就行了。 編譯、運行后應該能看到如下效果:

    使用濾鏡

    上面用Layer只是簡單的先顯示一下人臉的區(qū)域,我們沒有調整圖像輸出時的CIImage,所以并不能被錄制到視頻或被保存圖片到圖庫中。 接下來我們就修改之前的代碼,使其能同時支持整體濾鏡和部分濾鏡。 首先把VC中記錄的屬性改一下:?

    ......

    //?標記人臉

    // var faceLayer: CALayer?

    var?faceObject:?AVMetadataFaceObject?

    ......

    我們就不用Layer作人臉范圍的標記了,而是直接把濾鏡應用到輸出的CIImage上,為此,我們需要在AVCaptureMetadataOutput對象的delegate回調方法中記錄識別到的臉部元數(shù)據(jù):

    // MARK: - AVCaptureMetadataOutputObjectsDelegate

    func?captureOutput(captureOutput:?AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection:?AVCaptureConnection!) {

    ? ??// println(metadataObjects)

    ? ??if?metadataObjects.count?>?0?{

    ? ? ? ??//識別到的第一張臉

    ? ? ? ??faceObject?= metadataObjects.first?as??AVMetadataFaceObject

    ?? ? ? ?

    ? ? ? ??/*

    ? ? ? ? if faceLayer == nil {

    ? ? ? ? ? ? faceLayer = CALayer()

    ? ? ? ? ? ? faceLayer?.borderColor = UIColor.redColor().CGColor

    ? ? ? ? ? ? faceLayer?.borderWidth = 1

    ? ? ? ? ? ? view.layer.addSublayer(faceLayer)

    ? ? ? ? }

    ? ? ? ? let faceBounds = faceObject.bounds

    ? ? ? ? let viewSize = view.bounds.size

    ?

    ? ? ? ? faceLayer?.position = CGPoint(x: viewSize.width * (1 - faceBounds.origin.y - faceBounds.size.height / 2),

    ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? y: viewSize.height * (faceBounds.origin.x + faceBounds.size.width / 2))

    ?? ? ? ?

    ? ? ? ? faceLayer?.bounds.size = CGSize(width: faceBounds.size.height * viewSize.width,

    ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? height: faceBounds.size.width * viewSize.height)

    ? ? ? ? print(faceBounds.origin)

    ? ? ? ? print("###")

    ? ? ? ? print(faceLayer!.position)

    ? ? ? ? print("###")

    ? ? ? ? print(faceLayer!.bounds)

    ? ? ? ? */

    ? ? }

    }?

    之前的Layer相關代碼都注釋掉,只簡單地把識別到的第一張臉記錄在VC的屬性中。 然后修改AVCaptureSession的delegate回調,在錄制視頻的代碼之前,全局濾鏡的代碼之后,添加臉部處理代碼:

    ......

    if?self.filter?!=?nil?{ ? ?// 之前做的全局濾鏡?

    ? ??self.filter.setValue(outputImage, forKey:?kCIInputImageKey)

    ? ? outputImage =?self.filter.outputImage

    }

    if?self.faceObject?!=?nil?{ ? ?// 臉部處理

    ? ? outputImage =?self.makeFaceWithCIImage(outputImage, faceObject:?self.faceObject!)

    }

    ......?

    我們寫了個makeFaceWithImage的方法來專門為臉部應用濾鏡,應用的效果是上一篇中提到的馬賽克效果。 makeFaceWithCIImage的方法實現(xiàn):

    func?makeFaceWithCIImage(inputImage:?CIImage, faceObject:?AVMetadataFaceObject) ->?CIImage?{

    ? ??var?filter =?CIFilter(name:?"CIPixellate")

    ? ? filter.setValue(inputImage, forKey:?kCIInputImageKey)

    ? ??// 1.

    ? ? filter.setValue(max(inputImage.extent().size.width, inputImage.extent().size.height)?/?60, forKey:?kCIInputScaleKey)

    ?? ?

    ? ??let?fullPixellatedImage = filter.outputImage

    ? ??var?maskImage:?CIImage!

    ? ??let?faceBounds = faceObject.bounds

    ?? ?

    ? ??// 2.

    ? ??let?centerX = inputImage.extent().size.width?*?(faceBounds.origin.x?+?faceBounds.size.width?/?2)

    ? ??let?centerY = inputImage.extent().size.height?*?(1?-?faceBounds.origin.y?-?faceBounds.size.height?/?2)

    ? ??let?radius = faceBounds.size.width?*?inputImage.extent().size.width?/?2

    ? ??let?radialGradient =?CIFilter(name:?"CIRadialGradient",

    ? ? ? ? withInputParameters: [

    ? ? ? ? ? ??"inputRadius0"?: radius,

    ? ? ? ? ? ??"inputRadius1"?: radius?+?1,

    ? ? ? ? ? ??"inputColor0"?:?CIColor(red:?0, green:?1, blue:?0, alpha:?1),

    ? ? ? ? ? ??"inputColor1"?:?CIColor(red:?0, green:?0, blue:?0, alpha:?0),

    ? ? ? ? ? ??kCIInputCenterKey?:?CIVector(x: centerX, y: centerY)

    ? ? ? ? ])

    ?

    ? ??let?radialGradientOutputImage = radialGradient.outputImage.imageByCroppingToRect(inputImage.extent())

    ? ??if?maskImage ==?nil?{

    ? ? ? ? maskImage = radialGradientOutputImage

    ? ? }?else?{

    ? ? ? ??println(radialGradientOutputImage)

    ? ? ? ? maskImage =?CIFilter(name:?"CISourceOverCompositing",

    ? ? ? ? ? ? withInputParameters: [

    ? ? ? ? ? ? ? ??kCIInputImageKey?: radialGradientOutputImage,

    ? ? ? ? ? ? ? ??kCIInputBackgroundImageKey?: maskImage

    ? ? ? ? ? ? ]).outputImage

    ? ? }

    ?? ?

    ? ??let?blendFilter =?CIFilter(name:?"CIBlendWithMask")

    ? ? blendFilter.setValue(fullPixellatedImage, forKey:?kCIInputImageKey)

    ? ? blendFilter.setValue(inputImage, forKey:?kCIInputBackgroundImageKey)

    ? ? blendFilter.setValue(maskImage, forKey:?kCIInputMaskImageKey)

    ?? ?

    ? ??return?blendFilter.outputImage

    }?

    這上面的代碼基本是復制上一篇里的代碼,改的地方只有兩處:
  • 把馬賽克的效果變大,kCIInputScaleKey默認值為0.5,你可以把這行代碼注釋掉后看效果
  • 計算臉部的中心點和半徑,計算方法和之前didOutputMetadataObjects這個delegate回調中的計算方法一樣,復制過來就行了
  • 如果你看到我的上一篇《iOS8 Core Image In Swift:人臉檢測以及馬賽克》的話,這里面的實現(xiàn)方式應該就很清楚了。 到此,對臉部的濾鏡也處理好了,編譯、運行,可以得到這樣的結果:

    ?

    ?

    ?

    GitHub下載地址

    我在GitHub上會保持更新。

    ?

    ?

    參考資料:

    1.?http://weblog.invasivecode.com/post/18445861158/a-very-cool-custom-video-camera-with

    2.?https://developer.apple.com/library/mac/documentation/graphicsimaging/conceptual/CoreImaging/ci_intro/ci_intro.html

    3.?http://en.wikipedia.org/wiki/YUV

    轉載于:https://www.cnblogs.com/Free-Thinker/p/5113850.html

    總結

    以上是生活随笔為你收集整理的iOS8 Core Image In Swift:视频实时滤镜的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內容還不錯,歡迎將生活随笔推薦給好友。