人臉檢測?識別一直是圖像算法領域一個主流話題。
前年?SeetaFace?開源了人臉識別引擎,一度成為熱門話題。
雖然后來SeetaFace?又放出來 2.0版本,但是,我說但是。。。
沒有訓練代碼,想要自己訓練一下模型那可就犯難了。
雖然可以閱讀源碼,從前向傳播的角度,反過來實現訓練代碼,
但是誰有那個閑功夫和時間,去折騰這個呢?
你還在為沒有學習平臺而苦惱嗎?你還在為沒有學習資料而煩心嗎?你還在為沒人指導而擔憂嗎?可以私信小編 C++或者 加群 710520381 驗證靈狐,為你提供學習的平臺和資料。
有的時候還是要站在巨人的肩膀上,你才能看得更遠。
而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等第三方庫。
編寫示例代碼完成后,還有不少工作要做,
不過第一步感覺已經符合我的小小預期。
你還在為沒有學習平臺而苦惱嗎?你還在為沒有學習資料而煩心嗎?你還在為沒人指導而擔憂嗎?可以私信小編 C++或者 加群 710520381 驗證靈狐,為你提供學習的平臺和資料。
完整示例代碼:
#include"mtcnn.h"#include "browse.h"#defineUSE_SHELL_OPEN#ifndef? nullptr#definenullptr 0#endif#ifdefined(_MSC_VER)#define_CRT_SECURE_NO_WARNINGS#include #else#include #endif#defineSTB_IMAGE_STATIC#defineSTB_IMAGE_IMPLEMENTATION#include "stb_image.h"http://ref:https://github.com/nothings/stb/blob/master/stb_image.h#defineTJE_IMPLEMENTATION#include "tiny_jpeg.h"http://ref:https://github.com/serge-rgb/TinyJPEG/blob/master/tiny_jpeg.h#include #include "timing.h"charsaveFile[1024];
unsigned char*loadImage(constchar*filename,int*Width,int*Height,int*Channels) {
? ? returnstbi_load(filename, Width, Height, Channels,0);
}voidsaveImage(constchar*filename,intWidth,intHeight,intChannels, unsignedchar*Output) {
? ? memcpy(saveFile + strlen(saveFile), filename, strlen(filename));
? ? *(saveFile + strlen(saveFile) +1) =0;
? ? //保存為jpgif(!tje_encode_to_file(saveFile, Width, Height, Channels,true, Output)) {
? ? ? ? fprintf(stderr, "save JPEG fail.\n");
? ? ? ? return;
? ? }
#ifdef USE_SHELL_OPEN
? ? browse(saveFile);#endif}voidsplitpath(constchar*path,char*drv,char*dir,char*name,char*ext) {
? ? constchar*end;
? ? constchar*p;
? ? constchar*s;
? ? if(path[0] && path[1] ==':') {
? ? ? ? if (drv) {
? ? ? ? ? ? *drv++ = *path++;
? ? ? ? ? ? *drv++ = *path++;
? ? ? ? ? ? *drv ='\0';
? ? ? ? }
? ? }
? ? elseif (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';
? ? }
}voidgetCurrentFilePath(constchar*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;
}voiddrawPoint(unsignedchar*bits,intwidth,intdepth,intx,inty,constuint8_t *color) {
? ? for(inti =0; i < min(depth,3); ++i) {
? ? ? ? bits[(y * width + x) * depth + i] = color[i];
? ? }
}voiddrawLine(unsignedchar*bits,intwidth,intdepth,intstartX,intstartY,intendX,int endY,
? ? constuint8_t *col) {
? ? if(endX == startX) {
? ? ? ? if(startY > endY) {
? ? ? ? ? ? inta = startY;
? ? ? ? ? ? startY = endY;
? ? ? ? ? ? endY = a;
? ? ? ? }
? ? ? ? for(inty = startY; y <= endY; y++) {
? ? ? ? ? ? drawPoint(bits, width, depth, startX, y, col);
? ? ? ? }
? ? }
? ? else {
? ? ? ? floatm =1.0f* (endY - startY) / (endX - startX);
? ? ? ? inty =0;
? ? ? ? if(startX > endX) {
? ? ? ? ? ? inta = startX;
? ? ? ? ? ? startX = endX;
? ? ? ? ? ? endX = a;
? ? ? ? }
? ? ? ? for(intx = startX; x <= endX; x++) {
? ? ? ? ? ? y = (int)(m * (x - startX) + startY);
? ? ? ? ? ? drawPoint(bits, width, depth, x, y, col);
? ? ? ? }
? ? }
}voiddrawRectangle(unsignedchar*bits,intwidth,intdepth,intx1,inty1,intx2,inty2,constuint8_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);
}intmain(intargc,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();
? ? ? ? return0;
? ? }
? ? constchar*model_path = argv[1];
? ? char*szfile = argv[2];
? ? getCurrentFilePath(szfile, saveFile);
? ? intWidth =0;
? ? intHeight =0;
? ? intChannels =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 finalBbox;
? ? MTCNN mtcnn(model_path);
? ? doublestartTime = now();
? ? mtcnn.detect(ncnn_img, finalBbox);
? ? doublenDetectTime = calcElapsed(startTime, now());
? ? printf("time: %d ms.\n ", (int)(nDetectTime *1000));
? ? intnum_box = finalBbox.size();
? ? printf("face num: %u \n", num_box);
? ? for(inti =0; i < num_box; i++) {
? ? ? ? constuint8_t red[3] = {255,0,0 };
? ? ? ? drawRectangle(inputImage, Width, Channels, finalBbox[i].x1, finalBbox[i].y1,
? ? ? ? ? ? finalBbox[i].x2,
? ? ? ? ? ? finalBbox[i].y2, red);
? ? ? ? constuint8_t blue[3] = {0,0,255 };
? ? ? ? for(intnum =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();
? ? return0;
}
效果圖來一個。