OpenCV之ios OpenCV的視頻輸入和相似度測量

OpenCV之ios OpenCV的視頻輸入和相似度測量

ws.jpg
w.jpg

目標(biāo)

現(xiàn)在找一個(gè)能拍攝視頻的設(shè)備真是太容易了沦辙。結(jié)果大家都用視頻來代替以前的序列圖像洒忧。視頻可能由兩種形式得到,一個(gè)是像網(wǎng)絡(luò)攝像頭那樣實(shí)時(shí)視頻流,或者由其他設(shè)備產(chǎn)生的壓縮編碼后的視頻文件。幸運(yùn)的是举户,OpenCV可以使用相同的C++類、用同一種方式處理這些視頻信息遍烦。在接下來的教程里你將學(xué)習(xí)如何使用攝像頭或者視頻文件俭嘁。

  • 如何打開和讀取視頻流
  • 兩種檢查相似度的方法:PSNR和SSIM

源代碼

由于項(xiàng)目中使用的兩個(gè)視頻源在調(diào)試過程中不能播放.因此,視頻使用的是兩個(gè)相同的視頻源,命名了兩個(gè)不同的文件,

//
//  VideoInputViewController.m
//  OpenCVThirdChapter_video-input
//
//  Created by glodon on 2019/11/19.
//  Copyright ? 2019 persion. All rights reserved.
//
#ifdef __cplusplus
#import <opencv2/opencv.hpp>
#import <opencv2/imgcodecs/ios.h>
#import <opencv2/imgproc.hpp>
#import <opencv2/highgui.hpp>
#import <opencv2/core/operations.hpp>

#import <opencv2/core/core_c.h>
using namespace cv;
using namespace std;

#endif
#import "VideoInputViewController.h"

@interface VideoInputViewController ()
@property (nonatomic ,strong) UIImageView * RefimageView ;
@property (nonatomic ,strong) UIImageView * tesimageView ;

@end

@implementation VideoInputViewController
VideoCapture captRefrnc;
VideoCapture  captUndTst;
int frameNum = -1;
 string sourceReference;
string sourceCompareWith;
int psnrTriggerValue;
Mat frameReference, frameUnderTest;
double psnrV;
 Scalar mssimV;
- (void)viewDidLoad {
    [super viewDidLoad];
    self.RefimageView = [self createImageViewInRect:CGRectMake(0, 100, 200, 200)];
    [self.view addSubview:self.RefimageView];
    self.tesimageView = [self createImageViewInRect:CGRectMake(0, 300, 200, 200)];
    [self.view addSubview:self.tesimageView];
     stringstream conv;
    NSString * sourceReferenceStr =[self getFilePathInName:@"1.mp4"];
    NSString * sourceCompareWithStr = [self getFilePathInName:@"2.mp4"];
    sourceReference =sourceReferenceStr.UTF8String;
    sourceCompareWith=sourceCompareWithStr.UTF8String;
    psnrTriggerValue = 35;
         // Frame counter
    
     captRefrnc= VideoCapture(sourceReference);
     captUndTst=VideoCapture(sourceCompareWith);
    if ( !captRefrnc.isOpened())
       {
           cout  << "Could not open reference " << sourceReference << endl;
           return ;
       }
    if( !captUndTst.isOpened())
       {
           cout  << "Could not open case test " << sourceCompareWith << endl;
           return ;
       }
    cv::Size refS = cv::Size((int) captRefrnc.get(CV_CAP_PROP_FRAME_WIDTH),
    (int) captRefrnc.get(CV_CAP_PROP_FRAME_HEIGHT)),
    uTSi = cv::Size((int) captUndTst.get(CV_CAP_PROP_FRAME_WIDTH),
    (int) captUndTst.get(CV_CAP_PROP_FRAME_HEIGHT));
    
    if (refS != uTSi)
      {
          cout << "Inputs have different size!!! Closing." << endl;
          return ;
      }

  cout << "Reference frame resolution: Width=" << refS.width << "  Height=" << refS.height
         << " of nr#: " << captRefrnc.get(CV_CAP_PROP_FRAME_COUNT) << endl;

     cout << "PSNR trigger value " <<
         setiosflags(ios::fixed) << setprecision(3) << psnrTriggerValue << endl;

    [self play];
}

-(void)play{
    [self createCADisplayLinkExeBlock:^(BOOL * _Nonnull stop) {
     static BOOL begin = NO;
        if (begin) {
            begin =NO;
            return ;
        }
       BOOL  isStop = [self asyPlay];
        if (isStop) {
            * stop = YES;
        }
    }];
}

-(void)playerViewRef:(Mat)frameReference andframeUnderTest:(Mat)frameUnderTest{
    dispatch_async(dispatch_get_main_queue(), ^{
        self.RefimageView.image = [self UIImageFromCVMat:frameReference];
        self.tesimageView.image = [self UIImageFromCVMat:frameUnderTest];
     });
}

-(BOOL)asyPlay{
    captRefrnc >> frameReference;
    captUndTst >> frameUnderTest;

    if( frameReference.empty()  || frameUnderTest.empty())
           {
               cout << " < < <  Game over!  > > > ";
               return YES;
           }
     ++frameNum;
     cout <<"Frame:" << frameNum <<"# ";
    psnrV = getPSNR(frameReference,frameUnderTest);                 //get PSNR
           cout << setiosflags(ios::fixed) << setprecision(3) << psnrV << "dB";
    if (psnrV < psnrTriggerValue && psnrV)
         {
             mssimV = getMSSIM(frameReference,frameUnderTest);

             cout << " MSSIM: "
                 << " R " << setiosflags(ios::fixed) << setprecision(2) << mssimV.val[2] * 100 << "%"
                 << " G " << setiosflags(ios::fixed) << setprecision(2) << mssimV.val[1] * 100 << "%"
                 << " B " << setiosflags(ios::fixed) << setprecision(2) << mssimV.val[0] * 100 << "%";
         }

         cout << endl;
    [self playerViewRef:frameReference andframeUnderTest:frameUnderTest];
    return NO;
}
Scalar getMSSIM( const Mat& i1, const Mat& i2)
{
    const double C1 = 6.5025, C2 = 58.5225;
    /***************************** INITS **********************************/
    int d     = CV_32F;

    Mat I1, I2;
    i1.convertTo(I1, d);           // cannot calculate on one byte large values
    i2.convertTo(I2, d);

    Mat I2_2   = I2.mul(I2);        // I2^2
    Mat I1_2   = I1.mul(I1);        // I1^2
    Mat I1_I2  = I1.mul(I2);        // I1 * I2

    /*************************** END INITS **********************************/

    Mat mu1, mu2;   // PRELIMINARY COMPUTING
    GaussianBlur(I1, mu1, cv::Size(11, 11), 1.5);
    GaussianBlur(I2, mu2, cv::Size(11, 11), 1.5);

    Mat mu1_2   =   mu1.mul(mu1);
    Mat mu2_2   =   mu2.mul(mu2);
    Mat mu1_mu2 =   mu1.mul(mu2);

    Mat sigma1_2, sigma2_2, sigma12;

    GaussianBlur(I1_2, sigma1_2, cv::Size(11, 11), 1.5);
    sigma1_2 -= mu1_2;

    GaussianBlur(I2_2, sigma2_2, cv::Size(11, 11), 1.5);
    sigma2_2 -= mu2_2;

    GaussianBlur(I1_I2, sigma12, cv::Size(11, 11), 1.5);
    sigma12 -= mu1_mu2;

    ///////////////////////////////// FORMULA ////////////////////////////////
    Mat t1, t2, t3;

    t1 = 2 * mu1_mu2 + C1;
    t2 = 2 * sigma12 + C2;
    t3 = t1.mul(t2);              // t3 = ((2*mu1_mu2 + C1).*(2*sigma12 + C2))

    t1 = mu1_2 + mu2_2 + C1;
    t2 = sigma1_2 + sigma2_2 + C2;
    t1 = t1.mul(t2);               // t1 =((mu1_2 + mu2_2 + C1).*(sigma1_2 + sigma2_2 + C2))

    Mat ssim_map;
    divide(t3, t1, ssim_map);      // ssim_map =  t3./t1;

    Scalar mssim = mean( ssim_map ); // mssim = average of ssim map
    return mssim;
}
double getPSNR(const Mat& I1, const Mat& I2)
{
    Mat s1;
    absdiff(I1, I2, s1);       // |I1 - I2|
    s1.convertTo(s1, CV_32F);  // cannot make a square on 8 bits
    s1 = s1.mul(s1);           // |I1 - I2|^2

    Scalar s = sum(s1);         // sum elements per channel

    double sse = s.val[0] + s.val[1] + s.val[2]; // sum channels

    if( sse <= 1e-10) // for small values return zero
        return 0;
    else
    {
        double  mse =sse /(double)(I1.channels() * I1.total());
        double psnr = 10.0*log10((255*255)/mse);
        return psnr;
    }
}


#pragma mark  - private
//brg
- (cv::Mat)cvMatFromUIImage:(UIImage *)image
{
  CGColorSpaceRef colorSpace =CGColorSpaceCreateDeviceRGB();
    
  CGFloat cols = image.size.width;
  CGFloat rows = image.size.height;
    Mat cvMat(rows, cols, CV_8UC4); // 8 bits per component, 4 channels (color channels + alpha)
  CGContextRef contextRef = CGBitmapContextCreate(cvMat.data,                 // Pointer to  data
                                                 cols,                       // Width of bitmap
                                                 rows,                       // Height of bitmap
                                                 8,                          // Bits per component
                                                 cvMat.step[0],              // Bytes per row
                                                 colorSpace,                 // Colorspace
                                                 kCGImageAlphaNoneSkipLast |
                                                 kCGBitmapByteOrderDefault); // Bitmap info flags
  CGContextDrawImage(contextRef, CGRectMake(0, 0, cols, rows), image.CGImage);
  CGContextRelease(contextRef);
    
    Mat dst;
    Mat src;
    cvtColor(cvMat, dst, COLOR_RGBA2BGRA);
    cvtColor(dst, src, COLOR_BGRA2BGR);

  return src;
}

-(UIImage *)UIImageFromCVMat:(cv::Mat)cvMat
{
//    mat 是brg 而 rgb
    Mat src;
    NSData *data=nil;
    CGBitmapInfo info =kCGImageAlphaNone|kCGBitmapByteOrderDefault;
    CGColorSpaceRef colorSpace;
    if (cvMat.depth()!=CV_8U) {
        Mat result;
        cvMat.convertTo(result, CV_8U,255.0);
        cvMat = result;
    }
  if (cvMat.elemSize() == 1) {
      colorSpace = CGColorSpaceCreateDeviceGray();
      data= [NSData dataWithBytes:cvMat.data length:cvMat.elemSize()*cvMat.total()];
  } else if(cvMat.elemSize() == 3){
      cvtColor(cvMat, src, COLOR_BGR2RGB);
       data= [NSData dataWithBytes:src.data length:src.elemSize()*src.total()];
      colorSpace = CGColorSpaceCreateDeviceRGB();
  }else if(cvMat.elemSize() == 4){
      colorSpace = CGColorSpaceCreateDeviceRGB();
      cvtColor(cvMat, src, COLOR_BGRA2RGBA);
      data= [NSData dataWithBytes:src.data length:src.elemSize()*src.total()];
      info =kCGImageAlphaNoneSkipLast | kCGBitmapByteOrderDefault;
  }else{
      NSLog(@"[error:] 錯誤的顏色通道");
      return nil;
  }
  CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
  // Creating CGImage from cv::Mat
  CGImageRef imageRef = CGImageCreate(cvMat.cols,                                 //width
                                     cvMat.rows,                                 //height
                                     8,                                          //bits per component
                                     8 * cvMat.elemSize(),                       //bits per pixel
                                     cvMat.step[0],                            //bytesPerRow
                                     colorSpace,                                 //colorspace
                                     kCGImageAlphaNone|kCGBitmapByteOrderDefault,// bitmap info
                                     provider,                                   //CGDataProviderRef
                                     NULL,                                       //decode
                                     false,                                      //should interpolate
                                     kCGRenderingIntentAbsoluteColorimetric                   //intent
                                     );
  // Getting UIImage from CGImage
  UIImage *finalImage = [UIImage imageWithCGImage:imageRef];
  CGImageRelease(imageRef);
  CGDataProviderRelease(provider);
  CGColorSpaceRelease(colorSpace);
  return finalImage;
 }
@end

程序解讀

如何讀取一個(gè)視頻流(攝像頭或者視頻文件)?

總的來說服猪,視頻捕獲需要的所有函數(shù)都集成在 VideoCapture C++ 類里面兄淫。雖然它底層依賴另一個(gè)FFmpeg開源庫,但是它已經(jīng)被集成在OpenCV里所以你不需要額外地關(guān)注它的具體實(shí)現(xiàn)方法蔓姚。你只需要知道一個(gè)視頻由一系列圖像構(gòu)成,我們用一個(gè)專業(yè)點(diǎn)兒的詞匯來稱呼這些構(gòu)成視頻的圖像:“幀”(frame)慨丐。此外在視頻文件里還有個(gè)參數(shù)叫做“幀率”(frame rate)的坡脐,用來表示兩幀之間的間隔時(shí)間,幀率的單位是(幀/秒)房揭。這個(gè)參數(shù)只和視頻的播放速度有關(guān)备闲,對于單獨(dú)的一幀圖像來說沒有任何用途。

你需要先定義一個(gè) VideoCapture 類的對象來打開和讀取視頻流捅暴。具體可以通過 constructor 或者通過 open 函數(shù)來完成恬砂。如果使用整型數(shù)當(dāng)參數(shù)的話,就可以將這個(gè)對象綁定到一個(gè)攝像機(jī)蓬痒,將系統(tǒng)指派的ID號當(dāng)作參數(shù)傳入即可泻骤。例如你可以傳入0來打開第一個(gè)攝像機(jī),傳入1打開第二個(gè)攝像機(jī)梧奢,以此類推狱掂。如果使用字符串當(dāng)參數(shù),就會打開一個(gè)由這個(gè)字符串(文件名)指定的視頻文件亲轨。

    NSString * sourceReferenceStr =[self getFilePathInName:@"1.mp4"];
    NSString * sourceCompareWithStr = [self getFilePathInName:@"2.mp4"];
    sourceReference =sourceReferenceStr.UTF8String;
    sourceCompareWith=sourceCompareWithStr.UTF8String;
    psnrTriggerValue = 35;
         // Frame counter
    
     captRefrnc= VideoCapture(sourceReference);
     captUndTst=VideoCapture(sourceCompareWith);

下面這種寫法也可以.上面這種寫法我只是將其定義為了全局變量而已

VideoCapture captRefrnc(sourceReference);
// 或者
VideoCapture captUndTst;
captUndTst.open(sourceCompareWith);

你可以用 isOpened 函數(shù)來檢查視頻是否成功打開與否:

 if ( !captRefrnc.isOpened())
       {
           cout  << "Could not open reference " << sourceReference << endl;
           return ;
       }
    if( !captUndTst.isOpened())
       {
           cout  << "Could not open case test " << sourceCompareWith << endl;
           return ;
       }

當(dāng)析構(gòu)函數(shù)調(diào)用時(shí)趋惨,會自動關(guān)閉視頻。如果你希望提前關(guān)閉的話惦蚊,你可以調(diào)用 release 函數(shù). 視頻的每一幀都是一幅普通的圖像器虾。因?yàn)槲覀儍H僅需要從 VideoCapture對象里釋放出每一幀圖像并保存成 Mat 格式讯嫂。因?yàn)橐曨l流是連續(xù)的,所以你需要在每次調(diào)用 read 函數(shù)后及時(shí)保存圖像或者直接使用重載的>>操作符兆沙。

Mat frameReference, frameUnderTest;
captRefrnc >> frameReference;

如果視頻幀無法捕獲(例如當(dāng)視頻關(guān)閉或者完結(jié)的時(shí)候)欧芽,上面的操作就會返回一個(gè)空的 Mat 對象。我們可以用下面的代碼檢查是否返回了空的圖像:

if( frameReference.empty()  || frameUnderTest.empty())
{
 // 退出程序
}

讀取視頻幀的時(shí)候也會自動進(jìn)行解碼操作挤悉。你可以通過調(diào)用 grabretrieve 函數(shù)來顯示地進(jìn)行這兩項(xiàng)操作渐裸。(本程序沒使用)

視頻通常擁有很多除了視頻幀圖像以外的信息,像是幀數(shù)之類装悲,有些時(shí)候數(shù)據(jù)較短昏鹃,有些時(shí)候用4個(gè)字節(jié)的字符串來表示。所以 get 函數(shù)返回一個(gè)double(8個(gè)字節(jié))類型的數(shù)據(jù)來表示這些屬性诀诊。然后你可以使用位操作符來操作這個(gè)返回值從而得到想要的整型數(shù)據(jù)等洞渤。這個(gè)函數(shù)有一個(gè)參數(shù),代表著試圖查詢的屬性ID属瓣。在下面的例子里我們會先獲得食品的尺寸和幀數(shù)载迄。

當(dāng)你需要設(shè)置這些值的時(shí)候你可以調(diào)用 set 函數(shù)。函數(shù)的第一個(gè)參數(shù)是需要設(shè)置的屬性ID抡蛙,第二個(gè)參數(shù)是需要設(shè)定的值护昧,如果返回true的話就表示成功設(shè)定,否則就是false粗截。接下來的這個(gè)例子很好地展示了如何設(shè)置視頻的時(shí)間位置或者幀數(shù):

captRefrnc.set(CV_CAP_PROP_POS_MSEC, 1.2);  // 跳轉(zhuǎn)到視頻1.2秒的位置
captRefrnc.set(CV_CAP_PROP_POS_FRAMES, 10); // 跳轉(zhuǎn)到視頻的第10幀

圖像比較 - PSNR and SSIM

當(dāng)我們想檢查壓縮視頻帶來的細(xì)微差異的時(shí)候惋耙,就需要構(gòu)建一個(gè)能夠逐幀比較視頻差異的系統(tǒng)。最常用的比較算法是PSNR( Peak signal-to-noise ratio)熊昌。這是個(gè)使用“局部均值誤差”來判斷差異的最簡單的方法绽榛,假設(shè)有這兩幅圖像:I1和I2,它們的行列數(shù)分別是i婿屹,j灭美,有c個(gè)通道。


PSNR公式如下:


每個(gè)像素的每個(gè)通道的值占用一個(gè)字節(jié)昂利,值域[0,255]届腐。這里每個(gè)像素會有MAX12個(gè)有效的最大值 注意當(dāng)兩幅圖像的相同的話,MSE的值會變成0蜂奸。這樣會導(dǎo)致PSNR的公式會除以0而變得沒有意義梯捕。所以我們需要單獨(dú)的處理這樣的特殊情況。此外由于像素的動態(tài)范圍很廣窝撵,在處理時(shí)會使用對數(shù)變換來縮小范圍傀顾。這些變換的C++代碼如下:

double getPSNR(const Mat& I1, const Mat& I2)
{
 Mat s1;
 absdiff(I1, I2, s1);       // |I1 - I2|
 s1.convertTo(s1, CV_32F);  // 不能在8位矩陣上做平方運(yùn)算
 s1 = s1.mul(s1);           // |I1 - I2|^2

 Scalar s = sum(s1);         // 疊加每個(gè)通道的元素

 double sse = s.val[0] + s.val[1] + s.val[2]; // 疊加所有通道

 if( sse <= 1e-10) // 如果值太小就直接等于0
     return 0;
 else
 {
     double  mse =sse /(double)(I1.channels() * I1.total());
     double psnr = 10.0*log10((255*255)/mse);
     return psnr;
 }
}

在考察壓縮后的視頻時(shí),這個(gè)值大約在30到50之間碌奉,數(shù)字越大則表明壓縮質(zhì)量越好短曾。如果圖像差異很明顯寒砖,就可能會得到15甚至更低的值。PSNR算法簡單嫉拐,檢查的速度也很快哩都。但是其呈現(xiàn)的差異值有時(shí)候和人的主觀感受不成比例。所以有另外一種稱作 結(jié)構(gòu)相似性 的算法做出了這方面的改進(jìn)婉徘。

建議你閱讀一些關(guān)于SSIM算法的文獻(xiàn)來更好的理解算法漠嵌,然而你直接看下面的源代碼,應(yīng)該也能建立一個(gè)不錯的映像盖呼。

Scalar getMSSIM( const Mat& i1, const Mat& i2)
{
 const double C1 = 6.5025, C2 = 58.5225;
 /***************************** INITS **********************************/
 int d     = CV_32F;

 Mat I1, I2;
 i1.convertTo(I1, d);           // 不能在單字節(jié)像素上進(jìn)行計(jì)算儒鹿,范圍不夠。
 i2.convertTo(I2, d);

 Mat I2_2   = I2.mul(I2);        // I2^2
 Mat I1_2   = I1.mul(I1);        // I1^2
 Mat I1_I2  = I1.mul(I2);        // I1 * I2

 /***********************初步計(jì)算 ******************************/

 Mat mu1, mu2;   //
 GaussianBlur(I1, mu1, Size(11, 11), 1.5);
 GaussianBlur(I2, mu2, Size(11, 11), 1.5);

 Mat mu1_2   =   mu1.mul(mu1);
 Mat mu2_2   =   mu2.mul(mu2);
 Mat mu1_mu2 =   mu1.mul(mu2);

 Mat sigma1_2, sigma2_2, sigma12;

 GaussianBlur(I1_2, sigma1_2, Size(11, 11), 1.5);
 sigma1_2 -= mu1_2;

 GaussianBlur(I2_2, sigma2_2, Size(11, 11), 1.5);
 sigma2_2 -= mu2_2;

 GaussianBlur(I1_I2, sigma12, Size(11, 11), 1.5);
 sigma12 -= mu1_mu2;

 ///////////////////////////////// 公式 ////////////////////////////////
 Mat t1, t2, t3;

 t1 = 2 * mu1_mu2 + C1;
 t2 = 2 * sigma12 + C2;
 t3 = t1.mul(t2);              // t3 = ((2*mu1_mu2 + C1).*(2*sigma12 + C2))

 t1 = mu1_2 + mu2_2 + C1;
 t2 = sigma1_2 + sigma2_2 + C2;
 t1 = t1.mul(t2);               // t1 =((mu1_2 + mu2_2 + C1).*(sigma1_2 + sigma2_2 + C2))

 Mat ssim_map;
 divide(t3, t1, ssim_map);      // ssim_map =  t3./t1;

 Scalar mssim = mean( ssim_map ); // mssim = ssim_map的平均值
 return mssim;
}

這個(gè)操作會針對圖像的每個(gè)通道返回一個(gè)相似度几晤,取值范圍應(yīng)該在0到1之間约炎,取值為1時(shí)代表完全符合。然而盡管SSIM能產(chǎn)生更優(yōu)秀的數(shù)據(jù)蟹瘾,但是由于高斯模糊很花時(shí)間圾浅,所以在一個(gè)實(shí)時(shí)系統(tǒng)(每秒24幀)中,人們還是更多地采用PSNR算法憾朴。
正是這個(gè)原因狸捕,最開始的源碼里,我們用PSNR算法去計(jì)算每一幀圖像众雷,而僅當(dāng)PSNR算法計(jì)算出的結(jié)果低于輸入值的時(shí)候府寒,用SSIM算法去驗(yàn)證。為了展示數(shù)據(jù)报腔,我們在例程里用兩個(gè)窗口顯示了原圖像和測試圖像并且在控制臺上輸出了PSNR和SSIM數(shù)據(jù)。就像下面顯示的那樣:


上面是應(yīng)該展示的結(jié)果,可是項(xiàng)目中提供的視頻播放不了,因此,我們獲取的結(jié)過都是一樣的.

播放動圖如下:


提示,上面的程序只是播放視頻,沒有考慮內(nèi)存以及內(nèi)存泄露等問題
上面的代碼沒法驗(yàn)證ssIm


github地址

摘錄博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末剖淀,一起剝皮案震驚了整個(gè)濱河市纯蛾,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌纵隔,老刑警劉巖翻诉,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異捌刮,居然都是意外死亡碰煌,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門绅作,熙熙樓的掌柜王于貴愁眉苦臉地迎上來芦圾,“玉大人,你說我怎么就攤上這事俄认「錾伲” “怎么了洪乍?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長夜焦。 經(jīng)常有香客問我壳澳,道長,這世上最難降的妖魔是什么茫经? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任巷波,我火速辦了婚禮,結(jié)果婚禮上卸伞,老公的妹妹穿的比我還像新娘抹镊。我一直安慰自己,他們只是感情好瞪慧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布髓考。 她就那樣靜靜地躺著,像睡著了一般弃酌。 火紅的嫁衣襯著肌膚如雪氨菇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天妓湘,我揣著相機(jī)與錄音查蓉,去河邊找鬼。 笑死榜贴,一個(gè)胖子當(dāng)著我的面吹牛豌研,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播唬党,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼鹃共,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了驶拱?” 一聲冷哼從身側(cè)響起霜浴,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蓝纲,沒想到半個(gè)月后阴孟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡税迷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年永丝,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片箭养。...
    茶點(diǎn)故事閱讀 39,779評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡慕嚷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情闯冷,我是刑警寧澤砂心,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站蛇耀,受9級特大地震影響辩诞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜纺涤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一译暂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧撩炊,春花似錦外永、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至骆膝,卻和暖如春祭衩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背阅签。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工掐暮, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人政钟。 一個(gè)月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓路克,卻偏偏與公主長得像,于是被迫代替她去往敵國和親养交。 傳聞我的和親對象是個(gè)殘疾皇子精算,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容