今天我們來看另外一個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);