一、效果展示

主要调用的dnn网络模型
image.png

二、技术实现思路

1、人脸检测与画框

  • 图片转换为灰色(降低为一维的灰度,减低计算强度)
  • 图片上画矩阵
  • 使用训练分类器查找人脸

2、人脸年龄识别

加载年龄识别网络模型
遍历图片上检测到的人脸,逐一检测年龄
在人脸矩形框上方显示年龄

3、显示年龄

因为opencv 原生的 Imgproc.putText 方法在图片上显示中文会 乱码,具体做法是,需要将性别文字制作成水印添加到图片上,这个过程中需要先将图片二位矩阵Mat转化成Image,添加完水印后再将Image转回成二位矩阵Mat

4、模型讲解

性别预测返回二分类结果,年龄预测返回8个年龄阶段,这个结果实际上是算出性别检测值在二维矩阵Mat的第一行中最大值的索引
定义两个集合:
性别集合:

  1. /**
  2. * 性别预测返回的是一个二分类结果 Male, Female
  3. */
  4. private final static List<String> genderList = new ArrayList<>(Arrays.asList("男", "女"));

年龄集合:

  1. /**
  2. * 年龄预测返回的是8个年龄的阶段!
  3. */
  4. private final static List<String> ageList = new ArrayList<>(Arrays.asList("(0-2)", "(4-6)", "(8-12)", "(15-20)", "(25-32)", "(38-43)", "(48-53)", "(60-100)"));

三、实现步骤

1、年龄检测

  1. /**
  2. * 年龄检测
  3. * @param img
  4. * @param rect
  5. * @param genderNet
  6. * @return
  7. */
  8. private static String getAge(Mat img, Rect rect, Net genderNet) {
  9. Mat face = new Mat(img, rect);
  10. // Resizing pictures to resolution of Caffe model
  11. Imgproc.resize(face, face, new Size(140, 140));
  12. // 灰度化
  13. Imgproc.cvtColor(face, face, Imgproc.COLOR_RGBA2BGR);
  14. // blob输入网络进行年龄的检测
  15. Mat inputBlob = Dnn.blobFromImage(face, 1.0f, new Size(227, 227), MODEL_MEAN_VALUES, false, false);
  16. genderNet.setInput(inputBlob, "data");
  17. // 年龄检测进行前向传播
  18. Mat probs = genderNet.forward("prob").reshape(1, 1);
  19. Core.MinMaxLocResult mm = Core.minMaxLoc(probs);
  20. // Result of gender recognition prediction.
  21. double index = mm.maxLoc.x;
  22. return ageList.get((int) index);
  23. }

四、完整代码

  1. package com.biubiu.example;
  2. import java.awt.*;
  3. import java.awt.image.BufferedImage;
  4. import java.awt.image.DataBufferByte;
  5. import java.io.ByteArrayInputStream;
  6. import java.io.InputStream;
  7. import java.util.ArrayList;
  8. import java.util.Arrays;
  9. import java.util.List;
  10. import javax.imageio.ImageIO;
  11. import org.opencv.core.Core;
  12. import org.opencv.core.CvType;
  13. import org.opencv.core.Mat;
  14. import org.opencv.core.MatOfByte;
  15. import org.opencv.core.MatOfRect;
  16. import org.opencv.core.Point;
  17. import org.opencv.core.Rect;
  18. import org.opencv.core.Scalar;
  19. import org.opencv.core.Size;
  20. import org.opencv.dnn.Dnn;
  21. import org.opencv.dnn.Net;
  22. import org.opencv.highgui.HighGui;
  23. import org.opencv.imgcodecs.Imgcodecs;
  24. import org.opencv.imgproc.Imgproc;
  25. import org.opencv.objdetect.CascadeClassifier;
  26. /**
  27. * @author :张音乐
  28. * @date :Created in 2021/4/18 下午1:25
  29. * @description:年龄识别
  30. * @email: zhangyule1993@sina.com
  31. * @version: 1.0
  32. */
  33. public class ImageAgeDetect {
  34. static {
  35. System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
  36. }
  37. /**
  38. * 年龄识别模型
  39. */
  40. private final static String ageProto = "D:/workspace/opencv/data/models/age_deploy.prototxt";
  41. private final static String ageModel = "D:/workspace/opencv/data/models/age_net.caffemodel";
  42. /**
  43. * 年龄预测返回的是8个年龄的阶段!
  44. */
  45. private final static List<String> ageList = new ArrayList<>(Arrays.asList("(0-2)", "(4-6)", "(8-12)", "(15-20)", "(25-32)", "(38-43)", "(48-53)", "(60-100)"));
  46. /**
  47. * 模型均值
  48. */
  49. private final static Scalar MODEL_MEAN_VALUES = new Scalar(78.4263377603, 87.7689143744, 114.895847746);
  50. public static void main(String[] args) {
  51. // 加载网络模型
  52. Net ageNet = Dnn.readNetFromCaffe(ageProto, ageModel);
  53. if (ageNet.empty()) {
  54. System.out.println("无法打开网络模型...\n");
  55. return;
  56. }
  57. // 加载图片矩阵
  58. String filePath = "D:\\upload\\gather.png";
  59. Mat img = Imgcodecs.imread(filePath);
  60. // 人脸检测
  61. MatOfRect faceRects = facePick(img);
  62. // 定义一个颜色
  63. Scalar color = new Scalar(0, 0, 255);
  64. // 遍历检测到的图片
  65. for(Rect rect : faceRects.toArray()) {
  66. // 人脸画矩形框
  67. drawRect(rect, img, color);
  68. // 检测年龄
  69. String gender = getAge(img, rect, ageNet);
  70. // 图片上显示中文的年龄 ,因为原生的 opencv putText 显示中文会乱码, 所以需要特殊处理一下
  71. img = putChineseTxt(img, gender, rect.x + rect.width / 2 - 5, rect.y - 10);
  72. // Imgproc.putText(img, new String(gender.getBytes(StandardCharsets.UTF_8)), new Point(x, y), 2, 2, color);
  73. }
  74. // 显示图像
  75. HighGui.imshow("预览", img);
  76. HighGui.waitKey(0);
  77. // 释放所有的窗体资源
  78. HighGui.destroyAllWindows();
  79. }
  80. /**
  81. * 在图片上的人脸区域画上矩形框
  82. * @param rect
  83. * @param img
  84. * @param color
  85. */
  86. private static void drawRect(Rect rect, Mat img, Scalar color) {
  87. int x = rect.x;
  88. int y = rect.y;
  89. int w = rect.width;
  90. int h = rect.height;
  91. Imgproc.rectangle(img, new Point(x, y), new Point(x + h, y + w), color, 2);
  92. }
  93. /**
  94. * 年龄检测
  95. * @param img
  96. * @param rect
  97. * @param genderNet
  98. * @return
  99. */
  100. private static String getAge(Mat img, Rect rect, Net genderNet) {
  101. Mat face = new Mat(img, rect);
  102. // Resizing pictures to resolution of Caffe model
  103. Imgproc.resize(face, face, new Size(140, 140));
  104. // 颜色空间转换
  105. Imgproc.cvtColor(face, face, Imgproc.COLOR_RGBA2BGR);
  106. // blob输入网络进行年龄的检测
  107. Mat inputBlob = Dnn.blobFromImage(face, 1.0f, new Size(227, 227), MODEL_MEAN_VALUES, false, false);
  108. genderNet.setInput(inputBlob, "data");
  109. // 年龄检测进行前向传播
  110. Mat probs = genderNet.forward("prob").reshape(1, 1);
  111. Core.MinMaxLocResult mm = Core.minMaxLoc(probs);
  112. // Result of gender recognition prediction.
  113. double index = mm.maxLoc.x;
  114. return ageList.get((int) index);
  115. }
  116. /**
  117. * 图片人脸检测
  118. * @return
  119. */
  120. private static MatOfRect facePick(Mat img) {
  121. // 存放灰度图
  122. Mat tempImg = new Mat();
  123. // 摄像头获取的是彩色图像,所以先灰度化下
  124. Imgproc.cvtColor(img, tempImg, Imgproc.COLOR_BGRA2GRAY);
  125. // OpenCV人脸识别分类器
  126. CascadeClassifier classifier = new CascadeClassifier("D:\\workspace\\opencv\\data\\haarcascades\\haarcascade_frontalface_default.xml");
  127. // # 调用识别人脸
  128. MatOfRect faceRects = new MatOfRect();
  129. // 特征检测点的最小尺寸, 根据实际照片尺寸来选择, 不然测量结果可能不准确。
  130. Size minSize = new Size(140, 140);
  131. // 图像缩放比例,可理解为相机的X倍镜
  132. double scaleFactor = 1.2;
  133. // 对特征检测点周边多少有效点同时检测,这样可避免因选取的特征检测点太小而导致遗漏
  134. int minNeighbors = 3;
  135. // 人脸检测
  136. // CV_HAAR_DO_CANNY_PRUNING
  137. classifier.detectMultiScale(tempImg, faceRects, scaleFactor, minNeighbors, 0, minSize);
  138. return faceRects;
  139. }
  140. /**
  141. * Mat二维矩阵转Image
  142. * @param matrix
  143. * @param fileExtension
  144. * @return
  145. */
  146. public static BufferedImage matToImg(Mat matrix, String fileExtension) {
  147. // convert the matrix into a matrix of bytes appropriate for
  148. // this file extension
  149. MatOfByte mob = new MatOfByte();
  150. Imgcodecs.imencode(fileExtension, matrix, mob);
  151. // convert the "matrix of bytes" into a byte array
  152. byte[] byteArray = mob.toArray();
  153. BufferedImage bufImage = null;
  154. try {
  155. InputStream in = new ByteArrayInputStream(byteArray);
  156. bufImage = ImageIO.read(in);
  157. } catch (Exception e) {
  158. e.printStackTrace();
  159. }
  160. return bufImage;
  161. }
  162. /**
  163. * BufferedImage转换成 Mat
  164. * @param original
  165. * @param imgType
  166. * @param matType
  167. * @return
  168. */
  169. public static Mat imgToMat(BufferedImage original, int imgType, int matType) {
  170. if (original == null) {
  171. throw new IllegalArgumentException("original == null");
  172. }
  173. if (original.getType() != imgType){
  174. // Create a buffered image
  175. BufferedImage image = new BufferedImage(original.getWidth(), original.getHeight(), imgType);
  176. // Draw the image onto the new buffer
  177. Graphics2D g = image.createGraphics();
  178. try {
  179. g.setComposite(AlphaComposite.Src);
  180. g.drawImage(original, 0, 0, null);
  181. } finally {
  182. g.dispose();
  183. }
  184. }
  185. byte[] pixels = ((DataBufferByte) original.getRaster().getDataBuffer()).getData();
  186. Mat mat = Mat.eye(original.getHeight(), original.getWidth(), matType);
  187. mat.put(0, 0, pixels);
  188. return mat;
  189. }
  190. /**
  191. * 在图片上显示中文
  192. * @param img
  193. * @param gender
  194. * @param x
  195. * @param y
  196. * @return
  197. */
  198. private static Mat putChineseTxt(Mat img, String gender, int x, int y) {
  199. Font font = new Font("微软雅黑", Font.PLAIN, 20);
  200. BufferedImage bufImg = matToImg(img,".png");
  201. Graphics2D g = bufImg.createGraphics();
  202. g.drawImage(bufImg, 0, 0, bufImg.getWidth(), bufImg.getHeight(), null);
  203. // 设置字体
  204. g.setColor(new Color(255, 10, 52));
  205. g.setFont(font);
  206. // 设置水印的坐标
  207. g.drawString(gender, x, y);
  208. g.dispose();
  209. // 加完水印再转换回来
  210. return imgToMat(bufImg, BufferedImage.TYPE_3BYTE_BGR, CvType.CV_8UC3);
  211. }
  212. }