1. 程式人生 > >OpenCV入門教程(2)-Mat類之畫素的讀寫

OpenCV入門教程(2)-Mat類之畫素的讀寫

一、矩陣元素的基本表達
對於單通道影象,其元素型別一般為 8U(即 8 位無符號整數),當然也可以是 16S、32F等;這些型別可以直接用 uchar、short、float 等 C/C++語言中的基本資料型別表達。
如果多通道影象,如 RGB 彩色影象,需要用三個通道來表示。在這種情況下,如果依然將影象視作一個二維矩陣,那麼矩陣的元素不再是基本的資料型別。
OpenCV 中有模板類 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;    //用 color 變數描述一種 RGB 顏色
color[0]=255;   //B 分量
color[1]=0;     //G 分量
color[2]=0;     //R 分量

二、畫素值的讀寫
很多時候,我們需要讀取某個畫素值,或者設定某個畫素值;在更多的時候,我們需要對整個影象裡的所有畫素進行遍歷。OpenCV 提供了多種方法來實現影象的遍歷。
2.1、at()函式
函式 at()來實現讀取矩陣中的某個畫素,或者對某個畫素進行賦值操作。下面兩行程式碼演示了 at()函式的使用方法。

// 讀出第 i 行第 j 列畫素值
uchar value = grayim.at<uchar>(i,j);
//將第 i 行第 j 列畫素值設定為 128
grayim.at<uchar>(i,j)=128; 

如果要對影象進行遍歷,可以參考下面的例程。這個例程建立了兩個影象,分別是單通道的 grayim 以及 3 個通道的 colorim,然後對兩個影象的所有畫素值進行賦值,最後現實結果。

#include <QCoreApplication>
#include <iostream>
#include "opencv2/opencv.hpp"
using namespace std;
using namespace cv;
int main(void)
{
    Mat grayim(400, 600, CV_8UC1);
    Mat colorim(400, 600, 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; //Blue    // B
            pixel[1] = j%255; //Green   // G
            pixel[2] = 0;               // R
            //Red
            colorim.at<Vec3b>(i,j) = pixel;
        }
    //顯示結果
    imshow("grayim", grayim);
    imshow("colorim", colorim);
    waitKey(0);
    return 0;
}

需要注意的是,如果要遍歷影象,並不推薦使用 at()函式。使用這個函式的優點是程式碼的可讀性高,但是效率並不是很高。

顯示結果如下圖所示:
這裡寫圖片描述
這裡寫圖片描述
2.2 使用迭代器
如果你熟悉 C++的 STL 庫,那一定了解迭代器(iterator)的使用。迭代器可以方便地遍歷所有元素。Mat 也增加了迭代器的支援,以便於矩陣元素的遍歷。
下面的例程功能跟上一節的例程類似,但是由於使用了迭代器,而不是使用行數和列數來遍歷,所以這兒沒有了 i 和 j 變數,影象的畫素值設定為一個隨機數。

#include <QCoreApplication>
#include <iostream>
#include "opencv2/opencv.hpp"
using namespace std;
using namespace cv;
int main(void)
{
    Mat grayim(600, 800, CV_8UC1);
    Mat colorim(600, 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("grayim", grayim);
    imshow("colorim", colorim);
    waitKey(0);
    return 0;
}

例程的輸出結果如圖,所示
這裡寫圖片描述

這裡寫圖片描述

2.3 通過資料指標
使用 IplImage 結構的時候,我們會經常使用資料指標來直接操作畫素。通過指標操作來訪問畫素是非常高效的,但是你務必十分地小心。C/C++中的指標操作是不進行型別以及越界檢查的,如果指標訪問出錯,程式執行時有時候可能看上去一切正常,有時候卻突然彈出“段錯誤”(segment fault)。
當程式規模較大,且邏輯複雜時,查詢指標錯誤十分困難。對於不熟悉指標的程式設計者來說,指標就如同噩夢。如果你對指標使用沒有自信,則不建議直接通過指標操作來訪問畫素。雖然 at()函式和迭代器也不能保證對畫素訪問進行充分的檢查,但是總是比指標操作要可靠一些。
如果你非常注重程式的執行速度,那麼遍歷畫素時,建議使用指標。下面的例程演示如何使用指標來遍歷影象中的所有畫素。此例程實現的操作跟第 2.1節中的例程完全相同。例程程式碼如下:

#include <QCoreApplication>
#include <iostream>
#include "opencv2/opencv.hpp"
using namespace std;
using namespace cv;
int main(int argc, char* argv[])
{
    Mat grayim(300, 400, CV_8UC1);
    Mat colorim(300, 400, CV_8UC3);
    //遍歷所有畫素,並設定畫素值
    for( int i = 0; i < grayim.rows; ++i)
    {
        //獲取第 i 行首畫素指標
        uchar *p = grayim.ptr<uchar>(i);
        //對第 i 行的每個畫素(byte)操作
        for( int j = 0; j < grayim.cols; ++j )
            p[j] = (i+j)%255;
    }
    //遍歷所有畫素,並設定畫素值
    for( int i = 0; i < colorim.rows; ++i)
    {
        //獲取第 i 行首畫素指標
        Vec3b * p = colorim.ptr<Vec3b>(i);
        for( int j = 0; j < colorim.cols; ++j )
        {
            p[j][0] = i%255;    //Blue
            p[j][1] = j%255;    //Green
            p[j][2] = 0;        //Red
        }
    }
    //顯示結果
    imshow("grayim", grayim);
    imshow("colorim", colorim);
    waitKey(0);
    return 0;
}

執行結果遇上面的一致。