一、实现思路

  • 打开本地摄像头
  • 遍历每一个图像帧检查出人脸
  • 对人脸进行灰度化
  • 戴白色面具的人,大多数时候Opencv 无法正确识别人脸,为克服这个困难,使用“阈值”函数转换黑白图像
  • 检测出每一个人脸上是否有嘴,或者鼻子
  • 如果检测出嘴,则没有带口罩,如果没有检测出嘴,则带了口罩或者面具,面纱遮挡等

二、完整代码

  1. package com.biubiu.example;
  2. import org.opencv.core.Point;
  3. import org.opencv.core.*;
  4. import org.opencv.highgui.HighGui;
  5. import org.opencv.imgcodecs.Imgcodecs;
  6. import org.opencv.imgproc.Imgproc;
  7. import org.opencv.objdetect.CascadeClassifier;
  8. import org.opencv.videoio.VideoCapture;
  9. import javax.imageio.ImageIO;
  10. import java.awt.*;
  11. import java.awt.image.BufferedImage;
  12. import java.awt.image.DataBufferByte;
  13. import java.io.ByteArrayInputStream;
  14. import java.io.InputStream;
  15. import static org.opencv.imgproc.Imgproc.threshold;
  16. /**
  17. * @author :张音乐
  18. * @date :Created in 2021/10/23 下午2:41
  19. * @description:口罩检测
  20. * @email: zhangyule1993@sina.com
  21. * @version:
  22. */
  23. public class MasksDetect {
  24. static {
  25. // 加载 动态链接库
  26. System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
  27. }
  28. public static void main(String[] args) {
  29. VideoCapture camera = new VideoCapture();
  30. // 参数0表示,获取第一个摄像头。
  31. camera.open(0);
  32. // 图像帧
  33. Mat frame = new Mat();
  34. for(;;) {
  35. camera.read(frame);
  36. draw(frame);
  37. // 等待用户按esc停止检测
  38. if(HighGui.waitKey(100) == 100) {
  39. break;
  40. }
  41. }
  42. // 释放摄像头
  43. camera.release();
  44. // 释放窗口资源
  45. HighGui.destroyAllWindows();
  46. }
  47. /**
  48. * 逐帧处理
  49. * @param frame
  50. */
  51. private static void draw(Mat frame) {
  52. Mat grayFrame = new Mat();
  53. Imgproc.cvtColor(frame, grayFrame, Imgproc.COLOR_BGR2GRAY);
  54. Mat thresh = new Mat();
  55. // 戴白色面具的人,大多数时候 OpenCV 无法正确识别人脸。为了克服这个困难,使用“阈值”函数转换黑白图像
  56. // 根据相机和周围光线在80 到 105 “阈值”范围内调整阈值 (bw_threshold) 值很重要。
  57. threshold(grayFrame, thresh, 80, 255, Imgproc.THRESH_BINARY | Imgproc.THRESH_OTSU);
  58. // OpenCv人脸识别分类器
  59. CascadeClassifier faceClassifier = new CascadeClassifier("/usr/local/share/OpenCV/haarcascades/haarcascade_frontalface_default.xml");
  60. // 图像缩放比例,可以理解为相机的X倍镜
  61. double scaleFactor = 1.1;
  62. // 对特征检测点周边多少有效检测点同时检测,这样可以避免选取的特征检测点大小而导致遗漏
  63. int minNeighbors = 4;
  64. // 用来存放人脸矩形
  65. MatOfRect faceRect = new MatOfRect();
  66. // 执行人脸检测 “灰色”图像
  67. faceClassifier.detectMultiScale(grayFrame, faceRect, scaleFactor, minNeighbors);
  68. // 用来存放人脸矩形
  69. MatOfRect faceRect1 = new MatOfRect();
  70. // 执行人脸检测 “黑白”图像
  71. faceClassifier.detectMultiScale(thresh, faceRect1, scaleFactor, minNeighbors);
  72. // OpenCv人脸识别分类器
  73. CascadeClassifier mouthClassifier = new CascadeClassifier("/usr/local/share/OpenCV/haarcascades/haarcascade_mcs_mouth.xml");
  74. Scalar color = new Scalar(0, 0, 255);
  75. // 遍历每一个图像帧上的每一张人脸
  76. for(Rect rect: faceRect.toArray()) {
  77. int x = rect.x;
  78. int y = rect.y;
  79. int w = rect.width;
  80. int h = rect.height;
  81. // 框出人脸
  82. Imgproc.rectangle(frame, new Point(x, y), new Point(x + h, y + w), color, 2);
  83. // 用来存放嘴矩形
  84. MatOfRect mouseRects = new MatOfRect();
  85. mouthClassifier.detectMultiScale(grayFrame, mouseRects, 1.5, 5);
  86. if(mouseRects.toArray().length == 0) {
  87. frame = putChineseTxt(frame, "有口罩/遮挡面具", x + w / 2 - 5, y - 10);
  88. }
  89. }
  90. HighGui.imshow("预览", frame);
  91. }
  92. /**
  93. * Mat二维矩阵转Image
  94. * @param matrix
  95. * @param fileExtension
  96. * @return
  97. */
  98. public static BufferedImage matToImg(Mat matrix, String fileExtension) {
  99. // convert the matrix into a matrix of bytes appropriate for
  100. // this file extension
  101. MatOfByte mob = new MatOfByte();
  102. Imgcodecs.imencode(fileExtension, matrix, mob);
  103. // convert the "matrix of bytes" into a byte array
  104. byte[] byteArray = mob.toArray();
  105. BufferedImage bufImage = null;
  106. try {
  107. InputStream in = new ByteArrayInputStream(byteArray);
  108. bufImage = ImageIO.read(in);
  109. } catch (Exception e) {
  110. e.printStackTrace();
  111. }
  112. return bufImage;
  113. }
  114. /**
  115. * BufferedImage转换成 Mat
  116. * @param original
  117. * @param imgType
  118. * @param matType
  119. * @return
  120. */
  121. public static Mat imgToMat(BufferedImage original, int imgType, int matType) {
  122. if (original == null) {
  123. throw new IllegalArgumentException("original == null");
  124. }
  125. if (original.getType() != imgType){
  126. // Create a buffered image
  127. BufferedImage image = new BufferedImage(original.getWidth(), original.getHeight(), imgType);
  128. // Draw the image onto the new buffer
  129. Graphics2D g = image.createGraphics();
  130. try {
  131. g.setComposite(AlphaComposite.Src);
  132. g.drawImage(original, 0, 0, null);
  133. } finally {
  134. g.dispose();
  135. }
  136. }
  137. byte[] pixels = ((DataBufferByte) original.getRaster().getDataBuffer()).getData();
  138. Mat mat = Mat.eye(original.getHeight(), original.getWidth(), matType);
  139. mat.put(0, 0, pixels);
  140. return mat;
  141. }
  142. /**
  143. * 在图片上显示中文
  144. * @param img
  145. * @param text
  146. * @param x
  147. * @param y
  148. * @return
  149. */
  150. private static Mat putChineseTxt(Mat img, String text, int x, int y) {
  151. Font font = new Font("微软雅黑", Font.PLAIN, 20);
  152. BufferedImage bufImg = matToImg(img,".png");
  153. Graphics2D g = bufImg.createGraphics();
  154. g.drawImage(bufImg, 0, 0, bufImg.getWidth(), bufImg.getHeight(), null);
  155. // 设置字体
  156. g.setColor(new Color(255, 10, 52));
  157. g.setFont(font);
  158. // 设置水印的坐标
  159. g.drawString(text, x, y);
  160. g.dispose();
  161. // 加完水印再转换回来
  162. return imgToMat(bufImg, BufferedImage.TYPE_3BYTE_BGR, CvType.CV_8UC3);
  163. }
  164. }