Java基於OpenCV檢測實現人臉頭像居中裁剪

實現思路:利用OpenCV檢測圖片中人臉的位置;根據人臉圖像在圖片的位置,實現居中裁剪。

一、OpenCV下載

下載網站:https://opencv.org/releases/

官網下載比較慢,可以使用IDM工具:

IDM工具下載鏈接:https://pan.baidu.com/s/1sAEJowbEfqwuV5mNtyVGDg  提取碼:p4lv 

本文下載windows版本庫。

下載安裝後點擊安裝文件,進行安裝。

如:

二、Java集成OpenCV

安裝後,可以在opencv安裝目錄找到對應文件,引入java工程。

1.引入dll庫

dll文件目錄:opencv\build\java\x64\opencv_java412.dll

2.jar包

jar目錄:opencv\build\java\opencv-412.jar

3.訓練好的分類器文件

分類器文件目錄:opencv\build\etc\haarcascades\haarcascade_frontalface_alt.xml

引入到java工程的目錄可參考:

在工程的路徑不是固定的,確保在代碼中能加載到即可。

三、檢測人臉位置步驟

步驟1.引入opencv 庫 Window引入dll 絕對路徑
步驟2.引入訓練好的XML格式的分類器文件
步驟3.讀取待處理的圖片
步驟4.進行人臉檢測。

主要代碼:

        // 1.引入opencv 庫 Window引入dll 絕對路徑
        System.load(OPENCV_DLL_PATH);

        // 2.引入訓練好的XML格式的分類器文件
        CascadeClassifier faceDetector = new CascadeClassifier(OPENCV_XML_PATH);
        if (faceDetector.empty()) {
            System.err.println("please import cascade classifier file");
            return null;
        }

        // 3.讀取待處理的圖片
        File imgFile = new File(imageFilePath);
        Mat image = Imgcodecs.imread(imgFile.getPath());


        // 4.進行人臉檢測
        MatOfRect faceDetections = new MatOfRect();
        faceDetector.detectMultiScale(image, faceDetections);

四、人臉頭像居中裁剪

思路:以檢測的人臉框爲中心Rect(x,y,w,h)放大。其中:x爲裁剪矩形的x座標,y爲裁剪矩形的y座標,w爲裁剪矩形的寬,h爲裁剪矩形的高。

記:原圖的寬爲W 高爲H,寬度單側增加:△W,則等比例放大後,單側高度增加:△W * h / w

因爲放大的圖像邊界不能超過原圖的四個邊界,所以:

向左:△W <= x  ;

向上:△W * h / w <=y ; 

向右:x+w+ △W<= W; 

向下:y+h+△W * h / w <=H

由上述四個條件,可以得到△W最大值爲:x  ; W-x-w;  y*w/h;(H-h-y)*w/h 中的最小者。

故居中裁剪的矩形框爲:Rect(x-△W,y-△W * h / w,w+2*△W,h+2*△W * h / w)。

代碼:

/**
     * 計算居中裁剪框
     * 在原圖邊界範圍內,以檢測的人臉框爲中心,向四周等比例放大,最大的裁剪框可保證人臉居中
     *
     * @param srcWidth  原始圖像寬度
     * @param srcHeight 原始圖像高度
     * @param rect      人臉框位置
     * @return 居中裁剪框
     */
    private static Rect estimateCenterCropBox(int srcWidth, int srcHeight, Rect rect) {
        System.out.println("estimateCenterCropBox ... ");
        int w0 = rect.x;
        int w1 = srcWidth - rect.x - rect.width;
        int w2 = (srcHeight - rect.y - rect.height) * rect.width / rect.height;
        int w3 = rect.width * rect.y / rect.height;

        if (w0 < 0 || w1 < 0 || w2 < 0 || w3 < 0) {
            return null;
        }
        int ret = w0;
        if (ret > w1) {
            ret = w1;
        }
        if (ret > w2) {
            ret = w2;
        }
        if (ret > w3) {
            ret = w3;
        }
        Rect centerRect = new Rect(rect.x - ret, rect.y - ret * rect.height / rect.width,
                rect.width + 2 * ret, rect.height + 2 * ret * rect.height / rect.width);

        return centerRect;
    }

五、完整代碼

package com.yx.test.image;

import org.opencv.core.Mat;
import org.opencv.core.MatOfRect;
import org.opencv.core.Rect;
import org.opencv.core.Size;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;

import java.io.File;
import java.io.IOException;
import java.util.Iterator;

import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream;

/**
 * ImageUtil
 *
 * @author yx
 * @date 2019/12/11 21:14
 */
public class ImageUtil {
    /**
     * dll庫路徑,絕對路徑,否則會提示報錯:Exception in thread "main" java.lang.UnsatisfiedLinkError: Expecting an absolute path of the library: libs\opencv_java412.dll
     */
    private static final String OPENCV_DLL_PATH =
            "libs\\opencv_java412.dll";
    /**
     * xml訓練文件
     */
    private static final String OPENCV_XML_PATH = "file/haarcascade_frontalface_alt.xml";

    public static void main(String[] args) throws IOException {
        detectFaceImage("file/333.jpg", "file/");
    }

    /**
     * @param imageFilePath 待處理圖片路徑
     * @param destDir       提前人臉後居中裁剪後的圖片存儲目錄
     * @return 居中裁剪的圖片路徑
     */
    private static String[] detectFaceImage(String imageFilePath, String destDir) {
        if (!isPicture(imageFilePath)) {
            System.err.println("imageFilePath: " + imageFilePath + " is not a image!");
            return null;
        }
        File dir = new File(destDir);
        if (!dir.exists()) {
            if (!dir.mkdirs()) {
                System.err.println("mkdir " + destDir + " failed");
                return null;
            }
        } else {
            if (dir.isFile()) {
                System.err.println("destDir is not dir");
                return null;
            }
        }

        System.out.println("start face detecting ...");
        // 1.引入opencv 庫 Window引入dll 絕對路徑
        System.out.println("load dll file ...");
        System.load(OPENCV_DLL_PATH);

        // 2.引入訓練好的XML格式的分類器文件
        System.out.println("load xml file ...");
        CascadeClassifier faceDetector = new CascadeClassifier(OPENCV_XML_PATH);
        if (faceDetector.empty()) {
            System.err.println("please import cascade classifier file");
            return null;
        }

        // 3.讀取待處理的圖片
        File imgFile = new File(imageFilePath);
        Mat image = Imgcodecs.imread(imgFile.getPath());

        int srcWidth = image.width();
        int srcHeight = image.height();

        // 4.進行人臉檢測
        MatOfRect faceDetections = new MatOfRect();
        faceDetector.detectMultiScale(image, faceDetections);
        Rect[] rectFace = faceDetections.toArray();
        System.out.println(String.format("face detected count: %s", rectFace.length));

        String[] targetFiles = new String[rectFace.length];
        // 5.裁剪檢測到的人臉圖片
        for (int i = 0; i < rectFace.length; i++) {
            Rect rect = rectFace[i];
            System.out.println(
                    "rect[" + i + "]=[" + rect.x + "," + rect.y + "," + rect.width + "," +
                            rect.height + "]");
            // 計算居中裁剪框
            Rect centerCropBox = estimateCenterCropBox(srcWidth, srcHeight, rect);
            // 裁剪框
            System.out.println(
                    "rect[" + i + "]=[" + centerCropBox.x + "," +
                            centerCropBox.y + "," +
                            centerCropBox.width + "," +
                            centerCropBox.height + "]");
            targetFiles[i] = destDir + File.separator + i + getImageType(imgFile);
            // 裁剪
            cutCenterImage(imgFile.getPath(), targetFiles[i], centerCropBox);
        }
        return targetFiles;
    }

    /**
     * @param filePath 文件路徑
     * @return 是否是圖片
     */
    public static boolean isPicture(String filePath) {
        File file = new File(filePath);
        if (file.exists() && file.isFile()) {
            ImageInputStream iis = null;
            try {
                iis = ImageIO.createImageInputStream(file);
            } catch (IOException e) {
                return false;
            }
            Iterator iter = ImageIO.getImageReaders(iis);
            if (iter.hasNext()) {
                return true;
            }
        }
        return false;
    }

    /**
     * 獲取文件後綴不帶.
     *
     * @param file 文件後綴名
     * @return
     */
    private static String getImageType(File file) {
        if (file != null && file.exists() && file.isFile()) {
            String fileName = file.getName();
            int index = fileName.lastIndexOf(".");
            if (index != -1 && index < fileName.length() - 1) {
                return fileName.substring(index);
            }
        }
        return null;
    }

    /**
     * 計算居中裁剪框
     * 在原圖邊界範圍內,以檢測的人臉框爲中心,向四周等比例放大,最大的裁剪框可保證人臉居中
     *
     * @param srcWidth  原始圖像寬度
     * @param srcHeight 原始圖像高度
     * @param rect      人臉框位置
     * @return 居中裁剪框
     */
    private static Rect estimateCenterCropBox(int srcWidth, int srcHeight, Rect rect) {
        System.out.println("estimateCenterCropBox ... ");
        int w0 = rect.x;
        int w1 = srcWidth - rect.x - rect.width;
        int w2 = (srcHeight - rect.y - rect.height) * rect.width / rect.height;
        int w3 = rect.width * rect.y / rect.height;

        if (w0 < 0 || w1 < 0 || w2 < 0 || w3 < 0) {
            return null;
        }
        int ret = w0;
        if (ret > w1) {
            ret = w1;
        }
        if (ret > w2) {
            ret = w2;
        }
        if (ret > w3) {
            ret = w3;
        }

        return new Rect(rect.x - ret, rect.y - ret * rect.height / rect.width,
                rect.width + 2 * ret, rect.height + 2 * ret * rect.height / rect.width);
    }

    /**
     * @param oriImg  原始圖像
     * @param outFile 裁剪的圖像輸出路徑
     * @param rect    剪輯矩形區域
     */
    private static void cutCenterImage(String oriImg, String outFile, Rect rect) {
        System.out.println("cutCenterImage ...");
        // 原始圖像
        Mat image = Imgcodecs.imread(oriImg);
        // Mat sub = new Mat(image,rect);
        Mat sub = image.submat(rect);
        Mat mat = new Mat();
        Size size = new Size(rect.width, rect.height);
        // 將人臉進行截圖
        Imgproc.resize(sub, mat, size);
        // 截圖保存到outFile
        Imgcodecs.imwrite(outFile, mat);
        System.out.println(String.format("cutCenterImage done! outPutFile: %s", outFile));
    }

}

六.運行效果

原圖:

人臉居中裁剪:

實現了將原圖中在中上位置的人臉裁剪到圖片中間。

ps:目前程序只支持圖片中有且僅有一個人臉的圖片,對於圖片中有多個人臉或者沒有人臉的情況會存在一些問題。

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