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。
若有其他相關問題或者需求也可以郵件聯系俺探討。
郵箱地址是:
[email protected]
MTCNN人臉檢測 附完整C++代碼