1. 程式人生 > >MTCNN人臉檢測 附完整C++代碼

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

若有 文件 nta return 版本 list warnings rtt task

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

前年 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。

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

郵箱地址是:
[email protected]

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