MTCNN人脸检测的详细介绍及完整C++代码你能学会吗?

人脸检测识别一直是图像算法领域一个主流话题。
前年seetaface开源了人脸识别引擎,一度成为热门话题。
虽然后来seetaface又放出来 2.0版本,但是,我说但是。。。
没有训练代码,想要自己训练一下模型那可就犯难了。
虽然可以阅读源码,从前向传播的角度,反过来实现训练代码,
但是谁有那个闲功夫和时间,去折腾这个呢?
有的时候还是要站在巨人的肩膀上,你才能看得更远。
而seetaface不算巨人,只是当年风口上的猪罢了。
前年,为了做一个人脸项目,也是看遍了网上各种项目。
林林总总,各有优劣。
不多做评价,很多东西还是要具体实操,实战才能见真知。
有一段时间,用seetaface的人脸检测来做一些小的演示demo,
也花了一点小时间去优化它的算法。
不过很明显我只是把他当成玩具看待。
毕竟不能自己训练模型,这是很大的诟病。
直到后来深度学习大放异彩,印象最深刻莫过于mtcnn。
joint face detection and alignment using multi-task cascaded convolutional neural networks
大合照下,人脸圈出来很准确,壮观了去,这是第一印象。
上图,大家感受一下。
cnn的有三个网络结构。
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 #else#include#endif#define stb_image_static#define stb_image_implementation#includestb_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 #include timing.hchar savefile[1024];unsignedchar *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.); 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 = ''; } } else if (drv) *drv = ''; 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 = ''; } if (dir) { for (s = path; s < p;) *dir++ = *s++; *dir = ''; } }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 endy) { int a = starty; starty = endy; endy = a; } for (int y = starty; y 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); printf(blog:http://cpuimage.cnblogs.com/); if (argc < 2) { printf(usage: %s model_path image_file , argv[0]); printf(eg: %s ../models ../sample.jpg , argv[0]); printf(press any key to exit. ); getchar(); return 0; } const char *model_path = argv[1]; char *s***ile = argv[2]; getcurrentfilepath(s***ile, savefile); int width = 0; int height = 0; int channels = 0; unsigned char *inputimage = loadimage(s***ile, &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); double starttime = now(); mtcnn.detect(ncnn_img, finalbbox); double ndetecttime = calcelapsed(starttime, now()); printf(time: %d ms. , (int)(ndetecttime * 1000)); int num_box = finalbbox.size(); printf(face num: %u , 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. ); getchar(); return 0; }
效果图来一个。

对于苹果手机的屏下指纹功能你期待吗
PAC平台帮助石油天然气行业应对各种挑战
OLED电视或迎来普及时代
AirPods Pro正式发布支持降噪和嘿Siri等功能
即将改变家庭网络的3项无线技术
MTCNN人脸检测的详细介绍及完整C++代码你能学会吗?
温度传感器的特性与分类以及结构详解
资深玩家教您看笔记本键盘
特斯拉model3怎么样?特斯拉Model 3驾驶体验评测:车顶全玻璃 低价可定制 运动模式操作方便
有源晶振的EMC方面的设计考虑
基于功率IC LM12的80W功率音频放大器电路图
LoRa Basics 技术分享
电荷泵双极电源的PCB布局是什么样子的
MRAM正在研发支持先进网络技术的下一代嵌入式设备
2021年首场DevRun智能基座昇腾鲲鹏高校行活动圆满结束
低电压差分信号传输(LVDS)在汽车电子中的应用
汽车电子设计的2020年写作小结
神级快充荣耀Magic:30分钟92%的充电速率堪称黑科技!
关于尼得科研发出新型电动助力转向系统电机电源组的通知
FPGA定点小数的常规格式、相对于浮点小数的优势与劣势