OpenCV專題2 - 人臉檢測+自動尺寸裁剪

NDK系列文章:

俗話說:不基於需求的敲代碼都是耍流氓java


1、人臉檢測:

1.準備材料

首先須要準備人臉的訓練數據,這個在官方的Github能夠下載到,這裏用:lbpcascade_frontalface.xml
而後有請世界上,最傻最天真,最美麗,最善良的Girl登場:android


2.Java/Kotlin層面

本想全用Kotlin寫的,不過發現Kotlin居然沒法自動生成JNI函數...
但我又懶得找id,就混着用吧,使用TolyCV提供native方法。git

---->[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將文件考到包裏github

---->[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方法進行識別後返回識別到的結果集,這樣思路就清晰多了。數組

---->[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方法bash

---->[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介紹:微信

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中。如今把它在圖像上畫出來ide

#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
檢測也會出現偏差,此時能夠經過一些判斷來篩選結果,好比先檢測人臉,以外的部分能夠過濾
或者根據兩眼間距,計算出不可能的矩形,將其剔除,這也是圖片識別比較好玩的地方函數


2、自動尺寸裁剪

如今需求是:根據一張照片(尺寸任意),截取人臉及周圍,並裁成規定的尺寸,如兩寸:413*626 就像這樣:post


1.Java/Kotlin層

新定義一個native方法faceDetectorResize方法進行執行該功能,返回一個處理過的圖片

---->[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是不二人選

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);//返回圖片
}
複製代碼

知道這些信息,就很容易構建目標區域(紅色區域),剩下的就是裁切紅色區域了

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);//返回圖片
}
複製代碼

裁剪是很是簡單的

createBitmap(env,srcMat(zone),argb8888);//返回圖片
複製代碼

Mat類重載()運算符能夠傳入一個矩形,實現是經過構造生成一個新Mat
這樣就完成了既定比例的裁切,並保證人臉始終在中上部。

---->[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一下


最後只剩重設尺寸了,注意把你畫的矩形線給去掉,否則會輸出到結果中

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應該多了那麼一丟丟感受了吧,其實只是在調一調已有的方法
我是張風捷特烈,若是有什麼想要交流的,歡迎留言。也能夠加微信:zdl1994328

相關文章
相關標籤/搜索