Vision 是 iOS 上一个机器视觉的框架,它可以对图片和视频进行多种机器视觉相关的任务处理。Vision 里的人脸识别功能是最常用的功能之一,经过几次的迭代,它的识别效果已经很不错了,具体可以看看 WWDC2017 Session 506
, WWDC2018 Session 716、717
和 WWDC 2019 Session 222
,本文的 Demo-VisoinDetect 有些代码就是从这些 Session 中的示例代码修改而来。
这里我们要做的东西是: 将 DJISDK 提供给我们的视频流数据,传入 Vision 框架进行人脸识别,然后拿到人脸信息在图传界面显示出来。效果如下:
一、获取无人机图传视频流
相信大家对无人机App激活连接这部分已经比较熟悉了,这里就不赘述,不熟悉的话请查阅 DJISDK 文档
1. 注册 VideoFrameProcessor,获取到 VideoFrameYUV
视频流数据其实就是一帧帧的图片,而 Vision 可以接收 CVPixelBuffer
的图片数据,所以我们需要把图传数据转换成 CVPixelBuffer
。
这里我们利用 DJIWidget 的 VideoFrameProcessor
来获取视频流的帧数据。
1 | import DJISDK |
上面是我们常规获取视频流的方法,不过我们还调用了 registFrameProcessor
的方法,调用了该方法后,我们需要实现 VideoFrameProcessor
的代理方法,从代理方法中可以获取到视频流的 VideoFrameYUV
数据。
2. 将 VideoFrameYUV 转换成 CVPixelBuffer
1 | // MARK: - VideoFrameProcessor |
在 func videoProcessFrame(_ frame: UnsafeMutablePointer<VideoFrameYUV>!)
的代理方法中,我们可以拿到 VideoFrameYUV
的数据。
理论上,在支持 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 上的源码。
针对开启 HardwareDecode 获取到 cv_pixelbuffer_fastupload 的情况,目前我手头上的设备是无法获取得到,总是需要进行构建 CVPixelBuffer。这个问题在 DJIWidget Github issue9 有相关的讨论。
二、把 CVPixelBuffer 传给 Vision 处理
Vision 对数据的处理逻辑可以分为三步:
步骤 | |
---|---|
做什么 | VNRequest |
怎么做 | VNImageRequestHandler VNSequenceRequestHandler |
处理结果 | VNObservation |
1. 做什么: 识别人脸及五官信息
为了识别人脸及其五官信息,我们需要创建 VNDetectFaceLandmarksRequest
。
1 | let detectFaceRequest = VNDetectFaceLandmarksRequest(completionHandler: detectedFace) |
2. 怎么做: VNSequenceRequestHandler
因为我们需要处理视频流的一帧帧图片数据,所以我们用 VNSequenceRequestHandler
来执行 FaceLandmarksRequest
。
1 | do { |
调用 perform 方法时,除了传入要执行的 request 和 pixelBuffer 外,还需要注意传入图片的 Orientation 信息,以让 Vision 知道这个图片是倒着的还是反转的等等。因为我们的视频流是从无人机传过来的,这里测试发现都是 downMirrored
的,即照片的 (0, 0) 点在左下角。
3. 处理结果:绘制人脸图层
最终得到的结果是封装在 VNFaceObservation
的对象里的,通过该对象可以拿到人脸相对于图片的坐标:boundingBox
以及五官的坐标信息 landmarks
,从而可以绘制在图传界面上。具体绘制方法 drawFaceObservations
可以在 Github 上查看。
1 | func detectedFace(request: VNRequest, error: Error?) { |
总结
这里的关键点是在于:如何从无人机图传视频流里拿到 CVPixelBuffer
———— 这个 Vision 可以接受的数据。
另外一个的关键点是如何在图传界面上绘制出人脸信息,这里涉及到如何获取到视频图片的真实大小(Pixel单位)、ordination 等。
一旦处理好这些关键点,其余的问题就迎刃而解了。
欢迎关注我的公众号:HansonTalk