CvANN_MLP(OpenCV 的神經(jīng)網(wǎng)絡(luò)-多層感知器) 進(jìn)行路牌判別

原載于 用 CvANN_MLP 進(jìn)行路牌判別


這原是智能計(jì)算課程大作業(yè)。

(有時(shí)候我真的不知道怎么措辭,應(yīng)該用“分類識(shí)別”,還是“判別”,還是“斷別”?不在意這些細(xì)節(jié)。我說(shuō)的是區(qū)分一張圖片是不是路牌,就這么簡(jiǎn)單。)

使用 ANN-MLP(神經(jīng)網(wǎng)絡(luò)--多層感知器)方法。利用 Qt4、OpenCV2 程序庫(kù),進(jìn)行路牌的摳取、分類和識(shí)別。開(kāi)源在 GitHub:district10/SignProcessing: 路牌提取、分類,包括源碼、文檔、測(cè)試數(shù)據(jù)和可執(zhí)行文件。其中的qt4cv3vs2015分支將 OpenCV 更新到了 OpenCV3,重新梳理、整合了各個(gè)模塊。

快速入門

文檔貼在這里,原載 Issues · district10/SignProcessing

下載二進(jìn)制

DLL 依賴

Qt 和 OpenCV 的 dll,以及 VS2015 的 runtime,點(diǎn)此下載 (11.2 MB),和下面的二進(jìn)制執(zhí)行檔放到一起即可。

二進(jìn)制執(zhí)行檔

如何用已訓(xùn)練模型來(lái)測(cè)試圖片是否是路牌

SignProcessorDemo.exe

這個(gè) exe 有三個(gè)功能,主要是用已經(jīng)訓(xùn)練好的模型來(lái)預(yù)測(cè)(predict)。

功能 1,判別單張圖片

加載一個(gè)圖片,如果判定是一個(gè)路牌,用淡藍(lán)色顯示“Sign (pos)”:

如果不是一個(gè)路牌,用紅色顯示“Not Sign (neg)”:

功能 2,判別文件夾下所有圖片

批量判斷。選擇一個(gè)文件夾(這里是 dir_of_images)。

判斷結(jié)果輸出在文本框內(nèi),包括圖片數(shù)目、每張圖片的路徑以及判別結(jié)果:

To Predict 38 images.


    processing D:/tzx/git/SignProcessing/data/input/dir_of_images/0002-team7 (109)-shift-sx-36-sy-16.bmp... done. assigned to [pos]
    ...
    processing D:/tzx/git/SignProcessing/data/input/dir_of_images/dir2/dir22/0001-team7 (108)-rand-cx0623-cy0398-r135.bmp... done. assigned to [neg]

所選文件夾同一目錄下還會(huì)出現(xiàn)兩個(gè)目錄 posneg,分別把判別后的圖片拷貝到里里面:

功能 3,判別選擇的多張圖片

判別為路牌的,標(biāo)記“Pos”,不是路牌的,標(biāo)記“Neg”。

默認(rèn)用了
data/output/4.xml
來(lái)預(yù)測(cè),你也可以點(diǎn)擊【Load Another XML】加載別的已訓(xùn)練模型。


1)雙擊運(yùn)行 SignClassifierDemo.exe

2)加載模型

點(diǎn)擊【Load Trained】選擇 XML 文件(在 data/output 文件夾)。
我這里選擇了 4.xml

3)選擇想要測(cè)試的圖片

點(diǎn)擊【Unknown】,選擇一些圖片(在 data/input/tests 文件夾)。

如下圖,

還沒(méi)有預(yù)測(cè)的圖片下面有“???”標(biāo)記,它表示還未測(cè)試。

然后點(diǎn)擊【Predict】。

有的標(biāo)記變成了“Pos”(是路牌),有得標(biāo)記變成了“Neg”(不是路牌),
如下圖:

如何從數(shù)據(jù)訓(xùn)練模型

1)雙擊運(yùn)行 SignClassifierDemo.exe

2)添加正負(fù)樣本

點(diǎn)擊【Pos】,選擇一些正樣本。(可用 <kbd>Control+A</kbd> 全選)

同理,點(diǎn)擊【Neg】,選擇一些負(fù)樣本。(加載可能有點(diǎn)慢)

3)訓(xùn)練

點(diǎn)擊【Train】,訓(xùn)練開(kāi)始。訓(xùn)練完成后,會(huì)提示已經(jīng)訓(xùn)練的量。

記得點(diǎn)擊【Save Trained】保存訓(xùn)練參數(shù),下次就可以直接用【Load Trained】加載它了。

試試加載一些 Unknown,測(cè)試一下它的判斷是否正確。


就是這樣。

從源碼編譯

具體見(jiàn) 代碼編譯 · Issue #3 · district10/SignProcessing

更進(jìn)一步

可以看到,看到程序能基本準(zhǔn)確地分辨哪些是路牌,哪些不是。現(xiàn)在我從數(shù)據(jù)采集、處理,程序編寫(xiě)來(lái)把整個(gè)流程說(shuō)一遍。

源數(shù)據(jù)的處理

源數(shù)據(jù)是我們?cè)谖錆h街頭拍攝的路牌圖片。大概像這樣:

因?yàn)樵磮D片里面有車牌之類的沒(méi)有打碼(哪有這空啊……),就不分享了。

從源圖片中提取正負(fù)樣本,用的是我們組寫(xiě)的 Sign Cutter(路牌切割)程序(源碼),

Sign Cutter

利用它可以很快速地導(dǎo)入源影像,并進(jìn)行切片處理。【保存切片】操作會(huì)把框選區(qū)域保存成一個(gè)切片圖,作為我們?nèi)斯みx取的正樣本。同時(shí),正樣本需要轉(zhuǎn)化到 24 × 24 的 RGB 圖像,為保證圖片不失真,還要將之存儲(chǔ)為 BMP 格式。

![正樣本為人工選取的路牌][cutbmp]

因?yàn)檎龢颖镜臄?shù)量有限,需要通過(guò)平移、旋轉(zhuǎn)、鏡像(鏡像后再平移旋轉(zhuǎn))等方式從一張正樣本產(chǎn)生多張類似正樣本。在 SignCutter 中通過(guò)點(diǎn)擊【生成正樣本】實(shí)現(xiàn)。

![posnegclick]

負(fù)樣本則是從圖中非正樣本區(qū)域,隨機(jī)選取中心點(diǎn)和旋轉(zhuǎn)角度,選取而來(lái)。正負(fù)樣本選取比例我們?cè)O(shè)置為 1:7。通過(guò)點(diǎn)擊【生成負(fù)樣本】來(lái)快速地生成一系列負(fù)樣本。結(jié)果就是,一張?jiān)磮D片,一個(gè)人工框選區(qū)域,生成了 87 張正樣本,602 張負(fù)樣本。最終我們生成了 8 組共 25,230 張正樣本,69,894 張負(fù)樣本,用于訓(xùn)練和檢測(cè)。

你可以下載 圖片索引,每一行代表一個(gè)樣本,后面的 1 表示“是路牌”,0 表示“不是路牌”,大概長(zhǎng)這樣:

1/pos/0001-team7 (1)-___.bmp, 1
1/pos/0001-team7 (1)-___-r000.bmp, 1
1/pos/0001-team7 (1)-___-r030.bmp, 1
...
8/neg/0025-team7 (292)-rand-cx0469-cy2820-r108.bmp, 0
8/neg/0025-team7 (292)-rand-cx0471-cy2508-r232.bmp, 0
8/neg/0025-team7 (292)-rand-cx0473-cy2701-r358.bmp, 0

圖片數(shù)據(jù)在這里下載:

![正樣本 87 張 & 負(fù)樣本 602 張][posneg]

為了保證得到的正樣本足夠好,我們不是在切片上進(jìn)行這些操作,而是在源影像上,這就避免了旋轉(zhuǎn)平移后圖片中存在空白。這通過(guò)一個(gè) SignLogger 模塊實(shí)現(xiàn)(源碼),它記錄了切片的源圖片、歸一化了的中心點(diǎn)、歸一化了的寬度和高度。

![通過(guò)點(diǎn)擊【查看切片記錄】查看切片信息][cuttedoutinfo]

![這就是剛才那張路牌切片的 log 信息][posinfo]

整個(gè)正樣本和負(fù)樣本如圖,紅框內(nèi)為切片區(qū)域,綠色矩形為正樣本框(較密集),藍(lán)色為負(fù)樣本框(分散在源影像中非正樣本區(qū)域)。

![signcutdemo]

從文件名也能看出每個(gè)切片的信息,這里是正負(fù)樣本文件名的 Sample^[完整版可以在 http://gnat.qiniudn.com/sc/info-all.txt 下載。]

原圖片 team7 (148).jpg 產(chǎn)生的正負(fù)樣本

.
├── pos                            (正樣本 87 張)
│   ├── 0041-team7 (148)-___.bmp                 原切片
│   ├── 0041-team7 (148)-___-r030.bmp            原切片旋轉(zhuǎn) 30 度
│   ├──..............................
│   ├── 0041-team7 (148)-___-r330.bmp
│   ├── 0041-team7 (148)-L-R.bmp                 左右鏡像
│   ├── 0041-team7 (148)-L-R-r030.bmp            左右鏡像后,旋轉(zhuǎn) 30 度
│   ├── 0041-team7 (148)-L-R-r060.bmp
│   ├── 0041-team7 (148)-L-R-r090.bmp
│   ├── .............................
│   ├── 0041-team7 (148)-L-R-r330.bmp
│   ├── 0041-team7 (148)-shift-sx000-sy003.bmp   平移左右 0%,上下 3%
│   ├── .............................
│   ├── 0041-team7 (148)-shift-sx-10-sy-10.bmp
│   ├── 0041-team7 (148)-U-D.bmp                 上下鏡像
│   ├── .............................
│   └── 0041-team7 (148)-U-D-r330.bmp
├── neg                            (負(fù)樣本 602 張)
│   ├── 0041-team7 (148)-rand-cx0057-cy1450-r305.bmp 隨機(jī)中心點(diǎn)、旋轉(zhuǎn)角度
│   ├── ............................................
│   └── 0041-team7 (148)-rand-cx2393-cy2979-r299.bmp
└── team7 (148)-demo.jpg           (展示了正負(fù)樣本切片位置)

2 directories, 696 files

有了正負(fù)樣本,就看用 OpenCV 提供的 ANN_MLP 進(jìn)行基于神經(jīng)網(wǎng)絡(luò)方法的學(xué)習(xí)。這部分知識(shí)附在本文末尾。

從編碼的角度,利用神經(jīng)網(wǎng)絡(luò)多層感知器分類的實(shí)際比圖片預(yù)處理、正負(fù)樣本的生成要簡(jiǎn)單,代碼量也少很多。所以我從代碼大概說(shuō)一下使用方法。

首先,引入 OpenCV3 相關(guān)頭文件:

#include <opencv2/core.hpp>
#include <opencv2/ml.hpp>

我封裝了一個(gè) MLP 類,提供簡(jiǎn)單的訓(xùn)練接口(源碼)。這里是精簡(jiǎn)了的類聲明:

class MLP
{
public:
    MLP();                                          // 構(gòu)造函數(shù),mlp 的初始化
    ~MLP() { }

    void loadXML( const QString &xml  );            // 加載已經(jīng)訓(xùn)練好的模型
    void saveXML( const QString &xml  );            // 保存訓(xùn)練好的模型
    void loadCSV( const QString &csv );             // 加載正負(fù)樣本用于訓(xùn)練
    void train();                                   // 訓(xùn)練

    // 最后,對(duì)圖片進(jìn)行判別:是否為路牌
    //  輸入為一串圖片路徑
    //  輸出為圖片路徑和一個(gè)標(biāo)志,true 說(shuō)明圖片為路牌,false 說(shuō)明不是
    QList<QPair<QString, bool> > predictImages( const QStringList &images );

private:
    cv::Ptr<cv::ml::ANN_MLP> mlp;                   // 存儲(chǔ)了參數(shù)
};

其中在構(gòu)造函數(shù)中,要實(shí)例化 mlp,還要對(duì)它進(jìn)行配置:

mlp = cv::ml::ANN_MLP::create();
// 層數(shù)和每層 neuron 個(gè)數(shù)是“精選”出來(lái)的
mlp->setLayerSizes( (cv::Mat)(cv::Mat_<int>(1,5)
                                << FEATURENUM, FEATURENUM / 2, FEATURENUM / 6, FEATURENUM / 24, 1) );

// 激活函數(shù)設(shè)置為 sigmoid
mlp->setActivationFunction( cv::ml::ANN_MLP::SIGMOID_SYM, 1.0, 1.0 );

// 訓(xùn)練方法為反向傳播
mlp->setTrainMethod( cv::ml::ANN_MLP::BACKPROP, 0.1, 0.1 );
mlp->setTermCriteria( cv::TermCriteria(
                            cv::TermCriteria::COUNT + cv::TermCriteria::EPS
                            , 5000
                            , 0.01 ) );

激活函數(shù)和反向傳播的說(shuō)明見(jiàn)本文末尾附錄。反向傳播一個(gè)比較好的文檔見(jiàn)我的筆記:Principles of training multi-layer neural network using backpropagation

Layer 的層數(shù)和每層的 neuron 數(shù)目對(duì)訓(xùn)練結(jié)果有較大影響,這是我之前測(cè)試后畫(huà)的 Excel 表格:^[順便學(xué)習(xí)了 Excel 的 minimap 的用法哈哈。]

CvANN::MLP 中神經(jīng)網(wǎng)絡(luò)層數(shù)對(duì)正確率的影響
CvANN::MLP 中 layerSizes(即每層中 neuron 的個(gè)數(shù))對(duì)正確率的影響

設(shè)置好了,就可以用已經(jīng)準(zhǔn)備好的正負(fù)樣本訓(xùn)練它。我們先不考慮的是如何從 24 × 24 的 RGB 圖片,生成 feature 向量(也就是這里的 Utils::img2feature 函數(shù)的實(shí)現(xiàn)細(xì)節(jié))。

// pos 和 neg 是圖片路徑列表,分別是正負(fù)樣本集合
int np = pos.length();
int nn = neg.length();

// 生成數(shù)據(jù)集,這里的 features 和 flags 就是機(jī)器學(xué)習(xí)里
// 常說(shuō)的 data 和 label
float *features = new float[FEATURENUM*(np + nn)];
float *flags    = new float[np + nn];

// 用圖片初始化 features,并設(shè)置好 labels
for ( int i = 0; i < np; ++i ) {
    Utils::img2feature(qPrintable(pos.at(i)), features + FEATURENUM*i);
    *(flags + i)        =       1.0f;       // 正樣本為 1
}
for ( int i = 0; i < nn; ++i ) {
    Utils::img2feature(qPrintable(neg.at(i)), features + FEATURENUM*np+FEATURENUM*i);
    *(flags + np + i)   =       -1.0f;      // 負(fù)樣本為 -1
}

// 存到 OpenCV 的矩陣結(jié)構(gòu) Mat 里,并生成數(shù)據(jù)集
cv::Mat featureMat( np+nn, FEATURENUM,  CV_32F, features );
cv::Mat flagMat(    np+nn, 1,           CV_32F, flags );
cv::Ptr<cv::ml::TrainData> trainSet // mat 的每一行代表一條數(shù)據(jù)
    = cv::ml::TrainData::create( featureMat, cv::ml::ROW_SAMPLE, flagMat );

// 然后就可以訓(xùn)練了
mlp->train( trainSet );

// 不要忘了釋放內(nèi)存
delete[] features;
delete[] flags;

現(xiàn)在就能測(cè)試,

float feature[FEATURENUM];
Utils::img2feature( "path/to/image", feature );

cv::Mat featureMat  ( 1, FEATURENUM,    CV_32F, feature );
cv::Mat result      ( 1, 1,             CV_32FC1 );

mlp->predict( featureMat, result );
float *p = result.ptr<float>(0);

這個(gè) *p 大于 0,則圖片被判定為路牌,否則不是路牌。

我們還可以將訓(xùn)練好的模型保存起來(lái),方便下載加載:

// 保存
mlp->save( "path/to/save/model(xml file)" );

// 加載
mlp = cv::Algorithm::load<cv::ml::ANN_MLP>( "path/to/saved-model.xml" );

我訓(xùn)練了幾組數(shù)據(jù),得到的 xml 文件見(jiàn) data/output 文件夾。大概長(zhǎng)這樣。

最后我們看看那個(gè) Utils::img2feature 函數(shù)的實(shí)現(xiàn)(這里加上了額外的注釋,做了額外的對(duì)齊):

// feature 已經(jīng)分配好內(nèi)存了,內(nèi)存大小是 sizeof(float)*FEATURENUM
// FEATURENUM 是一個(gè)宏,定義為
//      #define FEATURENUM  ( 24*(3+3)+ 3 + 4*3 )
// 看了后面的數(shù)據(jù)處理你大概能懂這些數(shù)字每個(gè)代表什么

bool Utils::img2feature( const char *filePath, float *feature )
{
    Mat img = imread( filePath, cv::IMREAD_COLOR );
    if ( img.rows != IMGSIZE || img.cols != IMGSIZE ) {
        qDebug() << __FUNCDNAME__ << "failed, because of wrong dim," << "\tfilepath:" << filePath;
        return false;
    }

    int allCountR          =   0  , allCountG          =   0  , allCountB          =   0  ;
    int rowCountR[IMGSIZE] = { 0 }, rowCountG[IMGSIZE] = { 0 }, rowCountB[IMGSIZE] = { 0 };
    int colCountR[IMGSIZE] = { 0 }, colCountG[IMGSIZE] = { 0 }, colCountB[IMGSIZE] = { 0 };
    int tempR[4]           = { 0 }, tempG[4]           = { 0 }, tempB[4]           = { 0 };

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

            int b = img.at<cv::Vec3b>(i,j)[0];
            int g = img.at<cv::Vec3b>(i,j)[1];
            int r = img.at<cv::Vec3b>(i,j)[2];

            allCountR += r;     rowCountR[i] += r;     colCountR[j] += r;
            allCountG += g;     rowCountG[i] += g;     colCountG[j] += g;
            allCountB += b;     rowCountB[i] += b;     colCountB[j] += b;

            /*
             * 分成四個(gè)部分,統(tǒng)計(jì)各自區(qū)域內(nèi)的 rgb 比例,四個(gè)區(qū)域低編號(hào)為:
             *
             *           0 | 1
             *           --+--
             *           2 | 3
            */
            int index = 2 * (i < IMGSIZE / 2 ? 0 : 1) + (j < IMGSIZE / 2 ? 0 : 1);
            tempR[index] += r;
            tempG[index] += g;
            tempB[index] += b;
        }
    }

    for ( int i=0; i < IMGSIZE; ++i ) {
        feature[i*6+0] = (float)rowCountR[i]/allCountR; // 第 i 行的紅色占全圖紅色的比例
        feature[i*6+1] = (float)rowCountG[i]/allCountG; // 綠色
        feature[i*6+2] = (float)rowCountB[i]/allCountB; // 藍(lán)色
        feature[i*6+3] = (float)colCountR[i]/allCountR; // 第 i 列
        feature[i*6+4] = (float)colCountG[i]/allCountG;
        feature[i*6+5] = (float)colCountB[i]/allCountB;
    }
    // rgb 三種顏色的比例
    feature[IMGSIZE*6+0] = (float)allCountR / (allCountR + allCountG +allCountB);
    feature[IMGSIZE*6+1] = (float)allCountG / (allCountR + allCountG +allCountB);
    feature[IMGSIZE*6+2] = (float)allCountB / (allCountR + allCountG +allCountB);

    for ( int i = 0; i < 4; ++i ) {
        // 區(qū)域內(nèi)的 rgb 比例
        int total = tempR[i] + tempG[i] + tempB[i];
        feature[IMGSIZE*6+3 + 3*i+0] = (float)tempR[i] / total;
        feature[IMGSIZE*6+3 + 3*i+1] = (float)tempG[i] / total;
        feature[IMGSIZE*6+3 + 3*i+2] = (float)tempB[i] / total;
    }
    return true;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,698評(píng)論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,202評(píng)論 3 426
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 177,742評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,580評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,297評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,688評(píng)論 1 327
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,693評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,875評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,438評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,183評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,384評(píng)論 1 372
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,931評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,612評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 35,022評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,297評(píng)論 1 292
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,093評(píng)論 3 397
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,330評(píng)論 2 377

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