Java基于OpenCV检测实现人脸头像居中裁剪_java 人脸图片居中裁剪-程序员宅基地

技术标签: Java  

实现思路:利用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:目前程序只支持图片中有且仅有一个人脸的图片,对于图片中有多个人脸或者没有人脸的情况会存在一些问题。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/conconbenben/article/details/103494180

智能推荐

android 开发笔记 (黎活明)_android 开发 黎活明-程序员宅基地

文章浏览阅读928次。android 笔记 1.android UI布局方式: LinearLayout (线性布局)、AbsoluteLayout(绝对布局)、RelativeLayout(相对布局)、TableLayout(表格布局)、FrameLayout(帧布局) 2.android 拨号器: 2.1 manifest 配置拨号器权限: _android 开发 黎活明

【练习5.3】高斯平滑_调整调整4个参数的处理效果对比-程序员宅基地

文章浏览阅读356次。学习OpenCV》中文版第5章第3题提纲题目要求程序代码结果图片题目要求:a、设置param1=param2=9,依次将param3设为1和6对比b、设置param1=param2=0,依次将param3设为1和6对比c、设置param1=param2=0,但这时令param3=1,param4=9,处..._高斯平滑处理函数的参数

遇到跨域的问题(origin:* 多处使用,导致失效)_addallowedorigin多个-程序员宅基地

文章浏览阅读775次。在网关中使用了以下代码/** * 配置路由支持跨域 * * @return */ @Bean @Order(Integer.MAX_VALUE) public CorsFilter corsFilter() { final UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource_addallowedorigin多个

redis集群分布式锁的使用(Integration)_integration-redis-程序员宅基地

文章浏览阅读526次。基于Spring Integration使用分布式锁Spring Integration这个项目中已经实现了上面说的RedLock算法,开发中可以直接使用现成的分布式锁RedisLockRegistry 是实现对象,其obtain方法可以获得全局所对象;1.引入依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-b_integration-redis

mysql中select column_name from Information_schema.columns where table_Name = 'test'出现多个字段的问题_sqlquery error sqlcommand: select column_name from-程序员宅基地

文章浏览阅读1.4w次,点赞8次,收藏8次。在mysql中,通过sql查询某个表字段时,会出现一些这个表中没有的字段例:select column_name from Information_schema.columns where table_Name = 'test';因为这个表在其它库中也存在了,所以会出现多余的字段。解决办法:select column_name from Information_s_sqlquery error sqlcommand: select column_name from information_schema.column

java学习日志(二)_java学习日志200字-程序员宅基地

文章浏览阅读168次。1.编写程序:输出200以内所有奇数,并要求每行输出10个数。public class OddNumber{ public static void main(String[] args){ int g=0; for (int i=1;i<=200;i+=2){ System.out.print(i+"\t");g++; if (g%10==0) { Sys_java学习日志200字

随便推点

主线程和子线程的区别_进程主线程子线程-程序员宅基地

文章浏览阅读4.2k次。主线程和子线程的区别每个线程都有一个唯一标示符,来区分线程中的主次关系的说法。 线程唯一标示符:Thread.CurrentThread.ManagedThreadID;UI界面和Main函数均为主线程。被Thread包含的“方法体”或者“委托”均为子线程。委托可以包含多个方法体,利用this.Invoke去执行。也可以定义多种方法体,放在Thread里面去执行。则此方法体均为子线程。注意_进程主线程子线程

设计模式 4.Factory Method 模式_设计模式 factory method-程序员宅基地

文章浏览阅读157次。工厂模式是用模板模式来构建生成实例的工厂。Factou_设计模式 factory method

c语言程序编写高空坠球,初学python算法100例-案例19 球高空落地 弹跳N次后高度计算...-程序员宅基地

文章浏览阅读893次。题目:计算N次后高度一球从100米高度自由落下,每次落地后反跳回原高度的一半;再落下,求它在第10次落地时,共经过多少米?第10次反弹多高?解法1 列表程序分析通过列表将每次的高度以及反弹高度添加程序实现sum = []height = []hei = 100.0 # 起始高度for i in range(1, 11):# 从第二次开始,落地时的距离应该是反弹高度乘以2(弹到最高点再落下)if i..._c语言篮球从一定高度向下掉落,每一次弹起的高度

#SQL:函数依赖、范式、第一范式(1NF)、第二范式(2NF)、第三范式(3NF) @FDDLC_sql 函数依赖 范式-程序员宅基地

文章浏览阅读2.7k次。一、函数依赖与码若X->Y,即由X能确定Y,或者说一个已知的X能确定一个唯一的Y,则称Y依赖于Y(跟初中的函数定义一致)。一个学生只能属于一个学院,即知道学号X,就能确定对应的学院代码Y,所以学院代码Y依赖于学号X。1、部分函数依赖Y由X中的部分即能确定,比如(学号,姓名)->(系主任),显然,只需(学号,姓名)里的学号就能确定系主任!因此:(系主任)部分函数依赖于(学号,姓名)2、完全函数依赖(可对比部分函数依赖)Y由X中的全部属性确定,比如(学号,课程号)->某_sql 函数依赖 范式

【feign】SpringCloud OpenFeign Hystrix 统一异常处理及熔断机制_fegin 配置业务异常不进入fallback规则-程序员宅基地

文章浏览阅读5.6k次,点赞2次,收藏13次。文章目录问题`@FeignClient`加上`fallback`方法,并获取异常信息保留原始异常信息不进入熔断,直接抛出异常总结问题最近在项目开发中,使用 Feign 调用服务,当触发熔断机制时,遇到了以下问题:异常信息形如:TestService#addRecord(ParamVO) failed and no fallback available.;获取不到服务提供方抛出的原始异常信息;实现某些业务方法不进入熔断,直接往外抛出异常;接下来将一一解决上述问题。对于failed and n_fegin 配置业务异常不进入fallback规则

【转】【Flex】FLEX 学习网站分享-程序员宅基地

文章浏览阅读217次。【转:http://hi.baidu.com/tanghecaiyu/item/d662fbd7f5fbe02c38f6f764 】FLEX 学习网站分享http://blog.minidx.com/flex核心开发技术:http://blog.csdn.net/mervyn_lee/archive/2008/10/07/3027039.aspxfl部落:http://www.fltr...