天天看點

opencv c++函數 基礎10 與 OpenCV 1 同時使用

目的

對于OpenCV的開發團隊來說,持續穩定地提高代碼庫非常重要。我們一直在思考如何在使其易用的同時保持靈活性。新的C++接口即為此而來。盡管如此,向下相容仍然十分重要。我們并不想打斷你基于早期OpenCV庫的開發。是以,我們添加了一些函數來處理這種情況。在以下内容中你将學到:

  • 相比第一個版本,第二版的OpenCV在用法上有何改變
  • 如何在一幅圖像中加入高斯噪聲
  • 什麼事查找表及如何使用

概述

在用新版本之前,你首先需要學習一些新的圖像資料結構: Mat - 基本圖像容器 ,它取代了舊的 CvMat 和 IplImage 。轉換到新函數非常容易,你僅需記住幾條新的原則。

OpenCV 2 接受按需定制。所有函數不再裝入一個單一的庫中。我們會提供許多子產品,每個子產品都包含了與其功能相關的資料結構和函數。這樣一來,如果你僅僅需要使用OpenCV的一部分功能,你就不需要把整個巨大的OpenCV庫都裝入你的程式中。使用時,你僅需要包含用到的頭檔案,比如:

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
      

所有OpenCV用到的東西都被放入名字空間 cv 中以避免與其他庫的資料結構和函數名稱的命名沖突。是以,在使用OpenCV庫中的任何定義和函數時,你必須在名稱之前冠以 cv:: ,或者在包含頭檔案後,加上以下指令:

因為所有庫中函數都已在此名字空間中,是以無需加 cv 作為字首。據此所有新的C++相容函數都無此字首,并且遵循駝峰命名準則。也就是第一個字母為小寫(除非是單個單詞作為函數名,如 Canny)并且後續單詞首字母大寫(如 copyMakeBorder ).

接下來,請記住你需要将所有用到的子產品連結到你的程式中。如果你在Windows下開發且用到了 動态連結庫(DLL) ,你還需要将OpenCV對應動态連結庫的路徑加入程式執行路徑中。關于Windows下開發的更多資訊請閱讀 How to build applications with OpenCV inside the Microsoft Visual Studio ;對于Linux使用者,可參考 Using OpenCV with Eclipse (plugin CDT) 中的執行個體及說明。

你可以使用 IplImage 或 CvMat 操作符來轉換 Mat 對象。在C接口中,你習慣于使用指針,但此處将不再需要。在C++接口中,我們大多數情況下都是用 Mat 對象。此對象可通過簡單的指派操作轉換為 IplImage 和 CvMat 。示例如下:

Mat I;
IplImage pI = I;
CvMat    mI = I;
      

現在,如果你想擷取指針,轉換就變得麻煩一點。編譯器将不能自動識别你的意圖,是以你需要明确指出你的目的。可以通過調用 IplImage 和 CvMat 操作符來擷取他們的指針。我們可以用 & 符号擷取其指針如下:

Mat I;
IplImage* pI     = &I.operator IplImage();
CvMat* mI        =  &I.operator CvMat();
      

來自C接口最大的抱怨是它将所有記憶體管理工作交給你來做。你需要知道何時可以安全釋放不再使用的對象,并且确定在程式結束之前釋放它,否則就會造成讨厭的記憶體洩露。為了繞開這一問題,OpenCV引進了一種智能指針。它将自動釋放不再使用的對象。使用時,指針将被聲明為 Ptr 模闆的特化:

将C接口的資料結構轉換為 Mat 時,可将其作為構造函數的參數傳入,例如:

Mat K(piL), L;
L = Mat(pI);
      

執行個體學習

現在,你已經學習了最基本的知識。 這裡 你将會看到一個混合使用C接口和C++接口的例子。你也可以在可以再OpenCV的代碼庫中的sample目錄中找到此檔案samples/cpp/tutorial_code/core/interoperability_with_OpenCV_1/interoperability_with_OpenCV_1.cpp 。為了進一步幫助你認清其中差別,程式支援兩種模式:C和C++混合,以及純C++。如果你宏定義了 DEMO_MIXED_API_USE ,程式将按第一種模式編譯。程式的功能是劃分顔色平面,對其進行改動并最終将其重新合并。

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31      
#include <stdio.h>
#include <iostream>

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

using namespace cv;  // The new C++ interface API is inside this namespace. Import it.
using namespace std;
#define DEMO_MIXED_API_USE 

int main( int argc, char** argv )
{
    const char* imagename = argc > 1 ? argv[1] : "lena.jpg";

#ifdef DEMO_MIXED_API_USE
    Ptr<IplImage> IplI = cvLoadImage(imagename);      // Ptr<T> is safe ref-counting pointer class
    if(IplI.empty())
    {
        cerr << "Can not load image " <<  imagename << endl;
        return -1;
    }
    Mat I(IplI); // Convert to the new style container. Only header created. Image not copied.    
#else
    Mat I = imread(imagename);        // the newer cvLoadImage alternative, MATLAB-style function
    if( I.empty() )                   // same as if( !I.data )
    {
        cerr << "Can not load image " <<  imagename << endl;
        return -1;
    }
#endif
      

在此,你可一看到新的結構再無指針問題,哪怕使用舊的函數,并在最後結束時将結果轉換為 Mat 對象。

1
2
3
4
5
6      
// convert image to YUV color space. The output image will be created automatically. 
    Mat I_YUV;
    cvtColor(I, I_YUV, CV_BGR2YCrCb); 

    vector<Mat> planes;    // Use the STL's vector structure to store multiple Mat objects 
    split(I_YUV, planes);  // split the image into separate color planes (Y U V)
      

因為我們打算搞亂圖像的亮度通道,是以首先将圖像由預設的RGB顔色空間轉為YUV顔色空間,然後将其劃分為獨立顔色平面(Y,U,V)。第一個例子中,我們對每一個平面用OpenCV中三個主要圖像掃描算法(C []操作符,疊代,單獨元素通路)中的一個進行處理。在第二個例子中,我們給圖像添加一些高斯噪聲,然後依據一些準則融合所有通道。

運用掃描算法的代碼如下:

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21      
// Method 1. process Y plane using an iterator
    MatIterator_<uchar> it = planes[0].begin<uchar>(), it_end = planes[0].end<uchar>();
    for(; it != it_end; ++it)
    {
        double v = *it * 1.7 + rand()%21 - 10;
        *it = saturate_cast<uchar>(v*v/255);
    }
    
    for( int y = 0; y < I_YUV.rows; y++ )
    {
        // Method 2. process the first chroma plane using pre-stored row pointer.
        uchar* Uptr = planes[1].ptr<uchar>(y);
        for( int x = 0; x < I_YUV.cols; x++ )
        {
            Uptr[x] = saturate_cast<uchar>((Uptr[x]-128)/2 + 128);
            
            // Method 3. process the second chroma plane using individual element access
            uchar& Vxy = planes[2].at<uchar>(y, x);
            Vxy =        saturate_cast<uchar>((Vxy-128)/2 + 128);
        }
    }
      

此處可看到,我們可以以三種方式周遊圖像的所有像素:疊代器,C指針和單獨元素通路方式你可在 OpenCV如何掃描圖像、利用查找表和計時 中獲得更深入的了解。從舊的函數名轉換新版本非常容易,僅需要删除 cv 字首,并且使用 Mat 資料結構。下面的例子中使用了權重加法:

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34      
Mat noisyI(I.size(), CV_8U);           // Create a matrix of the specified size and type
    
    // Fills the matrix with normally distributed random values (around number with deviation off).
    // There is also randu() for uniformly distributed random number generation
    randn(noisyI, Scalar::all(128), Scalar::all(20)); 
    
    // blur the noisyI a bit, kernel size is 3x3 and both sigma's are set to 0.5
    GaussianBlur(noisyI, noisyI, Size(3, 3), 0.5, 0.5); 

    const double brightness_gain = 0;
    const double contrast_gain = 1.7;

#ifdef DEMO_MIXED_API_USE
    // To pass the new matrices to the functions that only work with IplImage or CvMat do:
    // step 1) Convert the headers (tip: data will not be copied).
    // step 2) call the function   (tip: to pass a pointer do not forget unary "&" to form pointers)
    
    IplImage cv_planes_0 = planes[0], cv_noise = noisyI;    
    cvAddWeighted(&cv_planes_0, contrast_gain, &cv_noise, 1, -128 + brightness_gain, &cv_planes_0);
#else
    addWeighted(planes[0], contrast_gain, noisyI, 1, -128 + brightness_gain, planes[0]);
#endif
    
    const double color_scale = 0.5;
    // Mat::convertTo() replaces cvConvertScale. 
    // One must explicitly specify the output matrix type (we keep it intact - planes[1].type())
    planes[1].convertTo(planes[1], planes[1].type(), color_scale, 128*(1-color_scale));

    // alternative form of cv::convertScale if we know the datatype at compile time ("uchar" here).
    // This expression will not create any temporary arrays ( so should be almost as fast as above)
    planes[2] = Mat_<uchar>(planes[2]*color_scale + 128*(1-color_scale));

    // Mat::mul replaces cvMul(). Again, no temporary arrays are created in case of simple expressions.
    planes[0] = planes[0].mul(planes[0], 1./255);
      

正如你所見,變量 planes 也是 Mat 類型的。無論如何,将 Mat 轉換為 IplImage 都可通過簡單的指派操作符自動實作。

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13      
merge(planes, I_YUV);                // now merge the results back
    cvtColor(I_YUV, I, CV_YCrCb2BGR);  // and produce the output RGB image

    
    namedWindow("image with grain", CV_WINDOW_AUTOSIZE);   // use this to create images

#ifdef DEMO_MIXED_API_USE
    // this is to demonstrate that I and IplI really share the data - the result of the above
    // processing is stored in I and thus in IplI too.
    cvShowImage("image with grain", IplI);
#else
    imshow("image with grain", I); // the new MATLAB style function show
      

新的 imshow highgui函數可接受 Mat 和 IplImage 資料結構。 編譯并運作例程,如果輸入以下第一幅圖像,程式将輸出以下第二幅或者第三幅圖像。

opencv c++函數 基礎10 與 OpenCV 1 同時使用

你可以在點選此處看到動态示例: YouTube here ,并可以 點選此處 下載下傳源檔案,或者在OpenCV源代碼庫中找到源檔案:samples/cpp/tutorial_code/core/interoperability_with_OpenCV_1/interoperability_with_OpenCV_1.cpp 。