OpenCV-9-直方圖和模板匹配

1 摘要

在分析圖像狐胎、物體和視頻信息的時(shí)候特姐,我們通常使用直方圖來表示我們關(guān)注的信息同木。直方圖可以描述很多不同的信息浮梢,如物體的顏色分布,物體的邊緣梯度模板彤路,假設(shè)的物體位置的概率分布秕硝。下圖簡單演示了如何使用直方圖進(jìn)行快速的手勢(shì)識(shí)別。

在該示例中模型手的邊緣梯度分布被分為5個(gè)不同的組洲尊,分別表示上远豺、下、左坞嘀、右和OK手勢(shì)躯护。通過網(wǎng)絡(luò)攝像頭分析用戶的手勢(shì)可以實(shí)現(xiàn)用戶通過手勢(shì)控制視頻的播放。在獲取到每一幀圖像后丽涩,首先分析感興趣的顏色區(qū)域棺滞,這里是用戶的手,然計(jì)算其邊緣的梯度直方圖矢渊,再將其和已知的5類手勢(shì)直方圖模型進(jìn)行匹配检眯,找到匹配程度最高的模型,從而達(dá)到手勢(shì)識(shí)別的目的昆淡。

圖中的豎直細(xì)長矩形條表示當(dāng)前幀的興趣區(qū)域邊緣直方圖和已有手勢(shì)模型直方圖的匹配程度锰瘸,水平灰色線條表示匹配程度的閾值,即只有當(dāng)匹配程度高于此值時(shí)這種匹配關(guān)系才是有效的昂灵。

在很多計(jì)算機(jī)視覺程序中都使用到了直方圖避凝,如視頻數(shù)據(jù)中每幀畫面的邊緣和顏色統(tǒng)計(jì)直方圖發(fā)生顯著變化表示場(chǎng)景的切換舞萄。你也可以通過興趣點(diǎn)附近特征的直方圖為其添加標(biāo)簽,從而識(shí)別興趣點(diǎn)管削。邊緣倒脓、顏色和角點(diǎn)等直方圖形成了目標(biāo)的通用特征,這些特征可以被分類器使用從而識(shí)別目標(biāo)對(duì)象含思。顏色和邊緣直方圖的序列也可以被用于鑒定視頻是否復(fù)制于網(wǎng)絡(luò)崎弃。類似的應(yīng)用實(shí)例還有很多,總之直方圖是計(jì)算機(jī)視覺的經(jīng)典工具含潘。

直方圖只是簡單的將原始數(shù)據(jù)分到預(yù)先定義好的多個(gè)組中饲做,并計(jì)算每個(gè)組內(nèi)的樣本數(shù)量。也可以先計(jì)算出原始數(shù)據(jù)中的梯度幅度遏弱、方向和顏色等特征盆均,然后再對(duì)這些特征計(jì)數(shù)。不管是哪一種情況漱逸,直方圖獲得的都是原始數(shù)據(jù)分布的統(tǒng)計(jì)圖像泪姨。通常直方圖的維度都比原始數(shù)據(jù)更低,如下圖中左上圖展示了一個(gè)二維分布的樣本集合饰抒,通過右上圖中定義的組統(tǒng)計(jì)每個(gè)組內(nèi)樣本的數(shù)量肮砾,則得到右下圖的一維統(tǒng)計(jì)直方圖。

由于直方圖可以處理任意含義的原始數(shù)據(jù)袋坑,因此它是一個(gè)用于表示圖像信息的便利工具仗处。

在生成直方圖時(shí)可能會(huì)遇到下圖演示的問題。左側(cè)的兩幅圖像的分組過寬咒彤,則得到的結(jié)果會(huì)過于粗糙疆柔,并且丟失了數(shù)據(jù)的分布細(xì)節(jié)信息咒精。右側(cè)的兩幅圖像的分組過細(xì)镶柱,每個(gè)組中就沒有足夠的數(shù)據(jù)點(diǎn)來準(zhǔn)確的表示分布信息,并且會(huì)出現(xiàn)一些細(xì)小的突刺單元模叙。

2 直方圖

新版本的OpenCV使用矩陣cv::Mat和稀疏矩陣cv::SparseMat對(duì)象來表示一維或者多維的直方圖歇拆,同時(shí)支持在每個(gè)維度上組的分布都可以是均勻的或者非均勻的,另外還提供了一些有用的函數(shù)來執(zhí)行常見的直方圖操作范咨。

盡管直方圖對(duì)象使用了和圖像數(shù)據(jù)同類型的對(duì)象故觅,盡管他們底層的數(shù)據(jù)結(jié)構(gòu)是相同的,但是它們內(nèi)部的數(shù)據(jù)含義卻不相同渠啊。對(duì)于n行矩陣表示的直方圖(N×1的矩陣除外)输吏,每個(gè)數(shù)據(jù)表示的都是在某個(gè)維度(以行索引標(biāo)識(shí))的某組數(shù)據(jù)(以列索引標(biāo)識(shí))的統(tǒng)計(jì)結(jié)果。數(shù)據(jù)組的索引和其實(shí)際含義是分離的替蛉,在使用直方圖時(shí)需要在它們之間轉(zhuǎn)換贯溅。例如拄氯,在表示人體重的直方圖中,數(shù)據(jù)組可能被分為20到40它浅、40到60译柏、60到80和80到100四組,它們的索引是0姐霍、1鄙麦、2、3镊折,幸運(yùn)的是OpenCV中的很多函數(shù)都會(huì)在內(nèi)部執(zhí)行這種轉(zhuǎn)換邏輯胯府。

如果需要使用高維的直方圖時(shí),通常情況下大部分元素的值都是0腌乡,此時(shí)可以選擇更合適的數(shù)據(jù)類型cv::SparseMat盟劫。實(shí)際上該類型的設(shè)計(jì)原因就是為了更好的處理直方圖數(shù)據(jù)。稠密矩陣的大部分函數(shù)都適用于稀疏矩陣,當(dāng)然我們也會(huì)介紹一些值得關(guān)注的特例勤庐。

2.1 創(chuàng)建直方圖

創(chuàng)建直方圖的函數(shù)原型如下发绢,需要注意直方圖的維度和輸入矩陣數(shù)組的維度無關(guān),僅和其數(shù)量及矩陣的數(shù)量相關(guān)影所,直方圖的每個(gè)維度反應(yīng)的是某個(gè)輸入矩陣的某個(gè)通道上的數(shù)據(jù)統(tǒng)計(jì)結(jié)果。當(dāng)然你也可以指定應(yīng)當(dāng)統(tǒng)計(jì)哪些輸入矩陣的哪些通道僚碎。

// images:C語言風(fēng)格的待處理的矩陣數(shù)組猴娩,基本數(shù)據(jù)類型為8U或者32F
// nimages:輸入矩陣的個(gè)數(shù)
// channels:C語言風(fēng)格的待處理通道列表,下文介紹
// mask:蒙版矩陣勺阐,只統(tǒng)計(jì)其中非0值對(duì)應(yīng)的原始數(shù)據(jù)中的點(diǎn)
// hist:統(tǒng)計(jì)結(jié)果直方圖
// dims:直方圖的維度卷中,必須小于cv::MAX_DIMS(32)
// histSize:C語言風(fēng)格數(shù)組,直方圖在每個(gè)維度應(yīng)當(dāng)分配的數(shù)據(jù)組數(shù)
// ranges:C語言風(fēng)格的數(shù)組渊抽,每個(gè)元素都是一個(gè)數(shù)組蟆豫,表示在對(duì)應(yīng)維度上的分組策略,
//        具體細(xì)節(jié)和uniform相關(guān)懒闷,下文介紹
// uniform:直方圖分組策略是均勻分組還是非均勻分組十减,下文介紹
// accumulate:在生成直方圖時(shí)是累加(false)還是清空(true)hist內(nèi)部的數(shù)據(jù)
void cv::calcHist(const cv::Mat* images, int nimages, const int* channels,
                  cv::InputArray mask, cv::OutputArray hist, int dims,
                  const int* histSize, const float** ranges, bool uniform = true,
                  bool accumulate = false);

// 和前一個(gè)函數(shù)類似,但是輸出數(shù)據(jù)類型為稀疏矩陣
void cv::calcHist(const cv::Mat* images, int nimages, const int* channels,
                  cv::InputArray mask, cv::SparseMat& hist, int dims,
                  const int* histSize, const float** ranges, bool uniform = true,
                  bool accumulate =false);

函數(shù)cv::calcHist()有三種形式愤估,除了上文列出的形式使用C語言風(fēng)格的數(shù)組作為輸入矩陣外帮辟,第三種使用STL向量模板容器作為參數(shù)類型。

函數(shù)的第一個(gè)參數(shù)images包含了構(gòu)建直方圖需要的nimages個(gè)數(shù)據(jù)矩陣玩焰。所有的矩陣尺寸必須相同由驹,但是通道數(shù)可以不同,基本事件類型可以是8位或者32位昔园,但是它們應(yīng)該是相同的蔓榄。channels指定了輸入數(shù)組中的哪些通道應(yīng)當(dāng)被處理的闹炉,通道的索引是按順序編號(hào)的,即images[0]中的第一個(gè)通道的索引是0润樱,然后遞增渣触,images[1]的通道索引在此基礎(chǔ)上繼續(xù)遞增∫既簦可以明顯看出channels的元素個(gè)數(shù)和最后得到的直方圖的維度相同嗅钻。

參數(shù)ranges表示直方圖各個(gè)維度上的分組策略,數(shù)組的具體含義和參數(shù)uniform的取值相關(guān)店展。如果參數(shù)uniform設(shè)置為true养篓,則每個(gè)組的區(qū)間是相同的,此時(shí)需要在參數(shù)ranges中指定每個(gè)組的下邊界和上邊界(不包含)赂蕴,例如ranges[i] = [0, 100.0)柳弄。如果參數(shù)uniform設(shè)置為false,如果在第i維度存在N個(gè)分組概说,則ranges[i]應(yīng)該包含N+1個(gè)元素碧注,其中的第j個(gè)元素表示第j-1分組的上界,以及低j分組的下界糖赔。如果使用的參數(shù)類型為STL的vector <float>萍丐,則和c語言數(shù)組的區(qū)別在于向量類型的參數(shù)中數(shù)據(jù)都是一維的。Ranges內(nèi)數(shù)據(jù)的含義如下圖放典。

2.2 基礎(chǔ)直方圖操作

盡管直方圖使用的數(shù)據(jù)類型是圖像數(shù)據(jù)使用的cv::Mat逝变,但是在OpenCV中對(duì)于直方圖含義的矩陣支持一些新的操作,在本小節(jié)將會(huì)介紹這些操作奋构,另外也會(huì)介紹如何使用矩陣本身已經(jīng)支持的函數(shù)來完成一些重要的直方圖操作壳影。

2.2.1 直方圖標(biāo)準(zhǔn)化

通常我們得到直方圖數(shù)據(jù)后需要將其標(biāo)準(zhǔn)化,使直方圖的每個(gè)維度上的數(shù)值都表示的是占整個(gè)直方圖的比值弥臼,其實(shí)現(xiàn)方式如下宴咧。

// 使用運(yùn)算符
cv::Mat normalized = my_hist / sum(my_hist)[0];
// 使用函數(shù)
cv::normalize(my_hist, my_hist, 1, 0, NORM_L1);
2.2.2 直方圖閾值處理

另外一個(gè)很常見的操作是你希望閾值化處理一個(gè)直方圖并丟棄其中所以值低于閾值的元素,此時(shí)你可以使用處理標(biāo)準(zhǔn)矩陣的閾值函數(shù)醋火,示例代碼如下悠汽。

cv::threshold(my_hist, my_thresholded_hist, threshold, 0, cv::THRESH_TOZERO);
2.2.3 尋找最大最小值

通常在處理概率分布直方圖時(shí)箱吕,你可能并不想做閾值處理芥驳,而是像找到其中的最大或者最小值,OpenCV提供一種函數(shù)來實(shí)現(xiàn)這個(gè)功能茬高,其中處理二維矩陣函數(shù)void cv::minMaxLoc()的原型如下兆旬。

// 處理二維矩陣的函數(shù)
// src:待查詢的矩陣
// minVal:最小值,設(shè)置為NULL時(shí)不會(huì)計(jì)算
// maxVal:最大值怎栽,設(shè)置為NULL時(shí)不會(huì)計(jì)算
// minLoc:最小值的位置丽猬,設(shè)置為NULL時(shí)不會(huì)計(jì)算
// maxLoc:最大值的位置宿饱,設(shè)置為NULL時(shí)不會(huì)計(jì)算
// mask:蒙板矩陣,數(shù)值為0的點(diǎn)對(duì)應(yīng)的原始矩陣在統(tǒng)計(jì)時(shí)將被忽略
void cv::minMaxLoc(cv::InputArray src, double* minVal, double* maxVal = 0,
                   cv::Point* minLoc = 0, cv::Point* maxLoc = 0,
                   cv::InputArray mask = cv::noArray());

// 處理任意維度稀疏矩陣的函數(shù)
void cv::minMaxLoc(const cv::SparseMat& src, double* minVal, double* maxVal = 0,
                   int* minLoc = 0, int* maxLoc = 0,
                   cv::InputArray mask = cv::noArray());

該函數(shù)的使用實(shí)例如下脚祟。提示:對(duì)于vector<>數(shù)組谬以,可以通過cv::Mat(vec).reshape(1)將其轉(zhuǎn)換為通道數(shù)為1的矩陣。

// 尋找二維矩陣表示的直方圖中的最大值及其位置
double max_val;
cv::Point max_pt;
cv::minMaxLoc(my_hist, NULL, &max_val, NULL,  &max_pt);

// 尋找任意維度稀疏矩陣表示的直方圖中的最大值及其位置
double maxval;
int max_pt[CV_MAX_DIM];
cv::minMaxLoc(my_hist, NULL, &max_val, NULL, max_pt);

函數(shù)cv::minMaxIdx()用于尋找任意維度矩陣中的最大最小值以及它們的位置由桌,其函數(shù)原型如下为黎。

void cv::minMaxIdx(cv::InputArray src, double* minVal, double* maxVal = 0,
                   int* minLoc = 0, int* maxLoc = 0,
                   cv::InputArray mask = cv::noArray());

需要注意的是如果使用了一維矩陣作為輸入函數(shù),參數(shù)minLocmaxLoc分配的矩陣仍需要包含兩個(gè)元素行您,因?yàn)樵摵瘮?shù)內(nèi)部會(huì)將一維矩陣轉(zhuǎn)換為二維矩陣铭乾。如果尋找到的元素索引為k,當(dāng)輸入矩陣尺寸為N??1娃循,返回值為(k, 0)炕檩,當(dāng)輸入矩陣尺寸為1??N,返回值為(0, k)捌斧。

2.2.4 直方圖比較

在某種指定的標(biāo)準(zhǔn)下比較直方圖的相似性是一個(gè)必不可缺的圖像處理工具笛质,該方法由Swain和Ballard發(fā)明,并由Schiele和Crowley加以推廣捞蚂。該方法可以有很多應(yīng)用場(chǎng)景经瓷,可以通過該函數(shù)比較兩幅圖像的直方圖的相似性完成圖像匹配任務(wù),也可以通過直方圖比較來搜索模型洞难。后者的做法是比較圖像不同子區(qū)域和目標(biāo)模型的直方圖舆吮,通過匹配程度判斷該子區(qū)域是否包含模型。其函數(shù)原型如下队贱。

// H1:待比較的直方圖1
// H2:待比較的直方圖2色冀,尺寸需要和H1相同
// method:比較方法,下文介紹
double cv::compareHist(cv::InputArray H1, cv::InputArray H2,
                       int method);

double cv::compareHist(const cv::SparseMat& H1, const cv::SparseMat& H2,
                       int method);

參數(shù)method指定了直方圖相似的判斷標(biāo)準(zhǔn)柱嫌,可以選擇的方法有四種锋恬。

相關(guān)性方法

定義相關(guān)性方法的值為cv::COMP_CORREL,該方法基于皮爾遜(Pearson)相關(guān)性系數(shù)實(shí)現(xiàn)编丘,當(dāng)H1和H2表示概率分布時(shí)与学,使用該方法非常合適。距離計(jì)算的公式如下嘉抓。

其中

N表示直方圖的分組數(shù)量索守,直方圖的匹配程度越高,使用該方法的匹配函數(shù)返回值越大抑片。1表示完全匹配卵佛,-1表示完全不匹配,而0表示兩個(gè)分布無關(guān)系,也就是隨機(jī)組合截汪。

Chi-square方法

定義Chi-square方法的值為cv::COMP_CHISQR_ALT疾牲,該方法基于chi-squared測(cè)試統(tǒng)計(jì)方法實(shí)現(xiàn),它同樣可以檢測(cè)兩個(gè)分布的相關(guān)性衙解,其函數(shù)原型如下阳柔。

直方圖的匹配程度越高,該方法的到的值越低蚓峦,完全匹配的結(jié)果為0盔沫,而完全不匹配的值取決于直方圖的大小。

交集法

定義交集法的值為cv::COMP_INTERSECT枫匾,該方法基于兩個(gè)直方圖的簡單交集實(shí)現(xiàn)架诞,其計(jì)算公式如下。

直方圖的匹配程度越高干茉,該方法計(jì)算得到的值越高谴忧,如果H1和H2是兩個(gè)經(jīng)過標(biāo)準(zhǔn)化處理的直方圖,則完全匹配的結(jié)果為1角虫,完全不匹配的結(jié)果為0沾谓。

巴氏距離

定義巴氏距離(Bhattacharyya)的值為cv::COMP_BHATTACHARYYA,也是基于兩個(gè)分布的重疊部分實(shí)現(xiàn)的一種距離計(jì)算方法戳鹅,其公式如下均驶。

直方圖的匹配程度越高,該方法計(jì)算得到的值越低枫虏,完全匹配的結(jié)果為0妇穴,完全不匹配的結(jié)果為1。盡管該方法內(nèi)部會(huì)有一個(gè)因子來對(duì)直方圖進(jìn)行標(biāo)準(zhǔn)化處理隶债,但是通常情況像你應(yīng)當(dāng)自行對(duì)輸入?yún)?shù)進(jìn)行標(biāo)準(zhǔn)化處理腾它,因?yàn)轭愃朴?jì)算直方圖交集的概念對(duì)于未標(biāo)準(zhǔn)化的直方圖是完全沒有意義的。

考慮簡單的只包含兩個(gè)分組的一維標(biāo)準(zhǔn)化直方圖死讹,模型左側(cè)分組的值為1.0瞒滴,右側(cè)分組的值為0,則使用上述4種方法計(jì)算出的匹配結(jié)果如下圖所示赞警。仔細(xì)觀察下圖會(huì)發(fā)現(xiàn)當(dāng)直方圖匹配模型只是簡單反轉(zhuǎn)時(shí)妓忍,即下圖的第2和第4行圖像,前4種方法計(jì)算得到的都是完全不匹配的結(jié)果愧旦,即使在某種程度上這兩種分布仍然有一定相似性世剖。

EMD也是一種距離算法忘瓦,在處理半匹配時(shí)該方法能夠得到更準(zhǔn)確的結(jié)果搁廓,下文還會(huì)詳細(xì)的介紹到EMD(Earth Mover‘s Distance)算法,這里暫時(shí)先不再討論耕皮。根據(jù)該書原作者的經(jīng)驗(yàn)境蜕,使用交集法處理快速粗糙的直方圖匹配效果更好,而chi-square方法和巴氏方法精度更高凌停,但是計(jì)算成本更高粱年,EMD算法給出的結(jié)果是最符合視覺感受的,但是它的計(jì)算速度更慢罚拟。

2.2.5 直方圖創(chuàng)建示例

示例Computation讀取了一副圖像台诗,將其轉(zhuǎn)換為HSV顏色空間,然后統(tǒng)計(jì)器色度分量H和飽和度分量S的分布直方圖赐俗,其核心代碼如下拉队。

int main(int argc, const char * argv[]) {
    // 讀取原圖
    cv::Mat src = cv::imread(argv[1], cv::IMREAD_COLOR);
    // 構(gòu)建HSV顏色空間數(shù)據(jù)
    cv::Mat hsv;
    cv::cvtColor(src, hsv, cv::COLOR_BGR2HSV);

    // 設(shè)定二維直方圖中的分組策略
    // HSV標(biāo)準(zhǔn)定義色調(diào)H取值區(qū)域?yàn)閇0, 360),S和V取值區(qū)域?yàn)閇0, 1]阻逮,而在OpenCV中為了能夠用8位
    // 統(tǒng)一表示粱快,H減半處理及,OpenCV中HSV的H值僅為實(shí)際值的一半叔扼,因此其取值范圍為[0, 180)事哭,
    // 而S和V的取值為[0, 255]
    float h_ranges[] = {0, 180};
    float s_ranges[] = {0, 256};
    const float * ranges[] = {h_ranges, s_ranges};
    int histSize[] = {30, 32};
    int ch[] = {0, 1};

    // 計(jì)算二維直方圖
    cv::Mat hist;
    cv::calcHist(&hsv, 1, ch, cv::noArray(), hist, 2, histSize, ranges, true);
    // 標(biāo)準(zhǔn)化處理直方圖
    cv::normalize(hist, hist, 0, 255, cv::NORM_MINMAX);
    
    // 定義單個(gè)數(shù)據(jù)點(diǎn)表示的方塊直徑
    int scale = 10;
    // 創(chuàng)建顯示二維直方圖的圖像
    cv::Mat hist_img(histSize[0] * scale, histSize[1] * scale, CV_8UC3);
    // 在圖像hist_img中繪制scale??scale像素的方塊表示二維直方圖hist中的每個(gè)數(shù)據(jù)點(diǎn)
    for (int h = 0; h < histSize[0]; h++) {
        for (int s = 0; s < histSize[1]; s++) {
            float hval = hist.at<float>(h, s);
            cv::rectangle(hist_img,
                          cv::Rect(h * scale, s * scale, scale, scale),
                          cv::Scalar::all(hval), -1);
        }
    }
    return 0;
}

程序的運(yùn)行結(jié)果如下圖。其中左圖為原始圖像瓜富,右圖為其色度和飽和度分布直方圖鳍咱,不用為圖中的大部分黑色區(qū)域感到好奇,這是因?yàn)樯群惋柡投鹊姆植歼^于集中引起的与柑。

在很多實(shí)際的應(yīng)用中膚色的顏色直方圖很有用谤辜,下圖包含了不同光照環(huán)境下的手掌照片及整張圖片的顏色直方圖,最左側(cè)一列是手掌的照片价捧,中間列是BGR顏色空間的直方圖每辟,而右側(cè)一列是HSV顏色空間的直方圖。從圖中可以看出隨著光照環(huán)境的改變干旧,手掌的膚色也會(huì)發(fā)生一些變化渠欺。

為了測(cè)試直方圖比較的結(jié)果,從某個(gè)圖片中選擇部分區(qū)域(如室內(nèi)圖片的上部分)椎眯,計(jì)算其顏色直方圖并將其和室內(nèi)環(huán)境的下半部分圖片及另外兩個(gè)光照環(huán)境下的整幅手掌圖片的顏色直方圖比較挠将。這里選擇使用HSV顏色空間的直方圖,它可以使用色度H和飽和度S的直方圖進(jìn)行膚色匹配编整,盡管還有變量亮度V的值未使用舔稀,但是這已經(jīng)足夠我們完成即使是跨種族的膚色匹配任務(wù)。

一個(gè)實(shí)現(xiàn)了上述膚色匹配任務(wù)的比較結(jié)果如下表掌测。其中内贮,室內(nèi)圖像的上半幅圖片和下半幅圖片對(duì)顏色直方圖匹配程度很高,和另外兩種光照環(huán)境下的顏色直方圖匹配程度較低。

比較目標(biāo) 相關(guān)性法 Chi-Square方法 交集法 巴氏距離
完全匹配參考目標(biāo) (1.0) (0.0) (1.0) (0.0)
下半幅室內(nèi)圖片 0.96 0.14 0.82 0.2
室外陰影圖片 0.09 1.57 0.13 0.8
室外明亮圖片 0.0 1.98 0.01 0.99

2.3 復(fù)雜直方圖方法

在介紹了一些基礎(chǔ)的直方圖操作后夜郁,接下來將會(huì)介紹一些高級(jí)的直方圖方法什燕,這些方法包括比較直方圖,計(jì)算或者可視化圖像的哪部分對(duì)指定部分的直方圖有貢獻(xiàn)竞端。

2.3.1 EMD距離

在前面的文章中我們已經(jīng)看見光照條件的改變會(huì)導(dǎo)致明顯的顏色位移屎即,如在前文的手掌照片及直方圖展示中就能觀察到這個(gè)現(xiàn)象。盡管這些位移不會(huì)改變顏色直方圖的形狀事富,但是會(huì)改變它的位置技俐,從而使得直方圖匹配度較低。這也是直方圖匹配度一個(gè)難點(diǎn)统台,我們經(jīng)常想要找到一個(gè)距離度量標(biāo)準(zhǔn)使得形狀相同但是發(fā)生位移的直方圖分布能夠得到一個(gè)匹配的比較結(jié)果雕擂,EMD距離(Earth Mover‘s Distance)就是這樣的距離度量標(biāo)準(zhǔn)。

該方法的基本思想是通過移動(dòng)部分或者整個(gè)直方圖到新的位置將其“搬到”另外一個(gè)直方圖中贱勃,并度量該操作耗費(fèi)的成本捂刺。如在上文演示函數(shù)cv::compareHist()使用的簡單匹配模型結(jié)果中,該方法的完美匹配直方圖計(jì)算得到的距離為0募寨,而對(duì)于半匹配的模型距離為0.5族展,而對(duì)于其中完全不匹配的情況需要將整個(gè)直方圖向由移動(dòng)一步,因此得到的距離為1拔鹰。

實(shí)際上EMD是一個(gè)相同通用的算法仪缸,它允許用戶自定義距離度量衡以及移動(dòng)成本矩陣。你可以通過記錄數(shù)據(jù)從一個(gè)直方圖的何處移動(dòng)到了另一個(gè)直方圖中的何處列肢,并且基于數(shù)據(jù)的先驗(yàn)信息(Prior Information)的到一個(gè)非線性的距離恰画。OpenCV提供的EMD函數(shù)原型如下。

// 參數(shù)詳細(xì)含義下文介紹
// signature1:待比較的直方圖轉(zhuǎn)換的簽名格式瓷马,尺寸為sz1??dms+1
// signature2:待比較的直方圖轉(zhuǎn)換的簽名格式拴还,尺寸為sz2??dms+1
// distType:距離類型,如cv::DIST_L1
// cost:移動(dòng)成本矩陣欧聘,尺寸為sz1??sz2片林,當(dāng)參數(shù)選擇cv::DIST_USER需要設(shè)置此參數(shù)
// lowerBound:兩個(gè)直方圖重心距離下界,可以同時(shí)作為輸入和輸出
// flow:尺寸為sz1??sz2怀骤,表示直方圖1中第I個(gè)元素流向直方圖2中第j個(gè)元素的質(zhì)量
float cv::EMD(cv::InputArray signature1, cv::InputArray signature2,
              int distType, cv::InputArray cost = noArray(),
              float* lowerBound = 0, cv::OutputArray flow = noArray());

在使用該函數(shù)比較直方圖距離的時(shí)候费封,必須將直方圖轉(zhuǎn)換為一種稱為簽名的格式,該格式需要傳入一個(gè)sz1??dms+1的矩陣蒋伦,每一行數(shù)據(jù)都包含直方圖在某個(gè)坐標(biāo)上的統(tǒng)計(jì)結(jié)果以及對(duì)應(yīng)的坐標(biāo)弓摘。如在三維直方圖中在點(diǎn)(7, 43, 11)的分組的值為537,則簽名矩陣對(duì)應(yīng)的某行數(shù)據(jù)為[537, 7, 43, 11]痕届。

參數(shù)distType表示EMD距離的度量衡韧献,其取值在前文很多地方都已經(jīng)有過介紹末患,可以是曼哈頓街區(qū)距離(Manhattan Distance),取值為cv::DIST_L1锤窑,歐式距離(Euclidean Distance)璧针,取值為cv::DIST_L2,也可以是棋盤距離(Checkerboard Distance)果复,取值為cv::DIST_C陈莽,或者是用戶自定義的距離衡渤昌,取值為cv::DIST_USER虽抄。當(dāng)選擇用戶自定義距離時(shí),需要通過參數(shù)cost指定移動(dòng)成本独柑,即自定義的移動(dòng)距離迈窟。其尺寸為sz1??sz2,該矩陣的每個(gè)元素(坐標(biāo)為ij)表示從直方圖1的第i個(gè)元素表示的位置到直方圖2第j個(gè)元素表示的位置之間的距離忌栅。

lowerBound可以同時(shí)作為輸入和輸出參數(shù)车酣。作為輸出參數(shù),它表示兩個(gè)直方圖的重心(Center of Mass)距離的下界索绪。為了計(jì)算這個(gè)下屆湖员,必須使用標(biāo)準(zhǔn)的距離度量衡,即參數(shù)distType不能設(shè)置為cv::DIST_USER瑞驱,并且兩個(gè)直方圖的總權(quán)重必須相同(經(jīng)過標(biāo)準(zhǔn)化處理的兩個(gè)直方圖總權(quán)重就是相同的娘摔,都為1)。做為輸入?yún)?shù)唤反,它必須被賦予有意義的值凳寺,即如果兩個(gè)直方圖的重心距離低于等于該值才會(huì)計(jì)算EMD距離,這種機(jī)制很有用彤侍,因?yàn)橛?jì)算重心距離會(huì)比計(jì)算EMD距離快很多肠缨,如果重心距離都已經(jīng)大于指定的值了,則我們認(rèn)為兩個(gè)直方圖已經(jīng)不匹配了盏阶,就沒有必要再計(jì)算其準(zhǔn)確的EMD距離绑谣。如果想跳過此邏輯,只需將參數(shù)lowerBound的值設(shè)置為0即可兄裂。

參數(shù)flow是一個(gè)可選的sz1??sz2矩陣钞楼,其中的元素E(i, j)表示直方圖1中第i個(gè)元素流向直方圖2中第j個(gè)元素的質(zhì)量,其實(shí)該參數(shù)就是表示數(shù)據(jù)流動(dòng)的過程蒸眠。

示例程序EMD使用已經(jīng)介紹過的5種距離比較了前文室內(nèi)手掌圖片上半幅圖與下半幅圖漾橙,以及與其他光照條件下的整幅手掌圖,以及一副完全無關(guān)圖像的顏色直方圖的相似程度楞卡。程序運(yùn)行后得到的原始圖像及其顏色直方圖表示如下霜运。

距離比較結(jié)果如下表脾歇。這里在進(jìn)行前四種方法比較直方圖時(shí)標(biāo)準(zhǔn)化的區(qū)域?yàn)?到255,使用EMD比較直方圖距離時(shí)的標(biāo)準(zhǔn)化方式是直方圖的所有元素?cái)?shù)據(jù)權(quán)重和為1淘捡。這里和前文原書中給出的比較結(jié)果不同藕各,推測(cè)應(yīng)當(dāng)是標(biāo)準(zhǔn)化策略以及圖像選取的范圍有差異。

比較目標(biāo) 相關(guān)性法 Chi-Square方法 交集法 巴氏距離 EMD距離
下半幅室內(nèi)圖片 0.501425 1648.11 301.039 0.55207 1.52593
室外陰影圖片 0.459135 244318 633.146 0.449522 2.73921
室外明亮圖片 0.676008 35634.4 756.639 0.408152 1.80114
完全無關(guān)的水果圖片 0.0938923 3380830 389.745 0.689889 2.8382
2.3.2 反向投影

反向投影(Back Projection)可以判斷像素集合與指定的直方圖表示的顏色分布的匹配程度焦除。例如假定我們有膚色的顏色直方圖激况,可以通過該技術(shù)尋找圖像中和膚色匹配的區(qū)域。從統(tǒng)計(jì)學(xué)的角度看膘魄,如果將已知的直方圖分布看作是在特定目標(biāo)上的顏色分布的先驗(yàn)概率分布(Prior Probability Distribution)乌逐,則反向投影就是計(jì)算圖像中的任意區(qū)域符合該先驗(yàn)概率分布的概率,也就是說屬于目標(biāo)物體的概率创葡。實(shí)際上反向投影就是計(jì)算每個(gè)像素在直方圖分布對(duì)應(yīng)分組中的計(jì)數(shù)值浙踢。

OpenCV提供了兩個(gè)函數(shù)分別用于處理密集矩陣和稀疏矩陣,其函數(shù)原型如下灿渴。直方圖中只包含了維度洛波、維度分組及每個(gè)分組的統(tǒng)計(jì)結(jié)果,但是不包含分組的區(qū)間骚露,所以需要額外的參數(shù)ranges確定蹬挤。

// images:反向投影的目標(biāo)查詢數(shù)組矩陣,單通道8U或者三通道32F棘幸,所以矩陣大小類型必須相同焰扳,
//         通道數(shù)可以不同
// nimages:images中包含矩陣的個(gè)數(shù)
// channels:需要比較的通道索引,索引的編號(hào)規(guī)則和函數(shù)cv::calcHist()相同够话,該數(shù)組的格式和
//           參數(shù)hist表示的直方圖的維度相同
// hist:比較的直方圖矩陣
// backProject:反向投影的結(jié)果蓝翰,和參數(shù)images中的矩陣大小和數(shù)據(jù)類型相同,單通道
// ranges:直方圖每個(gè)維度的分組策略女嘲,和函數(shù)cv::calcHist()相同
// scale:輸出結(jié)果的縮放系數(shù)畜份,通常反向投影得到的數(shù)據(jù)值都較低,有時(shí)適當(dāng)放大結(jié)果可視化效果更好
// uniform:分組策略是否為均勻分布欣尼,和函數(shù)cv::calcHist()相同
void cv::calcBackProject(const cv::Mat* images, int nimages, const int* channels,
                         cv::InputArray hist, cv::OutputArray backProject,
                         const float** ranges,
                         double scale = 1, bool uniform = true);

void cv::calcBackProject(const cv::Mat* images, int nimages, const int* channels,
                         const cv::SparseMat& hist, cv::OutputArray backProject,
                         const float** ranges,
                         double scale = 1, bool uniform = true);

void cv::calcBackProject(cv::InputArrayOfArrays images,
                         const vector<int>& channels,
                         cv::InputArray hist, cv::OutputArray backProject,
                         const vector<float>& ranges,
                         double scale = 1, bool uniform = true);

如果調(diào)用該函數(shù)使用的直方圖是經(jīng)過標(biāo)準(zhǔn)化處理的爆雹,則反向投影的結(jié)果就是某個(gè)像素是直方圖表示的分布中的一部分的概率。即對(duì)于前文講到的膚色示例而言愕鼓,如果C是某個(gè)像素的顏色值钙态,而F表示該像素屬于皮膚的概率。則可以通過p(C|F)表示在皮膚中像素C出現(xiàn)的概率菇晃,這和p(F|C)表示的含義不同册倒,后者表示顏色C屬于皮膚的概率,也是反向投影在某種程度上表示的含義磺送。但是這兩個(gè)概率可以通過如下貝葉斯公式計(jì)算驻子,其中p(F)表示場(chǎng)景中指定目標(biāo)的累計(jì)概率灿意,p(C)表示場(chǎng)景中顏色C的累計(jì)概率,當(dāng)然對(duì)于直方圖而言這是顏色區(qū)間的累計(jì)概率崇呵。

下圖使用膚色直方圖計(jì)算了一副圖像中像素顏色屬于皮膚的概率缤剧,直方圖計(jì)算的是HSV顏色空間中的色度H和飽和度S。其中左上角圖片為皮膚模型的顏色直方圖域慷,可以用于計(jì)算上述公式中的p(C|F)荒辕,右上側(cè)圖像為測(cè)試圖像,左下圖為測(cè)試圖像的顏色直方圖犹褒,右下角圖像是根據(jù)該顏色直方圖進(jìn)行反向投影得到的結(jié)果抵窒,也就是上述公式中的p(F|C)。需要注意這里進(jìn)行反向投影時(shí)傳入的直方圖為皮膚模型的顏色直方圖化漆。

通常情況下你可以通過如下三步來尋找感興趣的區(qū)域估脆。首先創(chuàng)建需要尋找的目標(biāo)或者區(qū)域的直方圖钦奋。然后使用該直方圖計(jì)算待查詢圖像的后向投影座云,其中較高的值表示和感興趣區(qū)域匹配程度更高。最后根據(jù)后向投影中的高值取原圖的部分區(qū)域付材,再計(jì)算其直方圖和目標(biāo)區(qū)域的直方圖進(jìn)行比較朦拖。

需要注意的是如果后向投影圖的基本數(shù)據(jù)類型為cv::U8,不要對(duì)直方圖進(jìn)行標(biāo)準(zhǔn)化厌衔,對(duì)于已經(jīng)標(biāo)準(zhǔn)化的直方圖要進(jìn)行放大處理璧帝,因?yàn)闃?biāo)準(zhǔn)后后的直方圖中的最大值為1,在cv::U8數(shù)據(jù)類型中除了1都會(huì)被轉(zhuǎn)換為0富寿。

3 模版匹配

模版匹配不依賴于直方圖睬隶,相反其實(shí)現(xiàn)方式是通過一個(gè)圖像塊在輸入圖像上滑動(dòng),匹配的方法下文介紹页徐,一個(gè)模版匹配的示例如下圖苏潜。其中左上角圖片表示了待匹配目標(biāo)HSV顏色模型下的HS直方圖。而右側(cè)圖像是需要查詢的圖片变勇,其中白色方塊表示滑動(dòng)圖像塊的大小恤左,這里需要查找的是咖啡杯。左下圖為待查詢圖像的HS直方圖搀绣,而右下圖為測(cè)試圖像應(yīng)用模版匹配的結(jié)果飞袋,可以明顯看出咖啡杯已經(jīng)被挑選出來。

另外一個(gè)模版匹配的場(chǎng)景如下圖链患,這里用的是一副包含人臉的圖像滑塊巧鸭,通過在輸入圖像滑動(dòng)該滑塊可以在整個(gè)圖像中尋找到最到最好的匹配效果來確定人臉的存在。

OpenCV提供的模版匹配函數(shù)如下麻捻。

// image:待查詢的圖片纲仍,8U或者32F的灰度或者彩色圖像览闰,大小為W??H
// templ:圖像滑塊,大小為w??h
// result:模版匹配的結(jié)果巷折,單通道压鉴,數(shù)據(jù)類型為32F,大小為W-w+1??H-h+1
// method:模版匹配使用的方法锻拘,下文介紹
void cv::matchTemplate(cv::InputArray image, cv::InputArray templ,
                       cv::OutputArray result, int method);

參數(shù)method指定了模版匹配的方法油吭,可選值如下,在下面的公式中將使用I表示輸入圖像署拟,T表示圖像滑塊婉宰,R表示模版匹配的結(jié)果。每一種方法都有一個(gè)標(biāo)準(zhǔn)化版本推穷,因?yàn)椴煌庹窄h(huán)境下的數(shù)據(jù)標(biāo)準(zhǔn)化后系數(shù)相同心包,可以排除光照條件帶來的干擾。

3.1 方差匹配方法

該方法取值為cv::TM_SQDIFF馒铃,比較的是兩個(gè)矩陣的方差蟹腾,得到的值越小表示越相似,其公式如下区宇。

3.2 歸一化方差匹配

歸一化方差匹配的取值為cv::TM_SQDIFF_NORMED娃殖,完全匹配情況下計(jì)算得到的值為0,其公式如下议谷。

3.3 相關(guān)性匹配

相關(guān)性匹配方法的取值為cv::TM_CCORR,該方法以乘法的方式匹配模版波附,越匹配的矩陣計(jì)算結(jié)果越大狈究,完全不匹配的矩陣計(jì)算結(jié)果為0,其公式如下拯勉。

3.4 歸一化相關(guān)性匹配

歸一化相關(guān)性匹配的取值為cv::TM_CCORR_NORMED妥曲,極度不匹配的矩陣計(jì)算結(jié)果趨于0,其公式如下波丰。

3.5 相關(guān)性系數(shù)匹配

相關(guān)性系數(shù)匹配的取值為cv::TM_CCOEFF掰烟,它比較的是兩個(gè)矩陣中元素和矩陣本身均值差值的相關(guān)性爽蝴,完美匹配矩陣的計(jì)算結(jié)果為1,完全不匹配矩陣的計(jì)算結(jié)果為-1纫骑,0表示無相關(guān)性蝎亚,其公式如下。

3.6 歸一化相關(guān)系數(shù)匹配

歸一化相關(guān)系數(shù)匹配的取值為cv::TM_CCOEFF_NORMED先馆,較好的匹配矩陣計(jì)算結(jié)果是較大正值发框,而匹配程度很差的矩陣計(jì)算結(jié)果是較大的負(fù)值,其公式如下煤墙,其中T’和I’計(jì)算方式通相關(guān)性系數(shù)匹配方法梅惯。

使用相對(duì)復(fù)雜的匹配方法(相關(guān)性系數(shù)匹配)替換相對(duì)簡單的匹配方法(如方差匹配)將會(huì)得到更準(zhǔn)確的匹配結(jié)果宪拥,但是會(huì)花費(fèi)更多的計(jì)算成本。最后嘗試所有的方法铣减,并在最終的應(yīng)用程序中權(quán)衡計(jì)算成本和結(jié)果精度選擇最合適的方法她君。需要注意的是除了方差匹配和歸一化方差匹配對(duì)于越好的匹配得到的計(jì)算結(jié)果越小,其他方法都是相反的葫哗。

當(dāng)模版匹配完成后犁河,可以通過函數(shù)cv::minMaxLoc()或者cv::minMaxIdx()尋找最佳匹配結(jié)果的位置。一個(gè)好的匹配模式應(yīng)當(dāng)是一個(gè)局部區(qū)域的像素點(diǎn)都取得較好的匹配結(jié)果魄梯,因?yàn)槟0嬖谙袼攸c(diǎn)臨域內(nèi)滑動(dòng)時(shí)桨螺,模版覆蓋區(qū)域內(nèi)像素點(diǎn)輕微改變通常不會(huì)使得匹配結(jié)果相差太多。同時(shí)為了避免圖像噪聲引起的異常高匹配酿秸,可以輕微的平滑模版匹配得到的結(jié)果再尋找極值灭翔。在這種場(chǎng)景下,前文介紹的圖像形態(tài)學(xué)的方法能夠發(fā)揮很好的作用辣苏。

示例程序Template實(shí)現(xiàn)了不同方法的模版匹配肝箱,其核心代碼如下。

int main(int argc, const char * argv[]) {
    // 讀取匹配模版圖像
    cv::Mat templ = cv::imread(argv[1], 1);
    // 讀取待查找圖像
    cv::Mat src = cv::imread(argv[2], 1);

    // 使用6種不同的方法執(zhí)行模版匹配操作
    cv::Mat ftmp[6];
    for (int i = 0; i < 6; ++i) {
        cv::matchTemplate( src, templ, ftmp[i], i);
        cv::normalize(ftmp[i],ftmp[i],1,0,cv::NORM_MINMAX);
    }
    
    return 0;
}

示例程序使用的模版圖像和待查找的圖像如下圖稀蟋,其中左側(cè)圖像是使用的模版滑塊煌张,右側(cè)圖像是待查找的圖像。

6種不同的模版匹配方法得到的效果圖如下退客,需要注意方差法和歸一化方差法的最佳匹配計(jì)算結(jié)果為0骏融,其他方法則剛好相反。因此在下圖中第一列的兩幅圖片中的黑色區(qū)域?yàn)槠ヅ浜玫膮^(qū)域萌狂,而右側(cè)兩列圖片中亮色區(qū)域?yàn)槠ヅ涑潭容^高的區(qū)域档玻。

4 小結(jié)

本章介紹了在OpenCV中如何使用矩陣和稀疏矩陣對(duì)象表示直方圖,在實(shí)際應(yīng)用中直方圖通常表示為概率密度函數(shù)茫藏,即其內(nèi)部的每個(gè)元素表示其對(duì)應(yīng)分組在整個(gè)隨機(jī)變量分布中的概率误趴。此外還介紹了如何使用直方圖識(shí)別物體和興趣區(qū)域。另外本章也介紹了直方圖的基本操作务傲,當(dāng)直方圖被看作是概率密度函數(shù)時(shí)這些操作通常能發(fā)揮較大的作用凉当。例如直方圖的標(biāo)準(zhǔn)化,以及比較直方圖售葡。在本章的最后看杭,我們討論了模版匹配,該技術(shù)能很好的處理高度結(jié)構(gòu)化的圖片天通。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末泊窘,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌烘豹,老刑警劉巖瓜贾,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異携悯,居然都是意外死亡祭芦,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門憔鬼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來龟劲,“玉大人,你說我怎么就攤上這事轴或〔” “怎么了?”我有些...
    開封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵照雁,是天一觀的道長蚕愤。 經(jīng)常有香客問我,道長饺蚊,這世上最難降的妖魔是什么萍诱? 我笑而不...
    開封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮污呼,結(jié)果婚禮上裕坊,老公的妹妹穿的比我還像新娘。我一直安慰自己燕酷,他們只是感情好籍凝,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著悟狱,像睡著了一般静浴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上挤渐,一...
    開封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音双絮,去河邊找鬼浴麻。 笑死,一個(gè)胖子當(dāng)著我的面吹牛囤攀,可吹牛的內(nèi)容都是我干的软免。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼焚挠,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼膏萧!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤榛泛,失蹤者是張志新(化名)和其女友劉穎蝌蹂,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體曹锨,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡孤个,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了沛简。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片齐鲤。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖椒楣,靈堂內(nèi)的尸體忽然破棺而出给郊,到底是詐尸還是另有隱情,我是刑警寧澤捧灰,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布丑罪,位于F島的核電站,受9級(jí)特大地震影響凤壁,放射性物質(zhì)發(fā)生泄漏吩屹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一拧抖、第九天 我趴在偏房一處隱蔽的房頂上張望煤搜。 院中可真熱鬧,春花似錦唧席、人聲如沸擦盾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽迹卢。三九已至,卻和暖如春徒仓,著一層夾襖步出監(jiān)牢的瞬間腐碱,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來泰國打工掉弛, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留症见,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓殃饿,卻偏偏與公主長得像谋作,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子乎芳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355