OpenCV學習筆記

掃盲

首先,在圖像和視頻分析方面,我們應該瞭解一些基本的假設和範式。對現在每個攝像機的記錄方式來說,記錄實際上是一幀一幀地顯示,每秒 30-60 次。但是,它們的核心是靜態幀,就像圖像一樣。因此,圖像識別和視頻分析大部分使用相同的方法。有些東西,如方向跟蹤,將需要連續的圖像(幀),但像面部檢測或物體識別等東西,在圖像和視頻中代碼幾乎完全相同。

接下來,大量的圖像和視頻分析歸結爲儘可能簡化來源。這幾乎總是起始於轉換爲灰度,但也可以是彩色濾鏡,漸變或這些的組合。從這裏,我們可以對來源執行各種分析和轉化。一般來說,這裏發生的事情是轉換完成,然後是分析,然後是任何覆蓋,我們希望應用在原始來源上,這就是你可以經常看到,對象或面部識別的“成品”在全色圖像或視頻上顯示。然而,數據實際上很少以這種原始形式處理。有一些我們可以在基本層面上做些什麼的例子。所有這些都使用基本的網絡攝像頭來完成,沒有什麼特別的:

  • 背景提取
  • 顏色過濾
  • 邊緣檢測
  • 用於對象識別的特徵匹配
  • 一般對象識別
import cv2
import numpy as np
from matplotlib 
import pyplot as plt
img = cv2.imread('watch.jpg',cv2.IMREAD_GRAYSCALE)
plt.imshow(img, cmap = 'gray', interpolation = 'bicubic')
plt.xticks([]), plt.yticks([]) 
# to hide tick values on X and Y axisplt.plot([200,300,400],[100,200,300],'c',linewidth=5)plt.show()

請注意,你可以繪製線條,就像任何其他 Matplotlib 圖表一樣,使用像素位置作爲座標的。 不過,如果你想繪製你的圖片,Matplotlib 不是必需的。 OpenCV 爲此提供了很好的方法。 當你完成修改後,你可以保存,如下所示:

cv2.imwrite('watchgray.png',img)

除了起始行,處理來自視頻的幀與處理圖像是一樣的。 我們來舉例說明一下:

import numpy as np
import cv2

cap = cv2.VideoCapture(0) 
while(True): 
	ret, frame = cap.read() 
	# 在這裏,我們定義一個新的變量gray,作爲轉換爲灰度的幀。 
	# 注意這個BGR2GRAY。 
	# 需要注意的是,OpenCV 將顏色讀取爲 BGR(藍綠色紅色),但大多數計算機應用程序讀取爲 RGB(紅綠藍)
	gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) 
	# 儘管是視頻流,我們仍然使用imshow
	cv2.imshow('frame',gray) 
	# 這個語句每幀只運行一次。 
	# 基本上,如果我們得到一個按鍵,那個鍵是q,我們將退出while循環,然後運行
	if cv2.waitKey(1) & 0xFF == ord('q'): 
		break
# 這將釋放網絡攝像頭,然後關閉所有的imshow()窗口。
cap.release()
cv2.destroyAllWindows()

某些情況下,你可能實際上需要錄製,並將錄製內容保存到新文件中。

import numpy as np
import cv2
cap = cv2.VideoCapture(1)
fourcc = cv2.VideoWriter_fourcc(*'XVID')
out = cv2.VideoWriter('output.avi',fourcc, 20.0, (640,480))
while(True): 
	ret, frame = cap.read() 
	gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) 
	out.write(frame) 
	cv2.imshow('frame',gray) 
	if cv2.waitKey(1) & 0xFF == ord('q'): 
		break
cap.release()
out.release()
cv2.destroyAllWindows()
# 主要要注意的是正在使用的編解碼器,以及在while循環之前定義的輸出信息。
# 然後,在while循環中,我們使用out.write()來輸出幀。 
# 最後,在while循環之外,在我們釋放攝像頭之後,我們也釋放out。

現在我們知道如何操作圖像和視頻。 如果你沒有網絡攝像頭,你可以使用圖像甚至視頻來跟隨教程的其餘部分。 如果你希望使用視頻而不是網絡攝像頭作爲源,則可以爲視頻指定文件路徑,而不是攝像頭號碼

圖上畫圖

在這個 Python OpenCV 教程中,我們將介紹如何在圖像和視頻上繪製各種形狀。 想要以某種方式標記檢測到的對象是相當普遍的,所以我們人類可以很容易地看到我們的程序是否按照我們的希望工作。

cv2.line(img,(0,0),(150,150),(255,255,255),15)
# 參數:圖片,開始座標,結束座標,顏色(bgr),線條粗細。
cv2.rectangle(img,(15,25),(200,150),(0,0,255),15)
# 參數:圖像,左上角座標,右下角座標,顏色和線條粗細。
cv2.circle(img,(100,63), 55, (0,255,0), -1)
# 參數是圖像/幀,圓心,半徑,顏色和
# 注意我們粗細爲-1。 這意味着將填充對象,所以我們會得到一個圓。

cv2.imshow('image',img)
cv2.waitKey(0)
cv2.destroyAllWindows()

一些概念

維度與通道

通道

OpenCV中圖像的通道可以是1、2、3和4。其中常見的是1通道和3通道,2通道和4通道不常見。

  • 1通道的是灰度圖
  • 2通道的圖像是RGB555和RGB565。2通道在程序處理中會用到,如傅里葉變換,可能會用到,一個通道爲實數,一個通道爲虛數,主要是編程方便。RGB是16位的,2個字節(5+6+5),第一個字節的前5位是R,後三位+第二個字節是G,第二個字節後5位是B,可見對原圖進行壓縮了。
  • 3通道的是彩色圖像,比如RGB圖像
  • 4通道的圖像是RGBA,是RGB加上一個A通道,也叫alpha通道,表示透明度。PNG圖像是一種典型的4通道圖像。alpha通道可以賦值0到1,或者0到255,表示透明到不透明。

通道:就是某一座標表示的數組有多少個。

比如單通道:P[0][0]=10,在座標(0,0)處只表示一個數組。
同理雙通道:P[0][0]={10,15},那麼就是雙通道。
我們常用的RBG(255,255,255)就是三通道。

那麼在opencv下如何表示單通道還是多通道?

cvInitMatHeader(&mat,3,6,CV_32FC1,P); //P就是上面的那個數組

此函數的第四參數:CV_32FC1--------32表示32位,FC表示通道,1表示單通道(若2表示雙通道)。

常用的函數如下:

  • cvGetD,cvSetD
  • cvGetReal1D,cvGetReal2D,cvGetReal3D,cvGetRealND
  • cvGet1D,cvGet2D,cvGet3D,cvGetND
  • cvSet*D 也有相應的函數系列
  • 這些函數的缺點是:效率低
  • 具有Real的函數只用在單通道。
  • cvGet1D,cvGet2D等。----2D表示維度。只用x、y座標軸使用cvGet2D;有x、y、z座標軸就使用cvGet3D。

維度

維度:就是我們平常所說的座標軸。二維:x,y。。三維:x,y,z。。


基礎類型

Point

cv::Point類是兩到三個原語類型的容器。
其成員是通過變量名稱x、y、z訪問的,而不是通過下標訪問。
Point類是通過自己的模板派生來的,這是一個基礎模板;實際上由兩個這樣的模板,分別是給二維、三維的點提供的。
這些類的實例有cv::Point2i、cv::Point2f、cv::Point2d或cv::Point3i、cv::Point3f、cv::Point3d(在這裏,最後一個字母表示構造該點所需要的原語,i是一個32位整形,f是一個32位浮點數,d是一個64位浮點數,還可以有無符號字符b和短整型s)。

操作 示例
默認構造函數 cv::Point2i p2;
cv::Point3i p3;
複製構造函數 cv::Point3i p1( p );
注:若p爲浮點型,則會自動取整
值構造函數 cv::Point2i( x0, x1 );
cv::Point3d( x0, x1, x2 );
構造成固定向量類 (cv::Vec3f) p;
成員訪問 p.x, p.y, p.z
點乘 float x = p1.dot( p );
雙精度點乘 double x = p1.dot( p );
叉乘 p1.cross( p );
注:只用於三維的點
判斷一個點p是否在矩形r內 p1.inside( r );
注:只用於二維的點

Size

Size類在實際操作時和Point類相似,可以進行互相轉換。主要區別在於Size類中對應的成員是width和height,而不是x和y,並且不支持轉換到固定向量類。Size類的別名有cv::Size、cv::Size2i和cv::Size2f,其中前兩個都表示整型,最後一個表示單精度浮點型。

操作 示例
默認構造函數 cv::Size sz;
cv::Size2i sz;
cv::Size2f sz;
複製構造函數 cv::Size2f sz2( sz1 );
值構造函數 cv::Size2f sz( w, h );
成員訪問 sz.width;
sz.height;
計算面積 sz.area();

Scalar

cv::Scalar是四維點類,是四維雙精度向量的快速表示。cv::Scalar直接從固定向量類模板實例(cv::Vec<double, 4>)中繼承而來,所以繼承了所有向量代數操作、成員訪問函數(比如[]操作符)和一些固定向量類的特性,如:其元素是通過整數下標來訪問的。

操作 示例
默認構造函數 cv::Scalar s;
複製構造函數 cv::Scalar s2( s1 );
值構造函數 cv::Scalar s( x0 );cv::Scalar s( x0, x1, x2, x3 );
元素相乘 s1.mul( s2 );
(四元數)共軛 s.conj();
// return cv::Scalar( x0, -x1, -x2, -x3 );
(四元數)真值測試 s.isReal();
// if x1 == x2 == x3 == 0{ return true; }

Rect

Rect類又稱矩形類,包含Point類的成員x和y(代表矩形左上角的座標)和Size類的成員width和height(代表矩形的大小),但並非從它們繼承過來。

操作 示例
默認構造函數 cv::Rect r;
複製構造函數 cv::Rect r2( r1 );
值構造函數 cv::Rect( x, y, w, h );
由起始點和大小構造 cv::Rect( p, sz );
由兩個對角構造 cv::Rect( p1, p2 );
成員訪問 r.x; r.y; r.width; r.height;
計算面積 r.area();
提取左上角 r.tl(); // top-left
提取右下角 r.br(); // bottom-left
判斷點p是否在矩形r內 r.contains( p );
操作 示例
矩形r1和矩形r2的交集 cv::Rect r3 = r1 & r2;
r1 &= r2;
同時包含矩形r1和矩形r2的最小面積矩形 cv::Rect r3 = r1 I r2;
r1 I= r2;
平移矩形r x個數量 cv::Rect rx = r + x;
r += x;
// x爲一個Point實例,左上角偏移量
擴大矩形r s大小 cv::Rect rs = r + s;
r += s;
// s爲一個Size實例,尺寸改變量
比較矩形r1和矩形r2是否相等 bool eq = ( r1 == r2 );
比較矩形r1和矩形r2是否不相等 bool ne = ( r1 != r2 );

Mat

關於Mat的第一件事是你不再需要手動分配其大小並且當你不需要它的時候你不再需要手動釋放它。雖然這樣做仍然是可能的,大多數 OpenCV 函數將手動分配其輸出數據。還有一個額外的好處是如果傳遞一個已存在Mat對象,它已經爲矩陣分配所需的空間,這段空間將被重用。也就是說我們在任何時候只使用與我們執行任務時所必須多的內存一樣多的內存。

Mat本質上是由兩個數據部分組成的類:
矩陣頭:包含信息有矩陣的大小,用於存儲的方法,矩陣存儲的地址等
指針:指向包含了像素值的矩陣(可根據選擇用於存儲的方法採用任何維度存儲數據)

矩陣頭部的大小是恆定的。然而,矩陣本身的大小因圖像的不同而不同,通常是較大的數量級。
因此,當你在您的程序中傳遞圖像並在有些時候創建圖像副本您需要花費很大的代價生成圖像矩陣本身,而不是圖像的頭部。

OpenCV 是圖像處理庫,它包含大量的圖像處理函數。若要解決的計算挑戰,最終大部分時間你會使用庫中的多個函數。

由於這一原因,圖像傳給庫中的函數是一種常見的做法(複製矩陣會耗費時間和空間)。我們不應忘記我們正在談論往往是計算量相當大的圖像處理算法。我們想要做的最後一件事是通過製作不必要的可能很大的圖像的拷貝進一步降低您的程序的速度。

爲了解決這一問題 OpenCV 使用引用計數系統:

  • 每個Mat具有自己的矩陣頭
  • 但矩陣指針指向同一個地址

其思想是Mat的每個對象具有其自己的頭,但可能他們通過讓他們矩陣指針指向同一地址的兩個實例之間共享該矩陣。此外,拷貝運算符將只能複製矩陣頭部,也還將複製指針到大型矩陣,但不是矩陣本身。

Mat A, C; // 僅創建了頭部
A = imread(argv[1], CV_LOAD_IMAGE_COLOR); // 在此我們知道使用的方法(分配矩陣)
Mat B(A); //使用拷貝構造函數
C = A; //賦值運算符

// ==============================================

Mat D (A, Rect(10, 10, 100, 100) ); // 用矩形界定
Mat E = A(Range:all(), Range(1,3)); // 用行和列來界定

最後一個使用它的對象。這對於使用引用計數的機制,每當有人複製Mat對象的頭,矩陣的計數器被增加。每當一個頭被清除,此計數器被下調。當該計數器變爲零,矩陣也就被釋放了。因爲有時會仍然也要複製矩陣的本身,存在着 clone() 或 copyTo() 函數

Mat F = A.clone();
 
Mat G;
 
A.copyTo(G);

Mat public Attrs

MatAllocator* allocator
 	// 如果需要創建一個新矩陣的內存空間,系統會調用MatAllocator類作爲分配符進行內存的分配。
 	
int rows 
	// 返回矩陣行數

int cols
	//返回矩陣的列數
	
uchar * data
 	// 指向矩陣的數據單元的指針

const uchar* dataend
const uchar* datalimit
const uchar* datastart
 	// helper fields used in locateROI and adjustROI More...
	// 這些都是用來控制ROI區域,來獲取一些圖像的局部切片,減少計算量或者特殊需求的。
 
int dims
 	// 矩陣的維數,但是這裏一般結果都是2,因爲opencv好像存儲多維矩陣也是通過二維矩陣來計,它和基本的channels不一樣。
 
int flags
 
MatSize size
	// 返回矩陣大小
 
MatStep step
 	// 矩陣元素尋址
 	// step[i]是Mat類中十分重要的一個屬性,表示第i維的總大小,單位字節
	// M.data指向存儲這列的首地址(類似於數組名)
	// M.dims是總維度
UMatData * u
 	// interaction with UMat More...

Mat public Method

/*=============================================
Mat創建和修改
=============================================*/
static MatExpr Mat::eye(int rows, int cols, inttype)
	// 返回一個恆等指定大小和類型矩陣。

void Mat::create(int rows, int cols, int type)
	// 分配新的陣列數據 (如果需要)。
	

void Mat::resize(size_t sz, const Scalar& s)
	// 該方法更改矩陣的 行數 。如果矩陣重新分配,第一最少(Mat::rows,sz) 行數要保留下來

void Mat::reserve(size_t sz)
	// 保留一定數量的行的空間。

void Mat::push_back(const Mat& elem)
	// 將元素添加到矩陣的底部。
	// 其類型和列的數目必須和矩陣容器是相同的。

template<typename T> voidMat::pop_back(size_t nelems=1)
	// 該方法從底部的列表中刪除一行或多行。
	
/*=============================================
ROI region of interest:定位與調整
=============================================*/
void Mat::locateROI(Size& wholeSize,Point& ofs) const
	// ROI:region of interest
	// 父矩陣內定位矩陣頭

Mat& Mat::adjustROI(int dtop, int dbottom,int dleft, int dright)
	// 調整子陣大小及其在父矩陣中的位置。
	// 該方法是 Mat::locateROI() 的互補性方法。這些函數的典型應用是確定父矩陣中子陣的位置,然後以某種方式改變位置
	// 參數是平移量,而不是位置
	
/*=============================================
取值,區域
=============================================*/
Mat Mat::operator()(const Rect& roi) const
     // 提取矩形子陣。 mat()

template<typename T> T& Mat::at(int i)const
template<typename T> const T&Mat::at(int i, int j) const
template<typename T> const T&Mat::at(Point pt) const
template<typename T> const T&Mat::at(int i, int j, int k) const	
	// i –索引 0 維度
	// j – 1 維度的索引
	// k – 沿 2 維度的索引
	// pt – Point(j,i) 作爲指定元素的位置。
	// idx – Mat::dims 數組的索引。
A.at<float>(k+4) 和 B.at<int>(2*i+1) 分別代替A.at<float>(0,k+4)和
B.at<int>(2*i+1,0)
	// 該模板方法返回指定數組元素的引用。爲了具有更高的性能,索引範圍檢查只在調試配置下執行。
	// 請注意使用具有單個索引 (i) 的變量可以訪問的單行或單列的2 維的數組元素。
	// 也就是比方說,如果A是1 x N 浮點矩陣和B是M x 1的整數矩陣,您只需編寫

uchar* Mat::ptr(int i=0)
	// 該方法返回uchar*,或指向由輸入指定矩陣   行   的指針。
	// 參看Mat::isContinuous()的中示例瞭解如何使用這些方法。

/*=============================================
矩陣頭 CvMat  、  IplImage
=============================================*/
Mat::operator CvMat() const
	// 該運算符創建矩陣 CvMat 的頭,而不復制的基礎數據。引用計數未被考慮到此操作中。
	// 因此,您應該確保CvMat 頭在使用的時候不釋放原始矩陣。該運算符對於新舊OpenCV API混用是有用的.

Mat::operator IplImage() const
     // 運算符創建矩陣 IplImage 頭,而不復制的基礎數據。您應該確保使用IplImage頭時不釋放原矩陣。
     // 與Mat::operatorCvMat類似,該運算符在OpenCV新舊API混用中很有用

/*=============================================
獲取矩陣特徵:
元素數目、是否連續存儲、矩陣元素大小、矩陣元素通道大小
、矩陣的元素類型、矩陣大小、是否爲空
=============================================*/
bool Mat::isContinuous() const
     // 如果在每一行的結尾無間隙連續存儲矩陣的元素,該方法返回 true。

size_t Mat::total() const
     // 該方法返回數組元素(如果該數組表示圖像的像素數)的數目。

size_t Mat::elemSize() const
     // 該方法返回以字節爲單位的矩陣元素大小。例如,如果矩陣類型是 CV_16SC3,該方法返回3*sizeof(short)或 6

size_t Mat::elemSize1() const
     // 該方法返回以字節爲單位的矩陣元素通道大小,也就是忽略通道的數量。例如, 如果矩陣類型是 CV_16SC3,該方法返回 sizeof(short) 或 2。

int Mat::type() const
     // 該方法返回一個矩陣的元素類型。這是兼容CvMat 類型系統,像 CV_16SC3標識符或 16 位有符號的3 通道陣列,等等。

int Mat::depth() const
     // 該方法返回矩陣元素深度(每個單獨的通道類型)的標識符

size_t const Mat::step1()
     // 該方法返回以矩陣的step除以Mat::elemSize1()。它對快速訪問任意矩陣元素很有用。

Size Mat::size() const
     // 該方法返回一個矩陣大小:Size(cols, rows)。矩陣超過 2 維時返回大小爲(-1,-1)。

 bool Mat::empty() const
     // 如果 Mat::total() 是 0 或 Mat::data 爲 NULL,則方法返回 true。
     // 因爲pop_back() 和 resize()方法M.total()= = 0,並不意味着M.data = =NULL。
	
/*=============================================
迭代器
=============================================*/

template<typename _Tp>MatConstIterator_<_Tp> Mat::begin() const
     // 該方法返回矩陣的只讀或讀寫的迭代器。
     // 矩陣迭代器的使用和雙向 STL 迭代器的使用是非常相似的

template<typename _Tp>MatIterator_<_Tp> Mat::end()
template<typename _Tp>MatConstIterator_<_Tp> Mat::end() const
     // 該方法返回矩陣只讀或讀寫的迭代器,設置爲緊隨最後一個矩陣元素的點

/*=============================================
計數器 與 釋放
=============================================*/
void Mat::addref()
	// 計數器參考。方法遞增與矩陣數據關聯的引用計數

void Mat::release()
	// 該方法遞減與矩陣的數據關聯的引用計數。
	// 當引用計數減爲0時,矩陣的數據將被釋放,數據和引用計數器指針設置爲 NULL。
	// 如果矩陣頭指向外部數據集 (見 Mat::Mat()), 引用計數爲 NULL,並且該方法在這種情況下無效。

詳解


OP

在 OpenCV 教程中,我們將介紹一些我們可以做的簡單圖像操作。 每個視頻分解成幀。 然後每一幀,就像一個圖像,分解成存儲在行和列中的,幀/圖片中的像素。 每個像素都有一個座標位置,每個像素都由
顏色值組成。 讓我們列舉訪問不同的位的一些例子。

我們將像往常一樣讀取圖像(如果可以,請使用自己的圖像,但這裏是我在這裏使用的圖像):

import cv2
import numpy as np
img = cv2.imread('watch.jpg',cv2.IMREAD_COLOR)

# 現在我們可以實際引用特定像素,像這樣:
px = img[55,55]
# 下面我們可以實際修改像素:
img[55,55] = [255,255,255]
# 之後重新引用
px = img[55,55]
print(px)
# 下面我們可以引用 ROI,圖像區域:
px = img[100:150,100:150]
print(px)
# 我們也可以修改 ROI,像這樣:
img[100:150,100:150] = [255,255,255]
# 引用我們的圖像的特定特徵:
print(img.shape)
print(img.size)
print(img.dtype)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章