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

CornerNet


前言

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

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

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


目录

    研究动机及背景

    概述

    整体框架

    角点检测

    角点配对

    Corner Pooling

    训练

    测试

    实验分析

    思考

研究动机及背景

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

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

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

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

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


概述

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

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

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

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

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


整体框架

Overview

首先将输入图像通过预处理模块: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是C \times H\times WC是物体类别数(不含背景),H \times W是特征图的尺寸。这样,每个通道就对应特定类别物体的角点判断。理想状态下,它是一个二值mask,值为1就代表该位置属于角点,而通常模型预测出来每个位置上的值是0~1,代表该位置属于角点的置信度。

Penalty Reduction

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

penalty reduction

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

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

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

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

a variant of focal loss

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

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


Radius Computation

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

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

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

这时,IoU = \frac{hw}{(h+2r)(w+2r)}

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

(4IoU)r^2+2IoU(h+w)r+(IoU-1)hw=0

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

a=4IoU,b=2IoU(h+w),c=(IoU-1)hw

易知,判别式b^2-4ac>0(因为IoU<1,所以c<0),于是有解:

r=\frac{-b\pm \sqrt{b^2-4ac} }{2a}

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

r=\frac{-b + \sqrt{b^2-4ac} }{2a}


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

这时,IoU=\frac{(h-2r)(w-2r)}{hw}

同上,移项整理得:

4r^{2}-2(h+w)r+(1-IoU)hw=0

此时,根的判别式因子:

a=4,b=-2(h+w),c=(1-IoU)hw

判别式:

b^{2}-4ac=4(h+w)^{2}-16hw(1-IoU) > 4(h+w)^{2}-16hw=4(h-w)^{2}\geq 0

于是,方程有解,并且此时两个根\frac{-b\pm\sqrt{b^{2}-4ac}}{2a}都是正根。为了兼容其它情况,我们需要取小的根,即:

r=\frac{-b-\sqrt{b^{2}-4ac}}{2a}


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

此时,IoU=\frac{(h-r)(w-r)}{2hw-(h-r)(w-r)}

移项整理得:

r^{2}-(h+w)r+\frac{1-IoU}{1+IoU}hw=0

根的判别式因子:

a=1,b=-(h+w),c=\frac{1-IoU}{1+IoU}hw

易证判别式b^{2}-4ac>0(请让CW偷下懒..),最终取较小的根:

r=\frac{-b-\sqrt{b^{2}-4ac}}{2a}

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

r=min(r1,r2,r3)


location offsets

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

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

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

量化误差

其中n是下采样率, (x_{k},y_{k}) 是角点k在原图的位置。

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

smooth l1 loss

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

假设在heatmap上位置(x,y)被预测为角点,其对应预测的offsets为\bar{o}=(\Delta x, \Delta y),那么其映射到原图上的位置就是 (x_{0}=\left[ (x+\Delta x)n \right], y_{0}=\left[ (y+\Delta y)n \right])


角点配对

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

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

pull loss & push loss

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

Corner Pooling

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

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

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

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

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

top-left corner pooling

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


训练

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

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

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

训练损失最终的形式为:

其中\alpha=\beta=0.1,\gamma=1

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


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的输入。


测试

prediction module

模型整体框架的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对这实在不能忍,看了源码后,发现是这样做的:

# new_height, new_width是原图高、宽缩放后的值

# (CornerNet其实还有个多尺度版本,支持对原图多个尺度进行检测)

# inp_height、inp_width就是要输入到网络中的图像高、宽

inp_height = new_height | 127

inp_width = new_width | 127

# 初始化一个全0的图像,这个就是要输入到网络中的图像

images = np.zeros((1, 3, inp_height, inp_width), dtype=np.float32)

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

最后,将原图裁剪下来放置在填充的全0图像中,保持中心对齐,同时会记录原图在这个填充图像中的区域边界:(y_{min}, y_{max}, x_{min},x_{max}),以便后续将检测结果还原到原图坐标空间。也就是说,在网络输入图像中,区域边界以外的部分都是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). 本质上采用了更高效的检测机制:仅使用O(wh)个corners就能替代了O(w^{2}h^{2})个可能的anchor boxes

对于以上b,解释下:

假设h \times w大小的图像,角点由于仅用位置信息就可代表其可能性,因此有O(wh)种;而anchor box的可能性除了与中心点位置有关,还与其长、宽相关。中心点位置可能性有O(wh)种,而一个anchor box在固定中心点又有O(wh)种长宽的可能,于是anchor boxes的可能性就是O(w^{2}h^{2})了。



End

以上就是本文的内容了,若朋友你觉得CW的理解有不妥之处或者你有任何idea想和我交流,欢迎在下方评论区show出。大家一起进行思想的碰撞,才更有意思,更好玩!毕竟,CW不要无聊的风格。

CW
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容