0%

TITLE: CSRNet: Dilated Convolutional Neural Networks for Understanding the Highly Congested Scenes

AUTHOR: Yuhong Li, Xiaofan Zhang, Deming Chen

ASSOCIATION: University of Illinois at Urbana-Champaign, Beijing University of Posts and Telecommunications

FROM: arXiv:1802.10062

CONTRIBUTION

  1. a pure fully convolutional network (CSRNet) is proposed for understanding the highly congested scenes.
  2. CSRNet achieves the state-of-the-art performance on four datasets (ShanghaiTech dataset, the UCF CC 50 dataset, the WorldEXPO’10 dataset, and the UCSD dataset).

METHOD

CSRNet Architecture

The proposed CSRNet is composed of two major components:

  1. a convolutional neural network (CNN) is utilized as the front-end for 2D feature extraction.
  2. a dilated CNN follows the front-end for the back-end, which uses dilated kernels to deliver larger reception fields and to replace pooling operations.

In this work, the front-end CNN is same as the first ten layers of VGG-16 with three pooling layers, considering the tradeoff between acuracy and the resource overhead. The back-end CNN is a series of dilated convolutional layers and the last layer is a $ 1 \times 1 $ convolutional layer producing density map. The network architecture is shown in the following figure

Architecture

Training

The ground truth is generated using geometry-adaptive kernels to tackle the highly congested scenes. By blurring each head annotation using a Gaussian kernel (which is normalized to 1), the ground truth is generated considering the spatial distribution of all images from each dataset.

9 patches are cropped from each image at different locations with 1/4 size of the original image. The first four patches contain four quarters of the image without overlapping while the other five patches are randomly cropped from the input image. After that, double the training set by mirroring the patches.

PERFORMANCE

Architecture Comparison

Performance Shanghai

Performance UCF

Performance WorldExpo

Performance UCSD

SOME THOUGHTs

  1. How about using more advanced CNNs?

TITLE: CornerNet: Detecting Objects as Paired Keypoints

AUTHOR: Hei Law, Jia Deng

ASSOCIATION: University of Michigan

FROM: arXiv:1808.01244

CONTRIBUTION

  1. CornerNet, a new one-stage approach to object detection is introduced that does without anchor boxes. We detect an object as a pair of
  2. a new type of pooling layer, coner pooling, that helps a convolutional network better localize corners of bounding boxes is introduced.

METHOD

Motivation

The use of anchor boxes has two drawbacks.

First, a very large set of anchor boxes are of need, e.g. more than 40k in DSSD and more than 100k in RetinaNet. This is because the detector is trained to classify whether each anchor box sufficiently overlaps with a ground truth box, and a large number of anchor boxes is needed to ensure sufficient overlap with most ground truth boxes, leading to huge imbalance between positive and negative anchor boxes.

Second, the use of anchor boxes introduces many hyperparameters and design choices. These include how many boxes, what sizes, and what aspect ratios. Such choices have largely been made via ad-hoc heuristics, and can become even more complicated when combined with multiscale architectures.

DetNet Design

In CornerNet, an object is detected as a pair of keypoints, the top-left corner and bottom-right corner of the bounding box. A convolutional network predicts two sets of heatmaps to represent the locations of corners of different object categories, one set for the top-left corners and the other for the bottom-right corners. The network also predicts an embedding vector for each detected corner such that the distance between the embeddings of two corners from the same object is small. To produce tighter bounding boxes, the network also predicts offsets to slightly adjust the locations of the corners. The framework is illustrated in the following figure.

Framework

To generate heatmaps, embeddings and offsets, the following network structure is utilized. The heatmaps’ goal is detecting corners. The embeddings are used to group corners that belong to the same object. Conner pooling is used in the network to extract features for corner detection.

Framework

The annotation of heatmaps is generated in a way that similar to that in detection joints in pose estimation methods. Embeddings are generated within pairs that generated from corners. The offsets are regressed from the difference between actual division of coordinates and downsampling ratio and the floored division.

The backbone of the network is two hourglasses.

PERFORMANCE

Performance

SOME THOUGHTs

  1. How to select numbers of hourglasses?
  2. The speed of the method is slow. The average inference time is 244ms per image on a Titan X.
  3. The key of the method is detecting corners and more work can be done in this task.

最近刚刚搬了家,突然有一些不一样的感受,最深刻的感受是跟过去的一次告别。

首先,就是因为懒。要收拾的东西太多,有些东西仔细想想好像一年也没怎么用过,甚至自从上一次搬家就没用过,它就一直在某个角落落灰。到了这次搬家,看着它脏脏的样子,掂量掂量还有点分量,再一想那边已经堆了十个收纳箱了,这东西就算了吧,遂直接留给房东,或者直接丢进垃圾箱。

等东西都搬到了新屋子,发现户型、家具都不一样,以前摆在外面的陈设要么跟房间不搭配、要么没有地方放,就收在收纳箱里。这些陈设可能是我们珍视的合影、得奖的奖状、前公司的工牌……那些定义了我们曾经是谁的东西都被收藏了起来。在搬家之前,它们潜移默化地提醒着我们曾经的经历,但是现在却会慢慢被遗忘了。

搬了新家,自然还要制备一些新事物,这可是体现我们喜新厌旧本性的时候。新买的肯定会得到更多的宠爱,而那些颜老色衰的就被慢慢冷落了。等下次再搬家的时候,估计它们就是那些被留下的一批了。

Rencently I’ve been trying to update my Ubuntu16.04 using apt and it failed again and again. At first, I thought it was because of the GFW and the sources. It turned out that any effort on these settings was in vain.

At last, after analyzing the update logs and digging into my deepest memories, I found that it was because I installed Nvidia JetPack to use Jetson TX1/2. I had to remove a pile of things related to JetPack by following commands.

1
2
apt-get remove .*:arm64
dpkg --remove-architecture arm64

写在30岁之前

人还是需要一些仪式感的,自从2017年7月份开始出来创业,自己的仪式感越来越弱,很多事情能省就省。可能过了刚刚出来日日担心饭辙的阶段,也可能是被折磨得麻木了,现在倒觉得人活着还是要开心一些想开一些,该有的仪式感还是要有,不然这日子过得挺没意思。因此,为了迎接自己的30岁,在这里胡乱絮叨絮叨。

除了仪式感,其实本来早就想写一写近期以来的感受,尤其是创业以来,随着角色的转变,不管是主动的还是被迫的,内心里有很多感触。有些时候,总不知道这些感受该对谁说。可能跟谁说,谁都会觉得我的感触都是一种矫情,写出来也权当一种发泄。

2007-2011

想了想,既然是写在30岁之前,除了这一年半的创业,还不如再往前追溯追溯,从高考开始写吧。都过去十年了,现在还能想起来的那些事,估计就是我人生中最重要的一些标记了。

高考

当时老师家长为了缓解大家的压力:人生今后遇到的坎多了,回过头来看高考真不算什么。不知道十年后有多少人真的是这样的感受,我不敢说高考真不算什么,起码它是我到现在的人生轨迹的起点。另外,这也真是一个坎。

高考前的那两个月一直很凉爽,经常下雨,不知道是不是就是因为贪凉,我的咳嗽一直没好。高考前第二天下了大雨,高考前一天放晴,天气突然很热。下午我在自习室里感觉身上很难受,忽冷忽热的,就去医务室量了体温,39度的高烧。也不知道是谁帮我找的老师,谁给我父母打了电话,印象中傍晚的时候家里开车来接我。临上车,我的语文老师对我说:志轩,没事!到了医院,大夫说是肺炎,不记得是不是说让我住院了,我说明天我要高考。在医院输了两个小时液,回家。现在已经完全不记得当晚是什么感受了。

高考第一天上午考语文,下午考数学。考语文的时候很虚弱,全身都没有力气,有一种世界离我而去的感觉,视野也在变窄。最后写作文,作文题目里有一首诗,完全没有读懂那首诗是什么意思,硬着头皮写。那感觉就好像在潜水,肺一直被水压着,呼吸困难。支持着我写完的就一个念想:考不上我认了,但是我绝不能因为没考完而去复读。下午考数学之前又开始发烧,去医务室打退烧针,大夫跟我说一定要多喝水,因为退烧需要出大量的汗来散热,很容易因为脱水而虚脱。下午我就是在湿透的衬衫下进行考试,估计也是因为太虚弱了,也没有什么胡思乱想,很专心的答题。晚上回去接着发烧,折腾半宿。

高考第二天上午考理综,下午考英语。身体状态和第一天差不多,心理状态倒好了很多,估计就是破罐破摔了。考试这两天几乎一点东西都没吃,吃了也吐出来。考完试,二话没说直接就去医院住院,高烧不退三四天吧。后来看拍的胸片,右边肺全部感染,左边肺一小半感染。我是直接略过了别人考完忐忑等成绩的那几天,住院半个月,直到出分去学校领成绩单。成绩也算反映了我的状态,语文考得是一塌糊涂,数学因为心态平稳考了个一般分,理综创纪录地考了一个历史最高,英语向来是我的长项,但是那一年题太简单,完全没有拉开分数段。有惊无险地考到了北邮,当时北京是考前报志愿,很保守地报了北邮,估计也是因为这样才能心态平稳地发挥。

学生会

在北邮的本科四年,印象最为深刻的应该就是学生会。在学生会的经历重塑了我的性格,在学生会结识的朋友是我一生的挚友。还记得那一个个奋斗的夜晚、跟春晚一样长的例会、北门的砂锅聚餐……

从小我其实是一个很内向而且自卑的人,也不爱说话。小时候见到父母的同事,从来也不叫人、不说话,跟自己家人说话都是“惜字如金”,“自来水”不说“自来水”,只说“自来”。上了大学之后,觉得自己这样不行啊,得尝试改变一下。听师兄师姐说在学生会能锻炼人,于是也就想着加入学生会吧。小时候喜欢鼓捣一些小制作,也喜欢在电脑上用PS做一些图,看来看去觉得科技部很适合我,于是就加入了科技部。大一做干事,大二做部长,大三做学生会副主席。这两三年,的确让我变得开朗了很多,有时候我甚至觉得这几年的学生会打开了我的一个开关,让我变得太贫了。但不可否认的,在学生会让我变得自信了。

在学生会里,有两个人我要尤其感谢,一个是枫哥,另一个是小昭。

枫哥当时是我们的分团委书记,学生会的直接领导,北邮土著,从本科一直到现在都在北邮,绝版青春的Logo代表着一段传奇。对于枫哥,一方面打心眼里敬重,得叫一声老师。另一方面,跟枫哥特别投脾气,私下里叫一声枫哥,亲近了很多。枫哥是我的贵人,后来能够读研,枫哥的推荐起了很大作用。枫哥也是我步入职场的启蒙导师,很多影响都是潜移默化的。嗯,还有就是变贫了,也是天天跟枫哥说相声练出来的。

小昭让我第一次接触到了计算机视觉,让我找到了我的兴趣所在,以及此后的硕士研究方向和毕业后的职业发展方向。大三的时候受小昭邀请,一起组队参与大创,我们的项目主要内容是检测人员摔倒并报警。现在看来,当时的算法思想十分简朴,但是让我第一次体验了如何通过科学实验验证自己的假设并解决问题,也让当时迷茫的我,找到了未来的方向,直到现在我依旧对计算机视觉抱有极大的热忱。如果没有小昭当时的邀请,估计我不会这么幸运地找到一个让我一直抱有兴趣的方向。

07112

07112应该是我们那一届凝聚力最强的班级了吧,大家一起耍的日子里也是各种各样的嗨。那时候每年都要参加评优活动,每到这个时候,就要做一个五分钟左右的视频来展示班级的珍贵回忆,但是五分钟实在实在太短了,根本不可能把我们一起经历的事情展示完,每次都要费尽心思地压缩。我们有太多的并肩奋斗,有太多的携手共度,有太多太多的美好回忆。

2011-2014

本科四年算是开开心心地玩过去的,接下来就是努力科研的研究生三年。真的很幸运可以在MCPRL读硕士。

科研

我应该是我们实验室考研分数最低的,毕业答辩的时候庄老师说:要不是实验室刚好多出一个名额,你就只能去找调剂了。很幸运我能够在实验室读研,在实验室所做的工作让我积累了很多在计算机视觉领域内的经验,锻炼了我实现算法的编码能力,以及在实际中提出问题、思考问题、解决问题的能力。

我在实验室没有做过任何横向或纵向项目,一直都是做TRECVID SED国际评测。这是一个监控视频事件检测任务,我们当时关注的事件包括”指 Pointing“、 “放东西 ObjectPut”、“跑 PersonRuns“、“拥抱 Embrace”、“聚集 PeopleMeet”和“分离 PeopleSplitUp”。做这个竞赛的好处就是让我对计算机视觉的基本问题都有了比较系统的了解,而且做了多个方面的工作,包括行人检测、多目标跟踪、人体姿态估计、动作识别、事件检测……

我记得研三在写毕业论文的时候,很多同学好像还在发愁写什么,我真的是忍不住想要多写一些,给大家分享我所做的工作,学到的知识,以及我对那些问题的思考。刚刚又去翻看了一下自己的毕业论文,最后的致谢是这样写的:

时光荏苒, 两年半的硕士研究生阶段就要结束了, 在此我必须对关心、指导和帮助过我的老师、同学和朋友们表达最衷心的感谢!

首先感谢赵衍运副教授在这两年多来对我的指导和帮助。赵老师严谨的治学态度和一丝不苟的工作精神时时刻刻都在激励并提醒我在科研中要勤奋刻苦、不能有半点马虎。同时,赵老师与我们分享她的人生阅历,也使我在科研之外的生活中获益匪浅。

同时感谢蔡安妮教授,蔡老师看问题的深刻程度和对问题的整体把握度都令我折服,蔡老师每一次参加我主讲的组会都会问到我准备最薄弱的部分,都会提出一些让我获得启发的问题, 和蔡老师的每一次交流都使我有所收获。还要感谢苏菲教授、庄伯金副教授和赵志诚老师,他们也在我的学业和科研中给予了极大的指导和帮助。

此外,还要感谢我的家人和实验室的同学,他们都在我需要的时候给我莫大的鼓励、帮助和安慰,使我不断战胜困难。最后,对所有支持、帮助过我的老师、同学和朋友致以最诚挚的谢意!

直至今天,每当想起实验室,还是这些感谢的话。

寒文轩

“寒文轩”是由三个人的名字中各取一个字组成的。

寒:小寒姐是一个精灵古怪的女孩,爱玩,很活泼也很有想法。读研期间小寒姐去西班牙交流了半年,搞科研的同时,不耽误自己在欧洲转了大半圈。跟小寒姐在一起聊天,会有一种感觉:世界上没有什么事是大不了的。

文:小F杰是那种让我羡慕的文艺青年,读了万卷书,行了万里路,追随自己内心的男子。他给自己取的昵称叫做“霞客”,非常合适。我们这个小团体的名字也是小F杰取的,也只有他能想到这么有才华的名字。小F杰应该是我们那一届考研分数第一。

轩:就是我自己了。

2014-2017

2014年硕士毕业了,到三星通信研究院工作,在这里积累了很多,结识了一帮技术达人,工作很开心,成长为职场人,不舍地离开。

现在,在计算机视觉领域最流行的框架应该就是卷积神经网络了,在我读研期间,其实完全没有接触过,到了三星之后才真正开始研究和使用卷积神经网络。2013年至2016年是深度学习大爆发的一段时间,每天都有各种各样的新理论、新结构、新方法被发表出来。因为三星特有的周期性研发节奏,让我有足够的时间汲取消化大量的新知识,并将这些技术应用到实际产品中,在这样的过程中,我也积累了宝贵的经验。在三星,参加竞赛、发专利、去韩国总部开发做报告、参与旗舰机功能的研发……还有一点就是做PPT的技术极大提升。

我们小组人员最齐整的时候有八个人,每个人有擅长的领域,也各有自己的性格,大家都是互相欣赏。我们几个人配合起来效率很高,有些工作甚至感觉像流水线一样,经过几个人的处理,马上就出活了。在技术上,大家的交流也很多,我从每一位身上都学到了很多。也正是这些同事,让我每天都十分开心地去上班,有一种打心眼里热爱工作和生活的感觉。

也正是这些小伙伴,让我最终从三星离职,其实在三星工作的期间,真的很开心。我也对当时的领导和同事抱有极大的感恩之心,是他们让我顺利地转变了角色,我也能体会到领导对我的重视和培养,也取得了一些成绩。但是,当身边的小伙伴一个个地离职的时候,用我们开玩笑的话来说就是:有一种失恋的感觉。我们八个人的小团体后来就以“图像叛将小分队”自称了,虽然大家现在在不同的公司,但都一直保持着联系,我相信职场中很少能有这样的感情。记得枫哥曾问过我,对我的工作打多少分,我说9.5分。

2017-今天

这一年多,按说应该是感触最多的一年。但是,猛地一想,好像没法很好的进行总结,或许是因为这些感触还需要沉淀,来一波意识流吧。

创业

创业其实就是为了折腾。当初选择创业就是想趁年轻折腾折腾,纯粹就是为了折腾。在选择创业之前,我还申请过英国的PhD。14年的12月到2月,工作日上班,周末去上雅思课,自己准备研究计划书,套词……很遗憾没能成行,现在想想就当提升英语能力了。读PhD没折腾成,就选择创业吧。

创业真不是人干的。在大公司里工作有个好处,尤其在大外企里工作,很容易就可以做到工作生活两不误。工作上,只要做好自己拿一份工作,只要不断提升自己的专业技能,几乎不用再操其他心了。节假日里,大可小资一番,旅游、读书、追剧、画画、健身、望天发呆……自从开始创业,尤其是最开始的三个月,一直都很焦虑,最大的焦虑来自于对未来的不确定性,不知道公司可以走多久走多远。然后是一团乱麻的工作,在大企业里,有各个部门来共同服务员工,各司其职,但是创业就是所有事都要自己操心,什么事都得想着,什么事都得惦记着。从一个研发人员转型为一个管理人员,这个角色转变太难了。而且作为一个草根创业者,我必须既是研发人员又是管理人员。可能我的创业伙伴可以在创业这件事本身上寻找到快乐,但是我是一个必须在生活中寻找乐趣的人,但是自从创业之后,哪怕周末没有去公司,心里其实也总是惦记着公司的事,好像不能完全放松,因此有时候总觉得生活没啥乐趣。哪怕有些时候真是有个半天空闲,现在都不知道可以干点什么了。

创业真的锻炼人。草根创业就是要做各种各样的工作,因此从技术上来说,我会接触到各种各样的场景,在尝试解决这些不同问题的时候,也是对自己技术的一种锻炼。除了技术,剩下更多的就是对心智的锻炼。提升最多是抗压能力,不管是面对别人的质疑挑战、deadline的压力、未知数的恐惧,也可能是麻木了,反正有时候会觉得无所谓。

创业可以让人对自己更加了解,诌一句英文就是:know who I am。上面的很多感受其实就是创业之后才知道的,比如我要从生活中获取乐趣;相比较跟人打交道,我更擅长跟机器打交道;我是一个相当保守的人;我相对来说比较固执;《三国演义》里形容袁绍的“多谋寡断”也适合我;我会逃避现实,做一些自我欺骗的事……

感谢创业路上一起拼搏的兄弟姐妹,感谢合作伙伴,感谢那些踩过的坑吧。

六人行

拿《老友记》形容我们再合适不过了,一行六人,三男三女,从小到大的死党。去年我们聚会之后还说,大家都有自己的小生活啦,可是见面还是以前的感觉,真好。大家要多聚聚,以后没准就是孩子们一起玩,我们一起打麻将。无论什么时候,每当那首《I’ll be there for you》响起,我都会想起我们六个人。说起来真是奇怪,越是深沉的感情,好像能够表达的语言反而越少,有的就是心里那种一团暖暖的感觉。

十年恋爱

这个感触太多,酸甜苦辣咸,其中百味估计要单独写了。感谢张大夫一直以来的陪伴。

30岁

说起30岁,感觉这真是一个坎,对于我自己而言可能就是:要接受自己是一个普通人了。27、28岁的时候,可能还能厚着脸皮谈理想。但是到了现在,估计只有一起创业的小伙伴们,一起喝酒之后,才会再提起理想吧。他们说只有等你有了孩子,才算真正长大了。我感觉自己好像还没准备好长大,就已经不能再说不想长大了。

2008-2018

最近开发一个需要从Java端将图像传至native层的功能,发现了一个奇怪现象,native层每次获取到的图像都会有一些像素值发生随机变化。原始代码类似如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
cv::Mat imgbuf2mat(JNIEnv *env, jbyteArray buf, int width, int height){
jbyte *ptr = env->GetByteArrayElements(buf, 0);
cv::Mat img(height, width, img_type, (unsigned char *)ptr);
env->ReleaseByteArrayElements(buf, ptr, 0);
return img;
}

static void nativeProcessImageBuff
(JNIEnv *env, jobject thiz,
jbyteArray img_buff,
jint width,
jint height)
{
cv::Mat img = imgbuf2mat(env, img_buff, width, height, img_type, img_rotate);
//do somethint to the image
process(img);
}

简单来说就是先把图像内容转成cv::Mat,然后对图像做一些处理,但是即使传入相同的一张图像,处理结果每次都不一样。后来发现其实应该先处理图像,再释放引用,修改后的代码类似如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
cv::Mat imgbuf2mat(JNIEnv *env, jbyte *ptr, int width, int height){
cv::Mat img(height, width, img_type, (unsigned char *)ptr);
return img;
}

static void nativeProcessImageBuff
(JNIEnv *env, jobject thiz,
jbyteArray img_buff,
jint width,
jint height)
{
jbyte *ptr = env->GetByteArrayElements(img_buff, 0);
cv::Mat img = imgbuf2mat(env, ptr, width, height);
//do somethint to the image
process(img);
env->ReleaseByteArrayElements(img_buff, ptr, 0);
}

MTCNN 训练记录

最近尝试使用 Caffe 复现 MTCNN,感觉坑很大,记录一下训练过程,目前还没有好的结果。网上也有很多童鞋在尝试训练 MTCNN,普遍反映使用 TensorFlow 可以得到比较好的结果,但是使用 Caffe 不是很乐观。

已经遇到的问题现象

  • 2018.09.11:目前只训练了 12net,召回率偏低。

    blankWorld/MTCNN-Accelerate-Onet为 baseline,blankWorld 在 FDDB 上的测试性能如下图

    FDDB Result

    这个效果很不错,但是我自己生成样本后训练 12net,召回率有明显下降。性能对比如下图

    FDDB 12net Compare

    暂且不管 12net 的测试结果为什么会这么差,两个模型的性能差距是可以反映的。

训练记录

  • 2018.09.11

    训练数据生成

    参考AITTSMD/MTCNN-Tensorflow提供的 prepare_data 进行数据生成。数据集情况如下表

Positive Negative Part Landmark
Training Set 156728/189530 470184/975229 156728/547211 313456/357604
Validation Set 10000 10000 10000 10000

其中 Pos:Neg:Part:Landmark = 1:3:1:2,样本比例参考原作的比例。Pos、Neg、Part 来自于WiderFace,Landmark 来自于CelebA。其中正样本进行了人工的数据筛选,筛选的原因是根据 WiderFace 生成的正样本,有很多都是质量很差的图像,包含人脸大面积遮挡或十分模糊的情况。之前召回率很差的性能来自没有经过筛选的训练集,因为使用了 OHEM,只有 loss 值在前 70%的样本才参与梯度计算,感觉如果质量差的样本占比较大,网络学习到的特征是错误的,那些质量好的图像可能得不到充分的学习。

训练参数设置

初始训练参数如下

1
2
3
4
5
6
7
type:"Adam"
momentum: 0.9
momentum2:0.999
delta:1e-8
base_lr: 0.01
weight_decay: 0.0005
batch_size: 256

第一轮训练在 75000 次迭代(17.5 个 epoch)时停止,测试记录如下

1
2
3
4
5
6
I0911 10:16:25.019253 21722 solver.cpp:347] Iteration 75000, Testing net (#0)
I0911 10:16:28.057858 21727 data_layer.cpp:89] Restarting data prefetching from start.
I0911 10:16:28.072748 21722 solver.cpp:414] Test net output #0: cls_Acc = 0.4638
I0911 10:16:28.072789 21722 solver.cpp:414] Test net output #1: cls_loss = 0.096654 (* 1 = 0.096654 loss)
I0911 10:16:28.072796 21722 solver.cpp:414] Test net output #2: pts_loss = 0.008529 (* 0.5 = 0.0042645 loss)
I0911 10:16:28.072801 21722 solver.cpp:414] Test net output #3: roi_loss = 0.0221648 (* 0.5 = 0.0110824 loss)

注意:分类测试结果是 0.4638 是因为测试集没有打乱,1-10000 为 pos 样本,10001-20000 为 neg 样本,20001-30000 为 part 样本,30001-40000 为 landmark 样本。因此,实际分类正确率应该是 0.9276

降低学习率至 0.001,训练 135000 次迭代(31.5 个 epoch)时停止,测试记录如下

1
2
3
4
5
6
I0911 13:14:36.482010 23543 solver.cpp:347] Iteration 135000, Testing net (#0)
I0911 13:14:39.629933 23660 data_layer.cpp:89] Restarting data prefetching from start.
I0911 13:14:39.645612 23543 solver.cpp:414] Test net output #0: cls_Acc = 0.4714
I0911 13:14:39.645649 23543 solver.cpp:414] Test net output #1: cls_loss = 0.0765401 (* 1 = 0.0765401 loss)
I0911 13:14:39.645656 23543 solver.cpp:414] Test net output #2: pts_loss = 0.00756469 (* 0.5 = 0.00378234 loss)
I0911 13:14:39.645661 23543 solver.cpp:414] Test net output #3: roi_loss = 0.0201988 (* 0.5 = 0.0100994 loss)

实际分类正确率是 0.9428。训练 260000 次迭代后停止,测试记录如下

1
2
3
4
5
6
I0911 16:58:47.514267 28442 solver.cpp:347] Iteration 260000, Testing net (#0)
I0911 16:58:50.624385 28448 data_layer.cpp:89] Restarting data prefetching from start.
I0911 16:58:50.639556 28442 solver.cpp:414] Test net output #0: cls_Acc = 0.471876
I0911 16:58:50.639595 28442 solver.cpp:414] Test net output #1: cls_loss = 0.0750447 (* 1 = 0.0750447 loss)
I0911 16:58:50.639602 28442 solver.cpp:414] Test net output #2: pts_loss = 0.0074394 (* 0.5 = 0.0037197 loss)
I0911 16:58:50.639608 28442 solver.cpp:414] Test net output #3: roi_loss = 0.0199694 (* 0.5 = 0.00998469 loss)

实际分类正确率是 0.943752。

问题: 训练结果看似还可以,但是召回率很低,在阈值设置为 0.3 的情况下,召回率也才将将达到 90%。阈值要设置到 0.05,才能达到 97%-98%的召回率,ROC 曲线如下图。严格来说这个测试并不严谨,应该用检测器直接在图像中进行检测,但是为了方便,我直接用 val 集上的性能画出了 ROC 曲线,其中的 FDDB 曲线是将的人脸区域截取出来进行测试得到的。

12net 1st ROC

  • 2018.09.14

    使用上述 12net 在 WiderFace 上提取正负样本,提取结果如下:

Thresholed Positive Negative Part
0.05 85210 36745286 632861
0.5 66224 6299420 354350
  • 2018.09.17

    准备 24net 的训练样本。由于生成 12net 检测到的正样本数目有限,训练 24net 的 pos 样本包含两部分,一部分是训练 12net 的正样本,一部分是经过筛选的 12net 检测到的正样本;neg 样本和 part 样本全部来自 12net 的难例;landmark 与 12net 共用样本。经过采样后达到样本比例 1:3:1:2,样本数目如下表:

Positive Negative Part Landmark
Training Set 225172 675516 225172 313456
Validation Set 10000 10000 10000 10000

训练过程与 12net 类似,学习率从 0.01 下降到 0.0001,最终的训练结果如下

1
2
3
4
5
6
I0917 15:19:00.631140 36330 solver.cpp:347] Iteration 70000, Testing net (#0)
I0917 15:19:03.305665 36335 data_layer.cpp:89] Restarting data prefetching from start.
I0917 15:19:03.317827 36330 solver.cpp:414] Test net output #0: cls_Acc = 0.481501
I0917 15:19:03.317865 36330 solver.cpp:414] Test net output #1: cls_loss = 0.0479137 (* 1 = 0.0479137 loss)
I0917 15:19:03.317874 36330 solver.cpp:414] Test net output #2: pts_loss = 0.00631254 (* 0.5 = 0.00315627 loss)
I0917 15:19:03.317879 36330 solver.cpp:414] Test net output #3: roi_loss = 0.0179083 (* 0.5 = 0.00895414 loss)

实际分类正确率是 0.963。ROC 曲线如下图,同样使用 val 集上的性能画出曲线。

24net 1st ROC

  • 2018.09.18

    使用 24net 在 WiderFace 上提取正负样本,提取结果如下:

Thresholed Positive Negative Part
0.5, 0.5 86396 83212 225285

利用以上数据生成 48net 的训练样本,由于 24net 生成的样本数量有限,结合前两次训练所用的数据,生成训练集:

Positive Negative Part Landmark
Training Set 283616 850848 283616 567232
Validation Set 10000 10000 10000 10000
  • 2018.09.19

    在训练 48net 的过程中,首先尝试了 Adam 算法进行优化,后来发现训练十分不稳定。转而使用 SGD 进行优化,效果好转。训练初始参数如下:

    1
    2
    3
    4
    type:"SGD"
    base_lr: 0.01
    momentum: 0.9
    weight_decay: 0.0005

    48net 的训练结果比较一般,性能如下:

    1
    2
    3
    4
    5
    6
    I0919 18:02:22.318362  3822 solver.cpp:347] Iteration 165000, Testing net (#0)
    I0919 18:02:25.877437 3827 data_layer.cpp:89] Restarting data prefetching from start.
    I0919 18:02:25.894898 3822 solver.cpp:414] Test net output #0: cls_Acc = 0.4662
    I0919 18:02:25.894937 3822 solver.cpp:414] Test net output #1: cls_loss = 0.0917524 (* 1 = 0.0917524 loss)
    I0919 18:02:25.894943 3822 solver.cpp:414] Test net output #2: pts_loss = 0.00566356 (* 1 = 0.00566356 loss)
    I0919 18:02:25.894948 3822 solver.cpp:414] Test net output #3: roi_loss = 0.0177907 (* 0.5 = 0.00889534 loss)

    实际的分类精度为 0.9324。整体来看基本实现了文章中参考文献[19]在验证集上的性能,性能对比如下表

CNN 12-net 24-net 48-net
[19] 94.4% 95.1% 93.2%
MTCNN 94.6% 95.4% 95.4%
Ours 94.3% 96.3% 93.2%
  • 2018.09.20

    整个系统连通后进行测试,发现人脸框抖动比较厉害,这应该是训练过程和样本带来的问题。

    比较奇怪的问题是在 Caffe 上进行 CPU 运算时,速度极慢,尤其 12net 运行速度慢 30 倍左右。通过观察参数分布发现,有大量 kernel 都是全零分布,初步感觉是因为 Adam 和 ignore label 相互作用的结果,即 ignore label 的样本会产生 0 值 loss,这些 loss 会影响 Adam 的优化过程,具体原因还需进一步理论推导。目前的解决方案是将含有大量 0 值 kernel 的层随机初始化,使用 SGD 进行训练。至于抖动的问题,需要进一步分析。重训后的模型性能如下表:

12-net 24-net 48-net
Accuracy 94.59% 96.52% 93.94%
  • 2018.09.26

    目前来看回归器的训练是完全失败。失败的原因可能有以下几点:

    1. 对欧拉损失层做了代码修改,用来支持 ignore label,可能代码出现问题
    2. 数据本身存在问题,需要验证数据的正确性

    先训练一个不带 ignore label 的回归器,看 loss 是否发生变化。

  • 2018.09.27

    做了一个实验,用 20 张图像生成一个数据集,经过训练,网络是可以完全过拟合这个数据集的,说明数据和代码是没有问题的,但是加大数据量后,bounding box 的回归依旧不好。通过跟大神讨论,发现自己犯了一个很弱智的低级错误,回归问题是不可以做镜像的,回归问题是不可以做镜像的,回归问题是不可以做镜像的,重要的事情说三遍,如果图像做镜像操作,那么标注也需要进行镜像操作。

    重训后的模型性能如下表:

12-net 24-net 48-net
Accuracy 94.35% 97.45% 94.67%
  • 2018.10.01

    实验还是不够顺利,有些受挫感。目前来看 12net 和 24net 的训练是相对顺利的,至少能够去除大量虚检并给出相对准确的回归框。48net 的训练比较失败,存在虚检、定位不准以及关键点定位不准的问题。

    在训练 12net 和 24net 时,因为网络较小,而且这两个网络都不负责输出关键点,所以训练时只训练了分类和框回归问题。训练 48net 时三个 loss 都很重要,感觉按照原文的 loss weight 进行配置会造成回归问题学习不充分的问题,需要提高回归问题的 loss weight。

    接下来的工作包括:

    1. 之前的训练为了快速验证思路,并没有严格按照后一阶段数据是前一阶段数据中的难例的原则,接下来需要重新走这个流程。
    2. 探索 48net 的训练过程。具体来说有几个点需要尝试,首先,是刚刚提到的难例挖掘;其次,调整各个 loss 的权重;最后,除了阶段间的难例挖掘,也应注意阶段内的难例挖掘。
    3. 以 24net 在 landmark 数据集上进行检测,将其输出作为 48net 进行 landmark 回归的输入。
  • 2018.10.13

    最近一次训练的记录如下:

    12net

    12net 的样本由随机采样得来。

Positive Negative Part
Training Set 156728 470184 156728
Validation Set 10000 10000 10000

验证集上的性能为:

1
2
3
Test net output #0: cls_Acc = 0.9435
Test net output #1: cls_loss = 0.0747717 (* 1 = 0.0747717 loss)
Test net output #2: roi_loss = 0.0168385 (* 0.5 = 0.00841924 loss)

24net

24net 的样本全部来自 12net 的检测结果。

Positive Negative Part
Training Set 60149 180447 120298
Validation Set 1500 1500 1500

验证集上的性能为:

1
2
3
Test net output #0: cls_Acc = 0.977588
Test net output #1: cls_loss = 0.0648633 (* 1 = 0.0648633 loss)
Test net output #2: roi_loss = 0.0192365 (* 5 = 0.0961826 loss)

48net

48net 的正样本和 part 样本来自于 24net 在 widerface 上的检测结果,负样本来自于 24net 在 widerface 和 celeba 上的检测结果,landmark 样本来自于 24net 在 celeba 上的检测结果。

Positive Negative Part landmark
Training Set 242862 728586 242862 485724
Validation Set 5000 5000 5000 5000

在验证集上的性能为:

1
2
3
4
Test net output #0: cls_Acc = 0.978155
Test net output #1: cls_loss = 0.0694968 (* 1 = 0.0694968 loss)
Test net output #2: pts_loss = 0.00119616 (* 5 = 0.00598078 loss)
Test net output #3: roi_loss = 0.0111277 (* 1 = 0.0111277 loss)

使用 0.5,0.5,0.5 作为阈值,在 FDDB 上测得的 discROC 曲线如下图

ROC 20181013

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

AUTHOR: Kaipeng Zhang, Zhanpeng Zhang, Zhifeng Li

ASSOCIATION: Chinese Academy of Sciences, The Chinese University of Hong Kong

FROM: IEEE Signal Processing Letters

CONTRIBUTION

  1. A new cascaded CNNs based framework is proposed for joint face detection and alignment, and a lightweight CNN architecture is carefully designed for real time performance.
  2. An effective method is proposed to conduct online hard sample mining to improve the performance.
  3. Extensive experiments are conducted on challenging benchmarks, to show significant performance improvement of the proposed approach compared to the state-of-the-art techniques in both face detection and face alignment tasks.

METHOD

This work is very much like a traditional slide-window based face detection method. A cascade classifier is used to classify an image patch. The first classifier is very simple so that it can efficiently remove easy negative patches. Then more complex classifiers are used to remove harder patches. The last CNN is used as a regressor to localize face landmarks. Along with the classifiers, bounding box is also localized. To handle multiple scales, an image pyramid is built.

The pipeline of this work is shown as follows. The input image is first resized to different scales to build an image pyramid, which is sent to the three-stage cascaded framework.

Framework

Stage 1: A fully convolutional network is proposed, called Proposal Network (P-Net), to obtain the candidate facial windows and their bounding box regression vectors. Then candidates are calibrated based on the estimated bounding box regression vectors. After that, non-maximum suppression (NMS) is utilized to merge highly overlapped candidates.

Stage 2: All candidates are fed to another CNN, called Refine Network (R-Net), which further rejects numerous false candidates, performs calibration with bounding box regression, and conducts NMS.

Stage 3: This stage is similar to the second stage, but this stage aims to identify face regions with more supervision. In particular, the network will output five facial landmarks’ positions.

The architecture of the three networks are shown in the following figure.

Architecture

At training stage, the face classification is trained as typical two-class classification task using cross-entropy loss. In each mini-batch, the samples are sorted by their losses and only the top 70% of them are selected to compute gradients. On the other hand, Bounding box regression and facial landmark localization are trained using Euclidean loss.

PERFORMANCE

Face detection performance:

Detection

Face landmark regression performance:

Landmark

IDEAS

  1. As the author provided no training code, I implement the training code based on caffe by adding ignore labels to EuclideanLossLayer and surpporting multiple label to DataLayer.
  2. In this work, samples are assigned as different types. In my own experiments, I assign samples with multiple labels, which means that one sample can be used for different losses at the same time. I’m not sure whether this modification can help improve the performance.
  3. Further experiments are of need to realize the performance of the work.

Jupyter Notebook 有两种键盘输入模式。编辑模式,允许你往单元中键入代码或文本;这时的单元框线是绿色的。命令模式,键盘输入运行程序命令;这时的单元框线是灰色。

  1. 命令模式 (按键 Esc 开启)
  • Enter : 转入编辑模式
  • Shift-Enter : 运行本单元,选中下个单元
  • Ctrl-Enter : 运行本单元
  • Alt-Enter : 运行本单元,在其下插入新单元
  • Y : 单元转入代码状态
  • M :单元转入markdown状态
  • R : 单元转入raw状态
  • 1 : 设定 1 级标题
  • 2 : 设定 2 级标题
  • 3 : 设定 3 级标题
  • 4 : 设定 4 级标题
  • 5 : 设定 5 级标题
  • 6 : 设定 6 级标题
  • Up : 选中上方单元
  • K : 选中上方单元
  • Down : 选中下方单元
  • J : 选中下方单元
  • Shift-K : 扩大选中上方单元
  • Shift-J : 扩大选中下方单元
  • A : 在上方插入新单元
  • B : 在下方插入新单元
  • X : 剪切选中的单元
  • C : 复制选中的单元
  • Shift-V : 粘贴到上方单元
  • V : 粘贴到下方单元
  • Z : 恢复删除的最后一个单元
  • D,D : 删除选中的单元
  • Shift-M : 合并选中的单元
  • Ctrl-S : 文件存盘
  • S : 文件存盘
  • L : 转换行号
  • O : 转换输出
  • Shift-O : 转换输出滚动
  • Esc : 关闭页面
  • Q : 关闭页面
  • H : 显示快捷键帮助
  • I,I : 中断Notebook内核
  • 0,0 : 重启Notebook内核
  • Shift : 忽略
  • Shift-Space : 向上滚动
  • Space : 向下滚动
  1. 编辑模式 ( Enter 键启动)
  • Tab : 代码补全或缩进
  • Shift-Tab : 提示
  • Ctrl-] : 缩进
  • Ctrl-[ : 解除缩进
  • Ctrl-A : 全选
  • Ctrl-Z : 复原
  • Ctrl-Shift-Z : 再做
  • Ctrl-Y : 再做
  • Ctrl-Home : 跳到单元开头
  • Ctrl-Up : 跳到单元开头
  • Ctrl-End : 跳到单元末尾
  • Ctrl-Down : 跳到单元末尾
  • Ctrl-Left : 跳到左边一个字首
  • Ctrl-Right : 跳到右边一个字首
  • Ctrl-Backspace : 删除前面一个字
  • Ctrl-Delete : 删除后面一个字
  • Esc : 进入命令模式
  • Ctrl-M : 进入命令模式
  • Shift-Enter : 运行本单元,选中下一单元
  • Ctrl-Enter : 运行本单元
  • Alt-Enter : 运行本单元,在下面插入一单元
  • Ctrl-Shift— : 分割单元
  • Ctrl-Shift-Subtract : 分割单元
  • Ctrl-S : 文件存盘
  • Shift : 忽略
  • Up : 光标上移或转入上一单元
  • Down :光标下移或转入下一单元