2015-04-05-《OpenCV 2 計(jì)算機(jī)視覺編程手冊(cè)》讀書筆記

鑒于中文語境下久免,學(xué)習(xí) OpenCV 的資料其實(shí)稀少,不是主要講解已經(jīng)過時(shí)de 1.x 版內(nèi)容《學(xué)習(xí) OpenCV》扭弧,就是各路博主碎片化的學(xué)習(xí)心得阎姥,《OpenCV 2 計(jì)算機(jī)視覺編程手冊(cè)》可以說是學(xué)習(xí) OpenCV 的最佳入門途徑了。最近需要將卷積神經(jīng)網(wǎng)絡(luò)的 Matlab 代碼轉(zhuǎn)換成 C++ 的鸽捻,我也向?qū)嶒?yàn)室同房間的一位學(xué)弟借了此書呼巴,大致看了一下,重點(diǎn)看了第 1 章御蒲、第 2 章衣赶、第 6 章和附錄。

因?yàn)檫@書是手冊(cè)性質(zhì)的删咱,都是一些函數(shù)實(shí)例屑埋,所以記錄下來豪筝,以便日后再用痰滋。下文是書中部分內(nèi)容的摘錄,夾雜一些我的理解续崖。全文可能有點(diǎn)長敲街,遂給出目錄如下:

第1章 接觸圖像
第2章 操作像素
第6章 圖像濾波
附錄 OpenCV3 介紹及代碼導(dǎo)讀

勘誤
我的困惑
下一步計(jì)劃

<div id="Section1">第1章 接觸圖像</div>

  • OpenCV 庫的結(jié)構(gòu)
  • 載入、顯示及保存圖像

OpenCV 庫的結(jié)構(gòu)

  • sources文件夾下的子文件夾:
    • doc 文件夾中包含的是文檔 + include 文件夾中是所有頭文件
    • modules 文件夾中包含所有的源程序
    • samples 文件夾中則是許多簡短的學(xué)習(xí)用范例

第 2 頁講了下怎么編譯的严望,對(duì)于新版 OpenCV 來說已經(jīng)沒有必要了多艇,解壓后的 build 文件夾就是編譯好的內(nèi)容。

第 3 頁介紹了各模塊的功能像吻,還有推薦的聲明方式峻黍,為什么要用這種聲明方式呢?

第 6 頁提到拨匆,為了遵循 ANSI C++ 標(biāo)準(zhǔn)姆涩,在用 Visual Studio 建立工程時(shí)選擇 Application Settings 時(shí),沒有勾選 Precompiled Header 選項(xiàng)惭每,這是 Visual Studio 的預(yù)編譯頭文件特性骨饿,可以加速編譯過程。

載入台腥、顯示及保存圖像

  • 聲明圖像變量
cv::Mat image;

創(chuàng)建寬高都為0的圖像宏赘,返回值是一個(gè)結(jié)構(gòu)體,

  • 圖像讀取黎侈、解碼以及內(nèi)存分配
image = cv::imread("img.jpg");
  • 檢查圖像是否被正確讀取
if (!image.data) {
    // 圖像尚未創(chuàng)建……
}

此處的成員變量data事實(shí)上是指向已分配的內(nèi)存塊的指針察署,包括圖像數(shù)據(jù)。當(dāng)不存在數(shù)據(jù)時(shí)峻汉,它被簡單設(shè)置為0.

  • 聲明一個(gè)需要進(jìn)行圖像顯示的窗口贴汪,接著指定需要顯示的圖像:
cv::namedWindow("Original Image");  // 定義窗口
cv::imshow("Original Image", image); // 顯示圖像

顯示圖像的這條語句之所以還要出現(xiàn)窗口名稱储藐,是為了指定究竟把圖像顯示到哪個(gè)窗口去,因?yàn)榭赡艽嬖诙鄠€(gè)窗口嘶是。

  • 圖像翻轉(zhuǎn)
cv::Mat result;
cv::flip(image, result, 1); // 1表示水平翻轉(zhuǎn)
                            // 0表示垂直翻轉(zhuǎn)
                            // 負(fù)數(shù)表示既有水平也有垂直翻轉(zhuǎn)
  • 等待用戶輸入
cv::waitKey(0)钙勃; //括號(hào)中填的數(shù)字是毫秒數(shù),0為一直等待

如果沒有這句話聂喇,顯示的圖像會(huì)一閃而過辖源。

  • 將圖像寫到磁盤
cv::imwrite("output.bmp", result);

文件的后綴名決定了圖像保存時(shí)的編碼格式。

  • 指定初始尺寸
cv::Mat ima(240, 320, CV_8U, cv::Scalar(100));

CV_8U對(duì)應(yīng)的是單字節(jié)的像素圖象希太,字母U意味著無符號(hào)的(Unsigned)克饶。對(duì)于彩色圖像,需要指定3個(gè)通道(CV_8UC3)誊辉。

當(dāng) cv::Mat 對(duì)象離開作用域后矾湃,分配的內(nèi)存將自動(dòng)釋放,從而避免內(nèi)存泄漏的困擾堕澄。
另外邀跃,cv::Mat 實(shí)現(xiàn)了引用計(jì)數(shù)以及淺拷貝,當(dāng)圖像之間進(jìn)行賦值時(shí)蛙紫,圖像數(shù)據(jù)并沒有發(fā)生復(fù)制拍屑,兩個(gè)對(duì)象都指向同一塊內(nèi)存塊。這也可用于參數(shù)傳值的圖像坑傅,以及返回值傳值的圖像僵驰。引用計(jì)數(shù)的作用是當(dāng)所有引用內(nèi)存數(shù)據(jù)的對(duì)象都被析構(gòu)后,才會(huì)釋放內(nèi)存塊唁毒。如果你希望創(chuàng)建的圖像擁有原始圖像的嶄新拷貝蒜茴,那么可以使用copyTo()方法。

cv::Mat image2, image3;
image2 = result; // 兩幅圖像擁有同一份數(shù)據(jù)
result.copyTo(image3); // 創(chuàng)建新的拷貝

如果翻轉(zhuǎn)output圖像浆西,并顯示image2和image3粉私,可以看到image2頁翻轉(zhuǎn)了,而image3沒有變室谚。

同理毡鉴,函數(shù)返回其實(shí)也是一次淺拷貝過程。

cv::Mat function() {
    // 創(chuàng)建圖像
    cv::Mat ima(240, 320, CV_8U, cv::Scalar(100));
    // 并返回它
    return ima;
}

// 得到灰度圖
cv::Mat gray = function();

在函數(shù)function內(nèi)秒赤,ima只是個(gè)局部變量猪瞬,在離開作用域時(shí)應(yīng)當(dāng)被析構(gòu)掉,但由于他所關(guān)聯(lián)的引用計(jì)數(shù)表示內(nèi)部圖像正在被另一個(gè)對(duì)象gray所引用入篮,因此內(nèi)存塊并不會(huì)被釋放陈瘦。

<div id="Section2">第2章 操作像素</div>

  • 彩色或灰度圖像存取像素值
void salt(cv::Mat &image, int n) {
    for (int k = 0; k < n; k++) {
        // rand() 是隨機(jī)數(shù)生成函數(shù)
        int i = rand() % image.cols;
        int j = rand() % image.rows;
        if (image.channels() == 1) { // 灰度圖
            image.at<uchar>(j,i) = 255;
        } else if (image.channels() == 3) { // 彩色圖
            image.at<cv::Vec3b>(j,i)[0] = 255;
            image.at<cv::Vec3b>(j,i)[1] = 255;
            image.at<cv::Vec3b>(j,i)[2] = 255;
        }
    }
}
  • 類 cv::Mat 有若干成員函數(shù)可以獲取圖像的屬性。公有成員變量 cols 和 rows 給出了圖像的寬和高潮售。成員函數(shù) at(int y, int x) 可以用來存取圖像元素。 但是必須在編譯期知道圖像的數(shù)據(jù)類型,因?yàn)?cv::Mat 可以存放任意數(shù)據(jù)類型的元素夏醉。這也是這個(gè)函數(shù)用模板函數(shù)來實(shí)現(xiàn)的原因。所以 at 方法要指定數(shù)據(jù)類型皱埠,而且 at 方法本身不會(huì)進(jìn)行任何數(shù)據(jù)類型轉(zhuǎn)換。

  • cv::Vec3b咖驮,即由三個(gè) unsigned char 組成的向量边器。

image.at<cv::Vec3b>(j,i)[channel] = value;

索引值 channel 標(biāo)明了顏色通道號(hào)。
類似的托修,還有二元素向量類 cv::Vec2b 和四元素向量類 cv::Vec4b忘巧,s 代表 short,i 代表 int睦刃,f 代表 float砚嘴,d 代表 double。所有這些類型都是使用模板類 cv::Vect<T, N> 定義的涩拙,其中 T 代表類型际长,N 代表向量中的元素個(gè)數(shù)。

  • 有時(shí)候使用 cv::Mat 的成員函數(shù)會(huì)很麻煩吃环,因?yàn)榉祷刂档念愋捅仨毻ㄟ^在調(diào)用時(shí)通過模板參數(shù)指定也颤。因此,OpenCV 提供了類 cv::Mat_郁轻,它是 cv::Mat 的一個(gè)模板子類。在事先知道矩陣類型的情況下文留,使用 cv::Mat_ 可以帶來一些便利好唯。這個(gè)類額外定義了一些方法,但是沒有任何成員變量燥翅,所以此類的指針或者引用可以直接進(jìn)行相互類型轉(zhuǎn)換。該類重載了操作符 ()森书,允許我們可以通過它直接存取矩陣元素靶端。因此,假設(shè)有一個(gè) uchar 類型的矩陣凛膏,我們可以這樣寫:
cv::Mat_<uchar> im2 = image; // im2 指向 image
im2(50, 100) = 0; // 存取第 50 行杨名,100列

由于 cv::Mat_ 的元素類型在創(chuàng)建實(shí)例的時(shí)候已經(jīng)聲明,操作符 () 在編譯期就知道要返回的數(shù)據(jù)類型猖毫。使用操作符 () 得到返回值和使用 cv::Mat 的 at 方法得到的返回值是完全一致的台谍,而且寫起來更加簡潔。

  • 雙重循環(huán)遍歷所有像素值:
void colorReduce(cv::Mat &image, int div = 64) {
    int nl = image.rows; // 行數(shù)
    int nc = image.cols * image.channels();
    for (int j = 0; j < nl; j++) {
        // 得到第 j 行的首地址
        uchar* data = image.ptr<uchar>(j);
        for (int i = 0; i < nc; i++) {
            data[i] = data[i] / div * div + div / 2;
        }
    }
}
  • OpenCV 默認(rèn)使用 BGR 的通道順序吁断,而且 size 成員函數(shù)返回的先是寬趁蕊,然后是高坞生,成員變量 cols 代表圖像的寬度(列數(shù)),rows 代表圖像的高度掷伙,step 代表以字節(jié)為單位的圖像的有效寬度是己,即使你的圖像元素類型不是 uchar,step 仍然帶代表著行的字節(jié)數(shù)任柜。圖像的通道數(shù)可以由 channels 方法得到赃泡,total 函數(shù)返回矩陣的像素個(gè)數(shù),像素大小可以從 elemSize 函數(shù)得到乘盼,對(duì)于一個(gè)三通道的 short 型矩陣 CV_16SC3升熊, elemSize 返回 6。

  • 為了簡化指針運(yùn)算绸栅,cv::Mat 提供了 ptr 函數(shù)可以得到圖像任意行的首地址级野。 ptr 函數(shù)是一個(gè)模板函數(shù),它返回第 j 行的首地址:

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

等效地使用指針運(yùn)算從一列移到下一列粹胯,所以蓖柔,也可以這么些:

*data++ = *data / div * div + div / 2;
  • 有一個(gè)知識(shí),跟能否快速遍歷圖像有關(guān)风纠,需要提前知道况鸣,那就是:

出于效率的考慮,OpenCV 可能會(huì)給矩陣的每行填補(bǔ)一些額外元素竹观。這是因?yàn)楦渑酰绻械拈L度是 4 或 8 的倍數(shù),一些多媒體處理芯片(如 Intel 的 MMX 架構(gòu))可以更高效地處理圖像臭增。這些額外的像素不會(huì)被顯示或者保存懂酱,填補(bǔ)的值將被忽略。OpenCV將填補(bǔ)后一行的長度指定為關(guān)鍵字誊抛。如果圖像沒有對(duì)行進(jìn)行填補(bǔ)列牺,那么圖像的有效寬度就等于圖像的真實(shí)寬度。
當(dāng)不對(duì)行進(jìn)行填補(bǔ)的時(shí)候拗窃,圖像可以被視為一個(gè)長為 W*H 的一維數(shù)組瞎领。我們可以通過 cv::Mat 的一個(gè)成員函數(shù) isContinuous 來判斷這幅圖像是否對(duì)行進(jìn)行了填補(bǔ)。如果 isContinuous 方法返回值為真的話随夸,說明這幅圖像沒有對(duì)行進(jìn)行填補(bǔ)九默。在一些圖像處理算法中,我們可以利用圖像的連續(xù)性逃魄,把整個(gè)處理過程使用一個(gè)循環(huán)完成荤西;

void colorReduce(cv::Mat &image, int div = 64) {
    int nl = image.rows; // 行數(shù)
    int nc = image.cols * image.channels();
    if (image.isContinuous()) {
        // 沒有額外的填補(bǔ)像素
        nc = nc * nl;
        nl = 1; // it is now a 1D array
    }
    // 對(duì)于連續(xù)圖像,本循環(huán)只執(zhí)行一次
    for (int j = 0; j < nl; j++) {
        // 得到第 j 行的首地址
        uchar* data = image.ptr<uchar>(j);
        for (int i = 0; i < nc; i++) {
            data[i] = data[i] / div * div + div / 2;
        }
    }
}

當(dāng)我們通過 isContinuous 函數(shù)得知圖像沒有對(duì)行進(jìn)行填補(bǔ)之后,我們就可以將寬設(shè)置為 1邪锌,高度設(shè)置為 W*H勉躺,從而消除外層循環(huán)。注意觅丰,我們也可以使用 reshape 方法來重寫這段代碼:

if (image.isContinous()) {
    // no padded pixels
    image.reshape(1, image.cols*image.rows); // 分別是行數(shù)和通道數(shù)
}
int nl = image.rows; // 列數(shù)
int nc = image.cols * image.channels();

reshape 不需要內(nèi)存拷貝或者重新分配就能改變矩陣的維度饵溅。兩個(gè)參數(shù)分別為新的通道數(shù)和新的行數(shù)。矩陣的列數(shù)可以根據(jù)新的通道數(shù)和行數(shù)來自適應(yīng)妇萄。
在這些視線中蜕企,內(nèi)存循環(huán)一次處理圖像的全部像素。這個(gè)方法在同時(shí)處理若干個(gè)小圖像時(shí)會(huì)很有優(yōu)勢(shì)冠句。

底層指針運(yùn)算

在類 cv::Mat 中轻掩,圖像數(shù)據(jù)以 unsigned char 形式保存在一塊內(nèi)存中。這塊內(nèi)存的首地址可以通過 data 成員變量得到懦底。data 是一個(gè) unsigned char 型的指針唇牧,uoyi循環(huán)可以以如下方式開始:

uchar *data = image.data;

從當(dāng)前行到下一行可以通過對(duì)指針加上行寬完成:

data += image.step; // 下一行

step 代表圖像的行寬(包括填補(bǔ)像素)。通常而言聚唐,你可以通過如下方式獲得第 j 行丐重、第 i 列像素的地址:

// (j, i) 處像素的地址為 &image.at(j, i)
data = image.data + j * image.step + i * image.elemSize();

但是,即使這種方式確實(shí)行之有效杆查,我們依然不建議使用這種處理方式扮惦。因?yàn)檫@種方式除了容易出錯(cuò),還不適用于帶有“感興趣區(qū)域”的圖像亲桦。

使用迭代器遍歷圖像

在面向?qū)ο蟮木幊讨醒旅郏闅v數(shù)據(jù)集合通常是通過迭代器來完成的。迭代器是一種特殊的類烙肺,它專門用來遍歷集合中的各個(gè)元素纳猪,同時(shí)隱藏了在給定的集合上元素迭代的具體實(shí)現(xiàn)方式。這種信息隱蔽原則的使用使得遍歷集合更加容易桃笙。另外,不管數(shù)據(jù)類型是什么沙绝,我們都可以使用相似的方式遍歷集合搏明。標(biāo)準(zhǔn)模板庫 STL 為每個(gè)容器類型都提供了迭代器,OpenCV 同樣為 cv::Mat 提供了與 STL 迭代器兼容的迭代器闪檬。
一個(gè) cv::Mat 實(shí)例的迭代器可以通過創(chuàng)建一個(gè) cv::MatIterator_ 的實(shí)例來得到星著。類似于子類 cv::Mat_,下劃線意味著 cv::MatIterator_ 是一個(gè)模板類粗悯。之所以如此是由于通過迭代器來存取圖像的元素虚循,就必須在編譯期知道圖像元素的數(shù)據(jù)類型。一個(gè)圖像迭代器可以用如下方式聲明:

cv::MatIterator_<cv::Vec3b> it;

另外一種方式是使用定義在 Mat_ 內(nèi)部的迭代器類型:

cv::Mat_<cv::Vec3b>::iterator it;

這樣就可以通過常規(guī)的 begin 和 end 這兩個(gè)迭代器方法來遍歷所有像素。值得指出的是横缔,如果使用后一種方式铺遂,那么 begin 和 end 方法也必須要使用對(duì)應(yīng)的模板化的版本。這樣茎刚,顏色縮減函數(shù)就可以重寫為:

void colorReduce(cv::Mat &image, int div = 64) {
    // 得到初始位置的迭代器
    cv::Mat_<cv::Vec3b>::iterator it = image.begin<cv::Vec3b>();
    // 得到終止位置的迭代器
    cv::Mat_<cv::Vec3b>::iterator itend = image.end<cv::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;
    }
}

注意襟锐,因?yàn)槲覀冞@里處理的彩色圖像,所以迭代器返回的是 cv::Vec3b膛锭,每個(gè)顏色分量可以通過操作符 [] 得到粮坞。

使用迭代器遍歷任何形式的集合都遵循同樣的模式。首先初狰,創(chuàng)建一個(gè)迭代器特化版本的實(shí)例莫杈。在我們的示例代碼中,就是 cv::Mat_<cv::Vec3b>::iterator (或者 cv::MatIterator_<cv::Vec3b>).
然后奢入,使用集合初始位置(圖像的左上角)的迭代器對(duì)其進(jìn)行初始化筝闹。初始位置的迭代器通常是通過 begin 方法得到的。對(duì)于一個(gè) cv::Mat 的實(shí)例俊马,你可以通過 image.begin<cv::Vec3b>() 來得到圖像左上角位置的迭代器丁存。你也可以通過對(duì)迭代器進(jìn)行代數(shù)運(yùn)算。例如:如果你想從圖像的第二行開始柴我,那么你可以用 image.begin<cv::Vec3b>() + image.rows 來初始化迭代器解寝。集合終止位置的迭代器可以通過 end 方法得到。但是end 方法得到的迭代器其實(shí)已經(jīng)超出了集合艘儒。這也意味著迭代過程必須在迭代器到達(dá)這個(gè)位置時(shí)結(jié)束聋伦。end 方法得到的迭代器也可以進(jìn)行代數(shù)運(yùn)算。如果界睁,你希望迭代過程在圖像最后一行之前停止觉增,那么迭代器的終止位置應(yīng)該是 image.end<cv::Vec3b>() - image.rows。一旦迭代器初始化完成之后翻斟,你就可以創(chuàng)建一個(gè)循環(huán)遍歷所有的元素知道到達(dá)終止位置逾礁。一個(gè)典型的 while 循環(huán)如下所示:

while (it != itend) {
    // do something
    ...
    ++it;
}

操作符 ++ 用來將迭代器從當(dāng)前位置移動(dòng)到下一個(gè)位置,你也可以使用更大的補(bǔ)償访惜,比如嘹履,用it+=10將迭代器每次移動(dòng) 10px。
在循環(huán)體內(nèi)部债热,你可以使用解引用操作符 * 來讀寫當(dāng)前元素砾嫉。都操作使用 element = *it,寫操作使用 *it = element窒篱。注意:如果你的操作對(duì)象是 const cv::Mat焕刮,或者你想強(qiáng)調(diào)當(dāng)前循環(huán)不會(huì)對(duì) cv::Mat 的實(shí)例進(jìn)行修改舶沿,那么你就應(yīng)該創(chuàng)建常量迭代器。常量迭代器的聲明如下:

cv::MatConstIterator_<cv::Vec3b> it;

或者

cv::Mat_<cv::Vec3b>::const_iterator it;

在本例中配并,迭代器的開始位置和終止位置是通過模板函數(shù) begin 和 end 得到的括荡。如果我們?cè)诒菊碌谝粍t秘訣中所做的那樣,我們可以通過 cv::Mat_ 的實(shí)例來得到他們荐绝。這樣可以避免在使用 begin 和 end 方法的時(shí)候還要置頂?shù)鞯念愋鸵黄V钥梢赃@樣,是因?yàn)橐粋€(gè) cv::Mat_ 引用在創(chuàng)建的時(shí)候就隱式聲明了迭代器的類型低滩。

cv::Mat_<cv::Vec3b> cimage = image;
cv::Mat_<cv::Vec3b>::iterator  it = cimage.begin();
cv::Mat_<cv::Vec3b>::iterator  itend = cimage.end();

之所以這個(gè)例子可以而前面那個(gè)例子不可以是因?yàn)檎偌校懊婺莻€(gè)例子的圖像類型是 cv::Mat, 而這個(gè)例子的圖像類型是 cv::Mat_恕沫。

獲取代碼運(yùn)行時(shí)間

OpenCV 有一個(gè)非常實(shí)用的函數(shù) cv::getTickCount() 可以用來測(cè)量一段代碼的運(yùn)行時(shí)間监憎。這個(gè)函數(shù)返回從上次開機(jī)算起的時(shí)鐘周期數(shù)。由于我們需要的是某個(gè)代碼段運(yùn)行的毫秒數(shù)婶溯,因此還需要另外一個(gè) cv::getTickFrequency()鲸阔。此函數(shù)返回沒秒內(nèi)的時(shí)鐘周期數(shù),用于統(tǒng)計(jì)函數(shù)(或一段代碼)耗費(fèi)時(shí)間的方法如下:

double duration;
duration = static_cast<double>(cv::getTickCount());
colorReduce(image); // 被測(cè)試的函數(shù)
duration = static_cast<double>(cv::getTickCount()) - duration;
duration /= cv::getTickFrequency(); // 運(yùn)行時(shí)間迄委,以 ms 為單位
訪問方式 時(shí)間
data[i] = data[i] / div * div + div / 2; 37ms
*data++ = *data / div * div + div / 2; 37ms
*data++ = v - v % div + div / 2; 52ms
*data++ = *data&mask + div / 2; 35ms
colorReduce(input, output); 44ms
i<image.cols*image.channels()>; 65ms
MatIterator 67ms
.at(j,i) 80ms
3-channel loop 29ms

當(dāng)輸出圖像需要被重新分配而不是以原地(in-place)方式處理時(shí)(第5行)褐筛,運(yùn)行時(shí)間為44ms,比 in-place的要慢叙身。額外的時(shí)間消耗來自于內(nèi)存分配渔扎。在循環(huán)體內(nèi)存,對(duì)于可提前計(jì)算的變量應(yīng)避免重復(fù)計(jì)算信轿。

圖像鄰域操作的一個(gè)例子

void sharpen(const cv::Mat &image, cv::Mat &result) {
    // 如有必要?jiǎng)t分配內(nèi)存
    result.create(image.size(), image.type());
    for(int j = 1; j < image.rows-1; j++) { // 處理除了第一行和最后一行之外的所有行
        const uchar* previous = image.ptr<const uchar>(j-1); // 上一行
        const uchar* current = image.ptr<const uchar>(j); // 當(dāng)前行
        const uchar* next  = image.ptr<const uchar>(j+1); // 下一行
        for(int i = 1; i < image.cols - 1; i++) {
            *output++ = cv::saturate_cast<uchar>(5*current[i]-current[i-1]-current[i+1]-previous[i]-next[i]);
        }
    }
    // 將未處理的像素設(shè)置為0
    result.row(0).setTo(cv::Scalar(0));
    result.row(result.rows-1).setTo(cv::Scalar(0));
    result.col(0).setTo(cv::Scalar(0));
    result.col(result.cols-1).setTo(cv::Scalar(0));
}

在計(jì)算輸出像素值時(shí)晃痴,模板函數(shù) cv::saturate_cast 被用來對(duì)計(jì)算結(jié)果進(jìn)行階段。
setTo 函數(shù)可以用來設(shè)置矩陣的值财忽,這個(gè)函數(shù)會(huì)將矩陣的所有元素都設(shè)為指定的值倘核。對(duì)于一個(gè)三通道的彩色圖像,需要用 cv::Scalar(a,b,c) 來指定像素三個(gè)通道的目標(biāo)值即彪。

void sharpen2D(const cv::Mat &image, cv::Mat &result) {
    // 構(gòu)造核(所有項(xiàng)都初始化為 0)
    cv::Mat kernel(3, 3, CV_32F, cv::Scalar(0));
    // 對(duì)核元素進(jìn)行賦值
    kernel.at<float>(1,1) = 5.0;
    kernel.at<float>(0,1) = -1.0;
    kernel.at<float>(2,1) = -1.0;
    kernel.at<float>(1,1) = -1.0;
    kernel.at<float>(1,2) = -1.0;
    // 對(duì)圖像進(jìn)行濾波
    cv::filter2D(image, result, image.depth(), kernel);
}
  • 函數(shù) cv::split 將彩色圖像的三個(gè)通道分別拷貝到三個(gè)獨(dú)立的 cv::Mat 實(shí)例中紧唱,然后在對(duì)這個(gè)通道單獨(dú)處理。
// 創(chuàng)建一個(gè)圖像向量
std::vector<cv::Mat> planes;
// 講一個(gè)三通道圖像分離為三個(gè)單通道圖像
cv::split(image1, planes);
planes[0] += image2;
// 將三個(gè)單通道圖像重新合并為一個(gè)三通道圖像
cv::merge(planes, result);

提取興趣區(qū)域(其實(shí)就是slicing)

imageROI = image(cv::Rect(colId, rowId, logo.cols, logo.rows));

定義ROI的一種方法是使用 cv::Rect隶校,顧名思義琼蚯,cv::Rect 表示一個(gè)矩形區(qū)域。指定矩形的左上角坐標(biāo)(構(gòu)造函數(shù)的前兩個(gè)參數(shù))和矩形的長寬(構(gòu)造函數(shù)的后兩個(gè)參數(shù))就可以定義一個(gè)矩形區(qū)域惠况。
另一種定義ROI的方式是指定感興趣行或列的范圍(Range)。Range是指從起始索引到終止索引(不包含終止索引)的一段連續(xù)序列宁仔。cv::Range 可以用來定義Range稠屠。如果用 cv::Range 來定義 ROI,那么前例中定義 ROI 的代碼可以重寫為:

cv::Mat imageROI = image(cv::Range(270,270+logo.rows), cv::Range(385,385+logo.cols));

cv::Mat 的 () 操作符返回另一個(gè) cv::Mat 實(shí)例,這個(gè)實(shí)例可以用在接下來的函數(shù)調(diào)用中权埠,因?yàn)镽OI和原始圖像共享數(shù)據(jù)緩沖區(qū)榨了,對(duì)ROI的任何變換都會(huì)影響到原始圖像的對(duì)應(yīng)區(qū)域。由于創(chuàng)建ROI時(shí)不會(huì)拷貝數(shù)據(jù)攘蔽,所以不論ROI的大小如何龙屉,創(chuàng)建ROI的運(yùn)行時(shí)間都是常量。
如果想創(chuàng)建包含原始圖像特定行的ROI满俗,可以使用如下代碼:

cv::Mat imageROI = image.rowRange(start, end);

類似地转捕,對(duì)于列:

cv::Mat imageROI = image.colRange(start, end);

在秘訣“遍歷圖像和鄰域操作”中使用到的row方法和col方法其實(shí)是rowRange和colRange方法的特例,即起始索引等于終止索引唆垃,等于是定義了一個(gè)單行或單列的ROI五芝。

<div id="Section6">第6章 圖像濾波</div>

  • 均值濾波
cv::blur(image, result, cv::Size(5,5));
  • 高斯濾波
cv::GaussianBlur(image, result, cv::Size(5,5), 1.5);

這個(gè) 1.5 就是高斯函數(shù)的$\sigma$,決定高斯函數(shù)平坦與否辕万。

  • 生成 1 維高斯核
cv::Mat gauss = cv::getGaussianKernel(9, sigma, CV_32F);

9就是一維高斯核向量的長度枢步。

  • 先對(duì)原圖應(yīng)用低通濾波,然后隔行渐尿、隔列取出像素
cv::Mat reducedImage; // 包含縮小后的圖像
cv::pyrDown(image, reducedImage); //將圖像尺寸減半

同理醉途,還存在 cv::pyrUp 函數(shù)將圖像尺寸放大一倍。

  • 指定目標(biāo)圖像的尺寸
cv::Mat reducedImage; // 包含改變尺寸后的圖像
cv::resize(image, reducedImage, cv::Size(image.cols/3, image.rows/3)); // 改變?yōu)?1/3 大小

還提到了 cv::boxFilter 和 cv::filter2D 函數(shù)

  • 中值濾波
cv::medianBlur(image, result, 5);
  • Sobel函數(shù)
    cv::Sobel
    cv::minMaxLoc
    sobel.convertTo
    cv::threshold
    cv::cartToPolar
    cv::Scharr

<div id="Section7">附錄

把附錄的內(nèi)容全部敲下來砖茸,因?yàn)樽屇愀玫乩斫釵penCV的組織架構(gòu)隘擎,以及它是什么,能做到什么渔彰?還有就是samples/cpp/ 文件夾中的范例介紹嵌屎,應(yīng)該有最純正的OpenCV編程風(fēng)格,可以用于學(xué)習(xí)恍涂。

OpenCV3的改動(dòng)在哪宝惰?
C風(fēng)格的API很快將會(huì)消失,完全被C++的API替代再沧,代碼風(fēng)格更加簡潔尼夺,不易出錯(cuò)。讀者如果想借助OpenCV最新的功能炒瘸,記得清理代碼中C風(fēng)格API
C++ API將更加簡潔
所有的算法都將繼承自 cv::Algorithm 接口
大型的模塊拆分為小模塊淤堵,模塊將在后面繼續(xù)講解。

OpenCV 3 的源代碼文件夾:

  • 3rdparty/: 包含第三方庫顷扩,如用視頻解碼用的 ffmpeg拐邪、jpg、png隘截、tiff 等圖片的解碼庫扎阶。
  • apps/: 包含進(jìn)行 Haar 分類器訓(xùn)練的工具汹胃,OpenCV 進(jìn)行人臉檢測(cè)便是基于 Haar 分類器。如果你想檢測(cè)人臉以外的圖片东臀,千萬不要錯(cuò)過這幾個(gè)工具着饥。
  • cmake/: 包含生成工程項(xiàng)目時(shí) cmake 的依賴文件,用于只能搜索第三方庫宰掉,普通開發(fā)者不需要關(guān)心這個(gè)文件夾的內(nèi)容赁濒。
  • data/: 包含 OpenCV 庫及范例中用到的資源文件,Haar 物體檢測(cè)的分類器位于 haarcascades 子文件中流部。
  • doc/: 包含生成文檔所需的源文件及輔助腳本戚绕。
  • include/: 包含入口頭文件舞丛。OpenCV 子文件夾中是 C 語言風(fēng)格的 API,也就是《學(xué)習(xí) OpenCV》中描述的 API 函數(shù)果漾,官方將逐漸淘汰 C 風(fēng)格函數(shù)球切,因此我不推薦大家使用該文件夾中的頭文件吨凑。OpenCV 2 子文件夾中只有一個(gè) opencv.hpp 文件鸵钝,這是 OpenCV 2 及 OpenCV 3 推薦使用的頭文件庐镐。
  • modules/: 包含核心代碼,OpenCV 真正的代碼都在這個(gè)文件夾中怠堪。OpenCV 從 2.0 開始以模塊的方式組織各種功能粟矿,近兩年模塊的數(shù)量增長得很快陌粹,后面我會(huì)依次介紹每個(gè)模塊的作用福压。
  • platforms/: 包含交叉編譯所需的工具鏈及額外的代碼,交叉編譯指的是在一個(gè)操作系統(tǒng)中編譯供另一個(gè)系統(tǒng)使用的文件。
  • samples/: 這是大家最喜歡的范例文件夾杆煞,后面我也會(huì)進(jìn)一步講解决乎。

CPU模塊

  • androidcamera/: 僅用于 Android 平臺(tái)派桩,使得可以通過與其他平臺(tái)相同的接口來控制 Android 設(shè)備的相機(jī)铆惑。
  • core/: 核心功能模塊员魏,定義了基本的數(shù)據(jù)結(jié)構(gòu),包括最重要的 Mat 類受裹、XML 讀寫棉饶、OpenGL 三維渲染等照藻。
  • imgproc/: 全稱為 Image Processing坑律,即圖像處理晃择,包括圖像濾波宫屠、集合圖像變換、直方圖計(jì)算抵栈、形狀描述子等古劲。圖像處理是計(jì)算機(jī)視覺的重要工具产艾。
  • highgui/: 高級(jí)圖形界面及多媒體文件讀寫闷堡,包括用戶界面、Qt弯菊、對(duì)圖像及視頻文件的讀寫操作管钳。
  • video/: 視頻分析模塊蹋嵌,包括背景提取栽烂、光流跟蹤恋脚、卡爾曼濾波等糟描,做視頻監(jiān)控的開發(fā)者會(huì)經(jīng)常使用這個(gè)模塊船响。
  • calib3d/: 相機(jī)標(biāo)定及三維重建见间。相機(jī)標(biāo)定用于取出相機(jī)自身缺陷導(dǎo)致的畫面形變米诉,還原真實(shí)的場(chǎng)景,確保計(jì)算的準(zhǔn)確性拴泌。三維重建通常用在雙目視覺(立體視覺)蚪腐,即兩個(gè)標(biāo)定后的攝像頭觀察同一個(gè)場(chǎng)景削茁,通過計(jì)算兩幅畫面中的相關(guān)性來估計(jì)像素深度茧跋。
  • features2d/: 包含 2D 特征值檢測(cè)的框架瘾杭。包含各種特征值檢測(cè)器及描述子哪亿,如 FAST蝇棉、MSER篡殷、OBRB板辽、BRISK 等劲弦。各類特征值擁有統(tǒng)一的算法接口,因此在不影響程序邏輯的情況下可以替換替換次坡。
  • objdetect/: 物體檢測(cè)模塊砸琅,包括 Haar 分類器明棍、SVM 檢測(cè)器及文字檢測(cè)摊腋。
  • ml/: 全稱為 Machine Learning兴蒸,即機(jī)器學(xué)習(xí)橙凳。包括統(tǒng)計(jì)模型、K 最近鄰钓觉、支持向量機(jī)荡灾、決策樹批幌、神經(jīng)網(wǎng)絡(luò)等經(jīng)典的機(jī)器學(xué)習(xí)算法荧缘。
  • flann/: 用于在多維空間內(nèi)聚類及搜索的近似算法截粗,做圖像檢索的開發(fā)者對(duì)它不會(huì)陌生桐愉。
  • photo/: 計(jì)算攝影學(xué)从诲,包括圖像修補(bǔ)系洛、去噪略步、HDR 成像趟薄、非真實(shí)感渲染等。如果讀者想實(shí)現(xiàn) Photoshop 的高級(jí)功能卒落,那么這個(gè)模塊必不可少儡毕。
  • stitching:/ 圖像拼接腰湾,可用于制作全景圖费坊。
  • nonfree/: 受專利保護(hù)的算法葵萎,包括 SIFT 和 SURF。從功能上來說磕昼,這兩個(gè)算法屬于 features2d 模塊票从,但由于它們都是受專利保護(hù)的峰鄙,相擁在項(xiàng)目中可能需要專利方的許可吟榴。
  • contrib:/ 包含新添加的實(shí)驗(yàn)性質(zhì)的代碼吩翻。開發(fā)者期待已久的人臉識(shí)別功能便位于這個(gè)模塊內(nèi)狭瞎,名為 FaceRecognizer熊锭。
  • legacy/: 英文含義為遺產(chǎn)碗殷,即廢棄已久的代碼亿扁,官方不推薦使用這個(gè)模塊中的功能从祝。
  • optim/: 全稱為 Optimization牍陌,這個(gè)模塊包含通用的數(shù)值優(yōu)化毒涧。包含線性規(guī)劃等算法契讲。
  • shape/: 形狀匹配算法模塊捡偏,用于描述形狀银伟、比較形狀彤避。
  • softcascade/: 另一種物體檢測(cè)算法琉预,Soft Cascade 分類器模孩,包含檢測(cè)模塊和訓(xùn)練模塊榨咐。
  • superres/: 全稱為 Super Resolution块茁,用于增強(qiáng)圖像的分辨率。
  • videostab/: 全稱為 Video Stabilization崎场,用于解決相機(jī)移動(dòng)拍攝時(shí)視頻不夠穩(wěn)定的問題谭跨。
  • viz/: 三維可視化模塊螃宙∽辉可以認(rèn)為這個(gè)模塊實(shí)現(xiàn)了一個(gè)簡單的三維可視化引擎堂湖,有各種 UI 空間和鍵盤无蜂、鼠標(biāo)交互方式。底層實(shí)現(xiàn)基于 CTK 這個(gè)第三方庫。

CUDA模塊

這些模塊的名稱都以 cuda 開始慰照,cuda 是顯卡制造商 NVIDIA 推出的通用計(jì)算語言毒租,在 OpenCV 3 中有大量的模塊已經(jīng)被移植到了 cuda 語言墅垮。讓我們依次看一下算色。

  • cuda/: CUDA- 加速的計(jì)算機(jī)視覺算法灾梦,包括數(shù)據(jù)結(jié)構(gòu) cuda::GpuMat若河、基于 cuda 的相機(jī)標(biāo)定及三維重建等萧福。
  • cudaarithm/: CUDA- 加速的矩陣運(yùn)算模塊膏燕。
  • cudabgsegm/: CUDA- 加速的背景分割模塊煌寇,通常用于視頻監(jiān)控阀溶。
  • cudacodec/: CUDA- 加速的視頻編碼與解碼银锻。
  • cudafeatures2d/: CUDA- 加速的特征檢測(cè)與描述模塊击纬,與 features2d/ 模塊功能類似。
  • cudafilters/: CUDA- 加速的圖像濾波肯腕。
  • cudaimgproc/: CUDA- 加速的圖像處理算法实撒,包含直方圖計(jì)算知态、霍夫變換等负敏。
  • cudaoptflow/: CUDA- 加速的光流檢測(cè)算法友扰。
  • cudastereo/: CUDA- 加速的立體視覺匹配算法村怪。
  • cudawarping/: 實(shí)現(xiàn) CUDA- 加速的快速圖像變換甚负,包括透視變換梭域、旋轉(zhuǎn)、改變尺寸等既穆。

samples/ 文件夾

  • android/: Android 平臺(tái)的范例幻工。既有完全是 Java 的工程囊颅,也有完全是 C++ 的工程踢代,也有更為常見的 Java 與 C++ 共存的工程。
  • c/: 使用 C API 的范例。在 C API 逐漸退出歷史舞臺(tái)后儿惫,這個(gè)文件夾也應(yīng)該會(huì)隨之消失吧肾请。
  • cpp/: 由于 OpenCV 是一款 C++ 庫隔显,因此 C++ 的返利是最多的括眠,后面將重點(diǎn)介紹倍权。
  • directx/: directx (d3d) 是微軟的私有三維圖像 API掷豺,這個(gè)文件夾中的范例覆蓋了 d3d9、d3d10薄声、d3d11.
  • gpu/: 利用 cuda 加速的范例当船。
  • java/: OpenCV 3 官方支持 Java 語言綁定,因此這里演示如何使用 Java 版本的 OpenCV默辨。
  • python2/: OpenCV 3 官方支持 Python 語言綁定,因此這里演示使用 Python 2 版本的范例缩幸。
  • tapi/: tapi 是 OpenCV 3 的一個(gè)新特性壹置,使用 cv::UMat 替代 cv::Mat,實(shí)現(xiàn) CPU 和 GPU 的運(yùn)算使用統(tǒng)一的接口桌粉,不再需要顯式地在 CPU 和 GPU 之間傳遞數(shù)據(jù)蒸绩,方便開發(fā)人員。
  • winrt/: Windows RT 平臺(tái)的范例铃肯,開發(fā)語言是微軟的 C++ “方言”.

samples/cpp/ 文件夾中的范例介紹

  • 3calibration.cpp/: 同時(shí)標(biāo)定三臺(tái)水平放置的相機(jī)患亿。

  • bagofwords_classification.cpp/: 使用圖像檢測(cè)實(shí)現(xiàn)簡易的圖像搜索功能。

  • bgfg_gmg.cpp/: 演示 GMG 背景檢測(cè)算法的使用方式押逼。

  • bgfg_segm.cpp/: 演示高斯混合背景檢測(cè)算法的使用方式步藕。

  • brief_match_test.cpp/: 使用 BRIEF 特征值來匹配兩張圖像。

  • build3dmodel.cpp/: 演示如何使用基礎(chǔ)矩陣和特征值來創(chuàng)建三維模型挑格。

  • calibration.cpp/: 完整的多用途標(biāo)定程序咙冗。

  • calibration_artificial.cpp/: 在程序中生成一個(gè)虛擬的相機(jī),并進(jìn)行標(biāo)定漂彤。

  • camshiftdemo.cpp/: 讀取實(shí)時(shí)的攝像頭數(shù)據(jù)雾消,并演示基于均值偏移算法的視頻跟蹤。

  • chamfer.cpp/: 使用 Chamfer 算法匹配兩副邊緣圖像挫望。

  • cloning_demo.cpp/: 命令行模式的圖像克隆立润。

  • cloning_gui.cpp/: 圖形界面交互的圖像克隆。

  • connected_components.cpp/: 查找并繪制圖像中的連通區(qū)域媳板。

  • contours2.cpp/: 查找并繪制圖像中的輪廓桑腮。

  • convexhull.cpp/: 查找并繪制由點(diǎn)的集合組成的凸包。

  • cout_mat.cpp/: 使用 cout 來輸出各種格式化的 Mat 對(duì)象蛉幸。

  • create_mask.cpp/: 演示如何創(chuàng)建黑白掩碼圖像破讨。

  • dbt_face_detection.cpp/: 基于檢測(cè)的人臉跟蹤代碼丛晦。

  • delaunay2.cpp/: 通過鼠標(biāo)交互式地生成 Delaunay 三角形。

  • demhist.cpp/: 演示直方圖的用法提陶。

  • descriptor_extractor_matcher.cpp/: 演示 features2d 檢測(cè)框架的用法烫沙。

  • detection_based_tracker_sample.cpp/: 與 dbt_face_detection.cpp 類似。

  • detector_descriptor_evaluation.cpp/: 評(píng)估各種特征檢測(cè)器和描述子搁骑。

  • detector_descriptor_matcher_evaluation.cpp/: 評(píng)估各種特征檢測(cè)器和匹配器斧吐。

  • dft.cpp/: 演示一幅圖像的離散傅里葉變換。

  • distrans.cpp/: 顯示邊緣圖像的距離變換值仲器。

  • drawing.cpp/: 演示繪畫和文字顯示功能煤率。

  • edge.cpp/: 演示 Canny 邊緣檢測(cè)。

  • em.cpp/: 對(duì)隨機(jī)生成的數(shù)據(jù)點(diǎn)進(jìn)行 EM 聚類乏冀。

  • fabmap_sample.cpp/: 演示 FAB-MAP 圖像檢索算法蝶糯。

  • facerec_demo.cpp/: 人臉識(shí)別。

  • fback.cpp/: 實(shí)時(shí)的 Farneback 光流跟蹤辆沦。

  • ffilldemo.cpp/: 演示 floodFill() 像素填充算法昼捍。

  • filestorage.cpp/: 演示序列化到外部文件,如yml肢扯、xml等妒茬。

  • fitellipse.cpp/: 將輪廓點(diǎn)匹配到橢圓。

  • freak_demo.cpp/: 演示 FREAK 特征值的用法蔚晨。

  • gencolors.cpp/: 演示 generateColors()乍钻。

  • generic_descriptor_match.cpp/: 基于 SURF 的兩幅圖像間的匹配。

  • grabcut.cpp/: 演示 GrabCut 分割算法铭腕。

  • houghcircles.cpp/: 用霍夫算法檢測(cè)圓银择。

  • houghlines.cpp/: 用霍夫算法檢測(cè)直線。

  • hybridtrackingsample.cpp/: 混合跟蹤算法(Hybrid Tracker)的演示累舷。

  • image.cpp/: 來回轉(zhuǎn)換 cv::Mat 和 IplImage浩考。

  • image_alignment.cpp/: 演示 findTransformECC() 函數(shù)。

  • image_sequence.cpp/: 使用 VideoCapture 對(duì)象讀取序列幀被盈。

  • imagelist_creator.cpp/: 創(chuàng)建圖像列表到 xml 文件析孽。

  • inpaint.cpp/: 使用鼠標(biāo)交互地進(jìn)行圖像修補(bǔ)。

  • intelperc_capture.cpp/: Intel 感知計(jì)算設(shè)備相關(guān)的函數(shù)只怎。

  • kalman.cpp/: 使用卡爾曼濾波進(jìn)行二維跟蹤袜瞬。

  • kmeans.cpp/: Kmeans 聚類算法的演示。

  • laplace.cpp/: 拉普拉斯邊緣檢測(cè)尝盼。

  • latentsvm_multidetect.cpp/: latentSVM 檢測(cè)器。

  • letter_recog.cpp/: 字母識(shí)別佑菩。

  • linemod.cpp/: 基于 OpenNI 的體感設(shè)備應(yīng)用盾沫。

  • lkdemo.cpp/: 演示Lukas-Kanade 光流法裁赠。

  • logpolar_bsm.cpp/: 演示 LogPolar 盲點(diǎn)模型。

  • lsd_lines.cpp/: LSD 線段檢測(cè)赴精。

  • matcher_simple.cpp/: SURF 特征檢測(cè)佩捞。

  • matching_to_many_images.cpp/: 一對(duì)多的特征檢測(cè)筒狠。

  • meanshift_segmentation.cpp/: 演示基于均值漂移的色彩分割函數(shù)——meanShiftSegmentation()厂财。

  • minarea.cpp/: 尋找最小包圍盒浓恳、包圍圓扣墩。

  • morphology2.cpp/: 形態(tài)學(xué)圖像處理阳欲。

  • npr_demo.cpp/: 演示各種非真實(shí)感渲染效果刊橘。

  • opencv_version.cpp/: 輸出 OpenCV 庫的版本號(hào)尉姨。

  • openni_capture.cpp/: 演示 OpenNI 相關(guān)的體感設(shè)備翩活。

  • pca.cpp/: 基于 PCA 的人臉識(shí)別逐哈。

  • peopledetect.cpp/: 基于 cascade 或 hog 進(jìn)行物體(人)檢測(cè)芬迄。

  • phase_corr.cpp/: 演示 phaseCorrelate() 函數(shù)。

  • points_classifier.cpp/: 演示各種機(jī)器學(xué)習(xí)算法昂秃。

  • rgbdodometry.cpp/: 對(duì)深度傳感器如 Kinect 的數(shù)據(jù)進(jìn)行處理禀梳。

  • segment_objects.cpp/: 實(shí)時(shí)地在視頻或相機(jī)畫面中檢測(cè)前景物體。

  • shape_example.cpp/: 比較并檢索形狀肠骆。

  • shape_transformation.cpp/: 用 SURF 特征值檢測(cè)形狀并進(jìn)行變換算途。

  • squares.cpp/: 檢測(cè)圖像中的方塊形狀。

  • starter_imagelist.cpp/: 一個(gè) “hello worl” 性質(zhì)的入門范例蚀腿。

  • starter_video.cpp/: 另一個(gè) “hello worl” 性質(zhì)的入門范例嘴瓤。

  • stereo_calib.cpp/: 雙目視覺的標(biāo)定。

  • stereo_match.cpp/: 計(jì)算左右視覺的圖像的差異唯咬,生成點(diǎn)云文件纱注。

  • stitching.cpp/: 演示圖像拼接算法。

  • stitching_detailed.cpp/: 演示更多參數(shù)的圖像拼接算法胆胰。

  • textdetection.cpp/: 實(shí)時(shí)場(chǎng)景中的文字定位與識(shí)別狞贱。

  • train_HOG.cpp/: 訓(xùn)練 HOG 分類器。

  • ufacedetect.cpp/: 人臉檢測(cè)蜀涨。

  • video_homography.cpp/: 使用 FAST 特征值來跟蹤平面物體瞎嬉。

  • videostab.cpp/: 演示 videostab 中各個(gè)參數(shù)的用法。

  • watershed.cpp/: 演示著名的分水嶺圖像分割算法厚柳。

本書程序代碼及彩圖下載:
http://www.sciencep.com/downloads/
https://github.com/ITpublishing

<div id="Section8">勘誤</div>

  • P249 頁氧枣,倒數(shù)第 5-6 行,分別有兩個(gè)“圖像中”多余别垮。

<div id="Section9">我的困惑</div>

  • 深拷貝 image.clone() 和 copyTo 有什么區(qū)別便监?不是一樣的嗎

<div id="Section10">下一步計(jì)劃</div>

初寫于 2015-04-05,未完待續(xù)。
首發(fā)于 Yimian Dai's Homepage烧董,轉(zhuǎn)載請(qǐng)注明出處毁靶。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市逊移,隨后出現(xiàn)的幾起案子预吆,更是在濱河造成了極大的恐慌,老刑警劉巖胳泉,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拐叉,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡扇商,警方通過查閱死者的電腦和手機(jī)凤瘦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钳吟,“玉大人廷粒,你說我怎么就攤上這事『烨遥” “怎么了坝茎?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長暇番。 經(jīng)常有香客問我嗤放,道長,這世上最難降的妖魔是什么壁酬? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任次酌,我火速辦了婚禮,結(jié)果婚禮上舆乔,老公的妹妹穿的比我還像新娘岳服。我一直安慰自己,他們只是感情好希俩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布吊宋。 她就那樣靜靜地躺著,像睡著了一般颜武。 火紅的嫁衣襯著肌膚如雪璃搜。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天鳞上,我揣著相機(jī)與錄音这吻,去河邊找鬼。 笑死篙议,一個(gè)胖子當(dāng)著我的面吹牛唾糯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼移怯,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼拒名!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起芋酌,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎雁佳,沒想到半個(gè)月后脐帝,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡糖权,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年堵腹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片星澳。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡疚顷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出禁偎,到底是詐尸還是另有隱情腿堤,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布如暖,位于F島的核電站笆檀,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏盒至。R本人自食惡果不足惜酗洒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望枷遂。 院中可真熱鬧樱衷,春花似錦、人聲如沸酒唉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽黔州。三九已至耍鬓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間流妻,已是汗流浹背牲蜀。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留绅这,地道東北人涣达。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親度苔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子匆篓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

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