前言:

CornerNet于2019年3月份提出,CW近期回顾了下这个在当时引起不少关注的目标检测模型,它的亮点在于提出了一套新的方法论——将目标检测转化为对物体成对关键点(角点)的检测。通过将目标物体视作成对的关键点,其不需要在图像上铺设先验锚框(anchor),可谓实实在在的anchor-free,这也减少了整体框架中人工设计(handcraft)的成分。

CornerNet于2019年3月份提出,CW近期回顾了下这个在当时引起不少关注的目标检测模型,它的亮点在于提出了一套新的方法论——将目标检测转化为对物体成对关键点(角点)的检测。通过将目标物体视作成对的关键点,其不需要在图像上铺设先验锚框(anchor),可谓实实在在的anchor-free,这也减少了整体框架中人工设计(handcraft)的成分。CornerNet于2019年3月份提出,CW近期回顾了下这个在当时引起不少关注的目标检测模型,它的亮点在于提出了一套新的方法论——将目标检测转化为对物体成对关键点(角点)的检测。通过将目标物体视作成对的关键点,其不需要在图像上铺设先验锚框(anchor),可谓实实在在的anchor-free,这也减少了整体框架中人工设计(handcraft)的成分。

为了让自己的梳理工作更好地反馈到自身以实现内化,CW决定在此记录下自己对CornerNet的理解,同时也和大家进行分享,如果有幸能够帮助到你们,那我就更是happy了!

本文内容有些长,但是如果你打算认真回顾和思考有关CornerNet技术原理的细节,不妨耐心地看下去。CW也将本文的目录列出来了,大家也可根据自身需求节选部分内容来看。(见文末)

研究动机及背景

作者发现,目标检测中anchor-based方法存在以下问题:

1.为了给gt提供正样本,需要密集铺设多尺度的anchors,但这同时会造成正负样本不均衡;

2.anchor的存在就势必引入众多handcraft成分,如anchor数量、尺度、长宽比等,模型的训练效果极大地受到这些因素的影响,另外还会影响模型推断速度;

那么如何改进呢..不知怎地,作者灵光一闪,想到在解决人体姿态估计问题的方法中,有一类bottom-up框架,其方法是先对人体关键点部位进行检测,再将检测到的关键点部位拼接成人的姿态。

于是,作者脑回路:“咦,要不我也这么干好了!我也来检测关键点。目标检测最终不是要定位物体对应的预测框吗,那我就检测出框的左上和右下两个角点,这样我也能定位出整个框了,万岁!”。于是,CornerNet就这样机缘巧合地“出生”了。

概述

概括地说,CornerNet使用单个卷积网络来检测物体的左上角和右下角:

●  通过预测得到的热图(heatmaps)来判别各位置是否属于角点;

●  基于预测的角点嵌入向量(embeddings)来对角点进行配对(属于同一物体的一对角点的embeddings之间的距离会比较小,属于不同物体的则比较大),从而判断哪些左上角点和右下角点是属于同一物体的;

●  使用预测的偏移量(offsets)对角点位置进行调整;

另外,为了更好地检测角点,提出了新型的池化层——Corner Pooling

整体框架

首先将输入图像通过预处理模块:1个7×7的卷积模块(conv+bn+relu)+1个残差模块,分别下采样2倍,这会将输入图像尺寸缩小为原来的1/4(论文中使用的输入图像大小是511×511,于是下采样后得到128×128大小的输出特征图)。

然后将预处理模块输出的特征图输入到backbone提取特征,backbone采用的是沙漏网络(Hourglass Network)结构,这里串联(堆叠)了两个相同结构的Hourglass Network,其中每个在经过下采样操作后会上采样恢复到之前的大小,因此backbone输出特征图的大小与其输入一致。

Hourglass Network后连接着两个预测模块,分别用于预测左上角和右下角。每个模块包含其独立的角池化(Corner Pooling)模块。

接着,将Hourglass Network的输出特征输入到Corner Pooling模块得到池化特征。

最后,将池化特征分别输入到3个不同的卷积模块来预测heatmaps、embeddings以及offsets。

角点检测

检测包括分类+定位,这里主要是分类,即判断特征图上的各个(特征点)位置是否属于角点,不需要显式回归角点的位置,角点的位置基本由特征点的位置决定,然后通过预测的offsets进行调整。

●  Heatmaps

分类基于两组heatmaps,分别用于左上角和右下角的判断。每组heatmap的shape是,是物体类别数(不含背景),是特征图的尺寸。这样,每个通道就对应特定类别物体的角点判断。理想状态下,它是一个二值mask,值为1就代表该位置属于角点,而通常模型预测出来每个位置上的值是0~1,代表该位置属于角点的置信度。

●  Penalty Reduction

由此可知,对于每个角点,只有1个正样本位置。那么训练时,1个gt在heatmap上的标签就只有在其对应的位置上值为1,其余均为0。不知你有没feel到,这样的话,很容易由于正样本过少而导致低召回率。在实际情况中,即使我们选择一对与gt角点有一定程度偏离的角点来形成预测框,那么它也有可能与gt box有较高的重叠度(IoU),这样的预测框作为检测结果也是不错的选择(如下图,红框是gt bboxes,绿框是距离gt角点较近的角点对形成的bboxes)。

于是,对于那些距离gt角点位置较近的负样本位置,我们可以“在心里暗暗地将它们也作为候选的正样本”,转化到数学形式上,就是在计算loss时减低对它们的惩罚度,惩罚度与它们距离gt角点的远近相关(gt角点 to 负样本:你离我越近,我对你越温柔~)。

具体来说,距离gt角点在半径为 r 的圆内的那些负样本,我们重新计算其标签值为0~1之间的值(而非原来的0),离gt角点越近越接近1,否则越接近0:

以上x,y代表负样本位置与gt角点位置的横、纵坐标之差,i,j是特征点的位置,起到控制惩罚度严厉程度(变化快慢)的作用,值越大,惩罚越轻(可联想到高斯曲线越扁平)。你看看,这就是CornerNet对这批“候选正样本”的爱~

OK,已经感受到爱了,那么怎么用到loss计算上呢?作者设计了一种focal loss的变体:

代表模型预测的heatmap中位置属于类别 C 物体角点的置信度,。由上式可知,红色框部分就可以达到降低距离gt角点较近的那些负样本惩罚度的效果。而对于那些远离gt角点的负样本,它们对应的标签值依然是0,因此不受影响。

CornerNet告诉我们,许多事情不是非正即负、非0即1,世界本就是混沌。做人也一样,不能太死板,对待他人要理解与包容,适当的宽容能够在生活中获取小确幸(说不定还有大确幸呢)。

●  Radius Computation

以上只谈到对于距离gt角点在半径为的圆内的那些负样本“给予适当的宽容”,但并未说明半径如何计算,不急,因为要解方程式,可以先喝杯咖啡,休息下。

在作者制定的规则下,半径是基于这样一个条件计算出来的:在圆内的角点对形成的bbox(以下记为pred bbox)与gt box的IoU不小于(作者在实验中设置为0.3)。根据这个条件,可以分3种情况来考虑:

1. pred bbox包围着gt box,同时两边与圆相切

这时,

移项,整理得二元一次方程式:

还记得根的判别式因子吗?其各项依次为:

易知判别式(因为所以),于是有解:

但是,我们需要的半径应该是正根,于是最终:

2. gt box包围着pred bbox,同时两边与圆相切

这时,

移项整理得:

此时,根的判别式因子:

判别式:

于是,方程有解,并且此时两个根都是正根。为了兼容其它情况,我们需要取小的根,即 

3. pred bbox与gt box部分重叠,两者分别有两边与圆相切

此时,

移项整理得:

根的判别式因子:

易证判别式(请让CW偷下懒..),最终取较小的根:

以上3种情况都是根据求根公式计算出对应的半径值,在实现时,将代入计算。为了兼容各种情况,最终r的取值需要是三个解中的最小值:

●  location offsets

offsets用于调整预测的角点位置,使得定位更精确。注意,其和anchor-based框架中回归的偏移量不同,在这里,offsets的实质是量化误差

由于在卷积神经网络中存在着下采样层,于是将特征图中的位置重新映射到输入图像中的空间时,势必会存在量化误差,这极大地影响了小目标边界框的定位。

为了缓解这一现象,在训练时,计算gt角点位置映射到特征图位置时的量化误差,将其作为offsets的训练标签:

其中 n 是下采样率,是角点 k 在原图的位置。

训练模型让其学会预测这个误差值,以便在最终检测时重新调整预测的角点位置。使用smooth-l1 loss对这部分进行学习:

训练完毕后,在测试时,就可以这样调整预测的角点位置(实际实现时并非这样,这里仅仅打个简单的比方):

假设在heatmap上位置被预测为角点,其对应预测的offsets为,那么其映射到原图上的位置就是:

其中[]表示向下取整。

角点配对

在特征图的每个位置上,模型还会预测角点对应的嵌入向量(embeddings),用于将左上角点和右下角点进行配对。能否匹配成一对主要是由embeddings之间的距离来决定的(当然,其实还有其它条件,如预测的角点必须属于相同类别、右下角点的坐标必须大于左上角点的坐标)。理想状态下,同一物体的一对角点对应的embeddings之间的距离较小,而不同物体的则较大。那么,如何实现这一目标呢?

在训练时,CornerNet使用'pull loss'来拉近属于同一物体的角点的embeddings,同时使用'push loss'来远离属于不同物体的角点的embeddings:

其中分别为目标物体 K 的左上角和右下角对应的embeddings,则是两者的均值,delta=1,代表不同物体的角点对应的embeddings之间的margin下限(to:我们不熟,别靠太近,保持1米以外的文明距离)。 N 是目标物体的数量,也就是说,仅对gt角点位置对应的预测embeddings计算这些损失。

Corner Pooling

由于实际生活中许多物体并没有角状,比如圆形的餐盘、条形的绳子等,因此并没有直观明显的视觉特征来表征角点。这也就是说,通过现有的视觉滤波器(卷积层、池化层等)去捕捉图像的局部特征来检测角点,效果并不会太好。比如以下这些情况,物体的左上角和右下角点处并不存在物体本身的部分,即这些角点的位置本身并不存在物体的特征。

于是,为了在角点处获取到物体特征,我们需要将物体的特征汇集到角点处。比如对于左上角,可以将其水平向右以及竖直向下的特征都“收集”过来;而对于右下角点,则将其水平向左以及竖直向上的特征“收集”过来。

基于这种思想,作者提出了Corner Pooling,分别对用于收集左上角点特征和右下角点特征。对于左上角点,其处理如下:

其中分别表示池化层的输入特征图,它们的目标是分别将竖直方向和水平方向的特征不断汇集到上方和左方。这样,在中的左上角点就分别拥有了竖直方向和水平方向的极大值特征,分别代表中位置的特征值。

最终,将进行element-wise add得到输出特征图,于是,在其中的左上角点处就拥有了竖直加水平方向的极大值特征。

对于右下角点的处理也是同样道理,经Corner Pooling处理后,会在输出特征图的右下角点处汇聚到竖直和水平方向的极大值特征。

训练

网络模型在基于Pytorch的默认方式下进行随机初始化,并且没有在额外的数据集上预训练。

输入图像的分辨率设置为511×511,4倍下采样后输出特征的分辨率为128×128。为了减少过拟合,采用了不少数据增强技术,包括:随机水平翻转、随机缩放、随机裁剪以及随机色彩抖动(调整图像的亮度、饱和度和对比度)。最后,还将PCA应用于输入图像。

batch size设置为49,使用10个(Titan X PASCAL)GPUs来训练,其中每个batch在master GPU上分配4张图,其余GPUs各分配5张。

训练损失最终的形式为:

其中

使用Adam优化器进行优化,初始学习率设置为。初始先训练250k次迭代,在实验中与其它检测器进行比较时,额外再训练250k次迭代,并且在最后的50k次迭代中将学习率减低至

●  Intermediate Supervision

作者在训练时还添加了中间监督。前文提到过,backbone是两个相同结构的Hourglass Networks串联而成,中间监督的意思就是对第一个Hourglass Network的输出预测也实行监督。具体来说,就是将第一个Hourglass Network的输出特征图也输入到后面的预测模块:先经过corner pooling池化,然后分别输入到不同的卷积模块分别预测heatmaps、embeddings和offsets,对这部分的预测结果也计算损失进行训练。

那么可能有帅哥/靓女会疑问:那第二个Hourglass Network的输入是什么呢?

OK,CW也大方地补充说明下:其实在两个Hourglass Networks之间还有些中间处理模块,它们的实质都是conv+bn+relu和残差模块,将第一个Hourglass Network的输入、输出特征图经过这些中间模块处理后就是第二个Hourglass Network的输入。

测试

模型整体框架的pipeline就不细说了,前文已经详细解析过,概括来说就是:Preprocess (7x7Conv+Bn+Relu & Residual Module)->Hourglass Networks->Corner Pooling->Prediction Head(output Heatmaps, Embeddings & Offsets)。

这里主要说明下测试时对图像的处理和对模型输出的后处理。

●  测试图像处理

测试时对图像的处理方式还蛮有“个性”,作者在paper中一笔带过:

Instead of resizing an image to a fixed size, we maintain the original resolution of the image and pad it with zeros before feeding it to CornerNet.

意思是,不改变图像分辨率,但使用0填充。但是,具体怎么做的,填充多少部分却没有详细说明(能不能坦诚相对..)。CW对这实在不能忍,看了源码后,发现是这样做的:

代码中 ' | 127 ' 这种方式会将new height和new width的低7位全部置1,猜测作者这样做的意思应该是想使得输入图像的尺寸至少为128x128吧(联想到CornerNet训练时输入分辨率是511x511,输出特征图分辨率正好是128x128)。

最后,将原图裁剪下来放置在填充的全0图像中,保持中心对齐,同时会记录原图在这个填充图像中的区域边界:,以便后续将检测结果还原到原图坐标空间。也就是说,在网络输入图像中,区域边界以外的部分都是0。

另外,对于每张图片,还会将其水平镜像图片也一并输入到网络中(组成一个batch)进行测试,最终的检测结果是综合原图和镜像图片的结果。

OK,再来说说后处理过程,看是如何得到最终检测结果的。

1. 首先,对heatmaps使用kernel大小为3×3的最大池化层(pad=1),输出分辨率维持不变。将池化后的heatmaps与原heatmaps作比较,于是可以知道,值改变了的位置就是非极大值位置,将这些位置的值(即置信度)置0,那么这些位置在后续就不可能作为可能的角点位置了,这样起到了抑制非极大值的作用(paper中称为NMS,但其实和目标检测常用的NMS有所区别,这里特别说明下);

2. 然后,从heatmaps中根据置信度选择top100个左上角和右下角位置(在所有分类下进行,不区分类别),并且根据对应位置预测的offsets来调整角点位置;

3. 接着,计算左上角和右下角(每个左上角都和其余99个右下角)位置对应预测的embeddings之间的距离,距离大于0.5的、属于不同类别的、坐标关系不满足(右下角坐标需大于左上角)的角点对就不能匹配成一对

4. 紧接着,角点已经完成配对,再次根据每对角点的平均置信度(得分)选出top100对,同时它们的平均得分作为各目标的检测分数

5. 最后,结合原图和镜像图的以上结果,在各类别下对角点对形成的bbox实施soft-nms(也就是说,soft-nms是对原图和镜像图的预测bbox一并做的,但是分类别进行),如果之后每张图片保留下来的bbox大于100个,那么去掉多余的,仅保留得分top100的检测结果。

实验分析

●  性能瓶颈

CornerNet同时输出热图、嵌入和偏移,所有这些结果都会影响检测性能。比如:热图中漏检了任何一个角点就会丢失一个目标、不正确的嵌入将导致许多错误的边界框、预测的偏移不正确则严重影响边界框的定位。

为了理解每个部件对最终的误差有多大程度的影响,作者通过将预测的热图和偏移替换为gt,并在验证集上评估性能,以此来进行误差分析:

由实验结果可知,单独使用gt热图就可以将AP从38.5%提高到74.0%,这表明CornerNet的主要瓶颈在于角点的识别。

●  对负样本位置的惩罚度降低

CornerNet在训练过程中减少了在gt角点位置一定半径的圆内的负样本位置的惩罚。为了理解这对检测性能的影响,作者在实验中额外训练了一个没有降低惩罚度的网络和另一个有惩罚度降低但半径值是固定的网络,然后在验证集上将它们与CornerNet进行比较:

实验结果显示,即使使用固定的半径值,只要有惩罚度降低就可以将基线的AP提升2.7%,而使用基于物体大小计算出来的半径则可以进一步将AP提高2.9%。此外,我们看到减少惩罚度特别有利于大中型目标。

思考

最后,CW谈谈值得思考的几个点:

1.为何减少对部分负样本的惩罚有利于大中型目标的检测,却对小目标的不友好呢?

可以feel到,大中型目标的尺度相对较大,那么即使角点和gt有些许偏移,也是由较高的可能性生成与gt box充分重叠的bbox的。因此,降低惩罚度的背后实际是提供了更多潜在的正样本,于是提高了召回率。

相反,小目标尺度较小,对角点位置检测的要求也因此较为苛刻,对于大中型目标来说降低惩罚度提供了更多潜在的正样本,但对小目标来说可能它们就是实实在在的负样本了。另外,通过实验结果可知,降低惩罚度对于小目标来说其AP比基线也没有下降太多,可以(宽容地)认为没有太大影响。

2.后处理时使用max pooling进行非极大值抑制是否不妥?

想象下,如果两个物体的角点靠得非常近,那么其中一个物体的检测就很有可能被“误杀”掉,可怜不..

不知道为何不基于一个置信度阀值去卡掉不好的检测结果,作者也没有相关的实验说明。

3.测试时为何要连镜像图也一并输入进行检测?

关于这点,作者也没有给出理由,也没有给出实验结果对比如果单独使用原图检测效果如何。CW猜测,使用镜像图,可能是为了更充分地检测角点

在水平镜像图中,右上角和左下角会分别变成左上角和右下角。于是,使用镜像图的话,就可以对原图中相反方向的角点对进行检测,从而弥补在原图中检测角点对不够充分的问题(由前面的实验分析可知,CornerNet的主要瓶颈就在于角点的识别)。

4.为何不适用多尺度特征来进行预测?

作者在paper中强调过,仅使用最后一层特征来进行预测:

Unlike many other state-of-the-art detectors, we only use the features from the last layer of the whole network to make predictions.

怎么好像有点骄傲味道?

对于这点,在paper中其实有“半虚半实”地说到过。作者说,特征图相比于输入图像只下采样了4倍(因此对于小目标影响应该不会太严重),而backbone使用的是Hourglass Networks:在一系列下采样后又上采样至相同的分辨率,同时其中还添加了skip connection,因此最终的输出特征能够同时拥有浅层的全局信息(利于定位)与高层的局部信息(利于识别)

另外,作者也通过实验将backbone替换成ResNet with FPN,结果显示backbone还是使用Hourglass Networks比较好。但是!在实验中,作者仅使用FPN的最后一层进行预测,要是使用FPN多层的特征进行预测的话,性能谁搞谁低还真说不准..

5.为何基于角点能够比基于锚框的检测效果好?

作者在paper中展示了CornerNet与其它anchor-based的检测器的性能比较,结果显示CornerNet能够取得更优的性能。对于这个情况,作者认为:

a). anchor boxes的中心点需要依赖于四条边,而角点却只依赖于两条边,因此角点更易定位(CW觉得应该叫可确定性更加高比较合适,是否真的更易定位难说..)。同时加上使用了corner pooling(这个专门为角点检测而服务的大杀器),于是效果会比anchor-based更佳;

b). 本质上采用了更高效的检测机制:仅使用个corners就能替代了个可能的anchor boxes对于以上b,解释下:假设大小的图像,角点由于仅用位置信息就可代表其可能性,因此有;种;而anchor box的可能性除了与中心点位置有关,还与其长、宽相关。中心点位置可能性有种,而一个anchor box在固定中心点又有种长宽的可能,于是anchor boxes的可能性就是 了。


参考链接:

paper&code:

https://arxiv.org/abs/1808.01244

https://github.com/princeton-vl/CornerNet

作者简介

CW,毕业于中山大学(SYSU)数据科学与计算机学院,就职于腾讯技术工程与事业群(TEG)从事Devops工作,曾在AI LAB实习,实操过道路交通元素与医疗病例图像分割、视频实时人脸检测与表情识别、OCR等项目。

目前在一些自媒体平台上参与外包项目的研发工作,项目专注于CV领域(传统图像处理与深度学习方向)。


往期精彩回顾适合初学者入门人工智能的路线及资料下载机器学习及深度学习笔记等资料打印机器学习在线手册深度学习笔记专辑《统计学习方法》的代码复现专辑
AI基础下载机器学习的数学基础专辑温州大学《机器学习课程》视频
本站qq群851320808,加入微信群请扫码:

【深度学习】CornerNet: 将目标检测问题视作关键点检测与配对相关推荐

  1. CornerNet: 将目标检测问题视作关键点检测与配对

    前言: CornerNet于2019年3月份提出,CW近期回顾了下这个在当时引起不少关注的目标检测模型,它的亮点在于提出了一套新的方法论--将目标检测转化为对物体成对关键点(角点)的检测.通过将目标物 ...

  2. 深度学习论文阅读目标检测篇(七)中英对照版:YOLOv4《Optimal Speed and Accuracy of Object Detection》

    深度学习论文阅读目标检测篇(七)中英对照版:YOLOv4<Optimal Speed and Accuracy of Object Detection> Abstract 摘要 1. In ...

  3. 深度学习时代的目标检测算法综述

    目标检测VS其他计算机视觉问题 分类问题 这或许是计算机视觉领域内最著名的问题.它主要指将一张图像归为某种类别.学术界最流行的一类数据集是ImageNet,由数以百万计已分好类的图像组成,(部分)用于 ...

  4. HALCON 20.11:深度学习笔记(11)---目标检测

    HALCON 20.11:深度学习笔记(11)---目标检测 HALCON 20.11.0.0中,实现了深度学习方法. 本章讲解了如何使用基于深度学习的对象检测. 通过对象检测,我们希望在图像中找到不 ...

  5. 目标检测YOLO实战应用案例100讲-基于深度学习的显著性目标检测研究与应用(论文篇)

    目录 基于深度学习的显著性目标检测综述 基于深度学习的显著性目标检测分类及难点分析

  6. 深度学习论文阅读目标检测篇(四)中英文对照版:YOLOv1《 You Only Look Once: Unified, Real-Time Object Detection》

    深度学习论文阅读目标检测篇(四)中英文对照版:YOLOv1< You Only Look Once: Unified, Real-Time Object Detection> Abstra ...

  7. 深度学习论文阅读目标检测篇(一):R-CNN《Rich feature hierarchies for accurate object detection and semantic...》

    深度学习论文阅读目标检测篇(一):R-CNN<Rich feature hierarchies for accurate object detection and semantic segmen ...

  8. 基于深度学习的显著性目标检测方法综述

    源自:电子学报       作者:罗会兰  袁璞  童康 摘 要 显著性目标检测旨在对图像中最显著的对象进行检测和分割,是计算机视觉任务中重要的预处理步骤之一,且在信息检索.公共安全等领域均有广泛的应 ...

  9. 深度学习论文阅读目标检测篇(三):Faster R-CNN《 Towards Real-Time Object Detection with Region Proposal Networks》

    深度学习论文阅读目标检测篇(三):Faster R-CNN< Towards Real-Time Object Detection with Region Proposal Networks&g ...

最新文章

  1. 北理通信男找工作的经历及心得
  2. Hadoop伪分布式环境搭建
  3. 天津工业大学19年计算机考研大纲,2019年天津工业大学《计算机原理及接口技术》考研复试大纲...
  4. 【前端开发系列】—— 利用选择器添加内容
  5. 解读高效的神经架构搜索ENAS
  6. Python3 pymysql连接mysql数据库 windows
  7. 杨森翔的书法【斗方】
  8. Magento--判断checkout中是否使用了coupon code
  9. 教你如何塑造JavaScript牛逼形象
  10. MATLAB函数随笔之计算篇
  11. flume学习(九):使用Morphline Interceptor
  12. 关于学习数据库基础的一点心得体会
  13. 代购类网站商品高清晰大图片(1000x1000)的采集解决方案 - hackercai - 博客园
  14. 加减法叫做什么运算_加减法的意义及运算定律、性质
  15. ubuntu 安装uget 和 flashgot 下载软件相当于windows中的迅雷
  16. 理解Cookie机制
  17. mysql删除分表键_一文看懂 MySQL 分区和分表,提高表增删改查效率
  18. 在OpenCV里实现二维离散卷积1
  19. 支付宝小程序获取php用户id,02支付宝小程序(基于知晓云)~如何获取用户ID
  20. 英文有声读物网站(转贴)

热门文章

  1. Samba nsswitch/pam_winbind.c文件输入验证漏洞
  2. STL之list学习(2)(list代码实现)(只剩最后一步,迭代器升级!!)
  3. 渗透测试网络环境搭建
  4. LInux下装jdk
  5. java学习论坛汇总
  6. FckEditor的安装与设置
  7. jsp stc_为什么说jsp的本质是servlet?
  8. 社区医学的研究方法:调查、流行病学研究、方案评估、临床试验Research Methods in Community Medicine: Surveys, Epidemiological Resear
  9. 跟着JAMA论文学习重复测量资料分析方法
  10. 25接口之间的单继承