Java版人脸跟踪三部曲之二:开发设计

x33g5p2x  于2022-02-07 转载在 Java  
字(4.1k)|赞(0)|评价(0)|浏览(220)

欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos

本篇概览

  • 本篇是《Java版人脸跟踪三部曲》系列的第二篇,前文体验了人脸跟踪的效果,想要编码实现这样的效果,咱们需要做好设计工作,也就是本篇的任务
  • 本篇主要包含以下内容:
  1. 核心逻辑
  2. 重要知识点:HSV、HUE
  3. 重要知识点:反向投影
  4. 重要知识点:CamShift
  5. 重要知识点:JavaCV的API支持
  6. 如何开局?
  7. 前文的完整功能分析
  8. 异常处理
  9. 期待下一篇的实战(虎年贺岁作品)

核心逻辑

  • 本篇没有编码和操作实战,会略显枯燥,所以提前小结人脸跟踪核心逻辑,如此就算您受不了欣宸的啰嗦提前关闭网页,好歹也能带走些干货:
  • 如下图所示,人脸跟踪的核心逻辑,其实就是先拿人脸直方图hist,然后将每一帧都转为hist的概率分布图(也叫反向投影),再用MeanShift算法在图上做迭代计算,结果就是人脸位置:

  • 拿到每一帧的人脸位置后,在人脸上添加一个矩形框,此时,在预览窗口看到的效果就是视频中人脸上始终有矩形框,实现了跟踪的效果
  • 虽然尽可能简短的讲完了核心逻辑,但此时的您可能有一些疑问,例如:
  1. Hue分量是啥?
  2. 反向投影是啥?
  3. MeanShift又是啥?
  4. 前文提到过CamShift,这会儿咋又不提了?
  • 没错,上面几个疑问就是人脸跟踪功能依赖的关键技术,接下来咱们都简单了解一下吧

重要知识点:HSV、HUE

  • HSV:如下图,HSV是一种直观的颜色空间,把色调分布到一个圆盘上,Hue表示角度,所以Hue的值就代表一个具体的色调,然后,Saturation看做饱和度(我的感觉是添加黑色),把Value看做亮度(我的感觉是添加白色),刚才提到的Hue分量,其实就是指Hue的值,(Saturation和Value的值在后面的算法中不会用到)

  • 再来仔细看看圆盘中Hue的值对应的色调:

重要知识点:反向投影

  • 在使用JavaCV的CamShift算法API时,最重要的入参就是反向投影,每一帧最终都会被转成反向投影,也就是前面提到的用人脸Hue分量的直方图将第X帧转化成色彩概率分布图
  • 反向投影图是用输入图像的某一位置上像素值(多维或灰度)对应在直方图的一个bin上的值来代替该像素值
  • 反向投影在OpenCV中会经常见到,一般使用场景是在一个图像中查找特定图像的最匹配点或区域,或者说定位目标图像出现在指定图像的位置
  • 来看看用一张图片制作反向投影的过程,如下所示,先根据人脸得到直方图,然后对每一张图片都用这个直方图去计算出反向投影图(也就是拿着人脸直方图,去每一帧图片中计算人脸在此图片中的色彩概率分布),JavaCV为我们准备好了API(Imgproc.calcBackProject),我们只需准备好API所需参数即可:

  • 有了上面的流程,就能对每帧图片做反向投影,得到人脸在这张图片上的概率分布图,然后用MeanShitf算法对这个概率分布图做迭代计算,直到其收敛或者到达最大迭代次数,确定人脸在图片上的位置

重要知识点:CamShift算法

  • 实现人脸跟踪的关键是CamShift,全称ContinuouslyAdaptive Mean Shift,即连续自适应的MeanShift算法
  • Mean Shift算法是一种无参密度估计算法,不需要任何先验知识而完全依靠特征空间中样本点的计算其密度函数值,在很多领域都有成功应用,例如图像平滑、图像分割、物体跟踪等,本篇不会展开细说Mean Shift算法,就用下面这幅图简单说说,

  1. 上图每个圆心是一个质心,
  2. 以质心为原点画一个圆圈,圆圈内有很多红点
  3. 圆圈内每个点与圆心构成一个向量,把圆圈内向量相加,得到新的向量就是meanshift向量,即黄色箭头
  4. 以meanshift向量的重点为圆心,再画一个圆圈,在此圆圈内执行步骤3
  5. 不断重复上述过程,着该向量移动便能找到密度最大处,就是最终结果
  • 向量-> 移动 -> 向量 -> 移动,这和梯度下降有些相似之处啊
  • 以上就是meanshif算法,而将meanshift算法扩展到连续图像序列,就是camshift,它将视频的连续帧做meanshift
    计算,用上一帧结果作为下一帧meanshift算法搜索窗的初始值,来调整下一帧的中心位置和窗体大小,如此迭代下去,就可以实现对目标的跟踪。
  • 对应到OpenCV的实现中,就是输入一个图像(probImage),再输入一个开始迭代的窗口(window),以及迭代条件(criteria),而输出,就是迭代完成的位置(RotatedRect);

重要知识点:JavaCV对CamShift的支持

  • 关于核心功能的理论已经聊得七七八八了,再来看看JavaCV对核心知识点提供了哪些具体的API支持,如下表所示,前面涉及到的关键技术都覆盖到了:
序号API作用
1Imgproc.cvtColor从摄像头拿到的帧,其颜色空间是RGB格式的,需要转为HSV格式
2Core.mixChannels将HSV图片的Hue分量提取到另一个Mat中
3Imgproc.calcHists生成直方图
4Imgproc.calcBackProject生成反向投影
5Video.CamShift在反向投影图上执行CamShift计算
  • 至此,核心技术算是分析完了,但仅有核心技术是不够的,需要有主程序、分支逻辑、异常处理等诸多努力,才能实现完整的功能,接下来就以开发者的视角,开始咱们的开发设计
  • 首先要搞清楚的是:如何确定最初的那个人脸?

如何开局?

  • 在设计过程中,咱们要面临的第一个问题就是如何开局?换句话说:从哪里拿到人脸,用于生成直方图,并找好位置作为下一帧做CamShift计算的起始位置
  • 如果您之前在网上搜索过CamShift的文章,会发现大多都是用户用鼠标在预览窗口选定一个区域,然后程序取这个区域作为跟踪对象
  • 但是,欣宸这里不会沿用上述手动选择的方式,如果您之前看过《JavaCV的摄像头实战》系列,会发现该系列经常用到JavaCV提供的人脸检测功能,因此,咱们继续使用这个人脸检测功能来开局
  • 简单来说,当程序运行后,如果摄像头中出现了人脸,那么该人脸就被自动作为跟踪对象,会被计算Hue直方图,并且人脸位置也是下一帧做CamShift计算的起始位置
  • 为了简单起见,假设摄像头中只会出现一个人脸,代码处理也只针对一个人脸的场景
  • 如果您想了解人脸检测的更多细节,请参考《JavaCV的摄像头实战之八:人脸检测》

前文的完整功能分析(重要)

  • 咱们在前文体验的是一个功能完整的java应用,为了编码实现这个应用,自然是要先分析一下这个应用的主要流程
  • 来看看完整的应用主流程,如下图,检测到人脸后,就用此人脸生成直方图,对之后的每一帧都用反向投影+CamShift计算人脸位置,如果位置有效就表示跟踪成功,在图上添加矩形框,如果位置无效,表示跟踪失败(例如人已经离开摄像头),此时再不断的检测每一帧有没有人脸,一旦检测到,就重复前面的直方图和CamShift计算逻辑:

  • 以上就是主流程了,也就是大部分时间中应用的运行状态,相信此刻的您已经受够了这些文字和图表,迫不及待的想要敲打键盘,写出自己心目中的人脸跟踪应用,但我还是要强行劝您一句:咱们把异常流程也梳理和罗列一下,否则程序运行的时候会出现各种灵异现象,十分钟写代码,一小时查问题…

异常处理

  • 在实际运行过程中,可能会遇到以下六个问题:
  1. 提前准备必要文件之一,opencv在windows环境的动态链接库,下载地址(不用积分):https://download.csdn.net/download/boling_cavalry/75121158
  2. 提前准备必要文件之二,人脸检测的模型文件,下载地址:https://raw.github.com/opencv/opencv/master/data/haarcascades/haarcascade_frontalface_alt.xml
  3. native方法异常:BGR实例转为javacv的RGBA时,opencv_imgproc.cvtColor可能抛出异常,所以要注意捕获,避免程序退出
  4. JavaCV中,最常用的类来自org.bytedeco.opencv.opencv_core这个包,然而,在计算直方图、反向投影、CamShift的时候,大部分参数又来自org.opencv.core这个包,因此从摄像头取得的帧相关的数据对象,都要转换成另一个包下面的同名对象,才能顺利的执行人脸跟踪操作
  5. 人脸跟踪的时候,如何判断跟丢了?正常情况下,CamShift返回的是一个有效的矩形,人不再出现的帧,CamShift计算其反向投影的时候,返回的矩形的长和宽都小于等于零,但实际测试的时候,发现人脸消失后,CamShift还可能返回一个很小的矩形,这显然是必须要丢弃的,因此,判断是否跟丢的逻辑,我这里就改成:长或者宽比上一次的变化率是否超过百分八十,实测效果尚可,您也可以自行调整这个参数
  6. 假设人脸检测的结果是5060的矩形,能将整个人脸包括在此矩形中,但CamShift计算得到的矩形就未必是5060了,一般高度会更大,导致将人脸之下的脖子也包括进来,而且头发上面会包括进来,此刻,您可以按照自己的业务需求来调整这个矩形,我这里是将位置向下移动(不把头发包括进来),再把宽度的值设置成高度,这样看起来与人脸检测的结果比较接近,调整前后的效果如下图所示:

  • 以上就是之前的开发过程中遇到的典型问题,可见如果没有事先准备,怕是每个问题都能将爱学习的您折磨得痛苦不堪…

期待下一篇的实战(虎年贺岁作品)

  • 至此,图文并茂的设计篇已全部完成,和愉快的编码相比,这种设计和准备工作既枯燥且辛苦,但却是一个功能健全的应用不可或缺的一部分,只希望本篇能为您提供足够的理论知识信息,让咱们在下一篇的编码实战中做到胸有成竹,下笔如有神助
  • 新年即将到来,欣宸原创虎年贺岁作品:《Java版人脸跟踪三部曲之三:编码实战》,敬请期待~

你不孤单,欣宸原创一路相伴

  1. Java系列
  2. Spring系列
  3. Docker系列
  4. kubernetes系列
  5. 数据库+中间件系列
  6. DevOps系列

相关文章