一、效果展示

image.png

二、技术实现思路

1、人脸检测与画框

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

2、人脸性别识别

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

3、显示性别

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

三、实现步骤

1、人脸识别

  1. /**
  2. * 图片人脸检测
  3. * @return
  4. */
  5. private static MatOfRect facePick(Mat img) {
  6. // 存放灰度图
  7. Mat tempImg = new Mat();
  8. // 摄像头获取的是彩色图像,所以先灰度化下
  9. Imgproc.cvtColor(img, tempImg, Imgproc.COLOR_BGRA2GRAY);
  10. // OpenCV人脸识别分类器
  11. CascadeClassifier classifier = new CascadeClassifier("D:\\workspace\\opencv\\data\\haarcascades\\haarcascade_frontalface_default.xml");
  12. // # 调用识别人脸
  13. MatOfRect faceRects = new MatOfRect();
  14. // 特征检测点的最小尺寸, 根据实际照片尺寸来选择, 不然测量结果可能不准确。
  15. Size minSize = new Size(140, 140);
  16. // 图像缩放比例,可理解为相机的X倍镜
  17. double scaleFactor = 1.2;
  18. // 对特征检测点周边多少有效点同时检测,这样可避免因选取的特征检测点太小而导致遗漏
  19. int minNeighbors = 3;
  20. // 人脸检测
  21. // CV_HAAR_DO_CANNY_PRUNING
  22. classifier.detectMultiScale(tempImg, faceRects, scaleFactor, minNeighbors, 0, minSize);
  23. return faceRects;
  24. }

2、人脸画框

  1. /**
  2. * 在图片上的人脸区域画上矩形框
  3. * @param rect
  4. * @param img
  5. * @param color
  6. */
  7. private static void drawRect(Rect rect, Mat img, Scalar color) {
  8. int x = rect.x;
  9. int y = rect.y;
  10. int w = rect.width;
  11. int h = rect.height;
  12. Imgproc.rectangle(img, new Point(x, y), new Point(x + h, y + w), color, 2);
  13. }

3、性别识别

  1. /**
  2. * 性别检测
  3. * @param img
  4. * @param rect
  5. * @param genderNet
  6. * @return
  7. */
  8. private static String getGender(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. 1 = FEMALE, 0 = MALE
  21. double index = mm.maxLoc.x;
  22. return genderList.get((int) index);
  23. }

4、图片上显示中文

这一步主要是解决opencv中文显示乱码的问题

  1. /**
  2. * 在图片上显示中文
  3. * @param img
  4. * @param gender
  5. * @param x
  6. * @param y
  7. * @return
  8. */
  9. private static Mat putChineseTxt(Mat img, String gender, int x, int y) {
  10. Font font = new Font("微软雅黑", Font.PLAIN, 20);
  11. BufferedImage bufImg = matToImg(img,".png");
  12. Graphics2D g = bufImg.createGraphics();
  13. g.drawImage(bufImg, 0, 0, bufImg.getWidth(), bufImg.getHeight(), null);
  14. // 设置字体
  15. g.setColor(new Color(255, 10, 52));
  16. g.setFont(font);
  17. // 设置水印的坐标
  18. g.drawString(gender, x, y);
  19. g.dispose();
  20. // 加完水印再转换回来
  21. return imgToMat(bufImg, BufferedImage.TYPE_3BYTE_BGR, CvType.CV_8UC3);
  22. }
  23. /**
  24. * Mat二维矩阵转Image
  25. * @param matrix
  26. * @param fileExtension
  27. * @return
  28. */
  29. public static BufferedImage matToImg(Mat matrix, String fileExtension) {
  30. // convert the matrix into a matrix of bytes appropriate for
  31. // this file extension
  32. MatOfByte mob = new MatOfByte();
  33. Imgcodecs.imencode(fileExtension, matrix, mob);
  34. // convert the "matrix of bytes" into a byte array
  35. byte[] byteArray = mob.toArray();
  36. BufferedImage bufImage = null;
  37. try {
  38. InputStream in = new ByteArrayInputStream(byteArray);
  39. bufImage = ImageIO.read(in);
  40. } catch (Exception e) {
  41. e.printStackTrace();
  42. }
  43. return bufImage;
  44. }
  45. /**
  46. * BufferedImage转换成 Mat
  47. * @param original
  48. * @param imgType
  49. * @param matType
  50. * @return
  51. */
  52. public static Mat imgToMat(BufferedImage original, int imgType, int matType) {
  53. if (original == null) {
  54. throw new IllegalArgumentException("original == null");
  55. }
  56. if (original.getType() != imgType){
  57. // Create a buffered image
  58. BufferedImage image = new BufferedImage(original.getWidth(), original.getHeight(), imgType);
  59. // Draw the image onto the new buffer
  60. Graphics2D g = image.createGraphics();
  61. try {
  62. g.setComposite(AlphaComposite.Src);
  63. g.drawImage(original, 0, 0, null);
  64. } finally {
  65. g.dispose();
  66. }
  67. }
  68. byte[] pixels = ((DataBufferByte) original.getRaster().getDataBuffer()).getData();
  69. Mat mat = Mat.eye(original.getHeight(), original.getWidth(), matType);
  70. mat.put(0, 0, pixels);
  71. return mat;
  72. }

四、完成代码

  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/17 上午8:01
  29. * @description:图片人脸性别识别
  30. * @email: zhangyule1993@sina.com
  31. * @version: 1.0
  32. */
  33. public class ImageGenderDetect {
  34. static {
  35. System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
  36. }
  37. /**
  38. * 性别识别模型
  39. */
  40. private final static String genderProto = "D:/workspace/opencv/data/models/gender_deploy.prototxt";
  41. private final static String genderModel = "D:/workspace/opencv/data/models/gender_net.caffemodel";
  42. /**
  43. * 性别预测返回的是一个二分类结果 Male, Female
  44. */
  45. private final static List<String> genderList = new ArrayList<>(Arrays.asList("男", "女"));
  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 genderNet = Dnn.readNetFromCaffe(genderProto, genderModel);
  53. if (genderNet.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 = getGender(img, rect, genderNet);
  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 getGender(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. 1 = FEMALE, 0 = MALE
  113. double index = mm.maxLoc.x;
  114. return genderList.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. }