MTCNN人臉檢測 附完整C++代碼


人臉檢測 識別一直是圖像算法領域一個主流話題。

前年 SeetaFace 開源了人臉識別引擎,一度成為熱門話題。

雖然后來SeetaFace 又放出來 2.0版本,但是,我說但是。。。

沒有訓練代碼,想要自己訓練一下模型那可就犯難了。

雖然可以閱讀源碼,從前向傳播的角度,反過來實現訓練代碼,

但是誰有那個閑功夫和時間,去折騰這個呢?

有的時候還是要站在巨人的肩膀上,你才能看得更遠。

而SeetaFace 不算巨人,只是當年風口上的豬罷了。

前年,為了做一個人臉項目,也是看遍了網上各種項目。

林林總總,各有優劣。

不多做評價,很多東西還是要具體實操,實戰才能見真知。

有一段時間,用SeetaFace的人臉檢測來做一些小的演示demo,

也花了一點小時間去優化它的算法。

不過很明顯我只是把他當成玩具看待。

畢竟不能自己訓練模型,這是很大的詬病。

直到后來深度學習大放異彩,印象最深刻莫過於MTCNN。

Joint Face Detection and Alignment using Multi-task Cascaded Convolutional Neural Networks

 相關資料見:https://github.com/kpzhang93/MTCNN_face_detection_alignment

大合照下,人臉圈出來很准確,壯觀了去,這是第一印象。

上圖,大家感受一下。

MTCNN的有三個網絡結構。

Stage1: Proposal Net

Stage2: Refine Net

 

Stage3: Output Net

 

具體算法思路就不展開了。

我對MTCNN感興趣的點在於,

MTCNN的思路可以拓展到各種物體檢測和識別方向。

也許唯一缺少的就是打標好的數據,

而標注五個點,足夠用於適配大多數物體了。

符合小而美的理念,這個是我比較推崇的。

所以MTCNN是一個很值得品味的算法。

github上也有不少MTCNN的實現和資源。

基於mxnet 基於caffe 基於ncnn 等等。。。

很明顯,mxnet 和  caffe 不符合小而美的理念。

果斷拋棄了。

ncnn有點肥大,不合我心。

所以,我動了殺氣。。

移除NCNN 與mtcnn無關的層,

梳理ncnn的一些邏輯代碼。

簡單做了一些適配和優化。

砍掉一些邊邊角角。

不依賴opencv等第三方庫。

編寫示例代碼完成后,還有不少工作要做,

不過第一步感覺已經符合我的小小預期。

完整示例代碼:

#include "mtcnn.h"
#include "browse.h"
#define USE_SHELL_OPEN
#ifndef  nullptr
#define nullptr 0
#endif
#if defined(_MSC_VER)
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h> 
#else
#include <unistd.h>
#endif
#define STB_IMAGE_STATIC
#define STB_IMAGE_IMPLEMENTATION

#include "stb_image.h"
//ref:https://github.com/nothings/stb/blob/master/stb_image.h
#define TJE_IMPLEMENTATION

#include "tiny_jpeg.h"
//ref:https://github.com/serge-rgb/TinyJPEG/blob/master/tiny_jpeg.h

#include <stdint.h>
#include "timing.h"

char saveFile[1024];

unsigned char *loadImage(const char *filename, int *Width, int *Height, int *Channels) {
    return stbi_load(filename, Width, Height, Channels, 0);
}

void saveImage(const char *filename, int Width, int Height, int Channels, unsigned char *Output) {
    memcpy(saveFile + strlen(saveFile), filename, strlen(filename));
    *(saveFile + strlen(saveFile) + 1) = 0;
    //保存為jpg
    if (!tje_encode_to_file(saveFile, Width, Height, Channels, true, Output)) {
        fprintf(stderr, "save JPEG fail.\n");
        return;
    }

#ifdef USE_SHELL_OPEN
    browse(saveFile);
#endif
}

void splitpath(const char *path, char *drv, char *dir, char *name, char *ext) {
    const char *end;
    const char *p;
    const char *s;
    if (path[0] && path[1] == ':') {
        if (drv) {
            *drv++ = *path++;
            *drv++ = *path++;
            *drv = '\0';
        }
    }
    else if (drv)
        *drv = '\0';
    for (end = path; *end && *end != ':';)
        end++;
    for (p = end; p > path && *--p != '\\' && *p != '/';)
        if (*p == '.') {
            end = p;
            break;
        }
    if (ext)
        for (s = end; (*ext = *s++);)
            ext++;
    for (p = end; p > path;)
        if (*--p == '\\' || *p == '/') {
            p++;
            break;
        }
    if (name) {
        for (s = p; s < end;)
            *name++ = *s++;
        *name = '\0';
    }
    if (dir) {
        for (s = path; s < p;)
            *dir++ = *s++;
        *dir = '\0';
    }
}

void getCurrentFilePath(const char *filePath, char *saveFile) {
    char drive[_MAX_DRIVE];
    char dir[_MAX_DIR];
    char fname[_MAX_FNAME];
    char ext[_MAX_EXT];
    splitpath(filePath, drive, dir, fname, ext);
    size_t n = strlen(filePath);
    memcpy(saveFile, filePath, n);
    char *cur_saveFile = saveFile + (n - strlen(ext));
    cur_saveFile[0] = '_';
    cur_saveFile[1] = 0;
}

void drawPoint(unsigned char *bits, int width, int depth, int x, int y, const uint8_t *color) {
    for (int i = 0; i < min(depth, 3); ++i) {
        bits[(y * width + x) * depth + i] = color[i];
    }
}

void drawLine(unsigned char *bits, int width, int depth, int startX, int startY, int endX, int endY,
    const uint8_t *col) {
    if (endX == startX) {
        if (startY > endY) {
            int a = startY;
            startY = endY;
            endY = a;
        }
        for (int y = startY; y <= endY; y++) {
            drawPoint(bits, width, depth, startX, y, col);
        }
    }
    else {
        float m = 1.0f * (endY - startY) / (endX - startX);
        int y = 0;
        if (startX > endX) {
            int a = startX;
            startX = endX;
            endX = a;
        }
        for (int x = startX; x <= endX; x++) {
            y = (int)(m * (x - startX) + startY);
            drawPoint(bits, width, depth, x, y, col);
        }
    }
}

void drawRectangle(unsigned char *bits, int width, int depth, int x1, int y1, int x2, int y2, const uint8_t *col) {
    drawLine(bits, width, depth, x1, y1, x2, y1, col);
    drawLine(bits, width, depth, x2, y1, x2, y2, col);
    drawLine(bits, width, depth, x2, y2, x1, y2, col);
    drawLine(bits, width, depth, x1, y2, x1, y1, col);
}

int main(int argc, char **argv) {
    printf("mtcnn face detection\n");
    printf("blog:http://cpuimage.cnblogs.com/\n");

    if (argc < 2) {
        printf("usage: %s  model_path image_file \n ", argv[0]);
        printf("eg: %s  ../models ../sample.jpg \n ", argv[0]);
        printf("press any key to exit. \n");
        getchar();
        return 0;
    }
    const char *model_path = argv[1];
    char *szfile = argv[2];
    getCurrentFilePath(szfile, saveFile);
    int Width = 0;
    int Height = 0;
    int Channels = 0;
    unsigned char *inputImage = loadImage(szfile, &Width, &Height, &Channels);
    if (inputImage == nullptr || Channels != 3) return -1;
    ncnn::Mat ncnn_img = ncnn::Mat::from_pixels(inputImage, ncnn::Mat::PIXEL_RGB, Width, Height);
    std::vector<Bbox> finalBbox;
    MTCNN mtcnn(model_path);
    double startTime = now();
    mtcnn.detect(ncnn_img, finalBbox);
    double nDetectTime = calcElapsed(startTime, now());
    printf("time: %d ms.\n ", (int)(nDetectTime * 1000));
    int num_box = finalBbox.size();
    printf("face num: %u \n", num_box);
    for (int i = 0; i < num_box; i++) {
        const uint8_t red[3] = { 255, 0, 0 };
        drawRectangle(inputImage, Width, Channels, finalBbox[i].x1, finalBbox[i].y1,
            finalBbox[i].x2,
            finalBbox[i].y2, red);
        const uint8_t blue[3] = { 0, 0, 255 };
        for (int num = 0; num < 5; num++) {
            drawPoint(inputImage, Width, Channels, (int)(finalBbox[i].ppoint[num] + 0.5f),
                (int)(finalBbox[i].ppoint[num + 5] + 0.5f), blue);
        }
    }
    saveImage("_done.jpg", Width, Height, Channels, inputImage);
    free(inputImage);
    printf("press any key to exit. \n");
    getchar();
    return 0;
}

 

效果圖來一個。

項目地址:

https://github.com/cpuimage/MTCNN

 

參數也很簡單,

mtcnn 模型文件路徑 圖片路徑

例如: mtcnn ../models ../sample.jpg

 

用cmake即可進行編譯示例代碼,詳情見CMakeLists.txt。

 

若有其他相關問題或者需求也可以郵件聯系俺探討。

郵箱地址是: 
gaozhihan@vip.qq.com

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM