讀懂opencv第一天(圖像存儲、模板類、文件格式、頭文件、命名空間、數據類型,循環for,像素點操作,point,Mat與Mat_)

這篇文章先不去討論opencv的一堆模塊,二郎認爲,能點進來的同學們也只是看到opencv的程序有點懵,而不知道如何下手,至於想要編寫和如何調用都是後話。因此,沒必要先去記塊的名字和用法,應該先記住它的語言規則,如何排列以及函數之間的關係,這些都能幫助大家看懂程序,看懂纔有機會自己寫。

因爲二郎學opencv主要做圖像的,先談一下圖像的儲存
opencv和matlab的儲存方式不同,是按照字節儲存的,用三個字節存儲彩色圖像三通道,順序爲——BGR
儲存單位爲無符號8位整形(CV_8U),表示亮度(一般後面加上C3,表示三通道,C幾表示幾通道)

Mat M(4,5, CV_8UC3, Scalar(155,0,0));//一個3維矩陣,同時也是一個藍色的圖像(RGB),4行5列
cout << "M = " << endl << " " << M << endl;//輸出M矩陣
M=
[155,0,0,155,0,0,155,0,0,155,0,0,155,0,0;
155,0,0,155,0,0,155,0,0,155,0,0,155,0,0;
155,0,0,155,0,0,155,0,0,155,0,0,155,0,0;
155,0,0,155,0,0,155,0,0,155,0,0,155,0,0]

還有一點需要注意,在每個參數出現時,都必須有類型定義或者必須進行類型定義,所以大家會看到很多下面的情況

Mat canvasPart = canvas(Rect(w * 0, 0, w, h));    //Mat
for( int j = 0; j < image.cols; ++j )   //for

在參數賦值的時候,兩個變量的類型不一樣需要添加強制轉換

int* aa;                // 指向整型數的指針
float* bb;            // 指向浮點數的指針
aa = bb;            // 直接賦值會導致編譯錯誤
aa = (int*)bb;      // 強制類型轉換後進行賦值

很多情況下會看到,前面也定義類型,後面也定義類型,也屬於這種強制轉換的情況

double d1 = (double) ((i+j)%255);

以及指針

int myArray[3] = { 9, 10, 11 };   // 定義一個數組
int* myIndex = myArray;          // 將數組的起始地址賦值給指針pIndex
cout<<"指針指向的地址:"<<myIndex<<endl;       // 指針指向的地址
cout<<"指針所指向的數據的值:"<<*myIndex<<endl; // 指向的地址存儲的數值

myIndex++;   // 對指針進行加運算,使其指向數組中的下一個值
cout<<"指針指向的地址:"<<myIndex<<endl;        // 指針指向的地址
cout<<"指針所指向的數據的值:"<<*myIndex<<endl;  // 指向的地址存儲的數值

輸出結果

指針指向的地址:0012FA44
指針所指向的數據的值:9
指針指向的地址:0012FA48    //地址加了4,因爲數據類型是 int-4字節,double-16字節,char-1字節。
指針所指向的數據的值:10

這裏比較繞的是 myindex = 地址, *myindex = 地址位置存儲的數值。

而比較好記的是,我們定義的指針是myindex,它裏面的內容是地址,同時我們完全可以認爲myindex是另一個變量,
內部存儲數值,而只需要注意myindex和
myindex互相影響,對myindex操作,*myindex值會變。

這裏有時我們會看到

int a[3][4];
int (*p)[4]; //該語句是定義一個數組指針,指向含4個元素的一維數組。
p=a;        //將該二維數組的首地址賦給p,也就是a[0]或&a[0][0]
p++;       //該語句執行過後,也就是p=p+1;p跨過行a[0][]指向了行a[1][],所以數組指針也稱指向一維數組的指針,亦稱行指針。

不太對,剛纔上面還說了,賦值給P的應該是地址,*P應該是數值——這裏把一個數組賦給P,其實就相當於將數組的首地址賦給了P。

模板類

這個東西比較好玩,在很多情況下我們會見到不是我們之前的int c……之類的定意形式了,而是Mat c(當然其包含的內容也不一樣了)
point模板類

Point point;//創建一個2D點對象
point.x = 3;//初始化x座標值
point.y = 2;//初始化y座標值

以及 Mat模板類

Mat R = Mat(3, 2, CV_8UC3);

這個和我在python博文中提到的class類似
Mat R————R屬於Mat模板類,因此它含有該類中的統一屬性
例如:
R.rows代表了R矩陣的行數;R.cols代表R矩陣的列數。

文件格式

cpp、obj、lib、exe的關係
lib 靜態數據連接庫,將obj轉成庫文件,用戶無法直接看到foo文件,實現了代碼的加密。
在這裏插入圖片描述

1.頭文件

一堆的包含,其實只要不發生衝突,你把所有的頭都引進來也行,一般不要這麼做,這樣程序在函數搜索時會變慢。

#include <iostream>  
#include <opencv2/opencv_modules.hpp>
#include <opencv2/opencv.hpp>  

需要哪個包含進去哪個,這個在很多程序中會出現問題
……文件不存在,……丟失。
頭包含處出現問題:檢查包含格式是否正確,去文檔中找一下有無類似文件。
文中函數不存在:這個函數所在的模塊沒有在頭包含時加進來,這時需要在網上找該模塊來自哪個模塊,將模塊進行頭包含。
頭文件的用途知道這些就可以了。

2.命名空間

在opencv中你時常會看到**::**,這個是在強調命名空間,強調後面的函數在前面的函數的命名空間中。在opencv編程中命名空間有兩種形式
①在程序開始聲明
該方式避免了後面使用函數時需要考慮函數時c++函數還是opencv的函數。
一般這種方法在程序中函數不會混淆時使用

#include <opencv2/opencv.hpp>  

using namespace std;
using namespace cv;

②在函數前聲明

System::Math::Sqrt() 相當於System.Math.Sqrt()
cv::

3.數據類型

給原始的數據提供描述,描述符號格式爲:CV_{U|S|F}C(<number_of_channels>)
bit_depth—比特數:8bite,16bites,32bites,64bites

–S--代表—signed int—有符號整形
–U--代表–unsigned int–無符號整形
–F--代表–float---------單精度浮點型

C<number_of_channels>----代表—一張圖片的通道數,
–灰度圖片–grayImg----單通道圖像
–RGB彩色圖像----------3通道圖像
–帶Alph通道的RGB圖像–4通道圖像

比特數爲32bites的3元素浮點元→CV_32FC3

4.opencv2提供matlab類型矩陣創建

矩陣創建

Mat Z = Mat::zeros(4,3, CV_8UC1);
Mat O = Mat::ones(4, 3, CV_8UC1);
Mat E = Mat::eye(4, 3, CV_8UC1);

向量定義

向量定義的類型說明
typedef Vec<int, 2> Vec2i;
typedef Vec<int, 3> Vec3i;
    
typedef Vec<float, 4> Vec4f;
typedef Vec<float, 6> Vec6f;

向量創建

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

5.循環 for

相信大家對for一定使用相當多,尤其是在矩陣運算時……在matlab中,for其實可以直接改寫爲矩陣運算,速度可以提高十幾倍。

for( int i = 0; i < myimage.rows; ++i) //可以看出for循環和c++類似,
	 for( int j = 0; j < image.cols; ++j )
		 grayim.at<uchar>(i,j) = (i+j)%255;

for …………

6.對圖像像素點進行操作

操作就少不了我們需要知道我們的圖像一下基本信息,我們在matlab中用size確定圖像的行和列
opencv用更簡單的:myimage.rows和myimage.cols來反映行列數

①at

myImage.at<uchar>(j, i) //表示的是  j 行 i 列 的這個像素

myImage.at<uchar>(Point(j, i)) //表示的是 座標(j,i)的像素

uchar value = myImage.at<uchar>(i,j);//讀出第 i 行第 j 列像素值

這裏涉及一個矩陣標和座標的關係(好像沒有矩陣標這一概念,二郎自己想的,方便記憶)
矩陣標:按照人類習慣,在討論矩陣時總是先行後列,因此該標誌也是這個規律
座標:先x後y

在這裏其實不會混淆,因爲不是同一概念,一個是矩陣,一個是座標。大家只需要記住,自己在編程時,是在對矩陣進行操作還是對座標進行操作。

這裏有人會問了(i,j)能表示灰度圖,不能表示彩色圖
把每個像素點看成一個整體(僅有x,y),它的性質(GBR)看成另一個整體。

Vec3b pixel;
pixel[0] = i%255; //Blue
pixel[1] = j%255; //Green
pixel[2] = 0; //Red
colorim.at<Vec3b>(i,j) = pixel;//直接將一個三通道的數賦給像素點

②矩陣的一行或者一列, row()或 col();多行多列,Range()
row,col

A.row(j) = A.row(i)*5+3;

Range(需要強調一下,圖像的第一行和第一列的標號都爲0,而Range參數標號都爲1)

//創建一個單位陣
Mat A = Mat::ones(20, 10, CV_32U);
//提取第 1 到 3 列
Mat B = A(Range::all(), Range(1, 3));
//提取第 10 至 15 行
Mat C = A(Range(10, 15), Range::all());

③指針
指針定義了圖像每一行的首地址,而列以及三維數組均可以由數組表示

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


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)
	{
		//獲取第 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;
}

7.point點的模板類

2維點模板類Point_和3維點模板類Point3_

  typedef Point2i Point;
  typedef Point_<float> Point2f;
  typedef Point_<double> Point2d;
  typedef Point3_<int> Point3i;

實例

  Point point;//創建二維點對象
  point.x = 3;//初始化x座標值
  point.y = 4;//初始化y座標值
  等價於
  Point point = Point(3, 4);

8.Mat和Mat_

我們會發現,在opencv中出現了很多帶有下劃線的數據類型,這裏先寫一點,其他的以後補充
我們在用指針時
uchar * p = M.ptr(i);

提取地址時每次都需要指定數據類型,比較繁瑣
有了**Mat_**以後就不一樣了

Mat_<uchar> M1 = (Mat_<uchar>&)M;//變量M1聲明的同時確定了M1的指針類型
for( int i = 0; i < M1.rows; ++i)
{
uchar * p = M1.ptr(i);//上面已經聲明,下面便不再需要聲明。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章