利用 Vision 給無人機圖傳加上人臉識別功能

Vision 是 iOS 上一個機器視覺的框架,它能夠對圖片和視頻進行多種機器視覺相關的任務處理。Vision 裏的人臉識別功能是最經常使用的功能之一,通過幾回的迭代,它的識別效果已經很不錯了,具體能夠看看 WWDC2017 Session 506, WWDC2018 Session 71六、717WWDC 2019 Session 222,本文的 Demo-VisoinDetect 有些代碼就是從這些 Session 中的示例代碼修改而來。git

這裏咱們要作的東西是: 將 DJISDK 提供給咱們的視頻流數據,傳入 Vision 框架進行人臉識別,而後拿到人臉信息在圖傳界面顯示出來。效果以下:github

1、獲取無人機圖傳視頻流

相信你們對無人機App激活鏈接這部分已經比較熟悉了,這裏就不贅述,不熟悉的話請查閱 DJISDK 文檔bash

1. 註冊 VideoFrameProcessor,獲取到 VideoFrameYUV

視頻流數據其實就是一幀幀的圖片,而 Vision 能夠接收 CVPixelBuffer 的圖片數據,因此咱們須要把圖傳數據轉換成 CVPixelBufferapp

這裏咱們利用 DJIWidget 的 VideoFrameProcessor 來獲取視頻流的幀數據。框架

import DJISDK
import DJIWidget

@IBOutlet weak var videoPreview: UIView!

override func viewDidLoad() {
    super.viewDidLoad()

    DJIVideoPreviewer.instance().setView(videoPreview)
    DJIVideoPreviewer.instance().enableHardwareDecode = true
    DJIVideoPreviewer.instance().enableFastUpload = true
}

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    
    DJIVideoPreviewer.instance().type = .autoAdapt
    // 調用 registFrameProcessor 方法
    DJIVideoPreviewer.instance()?.registFrameProcessor(self)
    DJIVideoPreviewer.instance()?.start()
    DJISDKManager.videoFeeder()?.primaryVideoFeed.add(self, with: nil)
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    
    DJISDKManager.videoFeeder()?.primaryVideoFeed.remove(self)
    DJIVideoPreviewer.instance().unSetView()
    DJIVideoPreviewer.instance().close()
}
複製代碼

上面是咱們常規獲取視頻流的方法,不過咱們還調用了 registFrameProcessor 的方法,調用了該方法後,咱們須要實現 VideoFrameProcessor 的代理方法,從代理方法中能夠獲取到視頻流的 VideoFrameYUV 數據。async

2. 將 VideoFrameYUV 轉換成 CVPixelBuffer

// MARK: - VideoFrameProcessor
extension DJIVideoViewController: VideoFrameProcessor {
    
    func videoProcessorEnabled() -> Bool {
        return true
    }
    
    func videoProcessFrame(_ frame: UnsafeMutablePointer<VideoFrameYUV>!) {
        let resolution = CGSize(width: CGFloat(frame.pointee.width), height: CGFloat(frame.pointee.height))
        
        if frame.pointee.cv_pixelbuffer_fastupload != nil {
            // 把 cv_pixelbuffer_fastupload 轉換成 CVPixelBuffer 對象
            let cvBuf = unsafeBitCast(frame.pointee.cv_pixelbuffer_fastupload, to: CVPixelBuffer.self)
            setupCaptureDeviceResolution(resolution)
            detectFace(pixelBuffer: cvBuf)
        } else {
            // 自行構建 CVPixelBuffer 對象
            let pixelBuffer = frame.pointee.createPixelBuffer()
            setupCaptureDeviceResolution(resolution)
            guard let cvBuf = pixelBuffer else { return }
            detectFace(pixelBuffer: cvBuf)
        }
    }
}
複製代碼

func videoProcessFrame(_ frame: UnsafeMutablePointer<VideoFrameYUV>!) 的代理方法中,咱們能夠拿到 VideoFrameYUV 的數據。ide

理論上,在支持 HardwareDecode 的設備上,若是開啓了 HardwareDecodeFastupload , 返回的 VideoFrameYUV 裏的 luma, chromaB and chromaR 可能會是空的(就沒法構建 CVPixelBuffer),這時候能夠經過 cv_pixelbuffer_fastupload 獲取到 CVPixelBuffer 的值。因此上面的代碼裏先判斷 frame.pointee.cv_pixelbuffer_fastupload 是否不爲 nil。測試

若是 cv_pixelbuffer_fastupload 爲 nil 則咱們須要自行構建 CVPixelBuffer,這裏咱們給 VideoFrameYUV 添加了一個擴展方法 createPixelBuffer() 以構建 CVPixelBuffer,這裏就不貼代碼了,具體能夠查看 Github 上的源碼。ui

針對開啓 HardwareDecode 獲取到 cv_pixelbuffer_fastupload 的狀況,目前我手頭上的設備是沒法獲取獲得,老是須要進行構建 CVPixelBuffer。這個問題在 DJIWidget Github issue9 有相關的討論。spa

2、把 CVPixelBuffer 傳給 Vision 處理

Vision 對數據的處理邏輯能夠分爲三步:

步驟
作什麼 VNRequest
怎麼作 VNImageRequestHandler
VNSequenceRequestHandler
處理結果 VNObservation

1. 作什麼: 識別人臉及五官信息

爲了識別人臉及其五官信息,咱們須要建立 VNDetectFaceLandmarksRequest

let detectFaceRequest = VNDetectFaceLandmarksRequest(completionHandler: detectedFace)
複製代碼

2. 怎麼作: VNSequenceRequestHandler

由於咱們須要處理視頻流的一幀幀圖片數據,因此咱們用 VNSequenceRequestHandler 來執行 FaceLandmarksRequest

do {
    // 注意無人機圖傳中照片都是 downMirrored 的,即(0, 0)在左下角
    try sequenceRequestHandler.perform([detectFaceRequest], on: pixelBuffer, orientation: .downMirrored)
} catch {
    print("----執行 sequenceRequestHandler 失敗: \(error.localizedDescription)")
}
複製代碼

調用 perform 方法時,除了傳入要執行的 request 和 pixelBuffer 外,還須要注意傳入圖片的 Orientation 信息,以讓 Vision 知道這個圖片是倒着的仍是反轉的等等。由於咱們的視頻流是從無人機傳過來的,這裏測試發現都是 downMirrored 的,即照片的 (0, 0) 點在左下角。

3. 處理結果:繪製人臉圖層

最終獲得的結果是封裝在 VNFaceObservation 的對象裏的,經過該對象能夠拿到人臉相對於圖片的座標:boundingBox 以及五官的座標信息 landmarks,從而能夠繪製在圖傳界面上。具體繪製方法 drawFaceObservations 能夠在 Github 上查看。

func detectedFace(request: VNRequest, error: Error?) {
    if let error = error {
        print("---detectedFaceRequest Error: \(error.localizedDescription)")
        return
    }

    guard let results = request.results as? [VNFaceObservation] else { return }

    DispatchQueue.main.async {
        self.drawFaceObservations(results)
    }
}
複製代碼

總結

這裏的關鍵點是在於:如何從無人機圖傳視頻流裏拿到 CVPixelBuffer ———— 這個 Vision 能夠接受的數據。

另一個的關鍵點是如何在圖傳界面上繪製出人臉信息,這裏涉及到如何獲取到視頻圖片的真實大小(Pixel單位)、ordination 等。

一旦處理好這些關鍵點,其他的問題就迎刃而解了。

歡迎關注個人公衆號:HansonTalk

相關文章
相關標籤/搜索