這一篇打算將core部分的例子說完,這都是基於《opencv2.4.9tutorial.pdf》中的core部分,其實這些例子后期都很穩定的,也就是說就算是2.3.1和2.4.10 ,這幾個例子不會變,變化的是新增函數啊什么的,所以無需擔心這里的例子是否不適用新版本(opencv3按照他們小組的意思每次數字大變動,都會有很大的改變opencv3的alpha版本介紹說是重新定義了API,而且在CPU上進行了效果提升,在GPU上可以透明加速,也就是你在編程的時候不知道是在GPU上)。
看了下opencv3自帶的tutorial,大部分也都還是差不多。本文是想將那些零散的例子能夠更加的脫水,就是只說其中的幾個精髓的函數。這是core部分的例子,雖然之前所有例子都碼了一遍,不過覺得暫時自己用不到 【離散傅里葉變換】,所以這里沒放進來,有興趣的可以自己去看看。
正文:
一、計時函數
需要頭文件#include"opencv2/core/core.hpp"
計時函數就和matlab中的tic ,toc一樣,可以用來計算你的代碼跑了多久,其實如果不是為了衡量效率什么的,這也是不怎么會用吧,首先進行提取當前電腦的時鍾數,這時候返回的是基於上次某個事件(比如開機)開始計算的時鍾數;然后經過了一些操作,接着再次提取當前的時鍾數並減去之前的數值,這時候是時鍾數,需要除以時鍾頻率得到時間計數,記得這里是毫秒為單位,所以不要忘記乘以1000來表示多少秒。
二、矩陣掩碼
需要頭文件#include"opencv2/imgproc/imgproc.hpp"
這里的例子說的就是針對一個矩陣進行卷積,如果有過卷積神經網絡(CNN)背景的就知道,通過對一副圖像進行卷積然后得到另一個卷積后的圖像。OpenCV中實現這一想法的就是filter2D過濾器,不過它是基於圖像的,不像是matlab是基於完全的矩陣,所以當輸入的是彩色圖像的時候,它是在三個通道上獨立的運行的:也就是對BGR三個通道分成三個矩陣,每個矩陣獨立進行卷積,然后接着三個矩陣再次合並成一個新的圖像。
上面第一行是先創建一個2D過濾器,在CNN中也叫做卷積核,機器學習中也叫做特征提取器。這里的創建方法雖然在前面一個博文中未介紹,不過覺得這個很像是特地為filter2D函數設計的,所以放在這里說,這是個運算符重定義,先Mat_<char>(3,3)先建立個3×3大小的矩陣,然后接着使用重定義操作符《來進行初始化,將得到的結果在賦值給核kern。void filter2D(InputArray src, OutputArray dst, int ddepth, InputArray kernel, Point anchor=Point(-1,-1), double delta=0, int borderType=BORDER_DEFAULT )
filter2D的參數列表:輸入圖像,輸出圖像,輸入圖像的深度,卷積核,指定核的中心,卷積過程中加到每個像素上的值,指定在未定義區域上的行為。后兩個參數是可選參數,也就是有默認形參的。
depth()表示的是位深度,就是每個元素是多少位的,是8位還是16位。
refman中第246頁。在filter2D中如果第三個參數是-1,那么輸出的深度就和輸入是一樣的。
這個函數在圖像上應用的是一個線性過濾器。支持in-place操作。當這個滑框部分超出了圖像的時候,這個函數會按照具體的邊界模式來插補外部的像素值。這個函數實際上是計算相關性,而不是卷積:
也就是說,這個kernel不是圍繞着錨點鏡像的。如果真的需要一個卷積,可以使用flip()來操作,然后設置新的錨點:(kernel.cols - anchor.x - 1, kernel.rows - anchor.y - 1) 。
這個函數是使用基於DFT的算法來應對大kernel(11×11或者更大)的,並且使用直接的算法(通過函數createLinearFIlter()實現的)來應對小kernel。
三、圖像疊加
就是將兩幅圖進行不同程度的疊加,公式為:
這里g(x)為輸出圖像,f(x)為對應的兩幅圖像,其實我覺得可以多福圖像疊加,雖然沒什么意義,不過原理上應該說的通。
addWeighted函數就是將兩個源圖像加到一起,而且也是理解成每個對應通道對應相加。這里特別注意的是兩個圖像要一樣大小,所以在讀取不一樣大小的可以通過設定不同的ROI或者對圖像進行縮放來完成這個目的。
四、防止數值溢出
上面的式子就是針對一副圖像進行圖像對比度和亮度的調整,因為i是在一副圖像上增加數值,如果BGR都增加到最大,那么就呈白色了,也就是增加每個通道上的亮度。對於這個操作,會有一定的幾率出現結果超出255,那么就不能算是正常的值了,所以需要對結果進行限定,
上面的saturate_cast<uchar>()函數就是針對參數進行限定,乍一看還以為是cpp自帶的類似static_castn那種,其實這個是opencv小組寫的,template<typename _Tp> static inline _Tp saturate_cast(uchar v) { return _Tp(v); }
template<typename _Tp> static inline _Tp saturate_cast(schar v) { return _Tp(v); }
template<typename _Tp> static inline _Tp saturate_cast(ushort v) { return _Tp(v); }
template<typename _Tp> static inline _Tp saturate_cast(short v) { return _Tp(v); }
在<operations.hpp>頭文件中,是通過使用cpp語言的截斷功能來實現的,就是強制轉換,比如一個超出uchar的數值,那么進行uchar的強制轉換,直接丟棄超出的部分。
上面的convertTo()函數就是執行式子中的操作,第二個參數就是int rtype,如果是負數,那么輸出圖像就使用與輸入圖像一樣的類型。
五、xml及yaml文件操作
這部分還是挺重要的,因為opencv中的很多分類器都是放在xml文件中的,而且xml適合web傳輸,所以這部分還是得會的。
XML和YAML的串行化分別采用兩種不同的數據結構: mappings (就像STL map) 和 element sequence (比如 STL vector>。二者之間的區別在map中每個元素都有一個唯一的標識名供用戶訪問;而在sequences中你必須遍歷所有的元素才能找到指定元素。
1、打開和關閉
上面是進行打開對應的文本進行讀寫,這里兩種方法都可以(即在初始化的時候指定或者調用open函數),opencv針對xml和yaml文本有涉及到兩個數據結構:FileStorage和FileNode。
FileStorage:在OpenCV中標識XML和YAML的數據結構是FileStorage 。其中的第二個參數都以常量形式指定你要對文件進行操作的類型,包括:WRITE, READ 或 APPEND。文件擴展名決定了你將采用的輸出格式。如果你指定擴展名如 .xml.gz ,輸出甚至可以是壓縮文件。
FIleNode:對於數據讀取,可使用 FileNode 和 FileNodeIterator 數據結構。 FileStorage 的[] 操作符將返回一個 FileNode 數據類型。如果這個節點是序列化的,我們可以使用 FileNodeIterator 來迭代遍歷所有元素。
2、普通讀寫
如上面說的fs[]返回的是FileNode的數據類型,然后調用重載符>>來進行輸出其中節點為["iterationNr"]的值到itNr中。
如上面code,通過對fs進行建立“R”和“T”的節點,然后接着寫入其數據;下面就是進行搜尋對應節點然后在讀取數據。
3、多數據讀寫
對於序列來說。寫入:,在第一個元素前輸出”[“字符,並在最后一個元素后輸出”]“字符,中間就是所需要自己輸入的數據:
這里的結果就是:
如上面結果所示,在節點strings中,是沒有順序可言的,所以只能按照順序讀取然后進行比對結果。
讀取:采用FileNode數據結構先索引到對應的節點,然后采用Fileiterator迭代器進行一個一個的索引:
對於maps來說。 寫入:與序列不同的在於采用”{“和”}“作為分隔符:
如上圖,先建立個Mapping節點,但是后面的“{”告訴fs,將要采用maps的方式進行寫入,所以這里前面多了兩個可以索引的“one”和“two”,然后接着輸入數據。
讀取:
和上面一樣先使用FileNode,找到節點,然后直接進行n["One"]的索引,這里返回值應該也是FileNode類型,然后轉換成自己需要的類型就好。
如果要自定義數據類型,比如自定義類來包含一些數據和操作,為了便捷,記得重載<<,>>操作符。
更詳細的部分請參考:http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/core/file_input_output_with_xml_yml/file_input_output_with_xml_yml.html#fileinputoutputxmlyaml
六、基本繪圖
這里介紹文本的打印和圖形的繪制,首先介紹一個也許會用到的RNG類,然后介紹在圖像上如何輸出文字和基本的線啊,矩形啊,圓形啊什么的。
第一行是建立個RNG的對象,然后進行初始化種子(可有可無,如果每次的種子都是相同的,那么結果就有比較性,所以matlab中都需要rand('state',0)在代碼的開始);第二個是隨機提取個值,這個值是介於【a,b)之間的:
inline int RNG::uniform(int a, int b) { return a == b ? a : (int)(next()%(b - a) + a); }
inline float RNG::uniform(float a, float b) { return ((float)*this)*(b - a) + a; }
inline double RNG::uniform(double a, double b) { return ((double)*this)*(b - a) + a; }
可以看出,它的返回值就三個,所以如果不是這三個就需要進行強制轉換了,這三行代碼在<operations.hpp>中。
文本:
putText()函數的參數列表:所打印的位置的圖像,打印的文字,文字左下角的坐標,文字的字體的參數,文字的縮放,顏色,字體粗細程度,線的類型。其實后面還有個可選的參數,這里說下lineType,在《refman》中的line()函數中具體介紹了,有三種形式分別為8、4、CV_AA。這三種。
上面第一行就是提取文字的尺寸,采用getTextSize()函數getTextSize()函數的參數列表:輸入的文本字符串,fortFace(詳見refman中的putText()函數部分的解釋),字體的縮放大小(祥見第二個參數部分),同前兩個參數,這是個指針表示相對於最底部的文本的點上y坐標的輸出參數。
第二行就是建立文字需要擺放的左下角的坐標,這里的window_width和window_height是所被打印的圖像的寬和高。
基本圖形:
文字可以用來做圖像的標記,圖形可以用來框出感興趣的區域。
線:void line(InputOutputArray img, Point pt1, Point pt2, const Scalar& color, int thickness=1, int lineType=LINE_8, int shift=0 );
參數列表:被打印的圖像,起始點,終點,顏色,線粗細,線類型,平移。
int Drawing_Random_Lines( Mat image, char* window_name, RNG rng )
{
Point pt1, pt2;
for( int i = 0; i < NUMBER; i++ )
{
pt1.x = rng.uniform( x_1, x_2 );
pt1.y = rng.uniform( y_1, y_2 );
pt2.x = rng.uniform( x_1, x_2 );
pt2.y = rng.uniform( y_1, y_2 );
line( image, pt1, pt2, randomColor(rng), rng.uniform(1, 10), 8 );
imshow( window_name, image );
if( waitKey( DELAY ) >= 0 )
{ return -1; }
}
return 0;
}
待續。。
http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/core/basic_geometric_drawing/basic_geometric_drawing.html#drawing-1