OpenCV C++(二)----圖像數(shù)字化

一仆嗦、Mat類

MatMatrix的縮寫枝恋,代表矩陣或者數(shù)組的意思。該 類的聲明在頭文件opencv2\core\core.hpp中携丁, 所以使用Mat類時要引入該頭文件琢歇。

1.1、Mat類的構(gòu)造函數(shù)

構(gòu)造Mat對象相當(dāng)于構(gòu)造了一個矩陣(數(shù)組)梦鉴,需要四個基本要素:行數(shù)(高)李茫、列數(shù)(寬)、通道數(shù)及其數(shù)據(jù)類型肥橙。

    Mat(int rows, int cols, int type);

其中魄宏,

  • rows代表矩陣的行數(shù)
  • cols代表矩陣的列數(shù)
  • type代表類型, 包括通道數(shù)及其 數(shù)據(jù)類型存筏, 可以設(shè)置為CV_8UC(n) 宠互、 CV_8SC(n)CV_16SC(n) 椭坚、 CV_16UC(n) 予跌、 CV_32SC(n)CV_32FC(n)藕溅、 CV_64FC(n)匕得。
    其中8U8S巾表、 16S汁掠、 16U32S集币、 32F考阱、 64F前面的數(shù)字代表Mat中每一個數(shù)值所占的bit數(shù), 而 1byte=8bit鞠苟, 所以乞榨, 32F就是占4字節(jié)的float類型秽之, 64F是占8字節(jié)的doule類型, 32S是占4字 節(jié)的int類型吃既,8U是占1字節(jié)的uchar類型考榨, 其他的類似;
    C(n)代表通道數(shù)鹦倚,當(dāng)n=1時河质, 即構(gòu)造單通道矩陣或稱二維矩陣, 當(dāng)n>1時震叙, 即構(gòu)造多通道矩陣即三維矩陣掀鹅, 直觀上就是n個二維矩陣組成的三維矩陣。
    Mat(Size size, int type);

其中媒楼,需要注意的是乐尊, Size的第一個元素是矩陣的列數(shù)(寬),第二個元素是矩陣的行數(shù)(高)划址,即先存 寬扔嵌, 再存高。即Size(int cols,int rows)

Mat m;
m.create(2,3,CV_32FC1);
m.create(Size(3,2),CV_32FC1);

1.2猴鲫、初始化Mat類

Mat o=Mat::ones(2,3,CV_32FC1);
Mat m=Mat::zeros(Size(3,2),CV_32FC1);

Mat m=(Mat_<int>(2,3)<<1,2,3,4,5,6);

1.3对人、獲取單通道Mat的基本信息

1. 使用成員變量rows和 cols獲取矩陣的行數(shù)和列數(shù)
   //構(gòu)造矩陣
   Mat m=(Mat_<int>(3,2)<<1,2,3,4,5,6);
   //矩陣的行數(shù)
   cout<<"行數(shù):"<<m.rows<<endl;
   //矩陣的列數(shù)
   cout<<"列數(shù):"<<m.cols<<endl;
2. 使用成員函數(shù)size() 獲取矩陣的尺寸
   Size size=m.size();
   cout<<"尺寸:"<<size<<endl;
3. 使用成員函數(shù) channels() 得到矩陣的通道數(shù)
   cout<<"通道數(shù):"<<m.channels()<<endl;
4. 使用成員函數(shù) total()得到矩陣的行數(shù)乘以列數(shù), 即面積拂共。 注意和通道數(shù)無關(guān)牺弄, 返回的不是矩陣中數(shù)據(jù)的個數(shù)
   cout<<"面積:"<<m.total()<<endl;
5. 使用成員變量 dims 得到矩陣的維數(shù)。 顯然對于單通道矩陣來說就是一個二維矩陣宜狐, 對于多通道矩 陣來說就是一個三維矩陣势告。
   cout<<"維數(shù):"<<m.dims<<endl; 

1.4、訪問單通道Mat對象中的值

1. 利用成員函數(shù)at
   //構(gòu)造單通道矩陣
   Mat m=(Mat_<int>(3,2)<<11,22,33,44,55,66);
   //通過for循環(huán)打印M中的每一個值
   for(int r=0;r<m.rows;r++)
   {
    for(int c=0;c<m.cols;c++)
    {
           cout<<m.at<int>(r,c)<<",";//第r行第c列的值
           cout<<m.at<int>(Point(c,r))<<",";//等價于上面一行代碼
    }
    cout<<endl'
   }
2. 利用成員函數(shù)ptr

對于Mat中的數(shù)值在內(nèi)存中的存儲抚恒, 每一行的值是存儲在連續(xù)的內(nèi)存區(qū)域中的咱台, 通過成員函數(shù)ptr獲得指向每一行首地址的指針。 仍以“利用成員函數(shù)at”部分的m存儲為例俭驮, m中所有的值在內(nèi)存中的存儲方式如圖2-1所示回溺, 其中如果行與行之間的存儲是有內(nèi)存間隔的, 那么間隔也是相等的混萝。

   for(int=0;r<m.rows;r++)
   {
       //得到矩陣m的第r行行首的地址
       const int *ptr=m.ptr<int>(r);
       //打印第r行的所有值
       for(int c=0;c<m.cols;c++)
       {
           cout<<ptr[c]<<",";
       }
       cout<<endl;
   }
1. 利用成員函數(shù) isContinuous和ptr

每一行的所有值存儲在連續(xù)的內(nèi)存區(qū)域中遗遵, 行與行之間可能會有間隔, 如果isContinuous返回值為true逸嘀, 則代表行與行之間也是連續(xù)存儲的车要, 即所有的值都是連續(xù)存儲的。

   if(m.isContinuous())
   {
       //得到矩陣m的第一個值的地址
       int *ptr=m.ptr<int>(0);
       for(int n=0;c<m.rows*m.cols;n++)
       {
           cout<<ptr[n]<<",";
       }
   }
2. 利用成員變量step和data

對于單通道矩陣來說崭倘,step[0]代表每一行所占的字節(jié)數(shù)翼岁,而如果有間隔的話类垫, 這個間隔也作為字節(jié)數(shù)的一部分被計算在內(nèi);step[1]代表每一個數(shù)值所占的字節(jié)數(shù)琅坡,data是指向第一個數(shù)值 的指針悉患, 類型為uchar。 所以脑蠕, 無論哪一種情況购撼, 如訪問一個int類型的單通到矩陣的第r行 第c列的值跪削, 都可以通過以下代碼來實現(xiàn)谴仙。

     *((int*)(m.data+m.step[0]*r+c*m.step[1])) 

1.5、向量類Vec

默認是列向量

   //構(gòu)造一個長度為3碾盐,數(shù)據(jù)類型為int并且初始化為11晃跺、22、33的列向量
   Vec<int,3> vi(11,22,33);
   cout<<"向量的行數(shù)"<<vi.rows<<endl;
   cout<<"向量的列數(shù)<<vi.cols<<endl;
   cout<<"訪問滴0個元素:"<<vi[0]<<endl;

OpenCV為向量類的聲明取了一個別名毫玖,在matx.hpp 401行開始 例如:

   typedef Vec<uchar, 2> Vec2b;
   typedef Vec<uchar, 3> Vec3b;
   typedef Vec<uchar, 4> Vec4b;
   
   typedef Vec<int, 2> Vec2i;
   typedef Vec<int, 3> Vec3i;
   ...

單通道矩陣的每一個元素都是一個數(shù)值掀虎, 多通道矩陣的每一個元素都可以看作一個向量。

1.6付枫、構(gòu)造多通道的Mat對象

   Mat mm=(Mat_<Vec3f>(2,2)<<Vec3f(1,1,1),Vec3f(2,2,2),Vec3f(3,3,3),Vec3f(4,4,4));
   //打印第0行第0列的元素值
   int r=0;
   int c=0;
   cout<<mm.at<Vec3f>(r,c)<<endl烹玉;

其余同單通道方法,只是類型變成了向量Vec阐滩。
(1)分離通道

   vector<Mat> planes;
   split(mm,planes);

(2)合并通道

   //三個單通道矩陣
   Mat plane0=(Mat_<int>(2,2)(1,2,3,4);
   Mat plane1=(Mat_<int>(2,2)(11,12,13,14);
   Mat plane2=(Mat_<int>(2,2)(21,22,23,24);
   //用三個單通道矩陣初始化一個數(shù)組
   Mat plane[]={plane0,plane1,plane2};
   Mat mat;
   merge(plane,3,mat);
   //將三個單通道矩陣一次放入vector容器中
   vector<Mat> plane;
   plane.push_back(plane0);
   plane.push_back(plane1);
   plane.push_back(plane2);
   Mat mat;
   merge(plane,mat);

1.7二打、獲得Mat中某一區(qū)域的值

1. 使用成員函數(shù)row(i) 或 col(j) 得到矩陣的第i行或者第 j列
2. 使用成員函數(shù)rowRange或 colRange得到矩陣的連續(xù)行或者連續(xù)列
          Range(int _start,int _end);

這是一個左閉右開的序列[_start, _end)掂榔,比如Range(2继效, 5) 其實產(chǎn)生的是2、 3装获、 4 的序列瑞信,不包括5, 常用作rowRangecolRange的輸入?yún)?shù)穴豫,從而訪問矩陣中的連續(xù)行或者連續(xù)列

          Mat r_range=mm.rowRange(Range(2,4));
          //Mat r_range=mm.rowRange(2,4);等價于上面
          for(int r=0;r<r_range.rows;r++)
          {
              for(int c=0;c<r_range.cols;c++)
              {
                  cout<<r_range.at<int>(r,c)<<",";
              }
              cout<<endl;
          }

需要特別注意的是凡简, 成員函數(shù)rowcol精肃、 rowRange秤涩、 colRange返回的矩陣其實是指向原矩陣的;有時候, 我們只訪問原矩陣的某些行或列肋杖, 但是不改變原矩陣的值溉仑,需要使用復(fù)制的方法

3. 使用成員函數(shù) clone和copy To
      Mat r_range=mm.rowRange(2,4).clone();
      Mat c_range=mm.colRange(1,3).copyTo(c_range);
4. 使用 Rect類
  Rect的構(gòu)造函數(shù)
      Rect_(_Tp _x, _Tp _y, _Tp _width, _Tp _height);
      Rect_(const Rect_& r);
      Rect_(const Point_<_Tp>& org, const Size_<_Tp>& sz);
      Rect_(const Point_<_Tp>& pt1, const Point_<_Tp>& pt2);
      Mat ROI1=mm(Rect(2,1,2,2));
      Mat roi2=mm(Rect(Point(2,1),Size(2,2)));
      Mat roi3=mm(Rect(Point(2,1),Point(3,2)));

但是與使用colRangerowRange類似, 這樣得到的矩形區(qū)域是指向原矩陣的状植, 要改變roi中的值浊竟, matrix也會發(fā)生變化怨喘, 如果不想這樣, 則仍然可以使用clone或者copyTo振定。

二必怜、矩陣的運算

2.1、加法運算

矩陣的加法就是兩個矩陣對應(yīng)位置的數(shù)值相加

Mat src1=(Mat_<uchar>(2,2)<<11,22,33,60);
Mat src2=(Mat_<uchar>(2,2)<<191,192,193,204);
Mat dst=src1+src2;

注意:

  • 60+204=264后频,但是梳庆,實際打印出來的值是255,因為兩個矩陣的數(shù)據(jù)類 型都是uchar卑惜, 所以用“+”運算符計算出來的和也是uchar類型的膏执, 但是uchar類型范圍的最大值是255, 所以只好將264截斷為255露久。
  • 兩個Mat的數(shù)據(jù)類型必須是一 樣的更米,否則會報錯,也就是用“+”求和是比較嚴格的 毫痕。
  • 一個數(shù)值與一個Mat對象相 加征峦, 也可以使用“+”運算符, 但是無論這個數(shù)值是什么數(shù)據(jù)類型消请, 返回的Mat的數(shù)據(jù)類型 都與輸入的Mat相同 栏笆。

為了彌補“+”運算符的這兩個缺點, 我們可以使用OpenCV提供的另一 個函數(shù)

void add(InputArray src1, InputArray src2, OutputArray dst,InputArray mask = noArray(), int dtype = -1);

使用add函數(shù)時臊泰, 輸入矩陣的數(shù)據(jù)類型可以不同蛉加, 而輸出矩陣的數(shù)據(jù)類型 可以根據(jù)情況自行指定。 需要特別注意的是因宇, 如果給dtype賦值為-1七婴, 則表示dst的數(shù)據(jù)類型和src1src2是相同的察滑, 也就是只有當(dāng)src1src2的數(shù)據(jù)類型相同時打厘,才有可能令 dty pe=-1,否則仍然會報錯贺辰。

Mat dst;
add(src1,src2,dst,Mat(),CV_64FC1);

2.2户盯、減法運算

矩陣的減法與加法類似

Mat dst=src1-src2;

注意:

  • 輸出值不會最小為0,這是 因為src1src2均是uchar類型的饲化, 所以返回的dst也是uchar類型的莽鸭;而uchar類型的最小范圍是0, 所以會將小于0的數(shù)值截斷為0吃靠。
  • Mat對象與一個數(shù)值相減硫眨, 也可以使用“-”運算符。

當(dāng)然巢块, 也存在與“+”運算符 一樣的不足礁阁, OpenCV提供的函數(shù):

void subtract(InputArray src1, InputArray src2, OutputArray dst,
                           InputArray mask = noArray(), int dtype = -1);

可以實現(xiàn)不同的數(shù)據(jù)類型的Mat之間做減法運算巧号, 其與add函數(shù)類似。

2.3姥闭、點乘運算

矩陣的點乘即兩個矩陣對應(yīng)位置的數(shù)值相乘丹鸿。

Mat dst=src1.mul(src2);

注意

從打印結(jié)果就可以看出,也是對大于255的數(shù)值做了截斷處理棚品。 所以為了不損失精度靠欢,可以將兩個矩陣設(shè)置為intfloat等數(shù)值范圍更大的數(shù)據(jù)類型铜跑。

對于Mat的點乘门怪, 也可以利用OpenCV提供的函數(shù):

void multiply(InputArray src1, InputArray src2,
                           OutputArray dst, double scale = 1, int dtype = -1);

這里的dst=sclae*src1*src2, 即在點乘結(jié)果的基礎(chǔ)上還可以再乘以系數(shù)scale疼进。

2.4薪缆、點除運算

點除運算與點乘運算類似, 是兩個矩陣對應(yīng)位置的數(shù)值相除伞广。

Mat dst=src2/src1;

注意

  • 除數(shù)為0沒有意義,但是OpenCV在 處理這種分母為0的除法運算時疼电,默認得到的值為0嚼锄。
  • 用一個數(shù)值與Mat對象相除也可以使用“/”運算符, 且返回的Mat的數(shù)據(jù)類型與輸入的Mat的數(shù)據(jù)類型相同蔽豺, 與輸入數(shù)值 的數(shù)據(jù)類型是沒有關(guān)系的区丑。

對于Mat的點除, 也可以利用OpenCV提供的函數(shù):

divide(InputArray src1, InputArray src2, OutputArray dst,
                         double scale = 1, int dtype = -1);

2.5修陡、乘法運算

相當(dāng)于卷積

Mat dst=src1*src2;

注意:

  • 對于Mat對象的乘法沧侥, 需要注意兩個Mat只能同時是float或者double類型, 對于其他數(shù)據(jù)類型的矩陣做乘法會報錯魄鸦。
  • 兩個雙通道矩陣也可以相乘宴杀,這里是把Mat對象當(dāng)做了復(fù)數(shù)矩陣,其中第一個通道存放的是所有值的實部拾因,第二個通道存放的是對應(yīng)的每一個虛部旺罢,也就是將Vec2f看作一個復(fù)數(shù), 比如Vec2f(1绢记, 2) 可以看作1+2i扁达。

對于Mat的乘法, 還可以使用OpenCV提供的gemm函數(shù)來實現(xiàn)蠢熄。

void gemm(InputArray src1, InputArray src2, double alpha,
                       InputArray src3, double beta, OutputArray dst, int flags = 0);

注意:gemm也只能接受CV_32FC1跪解、 CV_64FC1CV_32FC2签孔、 CV_64FC2數(shù)據(jù)類型的Mat

該函數(shù)通過flags控制src1叉讥、 src2砾跃、 src3是否轉(zhuǎn)置來實現(xiàn)矩陣之間不同的運算, 當(dāng)將flags設(shè)置為不同的參數(shù)時节吮, 輸出矩陣為:
當(dāng)然抽高,flags可以組合使用, 比如需要src2src3都進行轉(zhuǎn)置透绩, 則令flags=GEMM_2_T+GEMM_3_T翘骂。

2.6、其他運算

開平方運算

void sqrt(InputArray src, OutputArray dst);

注意:sqrt的輸入矩陣的數(shù)據(jù)類型只能是 CV_32F或者CV_64F

冪指數(shù)運算

void pow(InputArray src, double power, OutputArray dst);

三帚豪、灰度圖像數(shù)字化

Mat imread( const String& filename, int flags = IMREAD_COLOR );   
void imshow(const String& winname, InputArray mat);

四碳竟、彩色圖像數(shù)字化

灰度圖像的每一個像素都是由一個數(shù)字量化的, 而彩色圖像的每一個像素都是由三個數(shù)字組成的向量量化的狸臣。 最常用的是由R莹桅、 GB三個分量來量化的烛亦, RGB模型使用加 性色彩混合以獲知需要發(fā)出什么樣的光來產(chǎn)生給定的色彩诈泼, 源于使用陰極射線管(CRT) 的彩色電視, 具體色彩的值用三個元素的向量來表示煤禽, 這三個元素的數(shù)值分別代表三種基色: Red铐达、Green、Blue的亮度檬果。 假設(shè)每種基色的數(shù)值量化成m=2^n個數(shù)瓮孙, 如同8位灰度 圖像一樣, 將灰度量化成28=256個數(shù)选脊。 RGB圖像的紅杭抠、綠、藍三個通道的圖像都是一張8 位圖恳啥, 因此顏色的總數(shù)為2563 =16777216偏灿, 如(0, 0角寸, 0) 代表黑色菩混,(255, 255扁藕, 255) 代表白色沮峡, (255, 0亿柑, 0) 代表紅色邢疙。

對于彩色圖像的每一個方格, 我們可以理解為一個Vec3b。 需要注意的是疟游, 每一個像素的向量不是按照R呼畸、 GB分量排列的颁虐, 而是按照B蛮原、 GR順序排列的另绩, 所以通過split函數(shù)分離通道后儒陨, 先后得到的是BG笋籽、 R通道蹦漠。

Mat img=imread("apple.jpg",CV_LOAD_IMAGE_GRAYSCALE);
if(img.empty())
{
    return -1;
}
imshow("BGR",img);
vector<Mat> planes;
split(img,planes);
imshow("B",planes[0]);
imshow("G",planes[1]);
imshow("R",planes[2]);
waitKey(0);

在OpenCV中實現(xiàn)將彩色像素(一個向量) 轉(zhuǎn)化為灰度像素(一個數(shù)值) 的公式如 下:

image.png

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市车海,隨后出現(xiàn)的幾起案子笛园,更是在濱河造成了極大的恐慌,老刑警劉巖侍芝,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件研铆,死亡現(xiàn)場離奇詭異,居然都是意外死亡竭贩,警方通過查閱死者的電腦和手機蚜印,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來留量,“玉大人,你說我怎么就攤上這事哟冬÷ハǎ” “怎么了?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵浩峡,是天一觀的道長可岂。 經(jīng)常有香客問我,道長翰灾,這世上最難降的妖魔是什么缕粹? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮纸淮,結(jié)果婚禮上平斩,老公的妹妹穿的比我還像新娘。我一直安慰自己咽块,他們只是感情好绘面,可當(dāng)我...
    茶點故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般揭璃。 火紅的嫁衣襯著肌膚如雪晚凿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天瘦馍,我揣著相機與錄音歼秽,去河邊找鬼。 笑死情组,一個胖子當(dāng)著我的面吹牛燥筷,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播呻惕,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼荆责,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了亚脆?” 一聲冷哼從身側(cè)響起做院,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎濒持,沒想到半個月后键耕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡柑营,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年屈雄,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片官套。...
    茶點故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡酒奶,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出奶赔,到底是詐尸還是另有隱情惋嚎,我是刑警寧澤,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布站刑,位于F島的核電站另伍,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏绞旅。R本人自食惡果不足惜摆尝,卻給世界環(huán)境...
    茶點故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望因悲。 院中可真熱鬧堕汞,春花似錦、人聲如沸囤捻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至视哑,卻和暖如春绣否,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背挡毅。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工蒜撮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人跪呈。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓段磨,卻偏偏與公主長得像,于是被迫代替她去往敵國和親耗绿。 傳聞我的和親對象是個殘疾皇子苹支,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,612評論 2 350

推薦閱讀更多精彩內(nèi)容