Java實現人臉檢測

一、前言

之前上傳了一個資源,就是Java實現人臉檢測,發現很多人都不會用,就是這個https://download.csdn.net/download/b379685397/10023135。各種亂七八糟評論都有,那就如大家所願,我發個教程吧。

當前很多博客實現人臉識別的大部分都是調用雲廠家的接口,如百度,阿里雲。以及我們樂橙開放平臺也支持人臉識別等人工智能服務。這些都比較簡單,會接開放平臺,走接口請求基本上都掌握了。缺點就是有限制,收費。

那麼我就在想,能不能不依賴第三方,自己實現人臉檢測呢。搜索了相關開源軟件,發現有幾款比較合適,如openCV,SeetaFaceEngine,openface等。經測試,SeetaFaceEngine準確率最高,因爲模型經過大量訓練。但存在內存泄漏問題。應該是底層庫沒有處理好(猜測,沒有深究)。openCV對JAVA支持性最好,但準確率差點。需要自己調參不斷嘗試。

接下來,開始使用Java+openCV實現人臉識別和人眼識別等功能.

其實功能比較簡單,主要就是使用了openCV,默認訓練好的分類器。接下來進入正片。

二、openCV安裝

1、opencv下載

打開opencv官網https://opencv.org/releases/。下載opencv。我使用的是3.43版本。本地爲windows、所以選擇windows的3.43版本。按照自己的需求進行下載。

2、安裝openCV

安裝比較簡單,一直下一步即可。但記得修改安裝路徑。

3、openCV重要目錄

安裝好opencv之後,有build以及source目錄。

 build目錄如下。有對應支持的語言的dll庫和引用包。

sources\data目錄下,存放着爲opencv實現的各種分類器。我們需要使用的人臉和人眼檢測的分類器都在裏面。如果想詳細瞭解的話可以在opencv的官網裏進行查看。

 

三、工程搭建

人臉識別項目已經上傳github,大家可以進行下載導入,下載地址爲https://github.com/379685397/FaceDetect。可以的話,幫忙加個星啊親~。哈哈

1、工程目錄

config目錄存放的爲opencv的分類器。此處使用了正面人臉以及人眼的分類器。

func爲實現人臉相關接口

image存放的爲測試用圖片

tmp爲測試使用輸出圖片。

lib裏包含opencv的使用jar包和本地dll庫。

工程導入完成之後,需要配置對應的jar包以及修改JDK。

IDEA的話通過file->Project Structure進行設置。選中加號,選擇外部jar包引用。選擇工程裏lib目錄下的openCV343.jar。

之後重新編譯。看是否報錯。沒有報錯的話項目導入成功

2、代碼實現

1、DetectFace

package com.facedetect.func;

import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;

/**
 * @Auther: DarkKing
 * @Date: 2019/10/2 11:06
 * @Description:
 */
public class DetectFace {
    //定義程序的基礎路徑
    private String basePath =System.getProperty("user.dir");
    //人眼識別分類器路徑
    private   String eyeConfigPath=basePath+"\\src\\com\\facedetect\\config\\haarcascade_eye_tree_eyeglasses.xml";
    //人臉識別分類器路徑
    private   String faceConfigPath=basePath+"\\src\\com\\facedetect\\config\\haarcascade_frontalface_alt2.xml";

    static{
        // 載入opencv的庫
        String opencvpath = System.getProperty("user.dir") + "\\libs\\x64\\";
        String opencvDllName = opencvpath + Core.NATIVE_LIBRARY_NAME + ".dll";
        System.load(opencvDllName);
    }
    /**
     * opencv實現人臉識別
     * @param imagePath
     * @param outFile
     * @throws Exception
     */
    public  void detectFace(String imagePath,  String outFile) throws Exception
    {

        System.out.println("Running DetectFace ...,config path is  "+faceConfigPath);
        String basePath =System.getProperty("user.dir");
        String path= basePath+ "\\src\\com\\facedetect\\tmp\\";
        // 從配置文件lbpcascade_frontalface.xml中創建一個人臉識別器,該文件位於opencv安裝目錄中,爲了方便從安裝方便放到了程序路徑裏
        CascadeClassifier faceDetector = new CascadeClassifier(faceConfigPath);
        //創建圖片處理對象
        Mat image = Imgcodecs.imread(imagePath);
        // 在圖片中檢測人臉
        MatOfRect faceDetections = new MatOfRect();
        //多條件結果檢測
        faceDetector.detectMultiScale(image, faceDetections);

        System.out.println(String.format("Detected %s faces", faceDetections.toArray().length));
        //檢測結果集
        Rect[] rects = faceDetections.toArray();

        // 在每一個識別出來的人臉周圍畫出一個方框
        for (int i = 0; i < rects.length; i++) {
            Rect rect = rects[i];
            Imgproc.rectangle(image, new Point(rect.x-2, rect.y-2),
                    new Point(rect.x + rect.width, rect.y + rect.height),
                    new Scalar(0, 255, 0));
            Mat copy = new Mat(image,rect);
            Mat temp  = new Mat();
            copy.copyTo(temp);
            //輸出圖片
            Imgcodecs.imwrite(path+i+".png", temp);
        }
        Imgcodecs.imwrite(outFile, image);
        System.out.println(String.format("人臉識別成功,人臉圖片文件爲: %s", outFile));


    }


    /**
     * opencv實現人眼識別
     * @param imagePath
     * @param outFile
     * @throws Exception
     */
    public  void detectEye(String imagePath,  String outFile) throws Exception {

        System.out.println("Running DetectFace ...,config path is  "+eyeConfigPath);
        CascadeClassifier eyeDetector = new CascadeClassifier(
                eyeConfigPath);

        Mat image = Imgcodecs.imread(imagePath);  //讀取圖片

        // 在圖片中檢測人臉
        MatOfRect faceDetections = new MatOfRect();

        eyeDetector.detectMultiScale(image, faceDetections, 2.0,1,1,new Size(20,20),new Size(20,20));

        System.out.println(String.format("Detected %s eyes", faceDetections.toArray().length));
        Rect[] rects = faceDetections.toArray();
        if(rects != null && rects.length <2){
            throw new RuntimeException("不是一雙眼睛");
        }
        Rect eyea = rects[0];
        Rect eyeb = rects[1];


        System.out.println("a-中心座標 " + eyea.x + " and " + eyea.y);
        System.out.println("b-中心座標 " + eyeb.x + " and " + eyeb.y);

        //獲取兩個人眼的角度
        double dy=(eyeb.y-eyea.y);
        double dx=(eyeb.x-eyea.x);
        double len=Math.sqrt(dx*dx+dy*dy);
        System.out.println("dx is "+dx);
        System.out.println("dy is "+dy);
        System.out.println("len is "+len);

        double angle=Math.atan2(Math.abs(dy),Math.abs(dx))*180.0/Math.PI;
        System.out.println("angle is "+angle);

        for(Rect rect:faceDetections.toArray()) {
            Imgproc.rectangle(image, new Point(rect.x, rect.y), new Point(rect.x
                    + rect.width, rect.y + rect.height), new Scalar(0, 255, 0));

        }
        Imgcodecs.imwrite(outFile, image);

        System.out.println(String.format("人眼識別成功,人眼圖片文件爲: %s", outFile));

    }
}

該函數主要實現了兩個方法,一個是人臉檢測,一個是人眼檢測。方法都差不多。主要是加載的分類器不同。以及結果集的過濾。其中重要的一個方法時是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));

    }
}

改類爲文件處理類。

3、DetectFaceTest 測試類

package com.facedetect;

import java.awt.AlphaComposite;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;

import com.facedetect.func.DetectFace;
import com.facedetect.func.ImageUtils;
import com.sun.imageio.plugins.common.ImageUtil;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.MatOfRect;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;

/**
 * DrakKing
 */
public class DetectFaceTest {

    public static void main(String[] args) throws Exception {
        DetectFace df = new DetectFace();
         String basePath =System.getProperty("user.dir");
        String s1= basePath+ "\\src\\com\\facedetect\\image\\test1.jpg";
        String s2= basePath+ "\\src\\com\\facedetect\\image\\test2.jpg";
        String s3= basePath+ "\\src\\com\\facedetect\\image\\test3.jpg";
        String e1= basePath+ "\\src\\com\\facedetect\\image\\1.png";
        String faceTemp = basePath+"\\src\\com\\facedetect\\tmp\\faceTemp.png";
        String eyeTemp = basePath+"\\src\\com\\facedetect\\tmp\\eyeTemp.png";
        //人臉識別
        df.detectFace(s1, faceTemp);

        //人眼識別
      df.detectEye(e1,  eyeTemp);

        //圖片裁切
       // ImageUtils.imageCut(s3,temp, 50, 50,100,100);

        //設置圖片爲半透明
//        ImageUtils.setAlpha(s, temp);


        //爲圖片添加水印
//        ImageUtils.watermark(s,"E:\\ling.jpg",temp, 0.2f);


        //圖片合成
//        ImageUtils.simpleMerge(s, "E:\\ling.jpg", 45, 50, temp);

    }
}

4、測試結果

測試拿test1圖片做人臉測試,1.png做人眼測試。查看輸出結果

執行DetectFaceTest。查看結果。人臉檢測到3張。人眼檢測到一張。

圖片輸出

到這教程就結束了,在實際使用過程中,我發現人眼識別準確率很低。可能是我的參數有問題。大家可以多進行測試一下。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章