Opencv 例程講解 2 ----如何實現與opencv1.0的兼容混合編程

今天我們來看另外一個opencv例程,就是 (TUTORIAL) interoperability_with_OpenCV_1。

衆所周知,opencv1.0的函數API以c語言編寫的,提供的都是c語言的函數接口,用來存儲圖片的結構類型則爲IplImage,而從opencv2.0開始,數據存儲的變爲了Mat 類。那麼如何實現Mat格式與opencv1.0中IplImage的兼容性和互用呢,從interoperability_with_OpenCV_1 這個例程中,我們可以找到部分解答。廢話少說,直接上源碼。

#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;
//  程序使用說明
static void help( char* progName)
{
    cout << endl << progName
        << " shows how to use cv::Mat and IplImages together (converting back and forth)." << endl
        << "Also contains example for image read, spliting the planes, merging back and "  << endl
        << " color conversion, plus iterating through pixels. "                            << endl
        << "Usage:" << endl
        << progName << " [image-name Default: lena.jpg]"                           << endl << endl;
}

// comment out the define to use only the latest C++ API   // 控制參數,只使用C++API還是使用C、C++的混合API
#define DEMO_MIXED_API_USE

int main( int argc, char** argv )
{
    help(argv[0]);
    const char* imagename = argc > 1 ? argv[1] : "lena.jpg";  //如果輸入參數不夠,則使用默認圖片"lena.jpg",注意應該在當前路徑

#ifdef DEMO_MIXED_API_USE      //使用混合API時候
    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. // 將IplImage 格式轉成Mat類型,注意只複製數據指針,沒有拷貝數據

#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

    // convert image to YUV color space. The output image will be created automatically.
    Mat I_YUV;
    cvtColor(I, I_YUV, COLOR_BGR2YCrCb);    //cvtColor API,定義在 imgpro庫中,用於顏色空間的轉換

    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)    // split ,api定義在core 庫中,用於分割將通道,分別存儲到ch[0] ch[1] ch[2]

#if 1 // change it to 0 if you want to see a blurred and noisy version of this processing   
    // Mat scanning    
    // Method 1. process Y plane using an iterator
    MatIterator_<uchar> it = planes[0].begin<uchar>(), it_end = planes[0].end<uchar>();  // 1.Mat的迭代器,用來遍歷圖像,其中<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.   // 2.用下標來遍歷圖像
        uchar* Uptr = planes[1].ptr<uchar>(y);      // 獲得第y行的起始指針
        for( int x = 0; x < I_YUV.cols; x++ )
        {
            Uptr[x] = saturate_cast<uchar>((Uptr[x]-128)/2 + 128);   // 通過下標訪問數據,遍歷圖像, saturate_cast<uchar>爲opencv定義的類型安全轉換。
            // Method 3. process the second chroma plane using individual element access // 3. 通過at訪問,帶邊界檢測
            uchar& Vxy = planes[2].at<uchar>(y, x);
            Vxy =        saturate_cast<uchar>((Vxy-128)/2 + 128);
        }
    }

#else

    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));  // randn , 在core庫中,生成一張噪聲圖片,第二個參數爲均值,第三個參數爲方差

    // 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);  //高斯濾波  , 在imgproc庫中,最後兩個參數分別爲x,y方向的方差

    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;   // 將Mat 轉換成IplImage,也是隻轉換數據起始指針,不復制數據
    cvAddWeighted(&cv_planes_0, contrast_gain, &cv_noise, 1, -128 + brightness_gain, &cv_planes_0);  // C格式的API,融合兩張圖片
#else
    addWeighted(planes[0], contrast_gain, noisyI, 1, -128 + brightness_gain, planes[0]);  // C++格式的API接口
#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)); 
    // Mat::converTo, 在core庫中,可以轉換在CV_8U,CV_16U,CV_16S,CV_32F,CV_64F數據類型中轉換,8U對應uchar,32F對應float,16S對應ushort
    // 可以設定轉換增益和偏差,
    // 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_<uchar> ,這裏需要事先知道plane[2]的類似是uchar

    // Mat::mul replaces cvMul(). Again, no temporary arrays are created in case of simple expressions.
    planes[0] = planes[0].mul(planes[0], 1./255);    //第三種實現增益的方法,利用mul 方法
#endif


    merge(planes, I_YUV);                // now merge the results back   //合併多個通道爲一個Mat
    cvtColor(I_YUV, I, CV_YCrCb2BGR);  // and produce the output RGB image  //色彩空間轉換,opencv默認讀入的數據按照BGR順序排列


    namedWindow("image with grain", 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);    // C 的顯示圖片接口
#else
    imshow("image with grain", I); // the new MATLAB style function show  //C++ 的顯示圖片接口
#endif
    waitKey();

    // Tip: No memory freeing is required!
    //      All the memory will be automatically released by the Vector<>, Mat and Ptr<> destructor.  由於使用了ptr指針,這裏不需要對IplImag格式自己釋放
    return 0;
}


注意到下面這行代碼,我們可以將“lena,jpg改成我們電腦中實際測試圖片的路徑,這樣就不需要對argc相關的代碼進行註釋了,一個很實用的技巧。

const char* imagename = argc > 1 ? argv[1] : "F:\\image_set\\lena.jpg";

下面運行程序



可以看出,程序通過C接口的IplImage將文件讀進來,然後將數據指針穿給C++接口中的Mat類型,之後對Mat進行一系列處理,最後用C接口的cvShowImage函數顯示 IplImage格式,這通過一個Ptr<>的智能指針對IplImage進行控制,使得不再需要對IplImage類型進行手動釋放。可以看出,C,C++兩種接口之間實現了無縫銜接。

現在將#if 1這裏的1改成0,則程序執行#else這一部分,運行結果如下。




可以看到,一部分程序通過示例展示了3種Mat數據的遍歷方式,而另一部分展示了 隨機噪聲的生成,高斯濾波器的使用,以及圖像增益和偏差的操作,其中還用到了C接口的cvAddWeighted 函數。

本例程中,使用到的API和方法較多,現在做一個簡單的梳理。

1. 使用技巧一:如下所示,不僅可以在調試中直接運行,而且保留了cmd命令行下運行的接口能力。

const char* imagename = argc > 1 ? argv[1] : "F:\\image_set\\lena.jpg";

2. Ptr<IplImage>,使用智能指針Ptr<>,實現IplImage的資源自動釋放

 Ptr<IplImage> IplI = cvLoadImage(imagename); 

3. Mat(),通過()操作將IplImage的數據頭指針複製給Mat,注意這裏數據並沒有被拷貝,對Mat 的修改會影響到對應的IplImage。

Mat I(IplI); 

4. cvtColor,色彩空間轉換函數,可以實現BRG,HLS,HSV,YCrCb,LUV,YUV等多種格式的轉換

cvtColor(I, I_YUV, COLOR_BGR2YCrCb);

5.split,實現一個多通道的Mat 的通道分離,通常利用vector<Mat>類型實現,對應的通道可以通過vector 的下標方法[ ]進行訪問。

    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)

6. 對應split的函數是merge,將分離的通道合併成一個多通道的Mat

    merge(planes, I_YUV);                // now merge the results back

7. Mat 數據遍歷方法,本例中介紹了三種方法

(1)利用迭代器 MatIterator_<uchar>,需要指定數據類型,uchar,ushort,float,double,對應CV_8U, CV_16S, CV_32F 和CV_64F

 // 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);
    }

(2)利用列指針下標訪問, 通過ptr<uchar>操作獲得列起始指指針

 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);

(3)利用at進行元素訪問,帶邊界檢測,也需要指定數據類型

   // 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);

8. 通過Size 和 Type 創建一個Mat

 Mat noisyI(I.size(), CV_8U);   

9. 隨機噪聲生成函數,randn,第二個參數爲均值,第三參數爲方差,生成一個符合這樣分佈的高斯隨機噪聲

   randn(noisyI, Scalar::all(128), Scalar::all(20));

10. Mat 轉換成IplImage,直接用 = 操作,和IplImage轉Mat類似,也不復制數據

   IplImage cv_planes_0 = planes[0], cv_noise = noisyI;

11. 增益和偏差函數,包括c和c++的接口, 第二個參數爲第一副圖像的增益,第4個參數爲第二副的增益,第5個參數爲偏差
cvAddWeighted(&cv_planes_0, contrast_gain, &cv_noise, 1, -128 + brightness_gain, &cv_planes_0);

    addWeighted(planes[0], contrast_gain, noisyI, 1, -128 + brightness_gain, planes[0]);

12.Mat::convertTo(),實現數據類型轉換,以及增益和偏差操作,第2個參數爲目標類型,CV_8U,CV_16S,CV_32F等,第3個參數爲增益,第4個參數爲偏差
 planes[1].convertTo(planes[1], planes[1].type(), color_scale, 128*(1-color_scale));

13. 另外兩種實現增益和偏差的方法,這兩種方法一樣都不會產生臨時數據。mul()實現乘法運算,

  planes[2] = Mat_<uchar>(planes[2]*color_scale + 128*(1-color_scale));
   planes[0] = planes[0].mul(planes[0], 1./255);

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章