opencv3-core之基本操作


這一篇打算將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"

double t = (double)getTickCount();
// 做點什么 ...
t = 1000*((double)getTickCount() - t)/getTickFrequency();
cout << "Times passed in seconds: " << t << endl;
計時函數就和matlab中的tic ,toc一樣,可以用來計算你的代碼跑了多久,其實如果不是為了衡量效率什么的,這也是不怎么會用吧,首先進行提取當前電腦的時鍾數,這時候返回的是基於上次某個事件(比如開機)開始計算的時鍾數;

然后經過了一些操作,接着再次提取當前的時鍾數並減去之前的數值,這時候是時鍾數,需要除以時鍾頻率得到時間計數,記得這里是毫秒為單位,所以不要忘記乘以1000來表示多少秒。

二、矩陣掩碼

需要頭文件#include"opencv2/imgproc/imgproc.hpp"    

這里的例子說的就是針對一個矩陣進行卷積,如果有過卷積神經網絡(CNN)背景的就知道,通過對一副圖像進行卷積然后得到另一個卷積后的圖像。OpenCV中實現這一想法的就是filter2D過濾器,不過它是基於圖像的,不像是matlab是基於完全的矩陣,所以當輸入的是彩色圖像的時候,它是在三個通道上獨立的運行的:也就是對BGR三個通道分成三個矩陣,每個矩陣獨立進行卷積,然后接着三個矩陣再次合並成一個新的圖像。

Mat kern = (Mat_<char>(3,3) <<  0, -1,  0,
                               -1,  5, -1,
                                0, -1,  0);
filter2D(I, K, I.depth(), kern );
imshow("your window's name",K);
上面第一行是先創建一個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)為對應的兩幅圖像,其實我覺得可以多福圖像疊加,雖然沒什么意義,不過原理上應該說的通。

beta = ( 1.0 - alpha );
addWeighted( src1, alpha, src2, beta, 0.0, dst);
imshow("your window's name",dst);
addWeighted函數就是將兩個源圖像加到一起,而且也是理解成每個對應通道對應相加。這里特別注意的是兩個圖像要一樣大小,所以在讀取不一樣大小的可以通過設定不同的ROI或者對圖像進行縮放來完成這個目的。


四、防止數值溢出


上面的式子就是針對一副圖像進行圖像對比度和亮度的調整,因為i是在一副圖像上增加數值,如果BGR都增加到最大,那么就呈白色了,也就是增加每個通道上的亮度。對於這個操作,會有一定的幾率出現結果超出255,那么就不能算是正常的值了,所以需要對結果進行限定,

for( size_t y = 0; y < image.rows; y++ )
{
    for( size_t x = 0; x < image.cols; x++ )
    {
        for( size_t c = 0; c < 3; c++ )
        {
            new_image.at<Vec3b>(y,x)[c] = saturate_cast<uchar>( alpha*( image.at<Vec3b>(y,x)[c] ) + beta );
        }
    }
}
image.convertTo(new_image, -1, alpha, beta);
上面的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、打開和關閉

string filename = "I.xml";
FileStorage fs(filename, FileStorage::WRITE);
\\...
fs.open(filename, FileStorage::READ);
fs.release();  
上面是進行打開對應的文本進行讀寫,這里兩種方法都可以(即在初始化的時候指定或者調用open函數),opencv針對xml和yaml文本有涉及到兩個數據結構:FileStorage和FileNode。

FileStorage:在OpenCV中標識XML和YAML的數據結構是FileStorage 。其中的第二個參數都以常量形式指定你要對文件進行操作的類型,包括:WRITE, READ 或 APPEND。文件擴展名決定了你將采用的輸出格式。如果你指定擴展名如 .xml.gz ,輸出甚至可以是壓縮文件。

FIleNode:對於數據讀取,可使用 FileNode 和 FileNodeIterator 數據結構。 FileStorage 的[] 操作符將返回一個 FileNode 數據類型。如果這個節點是序列化的,我們可以使用 FileNodeIterator 來迭代遍歷所有元素。

2、普通讀寫

int itNr;
fs["iterationNr"] >> itNr; //讀操作
或者 itNr = (int) fs["iterationNr"]; //讀操作
fs << "iterationNr" << 100;//寫操作
如上面說的fs[]返回的是FileNode的數據類型,然后調用重載符>>來進行輸出其中節點為["iterationNr"]的值到itNr中。

Mat R = Mat_<uchar >::eye  (3, 3),
    T = Mat_<double>::zeros(3, 1);

fs << "R" << R;                                      // 寫 cv::Mat
fs << "T" << T;

fs["R"] >> R;                                      // 讀 cv::Mat
fs["T"] >> T;
如上面code,通過對fs進行建立“R”和“T”的節點,然后接着寫入其數據;下面就是進行搜尋對應節點然后在讀取數據。

3、多數據讀寫

對於序列來說。寫入:,在第一個元素前輸出”[“字符,並在最后一個元素后輸出”]“字符,中間就是所需要自己輸入的數據:

fs << "strings" << "[";                              // 文本 - 字符串序列
fs << "image1.jpg" << "Awesomeness" << "baboon.jpg";
fs << "]";                                           // 序列結束

這里的結果就是:

<?xml version="1.0"?>
<opencv_storage>
<iterationNr>100</iterationNr>
<strings>
  image1.jpg Awesomeness baboon.jpg</strings>
如上面結果所示,在節點strings中,是沒有順序可言的,所以只能按照順序讀取然后進行比對結果。

讀取:采用FileNode數據結構先索引到對應的節點,然后采用Fileiterator迭代器進行一個一個的索引:

FileNode n = fs["strings"];                         // 讀取字符串序列 - 獲取節點
if (n.type() != FileNode::SEQ)
{
    cerr << "strings is not a sequence! FAIL" << endl;
    return 1;
}

FileNodeIterator it = n.begin(), it_end = n.end(); // 遍歷節點
for (; it != it_end; ++it)
    cout << (string)*it << endl;//這里可以進行所需要的操作,比如對比或者什么的。

對於maps來說寫入:與序列不同的在於采用”{“和”}“作為分隔符:

fs << "Mapping";                              // 文本 - mapping
fs << "{" << "One" << 1;
fs <<        "Two" << 2 << "}";
如上圖,先建立個Mapping節點,但是后面的“{”告訴fs,將要采用maps的方式進行寫入,所以這里前面多了兩個可以索引的“one”和“two”,然后接着輸入數據。

讀取

n = fs["Mapping"];                                // 從序列中讀取map
cout << "Two  " << (int)(n["Two"]) << "; ";
cout << "One  " << (int)(n["One"]) << endl << endl;
和上面一樣先使用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 rng( 0xFFFFFFFF );//或者直接RNG rng;
 int x = (int)rng.uniform( a, b);
第一行是建立個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( image, "Testing text rendering", org, rng.uniform(0,8),
         rng.uniform(0,100)*0.05+0.1, randomColor(rng), rng.uniform(1, 10), lineType);
putText()函數的參數列表:所打印的位置的圖像,打印的文字,文字左下角的坐標,文字的字體的參數,文字的縮放,顏色,字體粗細程度,線的類型。

其實后面還有個可選的參數,這里說下lineType,在《refman》中的line()函數中具體介紹了,有三種形式分別為8、4、CV_AA。這三種。

Size textsize = getTextSize("OpenCV forever!", CV_FONT_HERSHEY_COMPLEX, 3, 5, 0);
  Point org((window_width - textsize.width)/2, (window_height - textsize.height)/2);
上面第一行就是提取文字的尺寸,采用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




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM