iOS全景圖像拼接實(shí)現(xiàn)

本文為在iOS環(huán)境下利用OpenCV技術(shù)實(shí)現(xiàn)全景圖片合成并對(duì)合成圖片進(jìn)行剪裁的簡(jiǎn)單實(shí)現(xiàn)胎撤。

Image Stitching with OpenCV and Python

本文參考以上連接實(shí)現(xiàn)多張圖像的拼接囚戚,并通過(guò)C++和Objective-C代碼實(shí)現(xiàn)構(gòu)建一張全景圖婚温。

圖1

根據(jù)多個(gè)圖像創(chuàng)建全景圖的步驟為:

檢測(cè)兩張圖像的關(guān)鍵點(diǎn)特征(DoG某抓、Harris等)

計(jì)算不變特征描述符(SIFT、SURF或ORB等)

根據(jù)關(guān)鍵點(diǎn)特征和描述符坑质,對(duì)兩張圖像進(jìn)行匹配患雇,得到若干匹配點(diǎn)對(duì),并移除錯(cuò)誤匹配淮野;

使用Ransac算法和匹配的特征來(lái)估計(jì)單應(yīng)矩陣(homography matrix)捧书;

通過(guò)單應(yīng)矩陣來(lái)對(duì)圖像進(jìn)行仿射變換;

兩圖像拼接骤星,重疊部分融合经瓷;

裁剪以獲得美觀的最終圖像。

原理比較復(fù)雜洞难,本文先不講解舆吮,OpenCV中已經(jīng)實(shí)現(xiàn)了全景圖拼接的算法,cv::Stitcher::createDefault(try_use_gpu) (OpenCV 3.x) 和 cv::Stitcher::create()(OpenCV 4.x) 队贱。

該算法對(duì)以下條件具有較好的魯棒性:

輸入圖像的順序

圖像的方向

光照變化

圖像噪聲

OpenCV在Stitcher類中實(shí)現(xiàn)的拼接模塊流程()

OpenCV流程圖

一色冀、函數(shù)介紹

OpenCV 3.x 的 cv::Stitcher::createDefault() 函數(shù)原型為:

Stitcher stitcher = Stitcher::createDefault(try_use_gpu);

這個(gè)函數(shù)有一個(gè)參數(shù) try_use_gpu,它可以用來(lái)提升圖像拼接整個(gè)過(guò)程的速度柱嫌。

OpenCV 4 的 cv::Stitcher::create()函數(shù)原型為:

Ptr<Stitcher> create(Mode mode = Stitcher::PANORAMA);

要執(zhí)行實(shí)際的圖像拼接锋恬,我們需要調(diào)用 .stitch 方法:

OpenCV 3.x:
stitch(...) method of cv::Stitcher instance
Status cv::Stitcher::stitch(InputArrayOfArrays images, 
                            const std::vector< std::vector< Rect > > & rois,
                            OutputArray pano )

OpenCV 4.x:
stitch(...) method of cv::Stitcher instance
Status stitch(InputArrayOfArrays images, OutputArray pano);
    /** @brief These functions try to stitch the given images.
        @param images Input images.
        @param masks Masks for each input image specifying where to look for keypoints (optional).
        @param pano Final pano.
        @return Status code.
     */

該方法接收一個(gè)圖像列表,然后嘗試將它們拼接成全景圖像编丘,并進(jìn)行返回与学。

變量 status=0表示圖像拼接是否成功。

二嘉抓、圖像拼接算法實(shí)現(xiàn)

先將圖片讀取出來(lái)放入iOS原生數(shù)組內(nèi)

UIImage*image1 = [UIImageimageNamed:@"pano_01.jpg"];
UIImage*image2 = [UIImageimageNamed:@"pano_02.jpg"];
UIImage*image3 = [UIImageimageNamed:@"pano_03.jpg"];
NSArray*imageArray = @[image1, image2, image3];
[self.imgArr addObjectsFromArray:imageArray];

圖片順序沒(méi)有影響索守,不同的圖片順序,輸出全景圖都相同抑片。

調(diào)用CVWrapper內(nèi)圖像拼接方法

[CVWrapper processWithArray:self.imgArr stitchType:QKDefaultStitch];

該方法會(huì)將傳入的圖片調(diào)整方向后再轉(zhuǎn)成cv::Mat圖片矩陣蕾盯,再將矩陣加入cv::Mat泛型數(shù)組內(nèi),供OpenCV方法拼接圖像后,返回UIImage對(duì)象级遭。

//多張圖片合成處理
+ (UIImage*) processWithArray:(NSArray*)imageArray stitchType:(QKCVWrapperTypeCode)stitchType
{
    if ([imageArray count]==0){
        NSLog (@"imageArray is empty");
        return 0;
        }

    std::vector<cv::Mat> matImages;

    for (id image in imageArray) {
        if ([image isKindOfClass: [UIImage class]]) {
            /*
             All images taken with the iPhone/iPa cameras are LANDSCAPE LEFT orientation. 
             The  UIImage imageOrientation flag is an instruction to the OS to transform the image during display only. 
             When we feed images into openCV, they need to be the actual orientation that we expect 
             them to be for stitching. So we rotate the actual pixel matrix here if required.
             */
            UIImage* rotatedImage = [image rotateToImageOrientation];
            cv::Mat matImage = [rotatedImage CVMat3];
//            matImage = testaaaa(matImage);
            NSLog (@"matImage: %@",image);
            matImages.push_back(matImage);
        }
    }
    NSLog (@"stitching...");
    cv::Mat stitchedMat;

    switch (stitchType) {
        case QKDefaultStitch:
            stitchedMat = stitch(matImages);
            break;
        case QKFisheyeStitch:
            stitchedMat = fisheyeStitch(matImages);
            break;
        case QKPlaneStitch:
            stitchedMat = planeStitch(matImages);
            break;
        default:
            break;
    }


    UIImage* result =  [UIImage imageWithCVMat:stitchedMat];
    return result;
}

stitch望拖、fisheyeStitch或者planeStitch 方法實(shí)現(xiàn)圖片拼
我們暫時(shí)提供了三種拼接方法,分別為默認(rèn)拼接方法挫鸽、魚(yú)眼相機(jī)拼接方法和環(huán)視(平面曲翹)拼接方法说敏。本文我們主要介紹默認(rèn)拼接方法,其他方法暫不介紹丢郊。

圖片轉(zhuǎn)換好后盔沫,我們將執(zhí)行圖像拼接
首先構(gòu)造拼接對(duì)象stitcher,要注意OpenCV 3和4的構(gòu)造方法是不同的枫匾。

Ptr<Stitcher> stitcher = Stitcher::create();//4.0

然后再把圖像列表傳入.stitch函數(shù)架诞,該函數(shù)會(huì)返回狀態(tài)和拼接好的全景圖(如果沒(méi)有錯(cuò)誤):

//拼接
Stitcher::Status status = stitcher->stitch(imgs, pano);

完整代碼如下

cv::Mat stitch (vector<Mat>& images)
{
  imgs = images;
  Mat pano;//拼接圖
  /* 3.0
   Stitcher stitcher = Stitcher::createDefault(try_use_gpu);
   Stitcher::Status status = stitcher.stitch(imgs, pano);
   */
  Ptr<Stitcher> stitcher = Stitcher::create();//4.0

  //拼接
  Stitcher::Status status = stitcher->stitch(imgs, pano);

  if (status != Stitcher::OK)
  {
     cout << "Can't stitch images, error code = " << int(status) << endl;
  }

  return pano;
}

全景圖如下:

全景圖

通過(guò)openCV基礎(chǔ)方法我們實(shí)現(xiàn)了全景圖,但是周圍出現(xiàn)了一些黑色區(qū)域干茉。
這是因?yàn)闃?gòu)建全景時(shí)會(huì)做透視變換谴忧,透視變換時(shí)會(huì)產(chǎn)生黑色區(qū)域。
所以我們需要進(jìn)一步處理角虫,剪裁出全景圖內(nèi)的最大內(nèi)部矩形區(qū)域沾谓,也就是留下圖中紅色虛線邊框內(nèi)的全景區(qū)域。

提取紅色虛線框內(nèi)的區(qū)域

三戳鹅、圖像裁剪算法

在活的全景圖后均驶,我們需要對(duì)其進(jìn)行剪裁加工,以便我們得到更完美的圖片枫虏。

1.在全景圖四周各添加10像素寬的黑色邊框妇穴,以確保能夠找到全景圖的完整輪廓:

Mat stitched;//黑色邊框輪廓圖copyMakeBorder(inputMat, stitched,10,10,10,10, cv::BORDER_CONSTANT,true);

2.全景圖轉(zhuǎn)換灰度圖,并將所有大于0的像素全置為255隶债。

Matgray;cv::cvtColor(stitched,gray,cv::COLOR_BGR2GRAY);

3.中值濾波腾它,去除黑色邊際中可能含有的噪聲干擾。

int ksize: 濾波模板的尺寸大小燃异,必須是大于1的奇數(shù),如3继蜡、5回俐、7……
cv::medianBlur(gray, gray,7)

4.作為前景,其他像素灰度值為0稀并,作為背景仅颇。

Mat tresh;
threshold(gray, tresh, 0, 255, THRESH_BINARY);

經(jīng)過(guò)上述步驟操作,我們得到結(jié)果(tresh):

前景色與背景色處理

現(xiàn)在有了全景圖的二值圖碘举,其中白色像素(255)是前景忘瓦,黑色像素(0)是背景,通過(guò)我們的閾值圖像引颈,

我們可以應(yīng)用輪廓檢測(cè)耕皮,找到最大輪廓的邊界框(即全景圖本身的輪廓) 境蜕,并繪制邊框。

5.尋找最大輪廓

 vector<vector<Point>> contours; //contours:包含圖像中所有輪廓的python列表(三維數(shù)組),每個(gè)輪廓是包含邊界所有坐標(biāo)點(diǎn)(x, y)的Numpy數(shù)組凌停。
 vector<Vec4i> hierarchy = vector<cv::Vec4i>();//vec4i是一種用于表示具有4個(gè)維度的向量的結(jié)構(gòu)粱年,每個(gè)值都小于cc>
 findContours(tresh.clone(), contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);//傳入?yún)?shù)不一樣
 //計(jì)算最大輪廓的邊界框
 int index = getMaxContour(contours);
 if (index == -1) {
     return -1;
 }
 vector<Point> cnt = contours[index];
 //使用邊界矩形信息,將輪廓內(nèi)填充成白色
 drawContours(tresh, contours, index, Scalar(255,0,0));
 //蒙板
 Mat mask = Mat::zeros(tresh.rows, tresh.cols, CV_8UC1); // 0矩陣
 //依賴輪廓罚拟,繪制最大外接矩形框(內(nèi)部填充)
 Rect cntRect = cv::boundingRect(cnt);
 rectangle(mask, cntRect, cv::Scalar(255, 0, 0), -1);
//循環(huán)最大的輪廓邊框
int getMaxContour(std::vector<vector<cv::Point>> contours){
  double max_area = 0;
  int index = -1;
  for (int i = 0; i < contours.size(); i++) {
     double tempArea = contourArea(contours[i]);
     if (tempArea > max_area) {
         max_area = tempArea;
         index = i;
     }
  }
  return index;
}

經(jīng)過(guò)上述操作我們得到下圖:

最大輪廓

這個(gè)白色區(qū)域是整個(gè)全景圖可以容納下的最小的矩形區(qū)域台诗。
接下來(lái)我們將進(jìn)行微微關(guān)鍵和巧妙的部分。

首先我們創(chuàng)建一塊mask蒙板的兩個(gè)副本赐俗。
minRect拉队,這個(gè)mask的白色區(qū)域會(huì)慢慢縮小,直到它剛好可以完全放入全景圖內(nèi)部阻逮。
sub粱快,這個(gè)mask用于確定minRect是否需要繼續(xù)減小,以得到滿足要求的矩形區(qū)域夺鲜。

不斷地對(duì)minRect進(jìn)行腐蝕操作皆尔,然后用minRect減去之前得到的閾值圖像,得到sub币励,
再判斷sub中是否存在非零像素慷蠕,如果不存在,則此時(shí)的minRect就是我們最終想要的全景圖內(nèi)部最大矩形區(qū)域食呻。

sub和minRect在while循環(huán)中的變化情況如下動(dòng)圖所示:

腐蝕動(dòng)態(tài)效果

因?yàn)镺penCV中灰度圖像素值范圍0-255流炕,如果兩個(gè)數(shù)相減得到負(fù)數(shù)的話,會(huì)直接將其置為0仅胞;如果兩個(gè)數(shù)相加每辟,結(jié)果超過(guò)了255的話,則直接置為255干旧。

比如下面這個(gè)圖渠欺,左圖中白色矩形可以完全包含在全景圖中,但不是全景圖的最大內(nèi)接矩形椎眯,用它減去右邊的閾值圖挠将,

因?yàn)楹谏袼販p白色像素,會(huì)得到黑色像素编整,所以其結(jié)果圖為全黑的圖舔稀。

最大內(nèi)接矩形

既然我們已經(jīng)得到了全景圖內(nèi)的內(nèi)置最大矩形邊框,接下來(lái)就是找到這個(gè)矩形框的輪廓掌测,并獲取其坐標(biāo):

//第二次循環(huán)
cv::Mat minRectClone = minRect.clone();

cv::resize(minRectClone, minRectClone,
           cv::Size(minRectClone.cols * scale, minRectClone.rows * scale),
           (float)minRect.cols / 2, (float)minRect.rows / 2,INTER_LINEAR);

std::vector<std::vector<Point> > cnts;
vector<Vec4i> hierarchyA = vector<cv::Vec4i>();
findContours(minRectClone, cnts, hierarchyA, RETR_TREE, CHAIN_APPROX_SIMPLE);
int idx = getMaxContour(cnts);
if (idx == -1) {
    return -1;
}
//最終矩形輪廓
Rect finalRect = cv::boundingRect(cnts[idx]);

最后我們通內(nèi)接矩形輪廓内贮,提取最終的全景圖

//提取最終全景圖
outputMat = Mat(stitched, finalRect).clone();

得到最終結(jié)果圖如下:

最終結(jié)果

源代碼如下:

  #include "stitching.h"
  #include "algorithm"
  #include <iostream>
  #include <fstream>//openCV 2.4.x
  //#include "opencv2/stitching/stitcher.hpp"
  //openCV 3.x
  #include "opencv2/stitching.hpp"
  //cpenCV 4.x 以上調(diào)用混編,OC類需將引入的openCV頭文件放入s引入的最前方

  using namespace std;
  using namespace cv;
  bool try_use_gpu = false;
  vector<Mat> imgs;
  string result_name = "result.jpg";
  int thresh = 100;
  int max_thresh = 255;
  RNG rng(12345);

  const int scale = 2;
  void printUsage();
  int parseCmdArgs(int argc, char** argv);
  int getMaxContour(std::vector<vector<cv::Point>> contours);

  cv::Mat stitch (vector<Mat>& images)
  {
  imgs = images;
  Mat pano;//拼接圖
  /* 3.0
   Stitcher stitcher = Stitcher::createDefault(try_use_gpu);
   Stitcher::Status status = stitcher.stitch(imgs, pano);
   */
  Ptr<Stitcher> stitcher = Stitcher::create();//4.0

  //拼接
  Stitcher::Status status = stitcher->stitch(imgs, pano);

  if (status != Stitcher::OK)
  {
      cout << "Can't stitch images, error code = " << int(status) << endl;
  }

  return pano;
  }
  int corpBoundingRect(cv::Mat &inputMat, cv::Mat &outputMat)
  {
  //在全景圖四周各添加10像素寬的黑色邊框,以確保能夠找到全景圖的完整輪廓:
  Mat stitched;//黑色邊框輪廓圖
  copyMakeBorder(inputMat, stitched, 10, 10, 10, 10, cv::BORDER_CONSTANT, true);
  //全景圖轉(zhuǎn)換灰度圖夜郁,并將不為0的像素全置為255
  //作為前景尖啡,其他像素灰度值為0尤辱,作為背景。
  Mat gray;
  cv::cvtColor(stitched, gray, cv::COLOR_BGR2GRAY);

  //中值濾波,去除黑色邊際中可能含有的噪聲干擾
  cv::medianBlur(gray, gray, 7);

  //白色剪影與黑色背景
  Mat tresh;
  threshold(gray, tresh, 0, 255, THRESH_BINARY);

  //resize 縮小一半處理
  resize(tresh, tresh,
         Size(tresh.cols / scale, tresh.rows / scale),
         tresh.cols / 2,
         tresh.rows / 2, INTER_LINEAR);

  //現(xiàn)在有了全景圖的二值圖护昧,再應(yīng)用輪廓檢測(cè)滥玷,找到最大輪廓的邊界框两疚,
  vector<vector<Point>> contours; //contours:包含圖像中所有輪廓的python列表(三維數(shù)組),每個(gè)輪廓是包含邊界所有坐標(biāo)點(diǎn)(x, y)的Numpy數(shù)組量窘。
  vector<Vec4i> hierarchy = vector<cv::Vec4i>();//vec4i是一種用于表示具有4個(gè)維度的向量的結(jié)構(gòu),每個(gè)值都小于cc>
  findContours(tresh.clone(), contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);//傳入?yún)?shù)不一樣
  //計(jì)算最大輪廓的邊界框
  int index = getMaxContour(contours);
  if (index == -1) {
      return -1;
  }
  vector<Point> cnt = contours[index];
  drawContours(tresh, contours, index, Scalar(255,0,0));

  //蒙板
  Mat mask = Mat::zeros(tresh.rows, tresh.cols, CV_8UC1); // 0矩陣
  //依賴輪廓?jiǎng)?chuàng)建矩形
  Rect cntRect = cv::boundingRect(cnt);
  rectangle(mask, cntRect, cv::Scalar(255, 0, 0), -1);

  Mat minRect = mask.clone();//minRect的白色區(qū)域會(huì)慢慢縮小赵颅,直到它剛好可以完全放入全景圖內(nèi)部虽另。
  Mat sub = mask.clone();//sub用于確定minRect是否需要繼續(xù)減小,以得到滿足要求的矩形區(qū)域饺谬。

  //開(kāi)始while循環(huán)捂刺,直到sub中不再有前景像素
  while (cv::countNonZero(sub) > 0) {
  //        int zero = cv::countNonZero(sub);
  //        printf("剩余前景像素 %d \n",zero);
      cv::erode(minRect, minRect, Mat());
      cv::subtract(minRect, tresh, sub);
  }

  //第二次循環(huán)
  cv::Mat minRectClone = minRect.clone();

  cv::resize(minRectClone, minRectClone,
             cv::Size(minRectClone.cols * scale, minRectClone.rows * scale),
             (float)minRect.cols / 2, (float)minRect.rows / 2,INTER_LINEAR);

  std::vector<std::vector<Point> > cnts;
  vector<Vec4i> hierarchyA = vector<cv::Vec4i>();
  findContours(minRectClone, cnts, hierarchyA, RETR_TREE, CHAIN_APPROX_SIMPLE);
  int idx = getMaxContour(cnts);
  if (idx == -1) {
      return -1;
  }
  Rect finalRect = cv::boundingRect(cnts[idx]);
//
////    printf("finalRect {x = %d, y = %d, width = %d, height = %d \n", finalRect.x, finalRect.y, finalRect.width, finalRect.height);
  outputMat = Mat(stitched, finalRect).clone();

  return 0;
}

//魚(yú)眼拼接
cv::Mat fisheyeStitch (vector<Mat>& images) {
    imgs = images;
    Mat pano;
//    Stitcher stitcher = Stitcher::createDefault(try_use_gpu);

    Ptr<Stitcher> stitcher = Stitcher::create();//4.0
    Ptr<FisheyeWarper> fisheye_warper = makePtr<cv::FisheyeWarper>();
//    stitcher.setWarper(fisheye_warper);
    stitcher->setWarper(fisheye_warper);

    //拼接
    //    Stitcher::Status status = stitcher.stitch(imgs, pano);
    Stitcher::Status status = stitcher->stitch(imgs, pano);
    if (status != Stitcher::OK)
    {
        cout << "Can't stitch images, error code = " << int(status) << endl;
        //return 0;
    }
    return pano;
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市募寨,隨后出現(xiàn)的幾起案子族展,更是在濱河造成了極大的恐慌,老刑警劉巖拔鹰,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件仪缸,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡列肢,警方通過(guò)查閱死者的電腦和手機(jī)恰画,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)瓷马,“玉大人拴还,你說(shuō)我怎么就攤上這事∨菲福” “怎么了片林?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)怀骤。 經(jīng)常有香客問(wèn)我费封,道長(zhǎng),這世上最難降的妖魔是什么晒喷? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任孝偎,我火速辦了婚禮访敌,結(jié)果婚禮上凉敲,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好爷抓,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布势决。 她就那樣靜靜地躺著,像睡著了一般蓝撇。 火紅的嫁衣襯著肌膚如雪果复。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,784評(píng)論 1 290
  • 那天渤昌,我揣著相機(jī)與錄音虽抄,去河邊找鬼。 笑死独柑,一個(gè)胖子當(dāng)著我的面吹牛迈窟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播忌栅,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼车酣,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了索绪?” 一聲冷哼從身側(cè)響起湖员,我...
    開(kāi)封第一講書(shū)人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎瑞驱,沒(méi)想到半個(gè)月后娘摔,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡钱烟,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年晰筛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拴袭。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡读第,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出拥刻,到底是詐尸還是另有隱情怜瞒,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布般哼,位于F島的核電站吴汪,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蒸眠。R本人自食惡果不足惜漾橙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望楞卡。 院中可真熱鬧霜运,春花似錦脾歇、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至焦除,卻和暖如春激况,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背膘魄。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工乌逐, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人创葡。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓黔帕,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親蹈丸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子成黄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348