写着玩的Java opencv人脸识别——从无到有只需30分钟


世上本没有调包侠,调的越来越熟练也就成了调包侠…


前言(废话):

最近学校的项目要做个人脸表情识别的app,但是!我作为一个菜鸟,什么都不会,我就跑去问老师,能不能慢慢?

老师一听,什么???慢慢来???你在逗我吗???

在各种威逼利诱之下,无奈只能硬着头皮接了下来。搜遍了各种资料,发现其实还是蛮好做的这个上手好难啊。

在搜集了各种资料之后,先写了一个基于Java的小demo来实验一下,没想到就成了。

话不多说,有图有真相:

java 人名识别 java写人脸识别_System


正文:

就算是一个小demo也是要有设计的灵魂的:

先大致理一下思路,首先,要有包可以调。搜了一下,发现除了各大公司的API以外,能比较适合新手的包也就剩下opencv了

导包的第一步是装包(opencv的安装方法):

opencv 的Java包还是蛮好装的,去官网下一个安装包就可以了,为了省事,就直接用最新版了。
首先下载Windows下的安装包。

java 人名识别 java写人脸识别_Image_02


下载之后随便找个地方安装:

java 人名识别 java写人脸识别_Java_03


上面就是安装完了的样子。

然后就是配置的事情了,下面放出来eclipse下配置的方式:

首先新建一个项目,然后在项目的图标上右键选择Properties,然后点开Build Path,

java 人名识别 java写人脸识别_System_04


选择Add External JARS,打开你安装opencv的文件夹,一直点到如下图:

java 人名识别 java写人脸识别_System_05


选择jar包,然后打开,之后点击JRE System Library,选择Native Library Location ,双击打开

java 人名识别 java写人脸识别_System_06


点击External Folder ,一直点到刚才找到jre包的位置,根据自己的机器来选取环境,64位的电脑就选取x64,点击ok


这样,opencv的环境配置就算完成了。


导包的第二步是import:

首先试着建一个Mat对象看看:

import org.opencv.core.Core;
import org.opencv.core.Mat;

public class example {
    public static void main(String[] args) {
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);//这个一定要在程序运行前使用,这条语句规定了代码编译时候使用的库
        Mat m =new Mat();
        System.out.println(m);
    }
}

运行一下试试看:

Mat [ 0*0*CV_8UC1, isCont=false, isSubmat=false, nativeObj=0x766840, dataAddr=0x0 ]

Process finished with exit code 0

打印出来了如下的消息,说明opencv的配置是成功的。
接下来就是实现人脸识别的过程了。


导包的第三步是copy

俗话说得好,天下代码一大抄,看你会抄不会抄。

我这个人又菜又懒自然就只能选择抄代码了。但是抄并不是没灵魂的Ctrl+c和Ctrl+v,抄的时候也要思考一下这个是干什么的呀。

闲话就先说到这里,先看看如何导入一张图片吧。

首先写一个导入图片的函数

/**
    *导入图片
    * @param add
    * @return 一个bufferedImage
    */
   public static BufferedImage loadImage(String add){
       try {
           BufferedImage img= ImageIO.read(new File(add));
           return img;
       } catch (IOException e) {
           e.printStackTrace();
       }
       return null;
   }

根据图片地址导入之后,返回一个BufferedImage。
因为opencv处理图象的基本类是Mat,所以需要将得到的图片转换为Mat

为了后面省事就顺便把Mat转BufferedImage的方法也给出来:

/**
    * BufferedImage转换成Mat
    * 
    * @param src
    *            要转换的BufferedImage
    * 
    */
public static Mat bufImg2Mat(BufferedImage src) {
   	if(src.getType() != BufferedImage.TYPE_3BYTE_BGR) {
   		BufferedImage image = new BufferedImage(src.getWidth(), src.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
   		image.getGraphics().drawImage(src, 0, 0, null);
   		src = image;
   	}
   	WritableRaster raster = src.getRaster();
   	DataBufferByte buffer = (DataBufferByte)raster.getDataBuffer();
   	byte[] pixels = buffer.getData();
       Mat mat = Mat.eye(src.getHeight(), src.getWidth(), CvType.CV_8UC3);
       mat.put(0, 0, pixels);
       return mat;
   }
/**
    * Mat转换成BufferedImage
    *
    * @param matrix
    *            要转换的Mat
    * @param fileExtension
    *            格式为 ".jpg", ".png", etc
    * @return
    */
   public static BufferedImage mat2BI(Mat mat){
       int dataSize =mat.cols()*mat.rows()*(int)mat.elemSize();
       byte[] data=new byte[dataSize];
       mat.get(0, 0,data);
       int type=mat.channels()==1?
               BufferedImage.TYPE_BYTE_GRAY:BufferedImage.TYPE_3BYTE_BGR;
       if(type==BufferedImage.TYPE_3BYTE_BGR){
           for(int i=0;i<dataSize;i+=3){
               byte blue=data[i+0];
               data[i+0]=data[i+2];
               data[i+2]=blue;
           }
       }
       BufferedImage image=new BufferedImage(mat.cols(),mat.rows(),type);
       image.getRaster().setDataElements(0, 0, mat.cols(), mat.rows(), data);
       return image;
   }

写好了这两个方法之后就可以开工了,建立一个级联分类器,然后根据分类器返回的数据就可以在图片上把人脸标记出来了,是不是很简单啊?为了代码好看,先把检测人脸的方法封装成一个函数

public static Mat detectFace(Mat mat_img) {
       System.out.println("Running DetectFace ... ");
       // 从配置文件lbpcascade_frontalface.xml中创建一个人脸识别器,该文件位于opencv安装目录中
       CascadeClassifier faceDetector = new CascadeClassifier("E:\\Java\\Libs\\opencv_4.0.1\\opencv\\sources\\data\\haarcascades\\"
               + "haarcascade_frontalface_alt2.xml");
       // 在图片中检测人脸
       MatOfRect faceDetections = new MatOfRect();
       faceDetector.detectMultiScale(mat_img, faceDetections);
       Rect[] rects = faceDetections.toArray();
       if(rects != null && rects.length >= 1){
           for (Rect rect : rects) {
               System.out.println(rect);
               Imgproc.rectangle(mat_img, new Point(rect.x, rect.y), new Point(rect.x + rect.width, rect.y + rect.height),
                       new Scalar(0, 0, 255), 2);
           }
       }

通过这个方法返回了一个将人脸框出来的Mat图象,最后我们要做的就是将这个图象转化为BufferedImage然后用一个图像界面显示出来。
最后的代码如下:

public static void main(String[] args) {
       String address="C:\\Users\\imwxc\\Desktop\\timg1.jpg";
       try {
           System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
           //创建一个mat
           Mat img_mat=new Mat();
           img_mat= M2I.bufImg2Mat(loadImage(address));
           img_mat=detectFace(img_mat);//检测人脸
           BufferedImage img2paint=M2I.mat2BI(img_mat);
           featureDecter.frame f=new featureDecter.frame();
           f.setSize(img2paint.getWidth()+200,img2paint.getHeight()+200);
           f.draw(img2paint);
       }catch (Exception e){
           e.printStackTrace();
       }
   }

这里用了一个JFrame 顺便也给出来:

public static class frame extends JFrame {
       frame(){
           this.setDefaultCloseOperation(3);
           this.setBounds(0,0,800,800);
           this.setSize(800,800);
           this.setVisible(true);
       }
       public void draw(BufferedImage img){
           while(true){
               this.getGraphics().drawImage(img,100,100,img.getWidth(),img.getHeight(),null);
           }
       }
   }

最后的最后就是开篇的那个效果图