Vision 是 iOS 上一個機器視覺的框架,它能夠對圖片和視頻進行多種機器視覺相關的任務處理。Vision 裏的人臉識別功能是最經常使用的功能之一,通過幾回的迭代,它的識別效果已經很不錯了,具體能夠看看 WWDC2017 Session 506
, WWDC2018 Session 71六、717
和 WWDC 2019 Session 222
,本文的 Demo-VisoinDetect 有些代碼就是從這些 Session 中的示例代碼修改而來。git
這裏咱們要作的東西是: 將 DJISDK 提供給咱們的視頻流數據,傳入 Vision 框架進行人臉識別,而後拿到人臉信息在圖傳界面顯示出來。效果以下:github
相信你們對無人機App激活鏈接這部分已經比較熟悉了,這裏就不贅述,不熟悉的話請查閱 DJISDK 文檔bash
視頻流數據其實就是一幀幀的圖片,而 Vision 能夠接收 CVPixelBuffer
的圖片數據,因此咱們須要把圖傳數據轉換成 CVPixelBuffer
。app
這裏咱們利用 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
// 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 的設備上,若是開啓了 HardwareDecode
和 Fastupload
, 返回的 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
Vision 對數據的處理邏輯能夠分爲三步:
步驟 | |
---|---|
作什麼 | VNRequest |
怎麼作 | VNImageRequestHandler VNSequenceRequestHandler |
處理結果 | VNObservation |
爲了識別人臉及其五官信息,咱們須要建立 VNDetectFaceLandmarksRequest
。
let detectFaceRequest = VNDetectFaceLandmarksRequest(completionHandler: detectedFace)
複製代碼
由於咱們須要處理視頻流的一幀幀圖片數據,因此咱們用 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) 點在左下角。
最終獲得的結果是封裝在 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
![]()