人臉識別(五)

源碼位置:https://github.com/comhaqs/face_find.git  分支: develop_libfacedetection

之前的人臉檢測使用的是opencv的人臉檢測功能,識別率低,基本無法使用。網上查找的時候有幾個庫,一個是MTCNN相關庫,使用的是鵝廠的ncnn,不過看issues裏是說ncnn只針對arm處理器做了優化,PC端效率低,所以就沒有測試。另一個是libfacedetection,純粹C++編寫,故使用了該庫,

bool face_recognition::train(unsigned char *p_data, int width, int height){
    try {
        if(!mp_model || !mp_cascade){
            return false;
        }
        cv::Mat bgr(cv::Size(width, height), CV_8UC3);
        bgr.data = p_data;
        // DETECT_BUFFER_SIZE爲0x20000這個不能改,libfacedetection固定死了
        static unsigned char * pBuffer = (unsigned char *)malloc(DETECT_BUFFER_SIZE);
        // 這裏跟opencv的不同,是使用非灰度圖像,不要嘗試傳灰度圖像,不然會識別錯誤
        auto pResults = facedetect_cnn(pBuffer, (unsigned char*)(bgr.ptr(0)), bgr.cols, bgr.rows, (int)bgr.step);
        if(nullptr != pResults){
            for(int i = 0; i < *pResults; ++i){
                short * p = ((short*)(pResults+1))+142*i;
                int x = p[0];
                int y = p[1];
                int w = p[2];
                int h = p[3];
                int confidence = p[4];
                // 這個角度暫時沒有使用到
                // int angle = p[5];
                if(90 > confidence){
                    continue;
                }
                cv::Rect r(x,y,w,h);
                bool flag_find_face = false;
                std::string name;
                // 複製出檢測出來的人臉,然後轉換成灰度圖片,不轉換會報錯
                cv::Mat desc;
                bgr(r).copyTo(desc);
                cv::Mat gray_desc;
                gray_desc.create(desc.size(), desc.type());
                cv::cvtColor(desc, gray_desc, cv::COLOR_BGR2GRAY);
                int index = -1;
                double condi = 0.0;
                // 實際總會返回一個檢測結果,置信度暫時不知道怎麼使用
                mp_model->predict(gray_desc, index, condi);
                if(0 > index){

                }else{
                    auto iter = m_index_to_name.find(index);
                    if(m_index_to_name.end() == iter){
                        LOG_WARN("找不到對應人臉信息;序號:"<<index);
                    }else{
                        //LOG_INFO("找到人臉;名稱:"<<iter->second<<"; 可信度:"<<condi<<"; 序號:"<<index);
                        name = (boost::format("%s[%d][%d]") % iter->second % static_cast<int>(condi) % confidence).str();
                        flag_find_face = true;
                    }
                }
                if(flag_find_face){
                    cv::rectangle(bgr, r, CV_RGB(0, 255, 0), 2);
                    cv::putText(bgr, name, Point(r.x + 0.5 * r.width, r.y - 5), cv::FONT_HERSHEY_COMPLEX_SMALL, 1, CV_RGB(0, 255, 0));
                }else{
                    cv::rectangle(bgr, r, CV_RGB(255, 0, 0), 2);
                }
            }
        }
    } catch(const std::exception& e){
        LOG_ERROR("發生錯誤:"<<e.what());
    }

    return true;
}

 

整個類代碼如下:

#include <opencv2/opencv.hpp>

#include <boost/filesystem.hpp>
#include <boost/format.hpp>
#include <libfacedetection/facedetectcnn.h>

using namespace cv;
using namespace cv::face;


#define DETECT_BUFFER_SIZE 0x20000

face_recognition::face_recognition()
{

}

void face_recognition::start(){
    try{
        Ptr<LBPHFaceRecognizer> p_model = LBPHFaceRecognizer::create();
        // 獲取訓練圖片集合
        std::vector<info_recognition_ptr> infos;
        if(!find_face_info(infos, "./face")){
            return;
        }
        std::vector<Mat> images;
        std::vector<int> labels;
        for(auto& p_info : infos){
            for(auto& f : p_info->files){
                // 這裏記得是IMREAD_GRAYSCALE,即灰度圖片,不然會報錯
                images.push_back(imread(f, cv::IMREAD_GRAYSCALE));
                labels.push_back(p_info->index);
            }
            m_index_to_name.insert(std::make_pair(p_info->index, p_info->name));
        }
        // 訓練模型
        p_model->train(images, labels);
        mp_model = p_model;

        // 加載opencv提供的人臉檢測模型,識別率比較低
        std::string face_file("./haarcascade_frontalface_alt2.xml");
        if(boost::filesystem::exists(face_file)){
            mp_cascade = std::make_shared<cv::CascadeClassifier>();
            mp_cascade->load(face_file);
        }else{
            LOG_ERROR("找不到對應的文件檢測模型文件:"<<face_file);
        }
    }catch(const std::exception& e){
        LOG_ERROR("發生錯誤:"<<e.what());
    }
}

bool face_recognition::train(unsigned char *p_data, int width, int height){
    try {
        if(!mp_model || !mp_cascade){
            return false;
        }
        cv::Mat bgr(cv::Size(width, height), CV_8UC3);
        bgr.data = p_data;
        // DETECT_BUFFER_SIZE爲0x20000這個不能改,libfacedetection固定死了
        static unsigned char * pBuffer = (unsigned char *)malloc(DETECT_BUFFER_SIZE);
        // 這裏跟opencv的不同,是使用非灰度圖像,不要嘗試傳灰度圖像,不然會識別錯誤
        auto pResults = facedetect_cnn(pBuffer, (unsigned char*)(bgr.ptr(0)), bgr.cols, bgr.rows, (int)bgr.step);
        if(nullptr != pResults){
            for(int i = 0; i < *pResults; ++i){
                short * p = ((short*)(pResults+1))+142*i;
                int x = p[0];
                int y = p[1];
                int w = p[2];
                int h = p[3];
                int confidence = p[4];
                // 這個角度暫時沒有使用到
                // int angle = p[5];
                if(90 > confidence){
                    continue;
                }
                cv::Rect r(x,y,w,h);
                bool flag_find_face = false;
                std::string name;
                // 複製出檢測出來的人臉,然後轉換成灰度圖片,不轉換會報錯
                cv::Mat desc;
                bgr(r).copyTo(desc);
                cv::Mat gray_desc;
                gray_desc.create(desc.size(), desc.type());
                cv::cvtColor(desc, gray_desc, cv::COLOR_BGR2GRAY);
                int index = -1;
                double condi = 0.0;
                // 實際總會返回一個檢測結果,置信度暫時不知道怎麼使用
                mp_model->predict(gray_desc, index, condi);
                if(0 > index){

                }else{
                    auto iter = m_index_to_name.find(index);
                    if(m_index_to_name.end() == iter){
                        LOG_WARN("找不到對應人臉信息;序號:"<<index);
                    }else{
                        //LOG_INFO("找到人臉;名稱:"<<iter->second<<"; 可信度:"<<condi<<"; 序號:"<<index);
                        name = (boost::format("%s[%d][%d]") % iter->second % static_cast<int>(condi) % confidence).str();
                        flag_find_face = true;
                    }
                }
                if(flag_find_face){
                    cv::rectangle(bgr, r, CV_RGB(0, 255, 0), 2);
                    cv::putText(bgr, name, Point(r.x + 0.5 * r.width, r.y - 5), cv::FONT_HERSHEY_COMPLEX_SMALL, 1, CV_RGB(0, 255, 0));
                }else{
                    cv::rectangle(bgr, r, CV_RGB(255, 0, 0), 2);
                }
            }
        }
    } catch(const std::exception& e){
        LOG_ERROR("發生錯誤:"<<e.what());
    }

    return true;
}


bool face_recognition::find_and_save_face(const std::string& folder_desc, const std::string& folder_src){
    std::vector<std::string> files;
    if(!find_file_from_folder(files, folder_src)){
        return false;
    }
    cv::CascadeClassifier cascade;
    cascade.load("./haarcascade_frontalface_alt2.xml");
    for(auto& p : files){
        cv::Mat src = cv::imread(p);
        if(nullptr == src.data){
            LOG_ERROR("文件無法正常讀取:"<<p);
            continue;
        }
        cv::Mat gray;
        gray.create(src.size(), src.type());
        cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);

        // 人臉檢測
        std::vector<cv::Rect> rect;
        cascade.detectMultiScale(gray, rect, 1.1, 3, 0);
        if(rect.empty()){
            LOG_WARN("沒有檢測到人臉:"<<p);
            continue;
        }
        int index = 0;
        for (auto& r : rect)
        {
            cv::Mat desc;
            src(r).copyTo(desc);
            imwrite((boost::format("%s/%d.png") % folder_desc % (index++)).str(), desc);
        }
    }
    return true;
}
bool face_recognition::find_face_info(std::vector<info_recognition_ptr>& infos, const std::string& folder){
    std::vector<std::string> all_folders;
    if(!find_folder_from_folder(all_folders, boost::filesystem::system_complete(folder).string())){
        return false;
    }
    int index = 1;
    for(auto& d : all_folders){
        std::vector<std::string> files;
        if (!find_file_from_folder(files, d)) {
            continue;
        }
        auto path = boost::filesystem::system_complete(d);
        auto parent_folder = path.parent_path().string();
        auto name = path.string().substr(parent_folder.size() + 1);
        auto p_info = std::make_shared<info_recognition>();
        p_info->index = index++;
        p_info->name = name;
        for(auto& f : files){
            p_info->files.push_back(f);
        }
        infos.push_back(p_info);
    }
    return true;
}

bool face_recognition::find_file_from_folder(std::vector<std::string>& files, const std::string& folder, const std::string& extend){
    boost::filesystem::path path_folder(folder);
    if(!boost::filesystem::exists(path_folder)){
        LOG_ERROR("文件夾路徑不存在:"<<folder);
        return false;
    }
    boost::filesystem::directory_iterator end;
    for(boost::filesystem::directory_iterator iter(path_folder); iter != end; ++iter){
        auto path = iter->path();
        if (boost::filesystem::is_directory(path)) {
            continue;
        }
        if(!extend.empty() && extend != path.extension()){
            continue;
        }
        files.push_back(path.string());
    }
    return true;
}

bool face_recognition::find_folder_from_folder(std::vector<std::string>& all_folders, const std::string& folder){
    boost::filesystem::path path_folder(folder);
    if(!boost::filesystem::exists(path_folder)){
        LOG_ERROR("文件夾路徑不存在:"<<folder);
        return false;
    }
    boost::filesystem::directory_iterator end;
    for(boost::filesystem::directory_iterator iter(path_folder); iter != end; ++iter){
        auto path = iter->path();
        if (!boost::filesystem::is_directory(path)) {
            continue;
        }
        if("." == path.string() || ".." == path.string()){
            continue;
        }
        all_folders.push_back(path.string());
    }
    return true;
}

 

libfacedetection庫的人臉檢測成功率還是非常高的,比較穩定,符合預期。從測試來看,使用單線程人臉檢測分辨率576*324的圖像平均耗時55毫秒,人臉識別模塊耗時25毫秒,總耗時90毫秒,無法流暢解析視頻流,故需跳幀檢測,或是多線程處理,或是提高效率。另外,人臉識別模塊的成功率非常爛,得找另外的庫代替。默認的opencv人臉模塊還是靠不住,得自己優化。

發佈了36 篇原創文章 · 獲贊 11 · 訪問量 20萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章