opencv訪問mat
① 2019-10-01 opencv圖像數據結構之Mat
Mat的本質是具有兩個數據部分的類:矩陣頭(包含諸如矩陣大小,用於存儲的方法,用於存儲矩陣的地址等信息)以及指向真正圖像數據矩陣的指針。矩陣頭的大小是恆定的,但真正的圖像矩陣的大小是因實際圖像的大小而異。
opencv是圖像處理庫,包含有大量的圖像處理功能。這些功能在程序中表現為一個一個函數的形式,那麼為了處理圖像,一定少不了在函數間傳遞圖像。如果每傳遞一次圖像就為其新分配一次內存空間,必然會導致圖像處理速度的大大下降,同時也會佔用過高的內存空間團悶。為了解決這個問題,opencv是這樣做的:每個Mat對象都有其唯一的矩陣頭,但是實質性的圖像矩陣地址可以共享,訪問方式可以通過指針。在這種機制下,在程序中如果需要傳遞圖像時,僅傳遞圖像的搏或中矩陣頭信息和指針,而不會去傳遞實質性的圖像數據。
例子1: Mat A, C;// creates just the header parts
A = imread (argv[1], IMREAD_COLOR );// here we'll know the method used (allocate matrix)
Mat B(A);// Use the constructor
C = A;// Assignment operator
例子1中,3個Mat對象 A,B,C所指向的實質性的圖像數據矩陣是同一個,但是它們的矩陣頭信息(身份證號)是不同的。由於實質性的圖像數據矩陣是同一個,所以改變A,B,C中的任何一個,另外兩個也會改變。這種改變表現在它們的矩陣頭信息的變化上。例如,通過對A操作把圖像大小減小了一半,那麼B,C的矩陣頭信息中表徵圖像大小的信息部分一定也會變。在這種機制基礎之上會有一個有意思的事情發生,你完全可以創建一個只引用實質圖像矩陣的子部分信息的矩陣頭。這種應用有一個很好的例子,它是:ROI,只需創建具有新邊界的新標題即可,如下面例子2,其中 Mat 對象D,E只引用了A所引用的圖像的一部分。
例子2:Mat D (A, Rect (10, 10, 100, 100) );// using a rectangle
Mat E = A(Range::all(), Range(1,3));// using row and column boundaries
圖像在內存中的整個生命周期中會有一個計數變數與其相關,這個變數有什麼用呢?它用來表徵,有多少個Mat 對象,引用這個圖像,當這個計數變數值為0時,系統會釋放掉圖像 所佔內存。當然有時也許要真正完全傳遞整個圖像而不是指針,因此opencv提供了 cv :: Mat :: clone() 和 cv :: Mat :: To() 函數來解決這個問題,如下例子3。
例子3:Mat F = A.clone();
Mat G;
A.To(G);
例子3中,A,F,G指針所指的實質性圖像矩陣不是同一個,因此通過A,F,G對圖像操作,相互之間不會有影響 。
存放方法的描述是關於如何存基山儲像素值的(說明存放像素有多種方法可以選擇)。這種選擇,體現在色彩空間和數據類型的選擇上。色彩空間不同,也就是對顏色編碼的方式不同。最簡單的一種是灰度空間,在灰度空間下我們可以使用白色和黑色的不同組合來創建許多灰色陰影。
對應顏色,我們有多種編碼方式選擇,比較主流的是RGB。RGB主流的原因和我們人眼編碼顏色的方式有關系。它的基色是
紅色,綠色和藍色。為了編碼顏色的透明度,有時會添加第4個量:alpha(A)。
但是還有很多其他色彩空間系統,並且各有優點:
1、 RGB是最常見的一種,因為我們的眼睛使用的,就是類似的系統。但是opencv標准顯示系統使用BGR色彩空間(紅、藍通道切換)來編碼顏色。
2、 HSV和HLS將顏色分解為它們的色相,飽和度和值/亮度分量,這是我們描述顏色更自然的方法。例如,您可以忽略最後一個變數,從而使演算法對輸入圖像的光照條件不太敏感。
3、 流行的JPEG圖像格式使用的YCrCb。
4、CIE L*a*b*是一個感知上統一的色彩空間,如果您需要測量給定顏色與另一種顏色空間的差距,它會派上用場。
色彩空間解決的是一信號有多少通道的問題(多少變數),那麼數據類型解決的是,一個變數幾位數據位(是否有符號,是整數還是實數)度量它的問題。
② opencv怎麼給mat賦值
在OpenCV中有三種方式訪問矩陣中的數據元素:容易的方式,困難的方式,以及正確的方式。今天主要講容易方式:
最容易的方式是使用宏CV_MAT_ELEM( matrix, elemtype, row, col ),輸入參數是矩陣,不是指針,網上有很多人說是指針,矩陣元素類型,行,列,返回值是相應行,列的矩陣元素。CV_MAT_ELEM可以給矩陣賦值,也可以訪問矩陣元素。
CV_MAT_ELEM宏實際上會調用CV_MAT_ELEM_PTR(matrix,row,col)宏來完成任務。 CV_MAT_ELEM_PTR()宏的參數是矩陣,行,列。CV_MAT_ELEM()宏和CV_MAT_ELEM_PTR()宏的區別是,在調用CV_MAT_ELEM時,指向矩陣元素的指針的數據類型已經依據輸入參數中的元素類型而做了強制轉換:
如下程序:
CvMat* mat = cvCreateMat(3,3,CV_32FC1);//創建矩陣
cvZero(mat);//將矩陣置0
//為矩陣元素賦值
CV_MAT_ELEM( *mat, float, 0, 0 ) = 1.f; CV_MAT_ELEM( *mat, float, 0, 1 ) = 2.f;
CV_MAT_ELEM( *mat, float, 0, 2 ) = 3.f;
CV_MAT_ELEM( *mat, float, 1, 0 ) = 4.f;
CV_MAT_ELEM( *mat, float, 1, 1 ) = 5.f;
CV_MAT_ELEM( *mat, float, 1, 2 ) = 6.f;
CV_MAT_ELEM( *mat, float, 2, 0 ) = 7.f;
CV_MAT_ELEM( *mat, float, 2, 1 ) = 8.f;
CV_MAT_ELEM( *mat, float, 2, 2 ) = 9.f;
//獲得矩陣元素的值
float element = CV_MAT_ELEM(*mat,float,2,2);
float element_1_1 = 7.7f;
*((float*)CV_MAT_ELEM_PTR(m, 1, 1)) = element_1_1;
float element = CV_MAT_ELEM(m,float, 1,1 );
cout<<element<<endl;
以上使用矩陣中元素的方式很方便,但不幸的是,該宏在每次調用時,都會重新計算指針的位置。這意味著,先查找矩陣數據區中第0個元素的位置,然後,根據參數中的行和列,計算所需要的元素的地址偏移量,然後將地址偏移量與第0個元素的地址相加,獲得所需要的元素的地址。
所以,以上的方式雖然很容易使用,但是卻不是獲得矩陣元素的最好方式。特別是當你要順序遍歷整個矩陣中所有元素時,這種每次對地址的重復計算就更加顯得不合理。
③ Opencv中數據結構Mat的相關屬性
搬運自本人 CSDN 博客: 《Opencv中數據結構Mat的相關屬性》
以上摘自OpenCV 2.4.9的官方文檔opencv2refman.pdf。
以前雖然能夠比較熟練的使用OpenCV,但是最近感覺其實筆者自己對OpenCV的最底層數據結構Mat與IplImage都不怎麼熟悉…… 由於筆者比較反感總是需要管理內存的IplImage,所以對Mat數據結構做一下學習工作還是有必要的。
官方說明文檔opencv2refman.pdf中,寫出了Mat的定義如下:
下面筆者將從幾個方面總結Mat數據結構的主要組成。
參考網址:
《OpenCV中對Mat裡面depth,dims,channels,step,data,elemSize和數據地址計算的理解 》
《OpenCV Mat的常見屬性》
《OpenCV學世拿早習筆記(四十)——再談OpenCV數據結構Mat詳解》
參考文檔:
《opencv2refman.pdf》
如上面的Mat定義源碼,Mat類中有很多重要的數據類型成員。
下面進行簡單的列舉。
把這四個數據成員放在一起,是因為這四個數據成員相互之間有關系。
數據的存儲一直都是個值得關注的問題,所以數據元素存儲的位數和范圍就十分重要了。depth就體現了每一個像素的位數,即深度。
Mat中包含的圖像深度如下所示:
另外還需要注意:大部分OpenCV的函數支持的數據深度只有8位和32位,所以盡量使用CV_64F。
channels表示了矩陣擁有的通道數量,這個比較容易理解:
type表示矩陣中元素的類型(depth)與矩陣的通道個數(channels),可以理解成上面的depth與channels的綜合說明。type是一系列預定義的常量,命名規則如下:
<code>CV_+位數+數據類型+通敏簡道數</code>
具體有如下值:
表格中,行代表了通道數量channels,列代表了圖像深度depth。
例如CV_8UC3,可以拆分為:
註:type一般是在創建Mat對象時設定,若要去搜雀的Mat的元素類型,可以不使用type,使用depth。
elemSize表示了矩陣中每一個元素的數據大小,單位是位元組。公式如下:
<code>elemSize = channels * depth / 8</code>
例如type == CV_16SC3,則elemSize = 3 * 16 / 8 = 6 Bytes。
elemSize1表示了矩陣元素的一個通道佔用的數據大小,單位是位元組。公式如下:
<code>elemSize = depth / 8</code>
例如type == CV_16SC3,則elemSize1 = 16 / 8 = 2 Bytes。
使用OpenCV處理圖像時,最普遍的處理方式便是遍歷圖像,即訪問所有的圖像像素點。但有的演算法還需要訪問目標像素的鄰域,所以這時候就需要了解訪問Mat數據元素地址的方式。
假設有矩陣M,則數據元素的地址計算公式如下:
$$ addr(M_{i_{0}, i_{1}, ... i_{m-1}}) = M.data + M.step[0] * i_{0} + M.step[1] * i_{1} + ... + M.step[M.dims - 1] * i_{M_{dims-1}} $$
如果是二維數組,則上述公式就簡化成:
$$ addr(M_{i,j}) = M.data + M.step[0] * i + M.step[1] * j $$
註:式中m = M.dims,即矩陣的維度。
假設存在一個二維矩陣如下圖所示:
上面是一個3 × 4的矩陣。此時我們按照數據類型為CV_8U, CV_8UC3的情況,分別對其進行討論。
首先假設其數據類型為CV_8U,也就是單通道的uchar類型,則可以得出上面的數據成員情況分別為:
若假設其數據類型為CV_8UC3,也就是三通道的uchar類型,則可以得出上面的數據成員情況分別為:
假設存在一個三維矩陣如下圖所示:
上面是一個3 × 4 × 6的矩陣。假設其數據類型為CV_16SC4,此時對其進行討論。
關於OpenCV地址訪問方法及效率的部分,請見筆者的博文 《OpenCV像素點鄰域遍歷效率比較,以及訪問像素點的幾種方法 》 。
④ 【OPENCV】cv::Mat像素遍歷方法比較
像素級別遍歷是我們在圖像任務中經常遇到的問題,在實時的圖像處理中,能轎豎夠高效物搭的訪問像素數據是很重要的。OpenCV中的數據容器是cv::Mat,cv::Mat提供了三種數據訪問的方式分別是下標定址,指針訪問,迭代器訪問。下面我們對比下這幾種不同方式的訪問速度。
對比這幾種方式我們可以發現,最為高效的還是直接使用指針閉螞大計算地址偏移量, 然而這種方式必須保證Mat在內存的存儲是連續的,可以通過cv::Mat::isContinous()函數檢測,如果是連續的則可以處理為單行向量,使用最為高效的方式訪問。如果不想這么麻煩,其實method5是一種較為可取的方式,通過從cv::Mat::ptr()得到每一行的首地址,這樣就不需要保證連續存儲,速度和純粹使用指針也差不了多少。
實際上對於method5,不使用中間指針進行改寫的話:
重新測試下:
時間上已經十分接近method6,實際操作的時候直接使用method5,不使用中間指針即可。
⑤ OpenCV (一)Mat基本操作以及灰度圖轉化
開始寫OpenCV這篇文章的時候,不由想到,我的大學計算機圖形學的第一門實操課程就是灰度轉化,拉普拉斯銳化等。其中灰度圖的轉化,是計算機圖形學基礎中基礎,這里就順著OpenCV的灰度的轉化,來看看OpenCV一些基礎的api。
本文地址: https://www.jianshu.com/p/7963c7dbaf92
先來看看OpenCV,基礎對象Mat,矩陣。什麼是矩陣,實際上沒有必要解釋,一般人都能夠明白數學意義上矩陣的含義。
OpenCV把每一個M * N的寬高圖像,看成M*N的矩陣。矩陣的每一個單元就對應著圖像中像素的每一個點。
我們如果放大圖中某個部分,就會發現如下情況
圖像實際上就如同矩陣一樣每個單元由一個像素點構成。
因為OpenCV的Mat每一個像素點,包含的數據不僅僅只有一個單純的數字。每一個像素點中包含著顏色通道數據。
稍微解釋一下顏色通道,我們可以把世間萬物肉眼能識別的顏色由3種顏色(R 紅色,G 綠色,B 藍色)經過調節其色彩飽和度組成的。也就是說通過控制RGB三種的色值大小(0~255)來調配新的顏色。
當我們常見的灰度圖,一般是單個顏色通道,因為只用黑白兩種顏色。我們常見的圖片,至少是三色通道,因為需要RGB三種顏色通道。
我們常見Android的Bitmap能夠設置ARGB_8888的標志位就是指能夠通過A(透明通道),R,G,B來控制圖片載入的顏色通道。
OpenCV為了更好的控制這些數據。因此採用了數學上的矩陣的概念。當OpenCV要控制如RGB三色通道的Mat,本質上是一個M * N * 3的三維矩陣。
但是實際上,我們在使用OpenCV的Mat的時候,我們只需要關注每個圖片的像素,而每個像素的顏色通道則是看成Mat中每個單元數據的內容即可
我們先來看看Mat的構造方法
現階段,實際上我們值得我們注意的是構造函數:
舉個例子:
這個mat矩陣將會製造一個高20,寬30,一個1位元組的顏色通道(也是Mat中每一個像素數據都是1位元組的unchar類型的數據),同時顏色是白色的圖片。
在這裡面我們能夠看到一個特殊的宏CV_8UC1。實際上這是指代OpenCV中圖片帶的是多少顏色通道的意思。
這4個宏十分重要,要時刻記住。
當我們需要把Mat 中的數據拷貝一份出來,我們應該調用下面這個api:
這樣就能拷貝一份像素數據到新的Mat中。之後操作新的Mat就不會影響原圖。
實際上,在本文中,我們能夠看到OpenCV是這么調用api讀取圖片的數據轉化為Mat矩陣。
OpenCV會通過imread去讀圖片文件,並且轉化為Mat矩陣。
能看見imread,是調用imread_把圖片中的數據拷貝的img這個Mat對象中。接著會做一次圖片的顛倒。這個方面倒是和Glide很相似。
文件:moles/imgcodecs/src/loadsave.cpp
這裡面做了幾個事情,實際上和FFmpge的設計十分相似。
其核心也是操作Mat中的像素指針,找到顏色通道,確定指針移動的步長,賦值圖片的數據到Mat矩陣中。核心如下:
其中還涉及到jpeg的哈夫曼演算法之類的東西,這里就不深入源碼。畢竟這是基礎學習。
什麼是灰度圖,灰度度圖實際上我們經常見到那些灰白的也可以納入灰度圖的范疇。實際上在計算機圖形學有這么一個公式:
將RGB的多顏色圖,通過 的演算法,將每一個像素的圖像的三顏色通道全部轉化為為一種色彩,通過上面的公式轉為為一種灰色的顏色。
一旦了解了,我們可以嘗試編寫灰度圖的轉化。我們通過矩陣的at方法訪問每一個像素中的數據。
為了形象表示矩陣指針,指向問題,可以把RGB在OpenCV的Mat看成如下分布:
記住OpenCV的RGB的順序和Android的不一樣,是BGRA的順序。和我們Android開發顛倒過來。
因此,我們可以得到如下的例子
我們經過嘗試之後,確實能夠把一個彩色的圖片轉化一個灰色圖片。但是這就是
這里介紹一下Mat的一個api:
實際上OpenCV,內置了一些操作,可以把RGB的圖像數據轉化灰度圖。
我們看看OpenCV實際上的轉化出來的灰度圖大小。我們通過自己寫的方法,轉化出來的灰度圖是119kb,而通過cvtColor轉化出來的是44kb。
問題出在哪裡?還記得嗎?因為只有灰白兩種顏色,實際上只需要一種顏色通道即可,而這邊還保留了3個顏色通道,也就說圖片的每一個像素點中的數據出現了沒必要的冗餘。
這樣就是44kb的大小。把三顏色通道的數據都設置到單顏色通道之後,就能進一步縮小其大小。
實際上在Android中的ColorMatrix中也有灰度圖轉化的api。
對畫筆矩陣進行一次,矩陣變化操作。
實際上就是做了一次矩陣運算。繪制灰度的時候相當於構建了這么一個矩陣
接著通過矩陣之間的相乘,每一行的 0.213f,0.715f,0.072f控制像素的每個通道的色值。
對於Java來說,灰度轉化的演算法是: ,把綠色通道的比例調大了。
在OpenCV中有這么兩個API,add和addWidget。兩者都是可以把圖像混合起來。
add和addWidget都是將像素合並起來。但是由於是像素直接相加的,所以容易造成像素接近255,讓整個像素泛白。
而權重addWeighted,稍微能減輕一點這種問題,本質上還是像素相加,因此打水印一般不是使用這種辦法。
等價於
saturate_cast這個是為了保證計算的值在0~255之間,防止越界。
飽和度,圖片中色值更加大,如紅色,淡紅,鮮紅
對比度:是指圖像灰度反差。相當於圖像中最暗和最亮的對比
亮度:暗亮度
控制對比度,飽和度的公式: , ,
因此當我們想要控制三通道的飽和度時候,可以通過alpha來控制色值成比例增加,beta控制一個色值線性增加。
如下:
在這里,看到了OpenCV會把所有的圖片看成Mat矩陣。從本文中,能看到Mat的像素操作可以能看到有兩種,一種是ptr像素指針,一種是at。ptr是OpenCV推薦的更加效率的訪問速度。
當然還有一種LUT的核心函數,用來極速訪問Mat矩陣中的像素。其原理是對著原來的色值進行預先的變換對應(設置一個顏色通道)。用來應對設置閾值等情況。