多多色-多人伦交性欧美在线观看-多人伦精品一区二区三区视频-多色视频-免费黄色视屏网站-免费黄色在线

國內最全IT社區平臺 聯系我們 | 收藏本站
阿里云優惠2
您當前位置:首頁 > 互聯網 > iOS8 Core Image In Swift:視頻實時濾鏡

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

來源:程序員人生   發布時間:2014-09-30 01:33:10 閱讀次數:3317次

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操作的完全控制,那么我們將不能使用系統提供的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實現了AVCaptureVideoDataOutputSampleBufferDelegate協議,這個會在后面說。
要使用AV框架,必須先引入庫:import AVFoundation
在viewDidLoad里實現如下:

override func viewDidLoad() {

    super.viewDidLoad()

    

    previewLayer = CALayer()

    previewLayer.bounds = CGRectMake(00self.view.frame.size.heightself.view.frame.size.width);

    previewLayer.position = CGPointMake(self.view.frame.size.width / 2.0self.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: nilas 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()

}

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

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

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

我們現在完成一個session的建立過程,但這個session還沒有開始工作,就像我們訪問數據庫的時候,要先打開數據庫---然后建立連接---訪問數據---關閉連接---關閉數據庫一樣,我們在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

    })

}

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

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

(這張圖是通過手機截屏獲取的,容易手抖,所以不是很清晰)

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

kCVPixelFormatType_420YpCbCr8BiPlanarFullRange

替換為

kCVPixelFormatType_32BGRA

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

再把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)

生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈
程序員人生

------分隔線----------------------------
分享到:
------分隔線----------------------------
關閉
程序員人生
主站蜘蛛池模板: 亚洲欧美日韩久久精品第一区 | 精品久久久久久久一区二区伦理 | 久久久一区二区三区 | 亚洲国产欧美精品一区二区三区 | 最近的中文字幕 | 伊人久久大香线焦在观看 | 欧美精品成人 | 日韩欧美亚洲国产一区二区三区 | 国产精品福利视频手机免费观看 | 欧美美女xx| 男人久久 | 中文字幕第10页 | 午夜在线播放免费人成无 | 日本中文字幕在线视频站 | 欧美亚洲 尤物久久 综合精品 | 日本自己的私人影院 | 性欧美videos hd | 日产免费线路一区二区三区 | 欧美日韩不卡中文字幕在线 | 青青草久热精品视频在线观看 | 伊人久久大香线蕉久久婷婷 | 亚洲久久网站 | 在线观看国产亚洲 | 免费黄色网址网站 | 2020国产精品永久在线观看 | 亚洲视频免费 | 久久手机看片 | 69做爰视频在线观看 | 国产区图片区小说区亚洲区 | 欧美精品久久久亚洲 | 日韩一级一片 | 免费看的成人yellow视频 | 男人天堂亚洲 | 精品无码久久久久国产 | 午夜视频国语 | 成人精品视频在线观看 | 最好免费高清视频在线看 | 日本午夜片成年www 日本午夜三级 | 欧美日韩视频一区三区二区 | freefr性欧美69hd| 亚洲精品短视频 |