opencvmat訪問
Ⅰ 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 數據結構的用法
#include "stdafx.h"
#include <string>
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int _tmain(int argc, _TCHAR* argv[])
{
//創建一個用1+3j填充的 7 x 7 復矩陣-----1
Mat M(7, 7, CV_32FC2, Scalar(1,3));
//現在將 M轉換為100 x 60的CV_8UC(15)的矩陣,舊內容將會被釋放
M.create(100, 60, CV_8UC(15));//不能為矩陣設置初值
//第 5行,乘以 3,加到第 3 行,
M.row(3) = M.row(3) + M.row (5) * 3;
//現在將第7列復制到第1列, M.col(1) = M.col(7);//這個不能實現,對列操作時要新建一個Mat
Mat M1 = M.col(1);
M.col(7).To(M1);
//創建一種新的 320 x 240 圖像-----2
Mat img(Size(320,240), CV_8UC3, Scalar::all(255));
string strWindowName = "ShowImage";
namedWindow(strWindowName, WINDOW_AUTOSIZE);
imshow(strWindowName, img);
waitKey(0);
//選擇ROI(region of interest)
Mat roi(img, Rect(10, 10, 100, 100));
//填充 (0,255,0) 的ROI (這是RGB 空間中的綠色),320 x 240 原始圖像將被修改
roi = Scalar(0, 255, 0) ;
imshow(strWindowName, img);
waitKey(0);
//獲取數組中的子塊-----3
Mat A = Mat::eye(10, 10, CV_32S);
//提取 A 的1 (含)到 3 (不包含)列
Mat B = A(Range::all(), Range(1, 3));
//提取 B 的5 (含)到 9 (不包含)行,即 C ~ A(Range(5,9),Range (1,3))
Mat C = B(Range(5, 9), Range::all());
Size size;
Point ofs;
C.locateROI(size, ofs);//使用locateROI() 計運算元數組在主容器數組中的相對的位置
cout<<size.width<<" "<<size.height<<" "<<ofs.x<<" "<<ofs.y<<endl;
//快速初始化小矩陣-----4
double m[3][3] = {{1, 2, 3}, {1, 2, 5}, {3, 4, 6}};
Mat M2 = Mat(3, 3, CV_64F, m);//.inv();
Mat E = Mat::eye(4, 4, CV_64F);
cout<<"E = "<<endl<<" "<<E<<endl;
Mat O = Mat::ones(2, 2, CV_32F);
cout<<"O = "<<endl<<" "<<O<<endl;
Mat Z = Mat::zeros(3,3, CV_8UC1);
cout<<"Z = "<<endl<<" "<<Z<<endl;
//IplImage、Mat、CvMat互轉-----5
IplImage *img1 = cvLoadImage("aa.jpg", 2 | 4);
Mat mtx(img1);//IplImage *-> Mat,新的Mat類型與原來的IplImage類型共享圖像數據,轉換只是創建一個Mat矩陣頭// or : Mat mtx = img1;
CvMat oldmat = mtx;//Mat-> CvMat //只是創建矩陣頭,而沒有復制數據,oldmat不用手動釋放
CV_Assert((oldmat.cols == img1->width) && (oldmat.rows == img1->height) && (oldmat.data.ptr == (uchar *)img1->imageData) && (oldmat.step == img1->widthStep));
imshow(strWindowName, mtx);
waitKey(0);
cvNamedWindow(strWindowName.c_str(), 0);
cvShowImage(strWindowName.c_str(), &oldmat);
cvWaitKey(0);
IplImage img2 = mtx;//Mat->IplImage //只是創建圖像頭,而沒有復制數據,img2不用手動釋放
cvShowImage(strWindowName.c_str(), &img2);
cvWaitKey(0);
Mat mat3(&oldmat);//CvMat->Mat
imshow(strWindowName, mat3);
waitKey(0);
cvDestroyWindow(strWindowName.c_str());
cvReleaseImage(&img1);
//創建 3 x 3 雙精度恆等矩陣-----6
Mat M3 = (Mat_ <double>(3,3) <<1,0,0, 0,1,0, 0,0,1);
//訪問數組元素-----7
M2.at<double>(0, 0) += 10.f;
double sum = 0;//計算元素和,方法一
for (int i=0; i<M2.rows; i++)
{
const double *Mi = M2.ptr<double>(i) ;
for (int j=0; j<M2.cols; j++)
{
sum += std::max(Mi[j], 0.);
}
}
cout<<sum<<endl;
sum = 0;//計算元素和,方法二
int cols =M2.cols, rows = M2.rows ;
if (M2.isContinuous())
{
cols *= rows;
rows = 1 ;
}
for (int i=0; i<rows; i++)
{
const double *Mi = M2.ptr <double>(i);
for (int j=0; j<cols; j++)
{
sum += std::max(Mi[j], 0.);
}
}
cout<<sum<<endl;
sum = 0;//計算元素和,方法三
MatConstIterator_<double> it = M2.begin<double>(), it_end = M2.end<double>();
for(; it != it_end; ++it)
{
sum += std::max(*it, 0.);
}
cout<<sum<<endl;
return 0;
}
Ⅲ 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矩陣中的像素。其原理是對著原來的色值進行預先的變換對應(設置一個顏色通道)。用來應對設置閾值等情況。