2015-04-28-OpenCV for Matlab Users (2) - cv::Mat 类的属性和方法

我是 OpenCV 乃至 C++ 的初学者,过去的两个礼拜里刚把一个 Matlab 程序转化成使用了 OpenCV 的 C++ 代码,过程中遇到了很多困难。这些困难有些来源于我对 Matlab 和 OpenCV 之间语言和类库设计差异的不了解,有些是单纯的不知道 Matlab 函数的 OpenCV 对照物造成的,还有一些是由于 OpenCV 并不具有一些功能的函数。我打算将这些天的所学所得写下来,借鉴《NumPy for Matlab Users》,姑且将这个系列叫作《OpenCV for Matlab Users》。这篇是这个系列的第 2 篇,因为大体结构最早完成,所以也就先出来了。整个系列的大体安排如下:

  • OpenCV for Matlab Users (1) - MATLAB 与 OpenCV / C++ 设计上的差异
  • OpenCV for Matlab Users (2) - cv::Mat 类的属性和方法
  • OpenCV for Matlab Users (3) - MATLAB 函数的 OpenCV 对应实现
  • OpenCV for Matlab Users (4) - MATLAB 与 OpenCV / C++ 易搞混的语法
  • OpenCV for Matlab Users (5) - OpenCV 自身易搞混的函数辨析

下面开始本章正文:

以我浅薄的 C++ 知识来看,一个对象包括成员变量成员方法两类,有些貌似是成员变量,其实是方法,这篇先按照是成员变量还是方法分为两类。目前成员变量只有 5 个:rows,cols,dims,data,step,还有一些比如像 channels,depth,elemSize,empty,isContinuous,size,total,type 貌似是成员变量,其实是没有参数的成员方法。

1. 成员变量

  • rows

成员变量,返回矩阵的行数

    int imgRows = img.rows;
    std::cout<<"imgRows = "<<imgRows<<std::endl;

  • cols

是成员变量,返回矩阵的列数

    int imgCols = img.cols;
    std::cout<<"imgCols = "<<imgCols<<std::endl;

  • data

成员变量data事实上是指向已分配的内存块的指针,包括图像数据。当不存在数据时,它被简单设置为0.

    uchar *data = img.data;
    std::cout<<"*data = "<<(int)*data<<std::endl;
    std::cout<<"data[0] = "<<(int)data[0]<<std::endl;

  • dims

成员变量,返回一个图像的维数,但是好奇怪,我以为会有 3 维的,结果返回的还是 2

    int imgDims = img.dims;
    std::cout<<"imgDims = "<<imgDims<<std::endl;

  • step

成员变量,代表以字节为单位的图像的行宽,即列数(包括填补像素),即使你的图像元素类型不是 uchar,step 仍然带代表着行的字节数

    int imgStep = img.step;
    std::cout<<"imgStep = "<<imgStep<<std::endl;

2. 成员方法

  • at

成员函数,at(int y, int x) 可以用来存取图像元素。 但是必须在编译期知道图像的数据类型,因为 cv::Mat 可以存放任意数据类型的元素。所以使用 at 方法要指定数据类型,而且 at 方法本身不会进行任何数据类型转换。

    cv::Vec3b vec3b = img.at<cv::Vec3b>(0,0);
    uchar vec3b0 = img.at<cv::Vec3b>(0,0)[0];
    uchar vec3b1 = img.at<cv::Vec3b>(0,0)[1];
    uchar vec3b2 = img.at<cv::Vec3b>(0,0)[2];
    std::cout<<"vec3b = "<<vec3b<<std::endl;
    std::cout<<"vec3b0 = "<<(int)vec3b0<<std::endl;
    std::cout<<"vec3b1 = "<<(int)vec3b1<<std::endl;
    std::cout<<"vec3b2 = "<<(int)vec3b2<<std::endl;

cv::Vec3b,即由三个 unsigned char 组成的向量。

  • channels

方法,返回通道数

    int imgChannels = img.channels();
    std::cout<<"imgChannels = "<<imgChannels<<std::endl;

  • clone

貌似也是深拷贝,但是除了返回值类型不同,clone 返回 cv::Mat,而 copyTo 是 void 类型,其余 clone 跟 copyTo 有什么区别我还没搞清楚

    cv::Mat cloneMat1 = cv::Mat::ones(3,4,CV_64F);
    cv::Mat cloneMat2 = cloneMat1.clone();   
    cloneMat1.at<double>(0,0) = 0.0;
    std::cout<<"cloneMat1 = "<<cloneMat1<<std::endl;
    std::cout<<"cloneMat2 = "<<cloneMat2<<std::endl;

  • col

返回指定的一列(从 0 开始)

    cv::Mat colMat = img.col(0);
    std::cout<<"colMat.size() = "<<colMat.size()<<std::endl;

  • colRange

方法,返回若干列组成的矩阵

    cv::Mat colRangeImg = img.colRange(imgCols / 2, imgCols);
    cv::imshow("colRangeImg",colRangeImg);
    cv::waitKey();

  • convertTo

方法,在缩放或不缩放的情况下转换为另一种指定的数据类型

    cv::Mat doubleImg;
    img.convertTo(doubleImg, CV_64FC4);
    std::cout<<"doubleImg.channels() = "<<doubleImg.channels()<<std::endl;
    cv::imshow("doubleImg", doubleImg);
    cv::waitKey();

  • copyTo

把矩阵深拷贝赋值给另一个矩阵

    cv::Mat copyMat1 = cv::Mat::ones(3,4,CV_64F);
    cv::Mat copyMat2;
    copyMat1.copyTo(copyMat2);
    copyMat1.at<double>(0,0) = 0.0;
    std::cout<<"copyMat1 = "<<copyMat1<<std::endl;
    std::cout<<"copyMat2 = "<<copyMat2<<std::endl;

  • create

create 类似Mat(nrows,ncols,type [,fillValue])构造函数,把当前对象重新绑定到一个新的矩阵对象上

    cv::Mat createMat = cv::Mat::ones(3,4,CV_64F);
    createMat.create(5,6, CV_8UC(2));
    std::cout<<"createMat = "<<createMat<<std::endl;

  • cross

计算两个 3 元素向量的叉乘积,注意,必须是 3 个元素的。

    cv::Mat crossVec1 = cv::Mat::ones(1,3,CV_64F);
    cv::Mat crossVec2 = cv::Mat::ones(1,3,CV_64F);
    cv::Mat crossMat  = crossVec1.cross(crossVec2);
    std::cout<<"crossMat = "<<crossMat<<std::endl;

  • depth

方法,该方法返回矩阵元素深度(每个单独的通道类型)的标识符。

    int imgDepth = img.depth();
    std::cout<<"imgDepth = "<<imgDepth<<std::endl;

  • diag

抽取矩阵对角线上的元素,返回的是一个 min(rows,cols) * channels 的矩阵。

    cv::Mat diagMat = img.diag();
    //std::cout<<"diagMat = "<<diagMat<<std::endl;
    std::cout<<"diagMat.rows = "<<diagMat.rows<<std::endl;
    std::cout<<"diagMat.cols = "<<diagMat.cols<<std::endl;

  • dot

内积运算,最后返回一个 double 类型的数。两个矩阵必须是相同大小的。如果是非单行或者非单列矩阵,那么结果相当于把两个矩阵拉成一行或者一列后做内积。多个通道的,每个通道的内积会被加起来。

    cv::Mat onesMat1 = cv::Mat::ones(3,4,CV_64F);
    cv::Mat onesMat2 = cv::Mat::ones(3,4,CV_64F);
    double dotVal   = onesMat1.dot(onesMat2);
    std::cout<<"dotVal = "<<dotVal<<std::endl;

  • elemSize

方法,返回图像(矩阵)像素(元素)大小 (以字节为单位),因为我们这里读入的 PNG 格式,有 4 个通道,每个通道都是 uchar 类型的,所以是返回 4 个字节。

    int elementSize = img.elemSize();
    std::cout<<"elementSize = "<<elementSize<<std::endl;

  • elemSize1

方法,以字节为单位返回每个矩阵元素通道的大小,结果也就是上面的 elemSize 方法得到的除以通道数。

    int elementSize1 = img.elemSize1();
    std::cout<<"elementSize1 = "<<elementSize1<<std::endl;

  • empty

方法,如果是个空矩阵,则返回 true。

    bool isEmpty = img.empty();
    std::cout<<"isEmpty = "<<isEmpty<<std::endl;

  • eye

返回单位矩阵,跟 Matlab 类似,先是行数,然后列数,最后指定元素类型

    cv::Mat eyeMat = cv::Mat::eye(3,4,CV_64F);
    std::cout<<"eyeMat = "<<eyeMat<<std::endl;

  • isContinuous

方法,返回矩阵是否连续

    bool isContinuous = img.isContinuous();
    std::cout<<"isContinuous = "<<isContinuous<<std::endl;

  • ones

方法,产生全一矩阵,跟matlab的ones类似,先是行数,然后是列数,就是必须要显式地指定数据元素(像素)类型

    cv::Mat onesMat = cv::Mat::ones(4, 3, CV_64F);
    std::cout<<"onesMat = "<<onesMat<<std::endl;

  • ptr

函数,为了简化指针运算,ptr 函数可以得到图像给定行的首地址。ptr 函数是一个模板函数,它返回第 j 行的首地址:

    uchar *ptr = img.ptr<uchar>(0);
    std::cout<<"*ptr = "<<(int)*ptr<<std::endl;
    std::cout<<"ptr[0] = "<<(int)ptr[0]<<std::endl;

  • reshape

方法,返回一个改变了形状的矩阵,但要注意的是,跟 Matlab 不同,OpenCV 中的 reshape 的第一个参数是 通道数,第二个参数是 行数,且只有这两个参数,如果想维持通道数不变,那对应位置填 0 即可。

    cv::Mat reshapeMat1 = cv::Mat::ones(3,4,CV_64F);
    cv::Mat reshapeMat2 = reshapeMat1.reshape(0, 2);
    std::cout<<"reshapeMat1 = "<<reshapeMat1<<std::endl;
    std::cout<<"reshapeMat2 = "<<reshapeMat2<<std::endl;

  • row

方法,返回矩阵特定的某一行(行号从 0 开始)

    cv::Mat rowMat = img.row(0);
    std::cout<<"rowMat.size() = "<<rowMat.size()<<std::endl;

  • rowRange

方法,取规定的行,返回一个子矩阵

    int imgRows = img.rows;
    std::cout<<"imgRows = "<<imgRows<<std::endl;

  • setTo

方法,将矩阵元素都设置为某个值

    cv::Mat setMat = cv::Mat::ones(3,4,CV_64F);
    setMat.setTo(cv::Scalar(0));
    std::cout<<"setMat = "<<setMat<<std::endl;
    setMat.row(0).setTo(2);
    std::cout<<"setMat = "<<setMat<<std::endl;

  • size

方法,返回一个 cv::Size 对象,使用情形如下:

    cv::Size imgSize = img.size();
    int imgHeight = imgSize.height;
    int imgWidth  = imgSize.width;
    std::cout<<"imgSize = "<<imgSize<<std::endl;
    std::cout<<"imgHeight = "<<imgHeight<<std::endl;
    std::cout<<"imgWidth = "<<imgWidth<<std::endl;

需要注意的是,它只能返回2维的尺寸,还有就是它先返回的是列数,然后才是行数,行数列数分别可以用 height 和 width 两个成员变量得到。

  • t

函数,返回当前对象的转置矩阵

    cv::Mat tMat1 = cv::Mat::ones(3,4,CV_64F);
    tMat1.at<double>(1,2) = 0.0;
    cv::Mat tMat2 = tMat1.t();
    std::cout<<"tMat1 = "<<tMat1<<std::endl;
    std::cout<<"tMat2 = "<<tMat2<<std::endl;

  • total

方法,该方法返回数组元素(如果该数组表示图像的像素数)的数目,注意这个跟 Matlab 里面的 numel 可不同,Matlab 是将对象当作是矩阵(张量)来处理,只不过图像刚好可以用矩阵和张量来表示,本质上Matlab还是一个通用的数学软件,而 OpenCV 是讲对象当作图像来看待,是专门为图像而设计的,所以 即使这个图像有 RGB 3 个通道,total 返回的也只是 height * width,不过这也对,这就是像素数。

    int imgPixels = img.total();
    std::cout<<"imgPixels = "<<imgPixels<<std::endl;

  • type

方法,返回图像像素的数据类型

    int imgType = img.type();
    std::cout<<"imgType = "<<imgType<<std::endl;

具体返回的数值对应什么类型,可以看这篇文章《LIST OF MAT TYPE IN OPENCV

  • zeros

方法,产生全零矩阵,跟 Matlab 的 zeros 类似,先是行数,然后是列数,就是必须要显式地指定数据元素(像素)类型

    cv::Mat zerosMat = cv::Mat::zeros(4, 3, CV_64F);
    std::cout<<"zerosMat = "<<zerosMat<<std::endl;

3. 内存管理相关

有关引用计数的一些函数,作为普通用户,我们并不需要了解。

  • addref

该方法递增与矩阵数据关联的引用计数,通常情况下,为避免内存泄漏,不应显式调用该方法。

  • release

在必要的情况下,递减引用计数并释放该矩阵。

  • ~Mat

4. 待日后补充

下面是一些我目前还没有用到,或者在 Reference Manual 里面搜不到相关介绍的,留待日后补充:

  • adjustROI
  • AUTO_STEP
  • copySize
  • checkVector
  • CONTINUOUS_FLAG
  • SUBMATRIX_FLAG
  • allocate
  • assignTo
  • step1
  • deallocate
  • datastart
  • datalimit
  • dataend
  • flags
  • initEmpty
  • isSubmatrix
  • locateROI
  • MAGIC_VAL
  • operator cv::Matx<_Tp, m, n>
  • operator cv::Vec<_Tp, n>
  • operator_CvMat
  • operator CvMatND
  • operator IplImage
  • operator std::vector<_Tp, std::allocator><_Tp>>
  • operator()
  • operator=
  • push_back_
  • pop_back
  • refcount
  • reserve
  • mul
  • inv
  • begin
  • end

集合终止位置的迭代器,但是end 方法得到的迭代器其实已经超出了集合。这也意味着迭代过程必须在迭代器到达这个位置时结束

  • push_back

方法,如果对矩阵做,是添加行的。

还有一些函数,根据 Reference Manual 的应该是采用 cv::resize() 这样的方式调用,比如
resize

最后,上述代码整体如下:

#include <opencv2/opencv.hpp>
#include <iostream>
 
int main(int argc, char **argv)
{ 
    std::string imgPathStr("D:\\openCV_build\\doc\\opencv-logo2.png");
    cv::Mat img = cv::imread(imgPathStr,-1); // Read image
    //cv::imshow("img",img);
    //cv::waitKey();
 
    std::vector<cv::Mat> rgbImg;   
    cv::split(img,rgbImg);
    cv::Mat rgbImg_R = rgbImg[2];
    cv::Mat rgbImg_G = rgbImg[1];
    cv::Mat rgbImg_B = rgbImg[0];
 
    // Attributes
    int imgRows = img.rows;
    std::cout<<"imgRows = "<<imgRows<<std::endl;
    int imgCols = img.cols;
    std::cout<<"imgCols = "<<imgCols<<std::endl;
 
 
    // Methods
    cv::Mat rowRangeImg = img.rowRange(imgRows / 2, imgRows);
    //cv::imshow("rowRangeImg",rowRangeImg);
    //cv::waitKey();
 
    cv::Mat colRangeImg = img.colRange(imgCols / 2, imgCols);
    //cv::imshow("colRangeImg",colRangeImg);
    //cv::waitKey();
 
    cv::Size imgSize = img.size();
    int imgHeight = imgSize.height;
    int imgWidth  = imgSize.width;
    std::cout<<"imgSize = "<<imgSize<<std::endl;
    std::cout<<"imgHeight = "<<imgHeight<<std::endl;
    std::cout<<"imgWidth = "<<imgWidth<<std::endl;
 
    int imgChannels = img.channels();
    std::cout<<"imgChannels = "<<imgChannels<<std::endl;
 
    int imgType = img.type();
    std::cout<<"imgType = "<<imgType<<std::endl;
 
    int imgPixels = img.total();
    std::cout<<"imgPixels = "<<imgPixels<<std::endl;
 
    bool isContinuous = img.isContinuous();
    std::cout<<"isContinuous = "<<isContinuous<<std::endl;    
 
    int elementSize = img.elemSize();
    std::cout<<"elementSize = "<<elementSize<<std::endl;
 
    int elementSize1 = img.elemSize1();
    std::cout<<"elementSize1 = "<<elementSize1<<std::endl;
 
    int imgDepth = img.depth();
    std::cout<<"imgDepth = "<<imgDepth<<std::endl;
 
    bool isEmpty = img.empty();
    std::cout<<"isEmpty = "<<isEmpty<<std::endl;
 
    int imgDims = img.dims;
    std::cout<<"imgDims = "<<imgDims<<std::endl;
 
    cv::Mat onesMat = cv::Mat::ones(4, 3, CV_64F);
    std::cout<<"onesMat = "<<onesMat<<std::endl;
 
    cv::Mat zerosMat = cv::Mat::zeros(4, 3, CV_64F);
    std::cout<<"zerosMat = "<<zerosMat<<std::endl;
 
    int imgStep = img.step;
    std::cout<<"imgStep = "<<imgStep<<std::endl;
 
    cv::Mat doubleImg;
    img.convertTo(doubleImg, CV_64FC4);
    std::cout<<"doubleImg.channels() = "<<doubleImg.channels()<<std::endl;
    //cv::imshow("doubleImg", doubleImg);
    //cv::waitKey();
 
    cv::Mat createMat = cv::Mat::ones(3,4,CV_64F);
    createMat.create(5,6, CV_8UC(2));
    //std::cout<<"createMat = "<<createMat<<std::endl;
 
    cv::Mat copyMat1 = cv::Mat::ones(3,4,CV_64F);
    cv::Mat copyMat2;
    copyMat1.copyTo(copyMat2);
    copyMat1.at<double>(0,0) = 0.0;
    //std::cout<<"copyMat1 = "<<copyMat1<<std::endl;
    //std::cout<<"copyMat2 = "<<copyMat2<<std::endl;
 
    cv::Mat cloneMat1 = cv::Mat::ones(3,4,CV_64F);
    cv::Mat cloneMat2 = cloneMat1.clone();   
    cloneMat1.at<double>(0,0) = 0.0;
    //std::cout<<"cloneMat1 = "<<cloneMat1<<std::endl;
    //std::cout<<"cloneMat2 = "<<cloneMat2<<std::endl;
 
    cv::Mat eyeMat = cv::Mat::eye(3,4,CV_64F);
    std::cout<<"eyeMat = "<<eyeMat<<std::endl;
 
    cv::Mat diagMat = img.diag();
    //std::cout<<"diagMat = "<<diagMat<<std::endl;
    std::cout<<"diagMat.rows = "<<diagMat.rows<<std::endl;
    std::cout<<"diagMat.cols = "<<diagMat.cols<<std::endl;
 
    cv::Mat rowMat = img.row(0);
    std::cout<<"rowMat.size() = "<<rowMat.size()<<std::endl;
 
    cv::Mat colMat = img.col(0);
    std::cout<<"colMat.size() = "<<colMat.size()<<std::endl;
 
    cv::Mat onesMat1 = cv::Mat::ones(3,4,CV_64F);
    cv::Mat onesMat2 = cv::Mat::ones(3,4,CV_64F);
    double dotVal   = onesMat1.dot(onesMat2);
    std::cout<<"dotVal = "<<dotVal<<std::endl;
 
    cv::Mat crossVec1 = cv::Mat::ones(1,3,CV_64F);
    cv::Mat crossVec2 = cv::Mat::ones(1,3,CV_64F);
    cv::Mat crossMat  = crossVec1.cross(crossVec2);
    std::cout<<"crossMat = "<<crossMat<<std::endl;
 
    cv::Mat tMat1 = cv::Mat::ones(3,4,CV_64F);
    tMat1.at<double>(1,2) = 0.0;
    cv::Mat tMat2 = tMat1.t();
    std::cout<<"tMat1 = "<<tMat1<<std::endl;
    std::cout<<"tMat2 = "<<tMat2<<std::endl;
 
    cv::Mat reshapeMat1 = cv::Mat::ones(3,4,CV_64F);
    cv::Mat reshapeMat2 = reshapeMat1.reshape(0, 2);
    std::cout<<"reshapeMat1 = "<<reshapeMat1<<std::endl;
    std::cout<<"reshapeMat2 = "<<reshapeMat2<<std::endl;
 
    cv::Mat setMat = cv::Mat::ones(3,4,CV_64F);
    setMat.setTo(cv::Scalar(0));
    std::cout<<"setMat = "<<setMat<<std::endl;
    setMat.row(0).setTo(2);
    std::cout<<"setMat = "<<setMat<<std::endl;
 
    uchar *data = img.data;
    std::cout<<"*data = "<<(int)*data<<std::endl;
    std::cout<<"data[0] = "<<(int)data[0]<<std::endl;
 
    uchar *ptr = img.ptr<uchar>(0);
    std::cout<<"*ptr = "<<(int)*ptr<<std::endl;
    std::cout<<"ptr[0] = "<<(int)ptr[0]<<std::endl;
 
    cv::Vec3b vec3b = img.at<cv::Vec3b>(0,0);
    uchar vec3b0 = img.at<cv::Vec3b>(0,0)[0];
    uchar vec3b1 = img.at<cv::Vec3b>(0,0)[1];
    uchar vec3b2 = img.at<cv::Vec3b>(0,0)[2];
    std::cout<<"vec3b = "<<vec3b<<std::endl;
    std::cout<<"vec3b0 = "<<(int)vec3b0<<std::endl;
    std::cout<<"vec3b1 = "<<(int)vec3b1<<std::endl;
    std::cout<<"vec3b2 = "<<(int)vec3b2<<std::endl;
 
    std::system("PAUSE");
    return 0;
}

初写于 2015-04-28,未完待续。

首发于 Yimian Dai's Homepage,转载请注明出处。

参考资料:

OpenCV参考手册之Mat类详解(一)
OpenCV参考手册之Mat类详解(二)
OpenCV参考手册之Mat类详解(三)

《OpenCV 2 计算机视觉编程手册》

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

推荐阅读更多精彩内容