10.马赛克原理之简单提高图像算法性能(颜色空间缩减和计时函数) --- OpenCV从零开始到图像(人脸 + 物体)识别系列


本文作者:小嗷

微信号:aoxiaoji

简书链接:https://www.jianshu.com/u/45da1fbce7d0

关键词:颜色空间缩减和计时函数【两个简便的计时函数—getTickCount()和getTickFrequency()】


1.前言.png
2.前言.png
2. 简介及用途
  1. 计时函数用途:计算耗时工作

  2. 颜色空间缩减用途:颜色空间缩减。(公式如下)

3.颜色缩减法公式
  1. 如何遍历图像中的每一个像素点? 请看第8篇代码【更正曝光不足的图像(图像的对比度和亮度及轨迹条)】
  2. OpenCv的矩阵值是如何存储的? 请看第9篇恶心的内容【基本的图像容器(Mat详解)】
  3. 如何测试我们所实现算法的性能?通过算法耗时和效果的对比【计时函数】
  4. 查找表是什么?为什么要用它?等一下,会说到。

图像矩阵的大小取决于我们使用的颜色模型,确切的说,取决于所用通道数。如果是灰度图像,矩阵就会像这样:

4.灰度图像矩阵

而对于多通道图像来说,矩阵中的列会包含多个子列,其子列个数与通道数相等,例如,RGB颜色模型的矩阵:

5.RGB颜色矩阵

在这里,我们需要注意的是:子列的通道顺序是反过来的,是BGR,而不是我们数字图像书中常说的RGB.很多情况下,因为内存够大,可以实现连续存储。连续存储可以提高图像的扫描速度。我们可以使用isContinuous来判断矩阵是否连续矩阵。

6.颜色空间缩减和计时函数.png

3.1 颜色空间的缩减

如果矩阵元素存储的是单通道像素,使用C或C++的无符号字符类型,那么像素可能有256个不同的值.但若是三通道图像,这种存储格式的颜色数就太多了(确切的说,有一千六百多万种).用如此之多的颜色 可能会对我们算法的性能造成严重的影响。其实,有时候,我们仅适用这些颜色的一小部分,就足以达到同样的效果。

这种情况下,常用的一种方法就是【颜色空间缩减】.其做法是:将现有颜色值除以某个输入值,以获得较少的颜色数。例如:0到9可取新值0;10到19取值1,以此类推。

uchar(无符号字符,即0到255之间取值的数)类型除以int值,结果仍为char。因为结果是char类型的, 所以,求出来小数也要向下取整。利用这一点,刚才提到在uchar定义域中进行颜色缩减运算就可以表达为下列形式:

Inew=Iold/10*10;

这样的话,简单的颜色缩减算法就可以由以下两步组成:

1--遍历图像矩阵中的每一个像素 (第8篇用过)

2--对像素应用上述公式

值得注意的是,我们这里用到了除法和乘法运算,而这种运算又特别费时,所以,我们应尽可能用代价较低的加减,赋值等运算替换他们。

3.2 计时函数

  1. OpenCv为我们提供了两个简单的计时函数---getTickCount()和getTickFrequency()
  2. getTickCount()函数---返回CPU自某个事件以来走过的----时钟周期数
  3. getTickFrequency()函数返回CUP一秒钟所走的时钟周期数。这样,我们就可以轻松的以秒为单位对某运算计时

这两个函数组合起来的使用如下所示:

//【5】记录其实时间  
double timeStartPtr=static_cast<double>(getTickCount());  
//【6】调用颜色空间缩减函数  
colorReducePtr(srcImg,dstImgPtr,32);  
//【7】计算运行时间,并输出  
double timeExpensePtr=((double)getTickCount()-timeStartPtr)/getTickFrequency();  
cout<<"【1】ptr指针操作像素,此方法运行时间为---------->"<<timeExpensePtr<<endl;  

我一般都是复制粘贴计时函数,哈哈哈

3.3 访问图像中像素的三类方法

任何图像处理算法。都是从操作每个像素开始的。即使我们不会使用OpenCv提供的各种图像处理函数,只要我们了解图像处理算法的基本原理,也可以写出具有相同功能的程序.在OpenCv中,提供了三种访问每个像素的方法:

1--指针访问----------ptr<>()函数  
2--动态地址计算------at<>()函数  
3--迭代器iterator  
这三种方法,在访问速度上略有差异,指针比较快。 
11.相关代码
image.png
7.2代码图.PNG
7.3代码图.PNG
7.4代码图.PNG
7.5代码图.PNG
7.6代码图.PNG
image.png
7.8代码图.PNG
7.9代码图.PNG
7.10代码图.PNG
7.11代码图.PNG
7.12代码图.PNG
//      程序描述:来自一本国外OpenCV2书籍的示例-遍历图像像素的14种方法
//------------------------------------------------------------------------------------------------

/*------------------------------------------------------------------------------------------*\
This file contains material supporting chapter 2 of the cookbook:
Computer Vision Programming using the OpenCV Library.
by Robert Laganiere, Packt Publishing, 2011.

This program is free software; permission is hereby granted to use, copy, modify,
and distribute this source code, or portions thereof, for any purpose, without fee,
subject to the restriction that the copyright notice may not be removed
or altered from any source or altered source distribution.
The software is released on an as-is basis and without any warranties of any kind.
In particular, the software is not guaranteed to be fault-tolerant or free from failure.
The author disclaims all warranties with regard to this software, any use,
and any consequent failure, is purely the responsibility of the user.

Copyright (C) 2010-2011 Robert Laganiere, www.laganiere.name
\*------------------------------------------------------------------------------------------*/

//---------------------------------【头文件、命名空间包含部分】-----------------------------
//      描述:包含程序所使用的头文件和命名空间
//-------------------------------------------------------------------------------------------------
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
using namespace cv;
using namespace std;

//---------------------------------【宏定义部分】---------------------------------------------
//      描述:包含程序所使用宏定义
//-------------------------------------------------------------------------------------------------
#define NTESTS 14
#define NITERATIONS 20

//----------------------------------------- 【方法一】-------------------------------------------
//      说明:利用.ptr 和 []
//-------------------------------------------------------------------------------------------------
void colorReduce0(Mat &image, int div = 64) {

   int nl = image.rows; //行数
   int nc = image.cols * image.channels(); //每行元素的总元素数量

   for (int j = 0; j<nl; j++)
   {

       uchar* data = image.ptr<uchar>(j);

       for (int i = 0; i<nc; i++)
       {

           //-------------开始处理每个像素-------------------

           data[i] = data[i] / div*div + div / 2;

           //-------------结束像素处理------------------------

       } //单行处理结束                  
   }
}

//-----------------------------------【方法二】-------------------------------------------------
//      说明:利用 .ptr 和 * ++ 
//-------------------------------------------------------------------------------------------------
void colorReduce1(Mat &image, int div = 64) {

   int nl = image.rows; //行数
   int nc = image.cols * image.channels(); //每行元素的总元素数量

   for (int j = 0; j<nl; j++)
   {

       uchar* data = image.ptr<uchar>(j);

       for (int i = 0; i<nc; i++)
       {

           //-------------开始处理每个像素-------------------

           *data++ = *data / div*div + div / 2;

           //-------------结束像素处理------------------------

       } //单行处理结束              
   }
}

//-----------------------------------------【方法三】-------------------------------------------
//      说明:利用.ptr 和 * ++ 以及模操作
//-------------------------------------------------------------------------------------------------
void colorReduce2(Mat &image, int div = 64) {

   int nl = image.rows; //行数
   int nc = image.cols * image.channels(); //每行元素的总元素数量

   for (int j = 0; j<nl; j++)
   {

       uchar* data = image.ptr<uchar>(j);

       for (int i = 0; i<nc; i++)
       {

           //-------------开始处理每个像素-------------------

           int v = *data;
           *data++ = v - v%div + div / 2;

           //-------------结束像素处理------------------------

       } //单行处理结束                   
   }
}

//----------------------------------------【方法四】---------------------------------------------
//      说明:利用.ptr 和 * ++ 以及位操作
//----------------------------------------------------------------------------------------------------
void colorReduce3(Mat &image, int div = 64) {

   int nl = image.rows; //行数
   int nc = image.cols * image.channels(); //每行元素的总元素数量
   int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0));
   //掩码值
   uchar mask = 0xFF << n; // e.g. 对于 div=16, mask= 0xF0

   for (int j = 0; j<nl; j++) {

       uchar* data = image.ptr<uchar>(j);

       for (int i = 0; i<nc; i++) {

           //------------开始处理每个像素-------------------

           *data++ = *data&mask + div / 2;

           //-------------结束像素处理------------------------
       }  //单行处理结束            
   }
}

//----------------------------------------【方法五】----------------------------------------------
//      说明:利用指针算术运算
//---------------------------------------------------------------------------------------------------
void colorReduce4(Mat &image, int div = 64) {

   int nl = image.rows; //行数
   int nc = image.cols * image.channels(); //每行元素的总元素数量
   int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0));
   int step = image.step; //有效宽度
                          //掩码值
   uchar mask = 0xFF << n; // e.g. 对于 div=16, mask= 0xF0

                           //获取指向图像缓冲区的指针
   uchar *data = image.data;

   for (int j = 0; j<nl; j++)
   {

       for (int i = 0; i<nc; i++)
       {

           //-------------开始处理每个像素-------------------

           *(data + i) = *data&mask + div / 2;

           //-------------结束像素处理------------------------

       } //单行处理结束              

       data += step;  // next line
   }
}

//---------------------------------------【方法六】----------------------------------------------
//      说明:利用 .ptr 和 * ++以及位运算、image.cols * image.channels()
//-------------------------------------------------------------------------------------------------
void colorReduce5(Mat &image, int div = 64) {

   int nl = image.rows; //行数
   int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0));
   //掩码值
   uchar mask = 0xFF << n; // e.g. 例如div=16, mask= 0xF0

   for (int j = 0; j<nl; j++)
   {

       uchar* data = image.ptr<uchar>(j);

       for (int i = 0; i<image.cols * image.channels(); i++)
       {

           //-------------开始处理每个像素-------------------

           *data++ = *data&mask + div / 2;

           //-------------结束像素处理------------------------

       } //单行处理结束            
   }
}

// -------------------------------------【方法七】----------------------------------------------
//      说明:利用.ptr 和 * ++ 以及位运算(continuous)
//-------------------------------------------------------------------------------------------------
void colorReduce6(Mat &image, int div = 64) {

   int nl = image.rows; //行数
   int nc = image.cols * image.channels(); //每行元素的总元素数量

   if (image.isContinuous())
   {
       //无填充像素
       nc = nc*nl;
       nl = 1;  // 为一维数列
   }

   int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0));
   //掩码值
   uchar mask = 0xFF << n; // e.g. 比如div=16, mask= 0xF0

   for (int j = 0; j<nl; j++) {

       uchar* data = image.ptr<uchar>(j);

       for (int i = 0; i<nc; i++) {

           //-------------开始处理每个像素-------------------

           *data++ = *data&mask + div / 2;

           //-------------结束像素处理------------------------

       } //单行处理结束                   
   }
}

//------------------------------------【方法八】------------------------------------------------
//      说明:利用 .ptr 和 * ++ 以及位运算 (continuous+channels)
//-------------------------------------------------------------------------------------------------
void colorReduce7(Mat &image, int div = 64) {

   int nl = image.rows; //行数
   int nc = image.cols; //列数

   if (image.isContinuous())
   {
       //无填充像素
       nc = nc*nl;
       nl = 1;  // 为一维数组
   }

   int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0));
   //掩码值
   uchar mask = 0xFF << n; // e.g. 比如div=16, mask= 0xF0

   for (int j = 0; j<nl; j++) {

       uchar* data = image.ptr<uchar>(j);

       for (int i = 0; i<nc; i++) {

           //-------------开始处理每个像素-------------------

           *data++ = *data&mask + div / 2;
           *data++ = *data&mask + div / 2;
           *data++ = *data&mask + div / 2;

           //-------------结束像素处理------------------------

       } //单行处理结束                    
   }
}

// -----------------------------------【方法九】 ------------------------------------------------
//      说明:利用Mat_ iterator
//-------------------------------------------------------------------------------------------------
void colorReduce8(Mat &image, int div = 64) {

   //获取迭代器
   Mat_<Vec3b>::iterator it = image.begin<Vec3b>();
   Mat_<Vec3b>::iterator itend = image.end<Vec3b>();

   for (; it != itend; ++it) {

       //-------------开始处理每个像素-------------------

       (*it)[0] = (*it)[0] / div*div + div / 2;
       (*it)[1] = (*it)[1] / div*div + div / 2;
       (*it)[2] = (*it)[2] / div*div + div / 2;

       //-------------结束像素处理------------------------
   }//单行处理结束  
}

//-------------------------------------【方法十】-----------------------------------------------
//      说明:利用Mat_ iterator以及位运算
//-------------------------------------------------------------------------------------------------
void colorReduce9(Mat &image, int div = 64) {

   // div必须是2的幂
   int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0));
   //掩码值
   uchar mask = 0xFF << n; // e.g. 比如 div=16, mask= 0xF0

                           // 获取迭代器
   Mat_<Vec3b>::iterator it = image.begin<Vec3b>();
   Mat_<Vec3b>::iterator itend = image.end<Vec3b>();

   //扫描所有元素
   for (; it != itend; ++it)
   {

       //-------------开始处理每个像素-------------------

       (*it)[0] = (*it)[0] & mask + div / 2;
       (*it)[1] = (*it)[1] & mask + div / 2;
       (*it)[2] = (*it)[2] & mask + div / 2;

       //-------------结束像素处理------------------------
   }//单行处理结束  
}

//------------------------------------【方法十一】---------------------------------------------
//      说明:利用Mat Iterator_
//-------------------------------------------------------------------------------------------------
void colorReduce10(Mat &image, int div = 64) {

   //获取迭代器
   Mat_<Vec3b> cimage = image;
   Mat_<Vec3b>::iterator it = cimage.begin();
   Mat_<Vec3b>::iterator itend = cimage.end();

   for (; it != itend; it++) {

       //-------------开始处理每个像素-------------------

       (*it)[0] = (*it)[0] / div*div + div / 2;
       (*it)[1] = (*it)[1] / div*div + div / 2;
       (*it)[2] = (*it)[2] / div*div + div / 2;

       //-------------结束像素处理------------------------
   }
}

//--------------------------------------【方法十二】--------------------------------------------
//      说明:利用动态地址计算配合at
//-------------------------------------------------------------------------------------------------
void colorReduce11(Mat &image, int div = 64) {

   int nl = image.rows; //行数
   int nc = image.cols; //列数

   for (int j = 0; j<nl; j++)
   {
       for (int i = 0; i<nc; i++)
       {

           //-------------开始处理每个像素-------------------

           image.at<Vec3b>(j, i)[0] = image.at<Vec3b>(j, i)[0] / div*div + div / 2;
           image.at<Vec3b>(j, i)[1] = image.at<Vec3b>(j, i)[1] / div*div + div / 2;
           image.at<Vec3b>(j, i)[2] = image.at<Vec3b>(j, i)[2] / div*div + div / 2;

           //-------------结束像素处理------------------------

       } //单行处理结束                 
   }
}

//----------------------------------【方法十三】----------------------------------------------- 
//      说明:利用图像的输入与输出
//-------------------------------------------------------------------------------------------------
void colorReduce12(const Mat &image, //输入图像
   Mat &result,      // 输出图像
   int div = 64) {

   int nl = image.rows; //行数
   int nc = image.cols; //列数

                        //准备好初始化后的Mat给输出图像
   result.create(image.rows, image.cols, image.type());

   //创建无像素填充的图像
   nc = nc*nl;
   nl = 1;  //单维数组

   int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0));
   //掩码值
   uchar mask = 0xFF << n; // e.g.比如div=16, mask= 0xF0

   for (int j = 0; j<nl; j++) {

       uchar* data = result.ptr<uchar>(j);
       const uchar* idata = image.ptr<uchar>(j);

       for (int i = 0; i<nc; i++) {

           //-------------开始处理每个像素-------------------

           *data++ = (*idata++)&mask + div / 2;
           *data++ = (*idata++)&mask + div / 2;
           *data++ = (*idata++)&mask + div / 2;

           //-------------结束像素处理------------------------

       } //单行处理结束                   
   }
}

//--------------------------------------【方法十四】------------------------------------------- 
//      说明:利用操作符重载
//-------------------------------------------------------------------------------------------------
void colorReduce13(Mat &image, int div = 64) {

   int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0));
   //掩码值
   uchar mask = 0xFF << n; // e.g. 比如div=16, mask= 0xF0

                           //进行色彩还原
   image = (image&Scalar(mask, mask, mask)) + Scalar(div / 2, div / 2, div / 2);
}

//-----------------------------------【main( )函数】--------------------------------------------
//      描述:控制台应用程序的入口函数,我们的程序从这里开始
//-------------------------------------------------------------------------------------------------
int main()
{
   int64 t[NTESTS], tinit;
   Mat image0;
   Mat image1;
   Mat image2;

   system("color 4F");

   image0 = imread("D://1.png");
   if (!image0.data) {
       cout <<  " could not image " << endl;
   getchar();
   return -1;
}

   //时间值设为0
   for (int i = 0; i<NTESTS; i++)
       t[i] = 0;

   // 多次重复测试
   int n = NITERATIONS;
   for (int k = 0; k<n; k++)
   {
       cout << k << " of " << n << endl;

       image1 = imread("D://1.png");
       //【方法一】利用.ptr 和 []
       tinit = getTickCount();
       colorReduce0(image1);
       t[0] += getTickCount() - tinit;

       //【方法二】利用 .ptr 和 * ++ 
       image1 = imread("D://1.png");
       tinit = getTickCount();
       colorReduce1(image1);
       t[1] += getTickCount() - tinit;

       //【方法三】利用.ptr 和 * ++ 以及模操作
       image1 = imread("D://1.png");
       tinit = getTickCount();
       colorReduce2(image1);
       t[2] += getTickCount() - tinit;

       //【方法四】 利用.ptr 和 * ++ 以及位操作
       image1 = imread("D://1.png");
       tinit = getTickCount();
       colorReduce3(image1);
       t[3] += getTickCount() - tinit;

       //【方法五】 利用指针的算术运算
       image1 = imread("D://1.png");
       tinit = getTickCount();
       colorReduce4(image1);
       t[4] += getTickCount() - tinit;

       //【方法六】利用 .ptr 和 * ++以及位运算、image.cols * image.channels()
       image1 = imread("D://1.png");
       tinit = getTickCount();
       colorReduce5(image1);
       t[5] += getTickCount() - tinit;

       //【方法七】利用.ptr 和 * ++ 以及位运算(continuous)
       image1 = imread("D://1.png");
       tinit = getTickCount();
       colorReduce6(image1);
       t[6] += getTickCount() - tinit;

       //【方法八】利用 .ptr 和 * ++ 以及位运算 (continuous+channels)
       image1 = imread("D://1.png");
       tinit = getTickCount();
       colorReduce7(image1);
       t[7] += getTickCount() - tinit;

       //【方法九】 利用Mat_ iterator
       image1 = imread("D://1.png");
       tinit = getTickCount();
       colorReduce8(image1);
       t[8] += getTickCount() - tinit;

       //【方法十】 利用Mat_ iterator以及位运算
       image1 = imread("D://1.png");
       tinit = getTickCount();
       colorReduce9(image1);
       t[9] += getTickCount() - tinit;

       //【方法十一】利用Mat Iterator_
       image1 = imread("D://1.png");
       tinit = getTickCount();
       colorReduce10(image1);
       t[10] += getTickCount() - tinit;

       //【方法十二】 利用动态地址计算配合at
       image1 = imread("D://1.png");
       tinit = getTickCount();
       colorReduce11(image1);
       t[11] += getTickCount() - tinit;

       //【方法十三】 利用图像的输入与输出
       image1 = imread("D://1.png");
       tinit = getTickCount();
       Mat result;
       colorReduce12(image1, result);
       t[12] += getTickCount() - tinit;
       image2 = result;

       //【方法十四】 利用操作符重载
       image1 = imread("D://1.png");
       tinit = getTickCount();
       colorReduce13(image1);
       t[13] += getTickCount() - tinit;

       //------------------------------
   }
   //输出图像   
   imshow("原始图像", image0);
   imshow("结果", image2);
   imshow("图像结果", image1);

   // 输出平均执行时间
   cout << endl << "-------------------------------------------" << endl << endl;
   cout << "\n【方法一】利用.ptr 和 []的方法所用时间为 " << 1000.*t[0] / getTickFrequency() / n << "ms" << endl;
   cout << "\n【方法二】利用 .ptr 和 * ++ 的方法所用时间为" << 1000.*t[1] / getTickFrequency() / n << "ms" << endl;
   cout << "\n【方法三】利用.ptr 和 * ++ 以及模操作的方法所用时间为" << 1000.*t[2] / getTickFrequency() / n << "ms" << endl;
   cout << "\n【方法四】利用.ptr 和 * ++ 以及位操作的方法所用时间为" << 1000.*t[3] / getTickFrequency() / n << "ms" << endl;
   cout << "\n【方法五】利用指针算术运算的方法所用时间为" << 1000.*t[4] / getTickFrequency() / n << "ms" << endl;
   cout << "\n【方法六】利用 .ptr 和 * ++以及位运算、channels()的方法所用时间为" << 1000.*t[5] / getTickFrequency() / n << "ms" << endl;
   cout << "\n【方法七】利用.ptr 和 * ++ 以及位运算(continuous)的方法所用时间为" << 1000.*t[6] / getTickFrequency() / n << "ms" << endl;
   cout << "\n【方法八】利用 .ptr 和 * ++ 以及位运算 (continuous+channels)的方法所用时间为" << 1000.*t[7] / getTickFrequency() / n << "ms" << endl;
   cout << "\n【方法九】利用Mat_ iterator 的方法所用时间为" << 1000.*t[8] / getTickFrequency() / n << "ms" << endl;
   cout << "\n【方法十】利用Mat_ iterator以及位运算的方法所用时间为" << 1000.*t[9] / getTickFrequency() / n << "ms" << endl;
   cout << "\n【方法十一】利用Mat Iterator_的方法所用时间为" << 1000.*t[10] / getTickFrequency() / n << "ms" << endl;
   cout << "\n【方法十二】利用动态地址计算配合at 的方法所用时间为" << 1000.*t[11] / getTickFrequency() / n << "ms" << endl;
   cout << "\n【方法十三】利用图像的输入与输出的方法所用时间为" << 1000.*t[12] / getTickFrequency() / n << "ms" << endl;
   cout << "\n【方法十四】利用操作符重载的方法所用时间为" << 1000.*t[13] / getTickFrequency() / n << "ms" << endl;

   waitKey();
   return 0;
}

效果图

8.1 效果图.PNG
8.2 效果图.PNG
8.3 效果图.PNG

注意:第八种方法最快,利用 .ptr 和 * ++ 以及位运算 (continuous+channels)的方法所用时间为1.08502ms(以后就用第八种【以后复制粘贴第八种】,哈哈哈)

image.png
  1. 本人是抱着玩一玩的心态,学习opencv(其实深度学习没有外界说的这么高深,小嗷是白板,而且有工作在身并且于代码无关)
  2. 大家可以把我的数学水平想象成初中水平,毕竟小嗷既不是代码靠吃饭又不是靠数学吃饭,毕业N年
  3. 写文章主要是为了后人少走点弯路,多交点朋友,一起学习
  4. 如果有好的图像识别群拉我进去QQ:631821577
  5. 就我一个白板,最后还是成的,你们别怕,慢慢来把
7.二维码

分享可以无数次,转载成自己文章QQ邮箱通知一下,未经授权请勿转载。

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

推荐阅读更多精彩内容