前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >OpenCV专题2 - 人脸检测+自动尺寸裁剪

OpenCV专题2 - 人脸检测+自动尺寸裁剪

作者头像
张风捷特烈
发布2020-04-30 13:57:09
2.3K1
发布2020-04-30 13:57:09
举报

俗话说:不基于需求的敲代码都是耍流氓


一、人脸检测:
1.准备材料

首先需要准备人脸的训练数据,这个在官方的Github可以下载到,这里用:lbpcascade_frontalface.xml 然后有请世界上,最傻最天真,最美丽,最善良的Girl登场:


2.Java/Kotlin层面

本想全用Kotlin写的,不过发现Kotlin竟然无法自动生成JNI函数... 但我又懒得找id,就混着用吧,使用TolyCV提供native方法。

代码语言:javascript
复制
---->[src/main/java/com/toly1994/toly_cv/TolyCV.java]----
public class TolyCV {
    public static native int faceDetector(Bitmap bitmap, Bitmap.Config argb8888, String path);
}

在Kotlin的Activity中,点击图片时使用faceDetector,让C++对图片进行操作 由于人脸识别需要xml的模型文件,这里通过copyCascadeFile将文件考到包里

代码语言:javascript
复制
---->[src/main/java/com/toly1994/toly_cv/MainActivity.kt]----
class MainActivity : AppCompatActivity() {

    private lateinit var mCascadeFile: File
    private lateinit var mFaceBitmap: Bitmap

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        copyCascadeFile(R.raw.lbpcascade_frontalface,"lbpcascade_frontalface.xml")
        iv_photo.setOnClickListener {
            mFaceBitmap = BitmapFactory.decodeResource(resources, R.mipmap.kqq2)
           val count= TolyCV.faceDetector(mFaceBitmap,Bitmap.Config.ARGB_8888, mCascadeFile.absolutePath)
            title="检测到$count 个人脸"
            iv_photo.setImageBitmap(mFaceBitmap)
        }
    }
    
    companion object {
        init {
            System.loadLibrary("toly_cv")
        }
    }

    private fun copyCascadeFile( id:Int,name:String) {
        try {
            val inputStream = resources.openRawResource(id)
            val cascadeDir = getDir("cascade", Context.MODE_PRIVATE)
            mCascadeFile = File(cascadeDir, name)
            if (mCascadeFile.exists()) return
            val os = FileOutputStream(mCascadeFile)
            val buffer = ByteArray(4096)
            var bytesRead: Int = inputStream.read(buffer)
            while (bytesRead != -1) {
                os.write(buffer, 0, bytesRead)
                bytesRead = inputStream.read(buffer)
            }
            inputStream.close()
            os.close()
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }
}
复制代码

3.C++层面使用OpenCV进行人脸识别

很多教程都把代码塞到JNI的cpp里,感觉看着太混乱,太难受了 根据单一职责原则,这里定义一个FaceDetector类专门用于识别传入的图片数组 并通过detectorFace方法进行识别后返回识别到的结果集,这样思路就清晰多了。

代码语言:javascript
复制
---->[src/main/cpp/FaceDetector.h]----
#include <android/bitmap.h>
#include <opencv2/opencv.hpp>
using namespace cv;

#include <vector>
using std::vector;//有分号

class FaceDetector{
public:
    //加载文件
    static void loadCascade(const char *filename);
    //识别矩阵,返回脸的矩形列表
    static vector<Rect> detectorFace(Mat &src);
};

cpp文件进行方法的实现,核心是CascadeClassifier#detectMultiScale方法

代码语言:javascript
复制
---->[src/main/cpp/FaceDetector.cpp]----
#include "FaceDetector.h"

CascadeClassifier cascadeClassifier;
//人脸检测
vector<Rect> FaceDetector::detectorFace(Mat &src) {
    vector<Rect> faces;//脸的数组
    Mat temp_mat;//用于存放识别到的图像临时矩阵
    cvtColor(src, temp_mat, COLOR_BGRA2GRAY);//灰度图,加快解析速度
    equalizeHist(temp_mat, temp_mat);//直方图均衡化
    //多尺度人脸检测
    cascadeClassifier.detectMultiScale(temp_mat, faces,  1.1,3,0, Size(300,300));
    return faces;
}

void FaceDetector::loadCascade(const char *filename) {
    cascadeClassifier.load(filename);
}

核心方法detectMultiScale介绍:

代码语言:javascript
复制
CV_WRAP void detectMultiScale( InputArray image,    图像
            CV_OUT std::vector<Rect>& objects,      人脸目标矩形集
            double scaleFactor = 1.1,               每次图像尺寸减小的比例
            int minNeighbors = 3,                   构成检测目标的相邻矩形的最小个数(默认为3个)
            int flags = 0,                          标识
            Size minSize = Size(),                  目标的最小尺寸
            Size maxSize = Size() );                目标的最大尺寸

4.C++层进行方形的绘制,标识人脸

其实上面已经识别出人脸,并到存到一个vector中。现在把它在图像上画出来

代码语言:javascript
复制
#include "FaceDetector.h"
extern "C"
JNIEXPORT jint JNICALL
Java_com_toly1994_toly_1cv_TolyCV_faceDetector(JNIEnv *env, jclass clazz, jobject bitmap,
                                               jobject argb8888, jstring path_) {

    const char *path = env->GetStringUTFChars(path_, 0);//文件路径
    FaceDetector::loadCascade(path);//加载文件

    Mat srcMat;//图片源矩阵
    bitmap2Mat(env, bitmap, &srcMat);//图片源矩阵初始化
    auto faces = FaceDetector::detectorFace(srcMat);//识别图片源矩阵,返回矩形集
    
    for (Rect faceRect : faces) {// 在人脸部分画矩形
        rectangle(srcMat, faceRect, Scalar(0, 253, 255), 5);//在srcMat上画矩形
        mat2Bitmap(env, srcMat, bitmap);// 把mat放回bitmap中
    }
    env->ReleaseStringUTFChars(path_, path);//释放指针
    return faces.size();//返回尺寸
}

根据不同的模型数据,可以检测到不同的部位,比如眼睛:haarcascade_eye.xml 检测也会出现误差,此时可以通过一些判断来筛选结果,比如先检测人脸,之外的部分可以过滤 或者根据两眼间距,计算出不可能的矩形,将其剔除,这也是图片识别比较好玩的地方


二、自动尺寸裁剪

现在需求是:根据一张照片(尺寸任意),截取人脸及周围,并裁成规定的尺寸,如两寸:413*626 就像这样:


1.Java/Kotlin层

新定义一个native方法faceDetectorResize方法进行执行该功能,返回一个处理过的图片

代码语言:javascript
复制
---->[src/main/java/com/toly1994/toly_cv/TolyCV.java]----
public class TolyCV {
    public static native int faceDetector(Bitmap bitmap, Bitmap.Config argb8888, String path);
    public static native Bitmap faceDetectorResize(Bitmap bitmap, Bitmap.Config argb8888 , String path,int width,int height);
}

---->[src/main/java/com/toly1994/toly_cv/MainActivity.kt]----
iv_photo.setOnClickListener {
    mFaceBitmap = BitmapFactory.decodeResource(resources, R.mipmap.kqq)
   val bitmap= TolyCV.faceDetectorResize(mFaceBitmap,Bitmap.Config.ARGB_8888,
       mCascadeFile.absolutePath,413,626)
    iv_photo.setImageBitmap(bitmap)
}

2.C++层

这里只针对一个人脸,多个人脸可以采取问题分化的思想。 首先要解决的是区域的问题:这个Rect是何许人也?如果你对一个对象有疑惑,debug是不二人选

代码语言:javascript
复制
extern "C"
JNIEXPORT jint JNICALL
Java_com_toly1994_toly_1cv_TolyCV_faceDetectorResize(
                JNIEnv *env, jclass clazz, jobject bitmap,
                jobject argb8888, jstring path_, jint width, jint height) {
                
    const char *path = env->GetStringUTFChars(path_, 0);//文件路径
    FaceDetector::loadCascade(path);//加载文件

    Mat srcMat;//图片源矩阵
    bitmap2Mat(env, bitmap, &srcMat);//图片源矩阵初始化
    auto faces = FaceDetector::detectorFace(srcMat);//识别图片源矩阵,返回矩形集

    Rect faceRect= faces[0];
    rectangle(srcMat, faceRect, Scalar(0, 253, 255), 5);//在srcMat上画矩形

    env->ReleaseStringUTFChars(path_, path);//释放指针
    return createBitmap(env,srcMat,argb8888);//返回图片
}

知道这些信息,就很容易构建目标区域(红色区域),剩下的就是裁切红色区域了

代码语言:javascript
复制
extern "C"
JNIEXPORT jint JNICALL
Java_com_toly1994_toly_1cv_TolyCV_faceDetectorResize(JNIEnv *env, jclass clazz, jobject bitmap,
                                                     jobject argb8888, jstring path_, jint width,
                                                     jint height) {
    const char *path = env->GetStringUTFChars(path_, 0);//文件路径
    FaceDetector::loadCascade(path);//加载文件

    Mat srcMat;//图片源矩阵
    bitmap2Mat(env, bitmap, &srcMat);//图片源矩阵初始化
    auto faces = FaceDetector::detectorFace(srcMat);//识别图片源矩阵,返回矩形集
    Rect faceRect= faces[0];
    rectangle(srcMat, faceRect, Scalar(0, 253, 255), 5);//在srcMat上画矩形
    //识别目标区域区域---------------------------
    Rect zone;
    int a= faceRect.width;//宽
    int b= faceRect.height;//高
    int offSetLeft=a/4;//x偏移
    int offSetTop=b*0.5;
    zone.x=faceRect.x-offSetLeft;
    zone.y=faceRect.y-offSetTop;
    zone.width= a/4 *2+a;
    zone.height=zone.width*(height*1.0/width);
    rectangle(srcMat, zone, Scalar(253, 95, 47), 5);//在srcMat上画矩形
    
    env->ReleaseStringUTFChars(path_, path);//释放指针
    return createBitmap(env,srcMat,argb8888);//返回图片
}

裁剪是非常简单的

代码语言:javascript
复制
createBitmap(env,srcMat(zone),argb8888);//返回图片

Mat类重载()运算符可以传入一个矩形,实现是通过构造生成一个新Mat 这样就完成了既定比例的裁切,并保证人脸始终在中上部。

代码语言:javascript
复制
---->[mat.hpp#Mat::operator()]----
/** @overload
@param roi Extracted submatrix specified as a rectangle.
*/
Mat operator()( const Rect& roi ) const;

---->[mat.inl.cpp#Mat::operator()]----
inline
Mat Mat::operator()( const Rect& roi ) const
{
    return Mat(*this, roi);
}

另外有一点需要注意:当矩形范围超出Mat,会报错,应该可以通过添白来处理,Mark一下


最后只剩重设尺寸了,注意把你画的矩形线给去掉,不然会输出到结果中

代码语言:javascript
复制
extern "C"
JNIEXPORT jobject JNICALL
Java_com_toly1994_toly_1cv_TolyCV_faceDetectorResize(JNIEnv *env, jclass clazz, jobject bitmap,
                                                     jobject argb8888, jstring path_, jint width,
                                                     jint height) {
    //英雄所见...
    env->ReleaseStringUTFChars(path_, path);//释放指针
    resize(srcMat(zone),srcMat,Size(width,height));//<----重定义尺寸
    return createBitmap(env,srcMat,argb8888);//返回图片
}

OK,打完收工,再也不怕妹子让我帮她设置图片尺寸了。 对于大批量,形形色色的人物照片,想要裁剪规整,一个for循环搞定,程序是绝佳劳动力。


这样你对OpenCV应该多了那么一丢丢感觉了吧,其实只是在调一调已有的方法

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019年10月09日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、人脸检测:
    • 1.准备材料
      • 2.Java/Kotlin层面
        • 3.C++层面使用OpenCV进行人脸识别
          • 4.C++层进行方形的绘制,标识人脸
            • 二、自动尺寸裁剪
              • 1.Java/Kotlin层
                • 2.C++层
                相关产品与服务
                人脸识别
                腾讯云神图·人脸识别(Face Recognition)基于腾讯优图强大的面部分析技术,提供包括人脸检测与分析、比对、搜索、验证、五官定位、活体检测等多种功能,为开发者和企业提供高性能高可用的人脸识别服务。 可应用于在线娱乐、在线身份认证等多种应用场景,充分满足各行业客户的人脸属性识别及用户身份确认等需求。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档