经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Java » 查看文章
Java版人脸跟踪三部曲之三:编码实战
来源:cnblogs  作者:程序员欣宸  时间:2023/7/10 9:51:52  对本文有异议

欢迎访问我的GitHub

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

本篇概览

  • 作为《Java版人脸跟踪三部曲》系列的终篇,本文会与大家一起写出完整的人脸跟踪应用代码
  • 前文《开发设计》中,已经对人脸跟踪的核心技术、应用主流程、异常处理等方方面面做了详细设计,建议您简单回顾一下
  • 接下来,自顶向下,先整体设计好主框架和关键类

程序主框架和关键类

  • 听欣宸唠叨了两篇文章,终于要看具体代码了,整体上看,最关键的三个类如下图:
    在这里插入图片描述
  • 可见把功能、流程、知识点梳理清楚后,代码其实并不多,而且各司其职,分工明确,接下来开始编码,ObejctTracker负责实现跟踪功能,就从它开始

ObejctTracker.java:跟踪能力的提供者

  • 从前面的图中可知,与跟踪有关的服务都是ObejctTracker类提供的,此类涉及知识点略多,在编写代码前,先做一下简单的设计
  • 从功能看,ObejctTracker会对外提供如下两个方法:
方法名 作用 入参 返回 内部实现
createTrackedObject 主程序如果从视频帧中首次次检测到人脸,就会调用createTrackedObject方法,表示开始跟踪了 mRgba:出现人脸的图片
region:人脸在图片中的位置
提取人脸的hue,生成直方图
objectTracking 开始跟踪后,主程序从摄像头取到的每一帧图片后,都会调用此方法,用于得到人脸在这一帧中的位置 mRgba:图片 人脸在输入图片中位置 用人脸hue直方图对输入图片进行计算,得到反向投影图,在反向投影图上做CamShift计算得到人脸位置
  • 除了上述两个对外方法,ObejctTracker内部还要准备如下两个辅助方法:
方法名 作用 入参 返回 内部实现
rgba2Hue 将RGB颜色空间的图片转为HSV,再提取出hue通道,生成直方图 rgba:人脸图片 List<Mat>:直方图
lostTrace 对比objectTracking方法返回的结果与上次出现的位置,确定人有没有跟丢 lastRect:上次出现的位置
currentRect:objectTracking方法检测到的当前帧上的位置
true表示跟丢了,false表示没有跟丢 对比两个矩形的差距是否超过一个门限,正常情况下连续两帧中的人脸差别不会太大,所以一旦差别大了就表示跟丢了,currentRect的位置上不是人脸
  • 还有几个成员变量也很重要:
  1. // 每一帧图像的反向投影图都用这个成员变量来保存
  2. private Mat prob;
  3. // 保存最近一次确认的头像的位置,每当新的一帧到来时,都从这个位置开始追踪(也就是反向投影图做CamShift计算的起始位置)
  4. private Rect trackRect;
  5. // 直方图,在跟丢之前,每一帧图像都要用到这个直方图来生成反向投影
  6. private Mat hist;
  • 设计完成,现在可以给出完整的ObejctTracker.java源码了:
  1. package com.bolingcavalry.grabpush.extend;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.opencv.core.*;
  4. import org.opencv.imgproc.Imgproc;
  5. import org.opencv.video.Video;
  6. import java.util.Collections;
  7. import java.util.List;
  8. import java.util.Vector;
  9. /**
  10. * @author willzhao
  11. * @version 1.0
  12. * @description TODO
  13. * @date 2022/1/8 21:21
  14. */
  15. @Slf4j
  16. public class ObjectTracker {
  17. /**
  18. * 上一个矩形和当前矩形的差距达到多少的时候,才算跟丢,您可以自行调整
  19. */
  20. private static final double LOST_GATE = 0.8d;
  21. // [0.0, 256.0]表示直方图能表示像素值从0.0到256的像素
  22. private static final MatOfFloat RANGES = new MatOfFloat(0f, 256f);
  23. private Mat mask;
  24. // 保存用来追踪的每一帧的反向投影图
  25. private Mat prob;
  26. // 保存最近一次确认的头像的位置,每当新的一帧到来时,都从这个位置开始追踪(也就是反向投影图做CamShift计算的起始位置)
  27. private Rect trackRect;
  28. // 直方图
  29. private Mat hist;
  30. public ObjectTracker(Mat rgba) {
  31. hist = new Mat();
  32. trackRect = new Rect();
  33. mask = new Mat(rgba.size(), CvType.CV_8UC1);
  34. prob = new Mat(rgba.size(), CvType.CV_8UC1);
  35. }
  36. /**
  37. * 将摄像头传来的图片提取出hue通道,放入hueList中
  38. * 将摄像头传来的RGB颜色空间的图片转为HSV颜色空间,
  39. * 然后检查HSV三个通道的值是否在指定范围内,mask中记录了检查结果
  40. * 再将hsv中的hue提取出来
  41. * @param rgba
  42. */
  43. private List<Mat> rgba2Hue(Mat rgba) {
  44. // 实例化Mat,显然,hsv是三通道,hue是hsv三通道其中的一个,所以hue是一通道
  45. Mat hsv = new Mat(rgba.size(), CvType.CV_8UC3);
  46. Mat hue = new Mat(rgba.size(), CvType.CV_8UC1);
  47. // 1. 先转换
  48. // 转换颜色空间,RGB到HSV
  49. Imgproc.cvtColor(rgba, hsv, Imgproc.COLOR_RGB2HSV);
  50. int vMin = 65, vMax = 256, sMin = 55;
  51. //inRange函数的功能是检查输入数组每个元素大小是否在2个给定数值之间,可以有多通道,mask保存0通道的最小值,也就是h分量
  52. //这里利用了hsv的3个通道,比较h,0~180,s,smin~256,v,min(vmin,vmax),max(vmin,vmax)。如果3个通道都在对应的范围内,
  53. //则mask对应的那个点的值全为1(0xff),否则为0(0x00).
  54. Core.inRange(
  55. hsv,
  56. new Scalar(0, sMin, Math.min(vMin, vMax)),
  57. new Scalar(180, 256, Math.max(vMin, vMax)),
  58. mask
  59. );
  60. // 2. 再提取
  61. // 把hsv的数据放入hsvList中,用于稍后提取出其中的hue
  62. List<Mat> hsvList = new Vector<>();
  63. hsvList.add(hsv);
  64. // 准备好hueList,用于接收通道
  65. // hue初始化为与hsv大小深度一样的矩阵,色调的度量是用角度表示的,红绿蓝之间相差120度,反色相差180度
  66. hue.create(hsv.size(), hsv.depth());
  67. List<Mat> hueList = new Vector<>();
  68. hueList.add(hue);
  69. // 描述如何提取:从目标的0位置提取到目的地的0位置
  70. MatOfInt from_to = new MatOfInt(0, 0);
  71. // 提取操作:将hsv第一个通道(也就是色调)的数复制到hue中,0索引数组
  72. Core.mixChannels(hsvList, hueList, from_to);
  73. return hueList;
  74. }
  75. /**
  76. * 当外部调用方确定了人脸在图片中的位置后,就可以调用createTrackedObject开始跟踪,
  77. * 该方法中会先生成人脸的hue的直方图,用于给后续帧生成反向投影
  78. * @param mRgba
  79. * @param region
  80. */
  81. public void createTrackedObject(Mat mRgba, Rect region) {
  82. hist.release();
  83. //将摄像头的视频帧转化成hsv,然后再提取出其中的hue通道
  84. List<Mat> hueList = rgba2Hue(mRgba);
  85. // 人脸区域的mask
  86. Mat tempMask = mask.submat(region);
  87. // histSize表示这个直方图分成多少份(即多少个直方柱),就是 bin的个数
  88. MatOfInt histSize = new MatOfInt(25);
  89. // 只要头像区域的数据
  90. List<Mat> images = Collections.singletonList(hueList.get(0).submat(region));
  91. // 计算头像的hue直方图,结果在hist中
  92. Imgproc.calcHist(images, new MatOfInt(0), tempMask, hist, histSize, RANGES);
  93. // 将hist矩阵进行数组范围归一化,都归一化到0~255
  94. Core.normalize(hist, hist, 0, 255, Core.NORM_MINMAX);
  95. // 这个trackRect记录了人脸最后一次出现的位置,后面新的帧到来时,就从trackRect位置开始做CamShift计算
  96. trackRect = region;
  97. }
  98. /**
  99. * 在开始跟踪后,每当摄像头新的一帧到来时,外部就会调用objectTracking,将新的帧传入,
  100. * 此时,会用前面准备好的人脸hue直方图,将新的帧计算出反向投影图,
  101. * 再在反向投影图上执行CamShift计算,找到密度最大处,即人脸在新的帧上的位置,
  102. * 将这个位置作为返回值,返回
  103. * @param mRgba 新的一帧
  104. * @return 人脸在新的一帧上的位置
  105. */
  106. public Rect objectTracking(Mat mRgba) {
  107. // 新的图片,提取hue
  108. List<Mat> hueList;
  109. try {
  110. // 实测此处可能抛出异常,要注意捕获,避免程序退出
  111. hueList = rgba2Hue(mRgba);
  112. } catch (CvException cvException) {
  113. log.error("cvtColor exception", cvException);
  114. trackRect = null;
  115. return null;
  116. }
  117. // 用头像直方图在新图片的hue通道数据中计算反向投影。
  118. Imgproc.calcBackProject(hueList, new MatOfInt(0), hist, prob, RANGES, 1.0);
  119. // 计算两个数组的按位连接(dst = src1 & src2)计算两个数组或数组和标量的每个元素的逐位连接。
  120. Core.bitwise_and(prob, mask, prob, new Mat());
  121. // 在反向投影上进行CamShift计算,返回值就是密度最大处,即追踪结果
  122. RotatedRect rotatedRect = Video.CamShift(prob, trackRect, new TermCriteria(TermCriteria.EPS, 10, 1));
  123. // 转为Rect对象
  124. Rect camShiftRect = rotatedRect.boundingRect();
  125. // 比较追踪前和追踪后的数据,如果出现太大偏差,就认为追踪失败
  126. if (lostTrace(trackRect, camShiftRect)) {
  127. log.info("lost trace!");
  128. trackRect = null;
  129. return null;
  130. }
  131. // 将本次最终到的目标作为下次追踪的对象
  132. trackRect = camShiftRect;
  133. return camShiftRect;
  134. }
  135. /**
  136. * 变化率的绝对值
  137. * @param last 变化前
  138. * @param current 变化后
  139. * @return
  140. */
  141. private static double changeRate(int last, int current) {
  142. return Math.abs((double)(current-last)/(double) last);
  143. }
  144. /**
  145. * 本次和上一次宽度或者高度的变化率,一旦超过阈值就认为跟踪失败
  146. * @param lastRect
  147. * @param currentRect
  148. * @return
  149. */
  150. private static boolean lostTrace(Rect lastRect, Rect currentRect) {
  151. // 0不能做除数,如果发现0就认跟丢了
  152. if (lastRect.width<1 || lastRect.height<1) {
  153. return true;
  154. }
  155. double widthChangeRate = changeRate(lastRect.width, currentRect.width);
  156. if (widthChangeRate>LOST_GATE) {
  157. log.info("1. lost trace, old [{}], new [{}], rate [{}]", lastRect.width, currentRect.width, widthChangeRate);
  158. return true;
  159. }
  160. double heightChangeRate = changeRate(lastRect.height, currentRect.height);
  161. if (heightChangeRate>LOST_GATE) {
  162. log.info("2. lost trace, old [{}], new [{}], rate [{}]", lastRect.height, currentRect.height, heightChangeRate);
  163. return true;
  164. }
  165. return false;
  166. }
  167. }
  • 最核心的跟踪服务已经完成,接下来要实现完整业务逻辑,即:CamShiftDetectService.java

CamShiftDetectService.java:业务逻辑的提供者

  • 有了核心能力,接下来要做的就是在业务中使用这个能力,前文已设计好完整的业务逻辑,这里先简单回顾一下:
    在这里插入图片描述
  • 可见主要业务流程可以用两个状态+行为来表示:
  1. 还未开始跟踪:对每一帧做人脸检测,一旦检测到,就进入跟踪状态,并调用ObjectTracker.createTrackedObject生成人脸的hue直方图
  2. 已处于跟踪状态:对每一帧图像,都调用ObjectTracker.objectTracking去检查人脸在图像中的位置,直到到跟丢了为止,一旦跟丢了,就重新进入到还未开始跟踪的状态
  • 现在我们已经清楚了CamShiftDetectService.java要做的具体事情,接下来看看有哪些重要方法:
方法名 作用 入参 返回 内部实现
init 被主程序调用的初始化方法,在应用启动的时候会调用一次 加载人脸检测的模型
convert 每当主程序从摄像头拿到新的一帧后,都会调用此方法 frame:来自摄像头的最新一帧 被处理后的帧,会被主程序展现在预览窗口 convert方法内部实现了前面提到的两种状态和行为(还未开始跟踪、已处于跟踪状态)
releaseOutputResource 程序结束前,被主程序调用的释放资源的方法 释放一些成员变量的资源
  • 再来看看有哪些重要的成员变量,如下所示,isInTracing表示当前是否处于跟踪状态,classifier用于检测人脸:
  1. /**
  2. * 每一帧原始图片的对象
  3. */
  4. private Mat grabbedImage = null;
  5. /**
  6. * 分类器
  7. */
  8. private CascadeClassifier classifier;
  9. /**
  10. * 转换器
  11. */
  12. private OpenCVFrameConverter.ToMat converter = new OpenCVFrameConverter.ToMat();
  13. /**
  14. * 模型文件的下载地址
  15. */
  16. private String modelFilePath;
  17. /**
  18. * 存放RGBA图片Mat
  19. */
  20. private Mat mRgba;
  21. /**
  22. * 存放灰度图片的Mat,仅用在人脸检测的时候
  23. */
  24. private Mat mGray;
  25. /**
  26. * 跟踪服务类
  27. */
  28. private ObjectTracker objectTracker;
  29. /**
  30. * 表示当前是否正在跟踪目标
  31. */
  32. private boolean isInTracing = false;
  • 现在可以给出CamShiftDetectService.java的完整代码了:
  1. package com.bolingcavalry.grabpush.extend;
  2. import com.bolingcavalry.grabpush.Util;
  3. import lombok.extern.slf4j.Slf4j;
  4. import org.bytedeco.javacv.Frame;
  5. import org.bytedeco.javacv.OpenCVFrameConverter;
  6. import org.bytedeco.opencv.opencv_core.Mat;
  7. import org.bytedeco.opencv.opencv_core.Rect;
  8. import org.bytedeco.opencv.opencv_core.RectVector;
  9. import org.bytedeco.opencv.opencv_objdetect.CascadeClassifier;
  10. import java.io.File;
  11. import static org.bytedeco.opencv.global.opencv_imgproc.CV_BGR2GRAY;
  12. import static org.bytedeco.opencv.global.opencv_imgproc.cvtColor;
  13. @Slf4j
  14. public class CamShiftDetectService implements DetectService {
  15. /**
  16. * 每一帧原始图片的对象
  17. */
  18. private Mat grabbedImage = null;
  19. /**
  20. * 分类器
  21. */
  22. private CascadeClassifier classifier;
  23. /**
  24. * 转换器
  25. */
  26. private OpenCVFrameConverter.ToMat converter = new OpenCVFrameConverter.ToMat();
  27. /**
  28. * 模型文件的下载地址
  29. */
  30. private String modelFilePath;
  31. /**
  32. * 存放RGBA图片Mat
  33. */
  34. private Mat mRgba;
  35. /**
  36. * 存放灰度图片的Mat,仅用在人脸检测的时候
  37. */
  38. private Mat mGray;
  39. /**
  40. * 跟踪服务类
  41. */
  42. private ObjectTracker objectTracker;
  43. /**
  44. * 表示当前是否正在跟踪目标
  45. */
  46. private boolean isInTracing = false;
  47. /**
  48. * 构造方法,在此指定模型文件的下载地址
  49. * @param modelFilePath
  50. */
  51. public CamShiftDetectService(String modelFilePath) {
  52. this.modelFilePath = modelFilePath;
  53. }
  54. /**
  55. * 音频采样对象的初始化
  56. * @throws Exception
  57. */
  58. @Override
  59. public void init() throws Exception {
  60. log.info("开始加载模型文件");
  61. // 模型文件下载后的完整地址
  62. String classifierName = new File(modelFilePath).getAbsolutePath();
  63. // 根据模型文件实例化分类器
  64. classifier = new CascadeClassifier(classifierName);
  65. if (classifier == null) {
  66. log.error("Error loading classifier file [{}]", classifierName);
  67. System.exit(1);
  68. }
  69. log.info("模型文件加载完毕,初始化完成");
  70. }
  71. @Override
  72. public Frame convert(Frame frame) {
  73. // 由帧转为Mat
  74. grabbedImage = converter.convert(frame);
  75. // 初始化灰度Mat
  76. if (null==mGray) {
  77. mGray = Util.initGrayImageMat(grabbedImage);
  78. }
  79. // 初始化RGBA的Mat
  80. if (null==mRgba) {
  81. mRgba = Util.initRgbaImageMat(grabbedImage);
  82. }
  83. // 如果未在追踪状态
  84. if (!isInTracing) {
  85. // 存放检测结果的容器
  86. RectVector objects = new RectVector();
  87. // 当前图片转为灰度图片
  88. cvtColor(grabbedImage, mGray, CV_BGR2GRAY);
  89. // 开始检测
  90. classifier.detectMultiScale(mGray, objects);
  91. // 检测结果总数
  92. long total = objects.size();
  93. // 当前实例是只追踪一人,因此一旦检测结果不等于一,就不处理,您可以根据自己业务情况修改此处
  94. if (total!=1) {
  95. objects.close();
  96. return frame;
  97. }
  98. log.info("start new trace");
  99. Rect r = objects.get(0);
  100. int x = r.x(), y = r.y(), w = r.width(), h = r.height();
  101. // 得到opencv的mat,其格式是RGBA
  102. org.opencv.core.Mat openCVRGBAMat = Util.buildJavacvBGR2OpenCVRGBA(grabbedImage, mRgba);
  103. // 在buildJavacvBGR2OpenCVRGBA方法内部,有可能在执行native方法的是否发生异常,要做针对性处理
  104. if (null==openCVRGBAMat) {
  105. objects.close();
  106. return frame;
  107. }
  108. // 如果第一次追踪,要实例化objectTracker
  109. if (null==objectTracker) {
  110. objectTracker = new ObjectTracker(openCVRGBAMat);
  111. }
  112. // 创建跟踪目标
  113. objectTracker.createTrackedObject(openCVRGBAMat, new org.opencv.core.Rect(x, y, w, h));
  114. // 根据本次检测结果给原图标注人脸矩形框
  115. Util.rectOnImage(grabbedImage, x, y, w, h);
  116. // 释放检测结果资源
  117. objects.close();
  118. // 修改标志,表示当前正在跟踪
  119. isInTracing = true;
  120. // 将标注过的图片转为帧,返回
  121. return converter.convert(grabbedImage);
  122. }
  123. // 代码走到这里,表示已经在追踪状态了
  124. // 得到opencv的mat,其格式是RGBA
  125. org.opencv.core.Mat openCVRGBAMat = Util.buildJavacvBGR2OpenCVRGBA(grabbedImage, mRgba);
  126. // 在buildJavacvBGR2OpenCVRGBA方法内部,有可能在执行native方法的是否发生异常,要做针对性处理
  127. if (null==openCVRGBAMat) {
  128. return frame;
  129. }
  130. // 基于上一次的检测结果开始跟踪
  131. org.opencv.core.Rect rotatedRect = objectTracker.objectTracking(openCVRGBAMat);
  132. // 如果rotatedRect为空,表示跟踪失败,此时要修改状态为"未跟踪"
  133. if (null==rotatedRect) {
  134. isInTracing = false;
  135. // 返回原始帧
  136. return frame;
  137. }
  138. // 代码能走到这里,表示跟踪成功,拿到的新的一帧上的目标的位置,此时就在新位置上
  139. // Util.rectOnImage(grabbedImage, rotatedRect.x, rotatedRect.y, rotatedRect.width, rotatedRect.height);
  140. // 矩形框的整体向下放一些(总高度的五分之一),另外跟踪得到的高度过大,画出的矩形框把脖子也框上了,这里改用宽度作为高度
  141. Util.rectOnImage(grabbedImage, rotatedRect.x, rotatedRect.y + rotatedRect.height/5, rotatedRect.width, rotatedRect.width);
  142. return converter.convert(grabbedImage);
  143. }
  144. /**
  145. * 程序结束前,释放人脸识别的资源
  146. */
  147. @Override
  148. public void releaseOutputResource() {
  149. if (null!=grabbedImage) {
  150. grabbedImage.release();
  151. }
  152. if (null!=mGray) {
  153. mGray.release();
  154. }
  155. if (null!=mRgba) {
  156. mRgba.release();
  157. }
  158. if (null==classifier) {
  159. classifier.close();
  160. }
  161. }
  162. }
  • 至此·,功能已经完成得七七八八,再来写完主程序就可以运行了;

PreviewCameraWithCamShift.java:主程序

  • 《JavaCV的摄像头实战之一:基础》创建的simple-grab-push工程中已经准备好了父类AbstractCameraApplication,所以本篇继续使用该工程,创建子类PreviewCameraWithCamShift实现那些抽象方法即可
  • 编码前先回顾父类的基础结构,如下图,粗体是父类定义的各个方法,红色块都是需要子类来实现抽象方法,所以接下来,咱们以本地窗口预览为目标实现这三个红色方法即可:
    在这里插入图片描述
  • 新建文件PreviewCameraWithCamShift.java,这是AbstractCameraApplication的子类,其代码很简单,接下来按上图顺序依次说明
  • 先定义CanvasFrame类型的成员变量previewCanvas,这是展示视频帧的本地窗口:
  1. protected CanvasFrame previewCanvas
  • 把前面创建的DetectService作为成员变量,后面检测的时候会用到:
  1. /**
  2. * 检测工具接口
  3. */
  4. private DetectService detectService;
  • PreviewCameraWithCamShift的构造方法,接受DetectService的实例:
  1. /**
  2. * 不同的检测工具,可以通过构造方法传入
  3. * @param detectService
  4. */
  5. public PreviewCameraWithCamShift(DetectService detectService) {
  6. this.detectService = detectService;
  7. }
  • 然后是初始化操作,可见是previewCanvas的实例化和参数设置,还有检测、识别的初始化操作:
  1. @Override
  2. protected void initOutput() throws Exception {
  3. previewCanvas = new CanvasFrame("摄像头预览", CanvasFrame.getDefaultGamma() / grabber.getGamma());
  4. previewCanvas.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  5. previewCanvas.setAlwaysOnTop(true);
  6. // 检测服务的初始化操作
  7. detectService.init();
  8. }
  • 接下来是output方法,定义了拿到每一帧视频数据后做什么事情,这里调用了detectService.convert检测人脸并识别性别,然后在本地窗口显示:
  1. @Override
  2. protected void output(Frame frame) {
  3. // 原始帧先交给检测服务处理,这个处理包括物体检测,再将检测结果标注在原始图片上,
  4. // 然后转换为帧返回
  5. Frame detectedFrame = detectService.convert(frame);
  6. // 预览窗口上显示的帧是标注了检测结果的帧
  7. previewCanvas.showImage(detectedFrame);
  8. }
  • 最后是处理视频的循环结束后,程序退出前要做的事情,先关闭本地窗口,再释放检测服务的资源:
  1. @Override
  2. protected void releaseOutputResource() {
  3. if (null!= previewCanvas) {
  4. previewCanvas.dispose();
  5. }
  6. // 检测工具也要释放资源
  7. detectService.releaseOutputResource();
  8. }
  • 由于检测有些耗时,所以两帧之间的间隔时间要低于普通预览:
  1. @Override
  2. protected int getInterval() {
  3. return super.getInterval()/8;
  4. }
  • 至此,功能已开发完成,再写上main方法,代码如下,请注意人脸检测所需的模型文件的路径来自系统变量:
  1. public static void main(String[] args) {
  2. String modelFilePath = System.getProperty("model.file.path");
  3. log.info("模型文件本地路径:{}", modelFilePath);
  4. new PreviewCameraWithCamShift(new CamShiftDetectService(modelFilePath)).action(1000);
  5. }

运行程序要注意的地方

  1. 下载opencv在windows环境的动态链接库:https://download.csdn.net/download/boling_cavalry/75121158,我这里下载后放在:C:\study\javacv\lib\opencv_java453.dll
  2. 人脸检测的模型文件,在GitHub下载,地址是:https://raw.github.com/opencv/opencv/master/data/haarcascades/haarcascade_frontalface_alt.xml,我这里下载后放在:C:\study\javacv\model\haarcascade_frontalface_alt.xml
  3. 运行程序的时候,不论是打包成jar,还是直接在IDEA中运行,都要添加下面这两个命令参数,才能确保应用加载到dll和模型文件(请按照您自己的存储位置修改下面参数的值):
  • -Djava.library.path=C:\study\javacv\lib
  • -Dmodel.file.path=C:\study\javacv\model\haarcascade_frontalface_alt.xml
  • 程序运行起来后,具体的效果与像《Java版人脸跟踪三部曲之一:极速体验》中一模一样,这里就不再赘述了,您自行验证就好
  • 其实本篇不运行程序,还有一个原因就是要过年了,用来检测人脸的群众演员临时涨价,要两份盒饭,欣宸实在是负担不起...

源码下载

名称 链接 备注
项目主页 https://github.com/zq2599/blog_demos 该项目在GitHub上的主页
git仓库地址(https) https://github.com/zq2599/blog_demos.git 该项目源码的仓库地址,https协议
git仓库地址(ssh) git@github.com:zq2599/blog_demos.git 该项目源码的仓库地址,ssh协议
  • 这个git项目中有多个文件夹,本篇的源码在javacv-tutorials文件夹下,如下图红框所示:
    在这里插入图片描述
  • javacv-tutorials里面有多个子工程,《JavaCV的摄像头实战》系列的代码在simple-grab-push工程下:
    在这里插入图片描述
  • 至此,《Java版人脸跟踪三部曲》完美收官,但是《JavaCV的摄像头实战》系列还会继续呈现更多精彩内容,欢迎关注;

欢迎关注博客园:程序员欣宸

学习路上,你不孤单,欣宸原创一路相伴...

原文链接:https://www.cnblogs.com/bolingcavalry/p/17533899.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号