圖像的基本操作


圖像的表示

  在正式介紹之前,先簡單介紹一下數字圖像的基本概念。如圖 3.1 中所示 的圖像,我們看到的是 Lena 的頭像,但是計算機看來,這副圖像只是一堆亮度 各異的點。一副尺寸為 M × N 的圖像可以用一個 M × N 的矩陣來表示,矩 陣元素的值表示這個位置上的像素的亮度,一般來說像素值越大表示該點越 亮。如圖 3.1 中白色圓圈內的區域,進行放大並仔細查看,將會如圖 3.2 所 示。
 

一般來說,灰度圖用 2 維矩陣表示,彩色(多通道)圖像用 3 維矩陣(M × N × 3)表示。對於圖像顯示來說,目前大部分設備都是用無符號 8 位整 數(類型為 CV_8U)表示像素亮度。 圖像數據在計算機內存中的存儲順序為以圖像最左上點(也可能是最左下 點)開始,存儲如表 3-1 所示。

Iij 表示第 i 行 j 列的像素值。如果是多通道圖像,比如 RGB 圖像,則每個 像素用三個字節表示。在 OpenCV 中,RGB 圖像的通道順序為 BGR ,存儲如 表 3-2 所示。

Mat類

  早期的 OpenCV 中,使用 IplImage 和 CvMat 數據結構來表示圖像。IplImage 和 CvMat 都是 C 語言的結構。使用這兩個結構的問題是內存需要手動管理,開 發者必須清楚的知道何時需要申請內存,何時需要釋放內存。這個開發者帶來了 一定的負擔,開發者應該將更多精力用於算法設計,因此在新版本的 OpenCV 中 引入了 Mat 類。

  新加入的 Mat 類能夠自動管理內存。使用 Mat 類,你不再需要花費大量精 力在內存管理上。而且你的代碼會變得很簡潔,代碼行數會變少。但 C++接口唯 一的不足是當前一些嵌入式開發系統可能只支持 C 語言,如果你的開發平台支持 C++,完全沒有必要再用 IplImage 和 CvMat。在新版本的 OpenCV 中,開發者依 然可以使用 IplImage 和 CvMat,但是一些新增加的函數只提供了 Mat 接口。本書 中的例程也都將采用新的 Mat 類,不再介紹 IplImage 和 CvMat。

  Mat 類的定義如下所示,關鍵的屬性如下方代碼所示:

class CV_EXPORTS Mat
{
    public:
        //一系列函數
        ...
        /* flag 參數中包含許多關於矩陣的信息,如:
        -Mat 的標識
        -數據是否連續
        -深度
        -通道數目
        */
        int flags;
        //矩陣的維數,取值應該大於或等於 2
        int dims;
        //矩陣的行數和列數,如果矩陣超過 2 維,這兩個變量的值都為-1
        int rows, cols;
        //指向數據的指針
        uchar* data;


        //指向引用計數的指針
        //如果數據是由用戶分配的,則為 NULL
        int* refcount;

        //其他成員變量和成員函數
        ...
};

創建 Mat 對象

  Mat 是一個非常優秀的圖像類,它同時也是一個通用的矩陣類,可以用來創 建和操作多維矩陣。有多種方法創建一個 Mat 對象。

 構造函數方法

  Mat 類提供了一系列構造函數,可以方便的根據需要創建 Mat 對象。下面是 一個使用構造函數創建對象的例子。

Mat M(3,2, CV_8UC3, Scalar(0,0,255));  
cout << "M = " << endl << " " << M << endl; 

  第一行代碼創建一個行數(高度)為 3,列數(寬度)為 2 的圖像,圖像元 素是 8 位無符號整數類型,且有三個通道。圖像的所有像素值被初始化為(0, 0, 255)。由於 OpenCV 中默認的顏色順序為 BGR,因此這是一個全紅色的圖像。

  第二行代碼是輸出Mat類的實例M的所有像素值。Mat重定義了<<操作符, 使用這個操作符,可以方便地輸出所有像素值,而不需要使用 for 循環逐個像素 輸出。

Mat的常見構造方法

常用的構造函數有:
   Mat::Mat() 無參數構造方法;
   Mat::Mat(int rows, int cols, int type) 創建行數為 rows,列數為 col,類型為 type 的圖像;
   Mat::Mat(Size size, int type) 創建大小為 size,類型為 type 的圖像;
   Mat::Mat(int rows, int cols, int type, const Scalar& s) 創建行數為 rows,列數為 col,類型為 type 的圖像,並將所有元素初始 化為值 s;
   Mat::Mat(Size size, int type, const Scalar& s) 創建大小為 size,類型為 type 的圖像,並將所有元素初始化為值 s;
   Mat::Mat(const Mat& m) 將 m 賦值給新創建的對象,此處不會對圖像數據進行復制,m 和新對象 共用圖像數據;
   Mat::Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP) 創建行數為 rows,列數為 col,類型為 type 的圖像,此構造函數不創建 圖像數據所需內存,而是直接使用 data 所指內存,圖像的行步長由 step 指定。
   Mat::Mat(Size size, int type, void* data, size_t step=AUTO_STEP) 創建大小為 size,類型為 type 的圖像,此構造函數不創建圖像數據所需 內存,而是直接使用 data 所指內存,圖像的行步長由 step 指定。
   Mat::Mat(const Mat& m, const Range& rowRange, const Range& colRange) 創建的新圖像為 m 的一部分,具體的范圍由 rowRange 和 colRange 指 定,此構造函數也不進行圖像數據的復制操作,新圖像與 m 共用圖像數 據;
   Mat::Mat(const Mat& m, const Rect& roi) 創建的新圖像為 m 的一部分,具體的范圍 roi 指定,此構造函數也不進 行圖像數據的復制操作,新圖像與 m 共用圖像數據。

  這些構造函數中,很多都涉及到類型type。type可以是CV_8UC1,CV_16SC1, …, CV_64FC4 等。里面的 8U 表示 8 位無符號整數,16S 表示 16 位有符號整數,64F 表示 64 位浮點數(即 double 類型);C 后面的數表示通道數,例如 C1 表示一個 通道的圖像,C4 表示 4 個通道的圖像,以此類推。
  如果你需要更多的通道數,需要用宏 CV_8UC(n),例如:
  Mat M(3,2, CV_8UC(5));//創建行數為 3,列數為 2,通道數為 5 的圖像

create()函數創建對象

  除了在構造函數中可以創建圖像,也可以使用 Mat 類的 create()函數創建圖 像。如果 create()函數指定的參數與圖像之前的參數相同,則不進行實質的內存 申請操作;如果參數不同,則減少原始數據內存的索引,並重新申請內存。使用 方法如下面例程所示:

    Mat M(2,2, CV_8UC3);//構造函數創建圖像

    M.create(3,2, CV_8UC2);//釋放內存重新創建圖像

矩陣的基本元素表達

  對於單通道圖像,其元素類型一般為8U(即8位無符號整數),當然也可以是16S、32F等;這些類型可以直接用uchar、short、float等C/C++語言中的基本數據類型表達。如果多通道圖像,如RGB彩色圖像,需要用三個通道來表示。在這種情況下,如果依然將圖像視作一個二維矩陣,那么矩陣的元素不再是基本的數據類型。27OpenCV中有模板類Vec,可以表示一個向量。OpenCV中使用Vec類預定義了一些小向量,可以將之用於矩陣元素的表達。

    typedef Vec<uchar, 2> Vec2b;
    typedef Vec<uchar, 3> Vec3b;
    typedef Vec<uchar, 4> Vec4b;
    typedef Vec<short, 2> Vec2s;
    typedef Vec<short, 3> Vec3s;
    typedef Vec<short, 4> Vec4s;
    typedef Vec<int, 2> Vec2i;
    typedef Vec<int, 3> Vec3i;
    typedef Vec<int, 4> Vec4i;
    typedef Vec<float, 2> Vec2f;
    typedef Vec<float, 3> Vec3f;
    typedef Vec<float, 4> Vec4f;
    typedef Vec<float, 6> Vec6f;
    typedef Vec<double, 2> Vec2d;
    typedef Vec<double, 3> Vec3d;
    typedef Vec<double, 4> Vec4d;
    typedef Vec<double, 6> Vec6d;

  例如8U類型的RGB彩色圖像可以使用Vec3b,3通道float類型的矩陣可以使用Vec3f。

  對於Vec對象,可以使用[]符號如操作數組般讀寫其元素,如:

  

Vec3b color; //變量描述一種RGB顏色
color[0]=255; //B分量
color[1]=0; //G分量
color[2]=0; //R分量

像素值的讀寫

很多時候,我們需要讀取某個像素值,或者設置某個像素值;在更多的時候,我們需要對整個圖像里的所有像素進行遍歷。OpenCV提供了多種方法來實現圖像的遍歷。

at()函數

    函數at()來實現讀去矩陣中的某個像素,或者對某個像素進行賦值操作。下面兩行代碼演示了at()函數的使用方法。
uchar value = grayim.at<uchar>(i,j);//讀出第i行第j列像素值
grayim.at<uchar>(i,j)=128; //將第i行第j列像素值設置為128
    如果要對圖像進行遍歷,可以參考下面的例程。這個例程創建了兩個圖像,分別是單通道的grayim以及3個通道的colorim,然后對兩個圖像的所有像素值進行賦值,最后現實結果。

 

#include<iostream>
#include"opencv2/opencv.hpp"
using namespace std;
using namespace cv;
/*需要注意的是:
    如果要遍歷圖像,並不推薦使用at()函數。使用這個函數的優點是代碼的可讀性比較高但是at的效率不是很高*/
int main(int argc,char* argv[])
{
    Mat grayim(600,800,CV_8UC1);
    Mat colorim(600,800,CV_8UC3);

    //遍歷所有元素,並設置像素值
    for(int i=0;i<grayim.rows;i++)
        for(int j=0;j<grayim.cols;j++)
            grayim.at<uchar>(i,j)=(i+j)%255;

    //遍歷所有像素,並且設置像素值
    for(int i=0;i<colorim.rows;i++)
        for(int j=0;j<colorim.cols;j++)
        {
            Vec3b pixel;
            pixel[0]=i%255;
            pixel[1]=j%255;
            pixel[2]=0;
            colorim.at<Vec3b>(i,j)=pixel;
        }
    //顯示結果
    imshow("grayim",grayim);
    imshow("colorim",colorim);
    waitKey(0);
    return 0;
}

 

  如果你熟悉C++的STL庫那么你一定了解迭代器的使用。迭代器可以方便的遍歷所有的元素。Mat也增加了迭代器的支持,一邊矩陣元素的便利。下面的實例功能跟上一節的比較類似,但是由於使用了迭代器,而不是使用行數和列數來便利所有這兒沒有了i和j變量,像素的像素值為一個隨機數。

#include<iostream>
#include"opencv2/opencv.hpp"
using namespace std;
using namespace cv;
/*需要注意的是:
    如果要遍歷圖像,並不推薦使用at()函數。使用這個函數的優點是代碼的可讀性比較高但是at的效率不是很高*/
int main(int argc,char* argv[])
{
    Mat grayim(680,800,CV_8UC1);
    Mat colorim(680,800,CV_8UC3);

    //遍歷所有元素,並設置像素值
    MatIterator_<uchar> grayit,grayend;
    for(grayit=grayim.begin<uchar>(),grayend=grayim.end<uchar>();grayit!=grayend;grayit++)
        *grayit=rand()%255;

    MatIterator_<Vec3b> colorit,colorend;
    for(colorit=colorim.begin<Vec3b>(),colorend=colorim.end<Vec3b>();colorit!=colorend;colorit++)
    {
        (*colorit)[0]=rand()%255;//Blue
        (*colorit)[1]=rand()%255;//Green
        (*colorit)[2]=rand()%255;//Red
    }
    imshow("yuan",grayim);
    imshow("chong",colorim);
   // printf("asdddddddddddddd");
    waitKey(0);
    return 0;
}

 

通過數據指針

  使用iplimage結構的時候,我們會經常使用數據指針來直接操作像素。通過指針操作來訪問像素值非常高效的,但是C/C++中的指針操作是不進行類型以及是否越界檢查的,如果指針的訪問出錯程序運行時有時候可能看上去一切正常但是,有時候會彈出來一個“段錯誤”(segment fault)。

  當程序的規模比較大,並且邏輯十分復雜的時候,查找指針錯誤就十分困難。對於不熟悉指針的編程者來說指針就如同噩夢。如果你對指針使用沒有自信,則不建議使用。如果非常注重程序的運行速度,那么遍歷象素的時候,建議使用指針。


0.783

#include<iostream>
#include<time.h>
#include<stdlib.h>
#include"opencv2/opencv.hpp"
using namespace std;
using namespace cv;
/*需要注意的是:
    如果要遍歷圖像,並不推薦使用at()函數。使用這個函數的優點是代碼的可讀性比較高但是at的效率不是很高*/
int main(int argc,char* argv[])
{
    clock_t start,finish;          //計時之用
    double totalTime;               //計時之用
    start=clock();              //計時之用


    Mat grayim(680,800,CV_8UC1);
    Mat colorim(680,800,CV_8UC3);

    //遍歷所有元素,並設置像素值
    for(int i=0;i<grayim.rows;i++)
    {
        uchar *p=grayim.ptr<uchar>(i);
        for(int j=0;j<grayim.cols;j++)
        {
            p[j]=(i+j)%255;
        }
    }
    for(int i=0;i<colorim.rows;i++)
    {
        Vec3b *p=colorim.ptr<Vec3b>(i);
        for(int j=0;j<colorim.cols;j++)
        {
            p[j][0]=j%255;
            p[j][1]=j%255;
            p[j][2]=j;
        }
    }
    imshow("yuan",grayim);
    imshow("chong",colorim);
    finish=clock();//計時之用
    totalTime=(double)(finish-start)/CLOCKS_PER_SEC;//計時之用
    printf("------------%lf------------\n",totalTime);
    waitKey(0);
    return 0;
}

 

 

 


單行或者單列選擇

  提取矩陣的一行或者一列可以使用row()或者col()。函數的聲明如下:

Mat Mat::row(int i) const
Mat Mat::col(int j) const

 

  參數i和j分別是行標和列標。例如取出A矩陣的第i行可以使用如下代碼:

    Mat line = A.row(i);

  例如取出A矩陣的第i行,將這一行的所有元素都乘以2,然后賦值給第j行可以這樣寫:

    A.row(j) = A.row(i)*2;

  

用range選擇多行或多列

  Range是OpenCV中新增的類,該類有兩個關鍵變量star和end。Range對象可以用來表示矩陣的多個連續的行或者多個連續的列。其表示的范圍為從start到end,包含start但不包含end。Range類的定義如下。

class range
{
public:
    ...
    int start,end;
};

 

  Range類還提供了一個靜態方法all(),這個方法的作用如同Matlab中的“:”,表示所有的行或者列。

//創建一個單位陣
Mat A = Mat::eye(10,10,CV_32S);
//提取第一行到第三行(不包括三)
Mat B = A(Range::all(),Range(1,3));
//提取B的第5至第9行(不包括9)
//其實等價於C = A(Range(5,9),Range(1,3))
Mat C = B(Range(5,9),Range::all());

 

感興趣的區域

  從頭像中提取感興趣的區域有兩種方法,一種是使用構造函數,如下例所示:

//創建寬度為320,高度為241,的三通道圖像
Mat img(Size(320,240),CV_8UC3);
//roi是表示img中rect(10,10,100,100)區域的對象
Mat roi(img,rect(10,10,100,100));
//除了使用構造函數,還可以使用括號運算符,如下:
Mat roi2=img(Rect(10,10,100,100));
//當然也可以使用Range運算符來定義感興趣對象,如下:
Mat roi3=img(Range(10,100),Range(10,100));

 

Mat表達式

  利用C++中的運算符重載,OpenCV2中引入了Mat運算表達式。這一特點使得用C++進行編程的時候,就如同寫Matlab腳本一樣,代碼變得簡潔易懂,也便於維護。

  如果矩陣A和B大小相同,則可以使用如下表達式:

    C=A+B+1;

      其執行結果是矩陣A和B大小相同,則可以使用如下表達式:

        C=A+B+1;

  其執行結果是A和B的元素相加,然后再加上1,並將生成的矩陣賦值給C變量。

  下面是Mat表達式所支持的運算。下面的列表中使用A和B表示Mat類型的對象,使用s表示Scalar對象,alpha表示double值。

    *加法,減法,取負:A+B,A-B,A+s,A-s,s+A,s-A,-A

    *縮放取值范圍:A*alpha

    *矩陣對應元素的乘法和除法:A.mul(B),A/B ,alpha/A

    *矩陣乘法:A*B(此處是矩陣乘法,而不是矩陣對應元素相乘)

    *矩陣轉置:A.t()

    *矩陣求逆和求偽逆:A.inv()

    *矩陣比較運算:A cmpop B,A cmpop alpha , alphga cmpop A。此處的cmpop可以是>,>=,==,!=,<=,<。如果條件成立則結果矩陣?(8U類型矩陣)的對應元素被設置為255,否則設置為0。

    *矩陣位邏輯運算:A logicop B,A logicop s , s logicop A,~A,此處logicop可以是&,|和^。

    *矩陣對應元素的最大值和最小值:min(A,B), min(A,alpha), max(A,B), max(A,alpha)。

    *矩陣中元素的絕對值:abs(A)

    *叉積和點積:A.cross(B),A.dot(B)

  下面的例程展示了Mat表達式的使用方法,例程的輸出結果如下所示。

    

#include<iostream>
#include<stdio.h>
#include"opencv2/opencv.hpp"
using namespace std;
using namespace cv;
int main(int argc,char* argv[])
{
    Mat M(600,800,CV_8UC1);

    for(int i=0;i<M.rows;i++)
    {
        uchar *p=M.ptr<uchar>(i); //聲明一個uchar指針,並且將矩陣M的第i行頭指針賦給該指針,單位是uchar。
        for(int j=0;j<M.cols;j++)
        {
            double d1=(double)((i+j%255));
            //用at讀寫像素的時候需要指定類型。
            M.at<uchar>(i,j)=d1;
            //下面的代碼錯誤,應該使用at<uchar>()
            //但是編譯時不會提醒錯誤
            //運行結果不正確,d2不等於d1.
            double d2 = M.at<double>(i,j);
        }
    }
    //在變量聲明時指定矩陣元素類型
    Mat_<uchar> M1 = (Mat_<uchar>&)M;
    for(int i=0;i<M1.rows;i++)
    {
        uchar *p=M1.ptr(i);
        for(int j=0;j<M1.cols;j++)
        {
            double d1 = (double)((i+j)%255);
            M1(i,j)=d1;
            double d2=M1(i,j);
        }
    }
    return 0;
}

 

Mat類的內存管理

  使用Mat類,內存管理變得簡單,不再像使用iplimage那樣需要自己申請和釋放內存了。雖然不了解Mat的內存管理機制,也無妨Mat類的使用,但是如果清楚了解Mat的內存管理機制,會更清楚一些函數到底執行了那些數據。

  Mat是一個類,由兩個數據部分組成:矩陣頭(包含矩陣尺寸,儲存方法,儲存地址等信息)和一個指向儲存所有像素值的矩陣的指針,如圖所示矩陣頭的尺寸是常數值,但是矩陣的尺寸會依圖像的不同而不同,通常比矩陣頭大數個數量級。復制矩陣數據往往需要花費較多的時間,所以一般情況下不要賦值較大矩陣,能夠復用就盡量復用。

  為了解決矩陣數據的傳遞,OpenCV使用了引用計數機制。其思路是讓每個Mat對象有自己的矩陣頭信息,但是多個Mat對象可以共享一個矩陣數據。讓矩陣指針指向同一地址而實現這一目的。很多函數以及很多操作(比如函數參數傳遞),支付至矩陣頭信息,而不復制矩陣數據。

  前面提到過,有很多方法創建Mat類。如果Mat類自己申請數據空間,那么會多申請4個字節,多出的四個字節儲存數據被引用的次數。引用次數儲存於數據空間的后面,refcount指向這個位置,如圖所示當計數為0的時候就釋放該空間。

 關於多個矩陣對象共享同一矩陣數據,我們可以看這個例子:

  

Mat A(100,100,CV_8UC1);

Mat B=A;

Mat C = A(Rect(50,50,30,30));

 

  

上面的代碼有三個Mat對象,分別是A,B和C。這三者共有同一矩陣數據其示意圖如:

  

 

輸出

  從前面的例程,可以看到Mat類重載了<<運算符,可以方便的使用溜操作來輸出矩陣的內容。默認情況下輸出的格式是類似於Matlab中矩陣的輸出格式。除了默認格式,Mat也支持其他的輸出格式。代碼如下:

    首先創建一個矩陣,並用隨機數填充。填充的范圍由randu()函數的第二個和第三個參數去誒的那個,下面代碼是介於0到255之間。

  

 1 #include<iostream>
 2 #include<fstream>
 3 #include<iostream>
 4 #include<stdio.h>
 5 #include"opencv2/opencv.hpp"
 6 #include<stdlib.h>
 7 using namespace std;
 8 using namespace cv;
 9 int main(int argc,char* argv[])
10 {
11     FILE *f;
12     ofstream fileio("C:\\Users\\xpower\\Desktop\\jack.txt",ios::out|ios::in);
13 
14     Mat R = Mat(300,200,CV_8UC3);
15 
16     randu(R,Scalar::all(0),Scalar::all(255));//填充的范圍由randu()參數和第三個參數確定,下面代碼是介於0到255之間。
17 
18 //默認格式輸出的代碼如下:
19     imshow("jack",R);
20 
21     //cout<<"R (default) = "<<endl<<R<<endl<<endl;
22     fileio<<R<<endl;
23     fileio.close();
24     printf("end!\n");
25     waitKey(0);
26 }

 

Mat與iplimage和CvMat的轉換

  在OpenCV2中雖然引入了方便的Mat類,處於兼容性的考慮,OpenCV依然支持C語言接口的iplimage和CvMat結果,如果想要和以前的代碼兼容將會涉及到Mat與iplimage和CvMat的轉換。

Mat轉為IpLimage和CvMat的轉換

  。。。。。

 讀寫圖像文件

  將圖像文件讀入內存,可以使用imread()函數;將Mat對象以圖像文件格式寫入內存,可以使用imwrite()函數。

讀取圖像文件

  imread函數返回的是Mat對象,如果讀取文件失敗,則會返回一個空矩陣,即Mat::data的值食NULL.執行imread()之后, 需要檢查文件是否讀入成功, 你可以使用Mat::empty()函數進行檢查。imread()函數的聲明如下。

Mat imread(const string &filename,int flag=1)

 很明顯參數filename就是被讀取或者保存的文件名。在imread()函數中flag的取值有三種情況:

    flag>0, 該函數返回3通道圖像,如果磁盤上的圖像文件時單通道的則會被強制轉換為三通道;

    flag=0,該函數返回單通道圖像,如果磁盤的文件是強制轉化為單通道圖像。

    flag<0, 函數不對圖像做通道轉換。

imread()函數支持多種文件格式,且該函數是根據圖像文件的內容來確定文件格式,而不是根據文件的擴展名來確定,文件格式名如下。

  Windows位圖文件-BMP,DIB;

  JPEG文件-JPEG,JPG,JPE;

  便攜式網絡圖片-PNG

  便攜式圖像格式-PBM,PGM,PPM;

  Sun rasters - SR,RAS;

  TIFF文件-TIFF,TIF

  OpenEXR HDR圖片,EXR

  JPEG圖片 - jp2

你所安裝的OpenCV並不一定能支持上述所有格式,文件格式的支持需要特定的庫,只有在編譯OpenCV添加了相應的的文件格式庫,才可支持其格式,。

寫圖像文件

  將圖像寫入文件,可以使用imwrite()函數,該函數的聲明如下:

bool imwrite(const string &filename,InputArray image,const vector<int> &params=vector<int>())

 

  文件的格式由filename參數指定的文件擴展名確定。推薦使用PNG文件格式。BMP格式是無損格式,但是一般不進行壓縮,文件尺寸非常大;JPEG格式的文件嬌小,但是JPEG是有損壓縮,會丟失一些信息。PNG是無損壓縮格式,推薦使用。

imwrite()函數的第三個參數params可以指定文件格式的一些細節信息。這個參數里面的數值是跟文件格式相關的:

    *JPEG:表示圖像的質量,取值范圍從0到100。數值越大表示圖像質量越高,當然文件也越大。默認值是95。

    *PNG:表示壓縮級別,取值范圍是從0到9。數值越大表示文件越小,但是壓縮花費的時間也越長。默認值是3。

    *PPM,PGM或PBM:表示文件是以二進制還是純文本方式存儲,取值為0或1。如果取值為1,則表示以二進制方式存儲。默認值是1。

  並不是所有的Mat對象都可以存為圖像文件,目前支持的格式只有8U類型的單通道和3通道(顏色順序為BGR)矩陣;如果需要要保存16U格式圖像,只能使用PNG、JPEG 2000和TIFF格式。如果希望將其他格式的矩陣保存為圖像文件,可以先用Mat::convertTo()函數或者cvtColor()函數將矩陣轉為可以保存的格式。
  另外需要注意的是,在保存文件時,如果文件已經存在,imwrite()進行提醒,將直接覆蓋掉以前的文件。

下面例程展示了如何讀入一副圖像,然后對圖像進行Canny邊緣操作,最后將結果保存到圖像文件中。

#include<iostream>
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int main(int argc,char *argv[])
{
    //讀入圖像並將之轉化為單通道圖像
    Mat im = imread("C:\\Users\\xpower\\Desktop\\niao.jpg",0);
    //檢查是否讀取成功
    if(im.empty())
    {
        cout<<"讀取文件失敗"<<endl;
        return -1;
    }
    //進行Canny操作,並將結果儲存於result
    Mat result;
    Canny(im,result,50,150);
    //保存結果
    imwrite("C:\\Users\\xpower\\Desktop\\niao.jpg",result);
    imshow("niaoniao",result);
    waitKey(0);
}

 

讀寫視頻

  介紹OpenCV讀寫視頻之前,先介紹一下編解碼器(codec)。如果是圖像文件,我們可以根據文件擴展名得知圖像的格式。但是此經驗並不能推廣到視頻文件中。有些OpenCV用戶會碰到奇怪的問題,都是avi視頻文件,有的能用OpenCV打開,有的不能。
  視頻的格式主要由壓縮算法決定。壓縮算法稱之為編碼器(coder),解壓算法稱之為解碼器(decoder),編解碼算法可以統稱為編解碼器(codec)。視頻文件能讀或者寫,關鍵看是否有相應的編解碼器。編解碼器的種類非常多,常用的有MJPG、XVID、DIVX等,完整的列表請參考FOURCC網站3。因此視頻文件的擴展名(如avi等)往往只能表示這是一個視頻文件。

  OpenCV 2中提供了兩個類來實現視頻的讀寫。讀視頻的類是VideoCapture,寫視頻的類是VideoWriter。

讀視頻

  VideoCapture既可以從視頻文件讀取圖像,也可以從攝像頭讀取圖像。可以使用該類的構造函數打開視頻文件或者攝像頭。如果VideoCapture對象已經創建,也可以使用VideoCapture::open()打開,VideoCapture::open()函數會自動調用VideoCapture::release()函數,先釋放已經打開的視頻,然后再打開新視頻。

  如果要讀一幀,可以使用VideoCapture::read()VideoCapture類重載了操作符,實現了讀視頻幀的功能。下面的例程演示了使用VideoCapture類讀視頻。

 1 #include<iostream>
 2 #include"opencv2/opencv.hpp"
 3 using namespace std;
 4 using namespace cv;
 5 int main(int argc,char** argv)
 6 {
 7 
 8     //打開第一個攝像頭
 9     //VideoCapture cap(0);
10 
11     //打開視頻文件
12     VideoCapture cap("video.short.raw.avi");// 文件名就是這個。
13     if(!cap.isOpened())
14     {
15         cerr<<"Can not open a camera or file."<<endl;
16         return -1;
17     }
18     Mat edges;
19     namedWindow("jackchen",1);
20     int i=0;
21     for(;;)
22     {
23         cerr<<i<<endl;
24         i++;
25         Mat frame;
26         //從cap中讀一幀,存到frame。
27         cap>>frame;
28         //如果沒有讀到圖像
29         if(frame.empty())
30         {
31            // cerr<<"沒有讀取到圖像!"<<endl;
32             break;
33         }
34         //將讀取到的圖像轉化為灰度圖
35         cvtColor(frame,edges,CV_BGR2GRAY);
36         //進行邊緣提取操作
37         Canny(edges,edges,0,30,3);
38         //顯示結果
39         imshow("edges",edges);
40         if(waitKey(30)>=0)
41             break;
42 
43     }
44     return 0;
45 }

 

寫視頻

  使用OpenCV創建視頻也非常簡單,與毒食品不同的是,你需要在創建視頻時設置一系列參數,包括:文件名,編解碼器,幀率,寬度和高度等。編解碼器使用四個字符表示,可以是CV_FOURCC('M','J','P','G'), CV_FOURCC('X','V','I','D')以及CV_FOURCC('D','I','V','X');等。如果使用編碼器無法創建視頻文件,請嘗試用其他的編碼器。

  將圖像寫入視頻可以用VideoWrite::Write函數,VideoWrite類中也重載了<<運算符,使用起來非常方便。另外需要注意的是待寫入圖像的尺寸必須和寫入圖像的尺寸一致。

  下面的例程演示了如何寫視頻文件。本例程將生成一個視頻文件,視頻的第0幀上是一個紅色的“0”,第一幀上是一個紅色的“1”,以此類推共100幀。

#include<stdio.h>
#include<iostream>
#include"opencv2/opencv.hpp"

using namespace std;
using namespace cv;


int main(int argc,char **argv)
{
    //定義視頻的高度和寬度
    Size s(320,240);
    //創建Write,並指定Fourcc和FPS等參數
    VideoWriter writer = VideoWriter("MyVideo.avi",CV_FOURCC('M','J','P','G'),25,s);
    //檢查是否創建成功
    if(!writer.isOpened())
    {
        cerr<<"創建視頻文件失敗\n";
        return -1;
    }
    //視頻幀
    Mat frame(s,CV_8UC3);
    for(int i=0;i<100;i++)
    {
        //將圖像設置成黑色
        frame=Scalar::all(0);
        //將整數i轉化為字符串類型
        char text[128];
        snprintf(text,sizeof(text),"%d",i);
        //將數字繪到圖片上
        putText(frame,text,Point(s.width/3,s.height/3),FONT_HERSHEY_SCRIPT_SIMPLEX,3,Scalar(0,0,255),3,8);
        //將圖像寫入視頻
        writer<<frame;
    }
    //退出程序自動關閉視頻文件
    return 0;
}

 


免責聲明!

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



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