之前上传了一个资源,就是Java实现人脸检测,发现很多人都不会用,就是这个https://download.csdn.net/download/b379685397/10023135。各种乱七八糟评论都有,那就如大家所愿,我发个教程吧。
当前很多博客实现人脸识别的大部分都是调用云厂家的接口,如百度,阿里云。以及我们乐橙开放平台也支持人脸识别等人工智能服务。这些都比较简单,会接开放平台,走接口请求基本上都掌握了。缺点就是有限制,收费。
那么我就在想,能不能不依赖第三方,自己实现人脸检测呢。搜索了相关开源软件,发现有几款比较合适,如openCV,SeetaFaceEngine,openface等。经测试,SeetaFaceEngine准确率最高,因为模型经过大量训练。但存在内存泄漏问题。应该是底层库没有处理好(猜测,没有深究)。openCV对JAVA支持性最好,但准确率差点。需要自己调参不断尝试。
接下来,开始使用Java+openCV实现人脸识别和人眼识别等功能.
其实功能比较简单,主要就是使用了openCV,默认训练好的分类器。接下来进入正片。
打开opencv官网https://opencv.org/releases/。下载opencv。我使用的是3.43版本。本地为windows、所以选择windows的3.43版本。按照自己的需求进行下载。
安装比较简单,一直下一步即可。但记得修改安装路径。
安装好opencv之后,有build以及source目录。
build目录如下。有对应支持的语言的dll库和引用包。
sources\data目录下,存放着为opencv实现的各种分类器。我们需要使用的人脸和人眼检测的分类器都在里面。如果想详细了解的话可以在opencv的官网里进行查看。
人脸识别项目已经上传github,大家可以进行下载导入,下载地址为https://github.com/379685397/FaceDetect。可以的话,帮忙加个星啊亲~。哈哈
config目录存放的为opencv的分类器。此处使用了正面人脸以及人眼的分类器。
func为实现人脸相关接口
image存放的为测试用图片
tmp为测试使用输出图片。
lib里包含opencv的使用jar包和本地dll库。
工程导入完成之后,需要配置对应的jar包以及修改JDK。
IDEA的话通过file->Project Structure进行设置。选中加号,选择外部jar包引用。选择工程里lib目录下的openCV343.jar。
之后重新编译。看是否报错。没有报错的话项目导入成功
该函数主要实现了两个方法,一个是人脸检测,一个是人眼检测。方法都差不多。主要是加载的分类器不同。以及结果集的过滤。其中重要的一个方法时是MatOfRect的detectMultiScale方法。该方法共有7个参数。含义如下。
参数1:image--待检测图片,一般为灰度图像加快检测速度;参数2:objects--被检测物体的矩形框向量组;参数3:scaleFactor--表示在前后两次相继的扫描中,搜索窗口的比例系数。默认为1.1即每次搜索窗口依次扩大10%;参数4:minNeighbors--表示构成检测目标的相邻矩形的最小个数(默认为3个)。 如果组成检测目标的小矩形的个数和小于 min_neighbors - 1 都会被排除。 如果min_neighbors 为 0, 则函数不做任何操作就返回所有的被检候选矩形框, 这种设定值一般用在用户自定义对检测结果的组合程序上;参数5:flags--要么使用默认值,要么使用CV_HAAR_DO_CANNY_PRUNING,如果设置为 CV_HAAR_DO_CANNY_PRUNING,那么函数将会使用Canny边缘检测来排除边缘过多或过少的区域, 因此这些区 域通常不会是人脸所在区域;参数6、7:minSize和maxSize用来限制得到的目标区域的范围。2、ImageUtils
package com.facedetect.func; import org.opencv.core.Mat; import org.opencv.core.Rect; import org.opencv.core.Size; import org.opencv.imgcodecs.Imgcodecs; import org.opencv.imgproc.Imgproc; import javax.imageio.ImageIO; import javax.swing.*; import java.awt.*; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; /** * @Auther: DarkKing * @Date: 2019/10/2 11:12 * @Description: */ public class ImageUtils { /** * 裁剪图片并重新装换大小 * @param imagePath * @param posX * @param posY * @param width * @param height * @param outFile */ public static void imageCut(String imagePath,String outFile, int posX,int posY,int width,int height ){ //原始图像 Mat image = Imgcodecs.imread(imagePath); //截取的区域:参数,坐标X,坐标Y,截图宽度,截图长度 Rect rect = new Rect(posX,posY,width,height); //两句效果一样 Mat sub = image.submat(rect); //Mat sub = new Mat(image,rect); Mat mat = new Mat(); Size size = new Size(300, 300); Imgproc.resize(sub, mat, size);//将人脸进行截图并保存 Imgcodecs.imwrite(outFile, mat); System.out.println(String.format("图片裁切成功,裁切后图片文件为: %s", outFile)); } /** * * @param imagePath * @param outFile */ public static void setAlpha(String imagePath, String outFile) { /** * 增加测试项 * 读取图片,绘制成半透明 */ try { ImageIcon imageIcon = new ImageIcon(imagePath); BufferedImage bufferedImage = new BufferedImage(imageIcon.getIconWidth(), imageIcon.getIconHeight(), BufferedImage.TYPE_4BYTE_ABGR); Graphics2D g2D = (Graphics2D) bufferedImage.getGraphics(); g2D.drawImage(imageIcon.getImage(), 0, 0, imageIcon.getImageObserver()); //循环每一个像素点,改变像素点的Alpha值 int alpha = 100; for (int j1 = bufferedImage.getMinY(); j1 < bufferedImage.getHeight(); j1++) { for (int j2 = bufferedImage.getMinX(); j2 < bufferedImage.getWidth(); j2++) { int rgb = bufferedImage.getRGB(j2, j1); rgb = ( (alpha + 1) << 24) | (rgb & 0x00ffffff); bufferedImage.setRGB(j2, j1, rgb); } } g2D.drawImage(bufferedImage, 0, 0, imageIcon.getImageObserver()); //生成图片为PNG ImageIO.write(bufferedImage, "png", new File(outFile)); System.out.println(String.format("绘制图片半透明成功,图片文件为: %s", outFile)); } catch (Exception e) { e.printStackTrace(); } } /** * 为图像添加水印 * @param buffImgFile 底图 * @param waterImgFile 水印 * @param outFile 输出图片 * @param alpha 透明度 * @throws IOException */ private static void watermark(String buffImgFile,String waterImgFile,String outFile, float alpha) throws IOException { // 获取底图 BufferedImage buffImg = ImageIO.read(new File(buffImgFile)); // 获取层图 BufferedImage waterImg = ImageIO.read(new File(waterImgFile)); // 创建Graphics2D对象,用在底图对象上绘图 Graphics2D g2d = buffImg.createGraphics(); int waterImgWidth = waterImg.getWidth();// 获取水印层图的宽度 int waterImgHeight = waterImg.getHeight();// 获取水印层图的高度 // 在图形和图像中实现混合和透明效果 g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha)); // 绘制 g2d.drawImage(waterImg, 0, 0, waterImgWidth, waterImgHeight, null); g2d.dispose();// 释放图形上下文使用的系统资源 //生成图片为PNG ImageIO.write(buffImg, "png", new File(outFile)); System.out.println(String.format("图片添加水印成功,图片文件为: %s", outFile)); } /** * 图片合成 * @param image1 * @param image2 * @param posw * @param posh * @param outFile * @return */ public static void simpleMerge(String image1, String image2, int posw, int posh, String outFile) throws IOException{ // 获取底图 BufferedImage buffImg1 = ImageIO.read(new File(image1)); // 获取层图 BufferedImage buffImg2 = ImageIO.read(new File(image2)); //合并两个图像 int w1 = buffImg1.getWidth(); int h1 = buffImg1.getHeight(); int w2 = buffImg2.getWidth(); int h2 = buffImg2.getHeight(); BufferedImage imageSaved = new BufferedImage(w1, h1, BufferedImage.TYPE_INT_ARGB); //创建一个新的内存图像 Graphics2D g2d = imageSaved.createGraphics(); g2d.drawImage(buffImg1, null, 0, 0); //绘制背景图像 for (int i = 0; i < w2; i++) { for (int j = 0; j < h2; j++) { int rgb1 = buffImg1.getRGB(i + posw, j + posh); int rgb2 = buffImg2.getRGB(i, j); /*if (rgb1 != rgb2) { rgb2 = rgb1 & rgb2; }*/ imageSaved.setRGB(i + posw, j + posh, rgb2); //修改像素值 } } ImageIO.write(imageSaved, "png", new File(outFile)); System.out.println(String.format("图片合成成功,合成图片文件为: %s", outFile)); } }改类为文件处理类。
测试拿test1图片做人脸测试,1.png做人眼测试。查看输出结果
执行DetectFaceTest。查看结果。人脸检测到3张。人眼检测到一张。
图片输出
到这教程就结束了,在实际使用过程中,我发现人眼识别准确率很低。可能是我的参数有问题。大家可以多进行测试一下。