這篇博客將介紹一些OpenCV的瑣碎的概念知識以及容易出現錯誤的點。可能大家平時看博客感覺OpenCV沒什麼難的,無非是調用一些庫和函數,但是在實際操作過程中很容易出現翻車的現象。好了,廢話不多說開始本章的內容
內容安排
- OpenCV各個變量之間的轉換關係
- 採用OpenCV進行連通域分析的原理以及相關函數
- OpenCV連通域分析的應用-計算歐拉數(euler)
- 採用OpenCV進行濾波以及形態學處理的相關原理及函數
- OpenCV輪廓函數的介紹
- 參考文獻
1. OpenCV各個變量之間的轉換關係
第一個章節的概念來自於一個函數的應用,當時想要根據MATLAB上的閾值提取函數,實現一個C++版本的,然後在OpenCV上找到一個CVThreshold
的函數,因爲這個函數提示需要CvArr*
的變量作爲填充進去。但是我之前使用一般都是Mat類型的變量沒見過類似的。後來查了第一篇文獻才知道,這個新的函數變量是什麼。簡而言之,OpenCV各個變量之間的關係就是:
也就是IplImage是由CvMat派生;CvMat由CvArr派生。因此可以得出CvArr作爲函數的參數,無論是傳入CvMat或者IplImage,在函數內部都算是CvMat。
接下來再講講Mat與CvMat和IplImage之間的異同。首先這兩者都能夠顯示和代表圖像。其次Mat側重於計算,矩陣計算能力更好;CvMat和IplImage更側重於圖像,OpenCV對這兩個變量針對圖像的操作(縮放、單通道、圖像閾值操作等)做了優化。
然後對三個變量分別進行介紹:
-
Mat類型
在openCV中,Mat是一個多維的密集數據數組。可以用來處理向量和矩陣、圖像、直方圖等等常見的多維數據。
Mat有三個比較重要的函數:
Mat mat = imread(const String* filename); 讀取圖像
imshow(const string frameName, InputArray mat); 顯示圖像
imwrite (const string& filename, InputArray img); 儲存圖像
Mat類型比CvMat與IplImage類型具有更強的矩陣計算能力,因此在計算密集型應用中,應當首選Mat類型
-
CvMat類型
CvMat類似於向量,在創建基礎數據類型,比如二維矩陣:
CvMat* cvCreatMat(int rows ,int cols , int type);
其中type 可以是任意預定義數據類型,比如RGB或者其他多通道數據。
-
IplImage類型
IplImage類型繼承自CvMat類型. IplImage類型較之CvMat多了很多參數,比如
depth和nChannels。
一個重要的不便是對
原點
的定義不清楚,圖像來源,編碼格式,甚至操作系統都會對原地的選取產生影響。爲了彌補這一點,openCV允許用戶定義自己的原點設置。取值0表示原點位於圖片左上角,1表示左下角。
各個類型的相互轉換:
A.Mat -> IplImage:IplImage pImg= IplImage(imgMat);
B.Mat -> CvMat:CvMat cvMat = imgMat;
A.CvMat-> IplImage: IplImage* img = cvCreateImage(cvGetSize(mat),8,1);cvGetImage(matI,img);cvSaveImage("rice1.bmp",img);
B.CvMat->Mat:Mat::Mat(const CvMat* m, bool copyData=false);
A.IplImage -> Mat:CvMat mathdr, *mat = cvGetMat( img, &mathdr );
或者CvMat *mat = cvCreateMat( img->height, img->width, CV_64FC3 ); cvConvert( img, mat );
C.IplImage*-> BYTE* :BYTE* data= img->imageData;
2.採用OpenCV進行連通域分析的原理以及相關函數
將這個的原因是,我之前需要提取圖像的特徵包含求二值圖像的歐拉數。在MATLAB上還是比較好實現的,但是用OpenCV實現會遇到各種各樣的麻煩。首先我先介紹一下歐拉數的概念。
歐拉數
歐拉數:在二值圖像分析中歐拉數是非常重要的拓撲特徵,計算公式:E=N-H
,其中E 表示歐拉數;N表示聯通組件的數目;H表示聯通組件內部的空洞數量。
因此我們要求歐拉數就需要分析圖像的輪廓結構,然後根據輪廓層次結構計算。藉助OpenCV中的findContours
分析二值圖像的輪廓層次會被保存在Vec4i
的結構體內。其中這個函數的API及其參數解釋如下所示,具體在用的時候,大家還需再查查,因爲我當時被第二個博客的博主給坑了(雖然他寫的OpenCV博客質量還是很高的,但是OpenCV版本用的不對,會很蛋疼)。講下面這些代碼之前先介紹一些基本概念(來自最後一篇參考文獻)。
輪廓
輪廓是以某種方式表示圖像中的曲線的點的列表,表示一條曲線的方式有很多種。OpenCV中,輪廓是由STL風格的vector<>模板對象表示的,其中vector中的每個元素都編碼了曲線上,下一點的位置信息。
void cv::findContours(
InputOutputArray image,
OutputArrayOfArrays contours,
OutputArray hierarchy,
int mode,
int method,
Point offset = Point()
)
image參數表示輸入的二值圖像
contours表示所有的輪廓信息,每個輪廓是一系列的點集合
hierarchy表示對應的每個輪廓的層次信息,我們就是要用它實現對最大輪廓歐拉數的分析
mode表示尋找輪廓拓撲的方法,如果要尋找完整的層次信息,要選擇參數RETR_TREE
method表示輪廓的編碼方式,一般選擇簡單鏈式編碼,參數CHAIN_APPROX_SIMPLE
offset表示是否有位移,一般默認是0
這些參數中最重要的參數是hierarchy參數。其輸出是vector<Vec4i>
每個輪廓對應的Vec4i
結構體的四個值的解釋如下:
有了輪廓的層次信息與每個輪廓的信息之後,然後開始遍歷每個輪廓,通過調用findContours
就能夠獲得二值圖的輪廓層次信息,然後遍歷每個輪廓,進行層次遍歷,獲得每個層子輪廓的總數,最終根據洛克層級不同劃分爲空洞與連接輪廓數,兩者相減得到每個獨立外層輪廓的歐拉數。
二值化與輪廓發現的代碼
Mat gray,binary;
cvtColor(src,gray,COLOR_BGR2GRAY);
threshold(gray,binary,0,255,THRESH_BINARY|THRESH_OTSU);
vector<Vec4i>hireachy;
vector<vector<Point>>contours;
findContours(binary,contours,hireachy,RETR_TREE,CHAIN_APPROX_SIMPLE,Point());
**注意:**這裏有個坑,用OTSU
求二值化閾值的時候,一定要將傳入的圖像以及最終輸出的圖像轉爲CV_8UC1
,不然函數會各種報錯。
獲取同層輪廓的代碼
vector<int>current_layer_holes(vector<Vec4i>layers,int index){
int next =layers[index][0];
vector<int>indexes;
indexes.push_back(index);
while(next>=0){
indexes.push_back(next);
next = layers[next][0];
}
return indexes;
}
3. OpenCV-中值濾波
這個問題也是將MATLAB代碼轉化爲OpenCV時遇到的,其實不是什麼難題。
中值濾波
中值濾波是一種非線性濾波器,常用於消除圖像中的椒鹽噪聲。與低通濾波不同的是,中值濾波有利於保留邊緣的尖銳度,但是會洗去均勻介質區域中的紋理。
濾波的原理:
輸入圖像中,以任意一個像素爲中心設置一個確定的領域,的邊長爲。將領域內個像素的強度值按大小順序排列,取位於中間位置的那個值(中值)作爲該像素點的輸出值,濾波公式:
椒鹽噪聲
椒鹽噪聲是由圖像傳感器,傳輸信道,解碼處理等產生的黑白相間的亮暗點噪聲。椒鹽噪聲是指兩種噪聲,一種是鹽噪聲(白色,灰度值=255),另一種是胡椒噪聲(pepper noise,黑色,灰度值=0)。前者是高灰度噪聲,後者屬於低灰度噪聲。一般兩種噪聲同時出現,呈現在圖像上就是黑白雜點。對於彩色圖像,則表現爲單個像素三通道隨機出現255與0.
中值濾波函數
void medianBlur( InputArray src, OutputArray dst,int ksize );
//參數
/*
src — 輸入圖像
dst — 輸出圖像, 必須與 src 相同類型
ksize — 內核大小 (只需一個值,因爲使用正方形窗口),必須爲奇數。
*/
//演示代碼
cv::Mat image = imread("f:\\images\\castle.jpg",1);
cv::resize(image,image,cv::Size(),0.3,0.3);
// 增加噪聲
salt(image,3000);
pepper(image,3000);
//展示噪聲結果
cv::imshow("salt image",image);
//中值濾波
Mat result;
cv::medianBlur(image,result,3);
//展示濾波之後的結果
cv::imshow("nedian filted image",result);
cv::waitKey();
4.OpenCV-形態學處理
這一章的內容也是由MATLAB仿真過來的,主要實現的是形態學變換。形態學變換最基本的兩種變換是:腐蝕與膨脹。然後以這兩種操作可以發展出多種新的形態學操作:開閉運算、形態學梯度、“頂帽”、“黑帽”等
-
開運算(opening operation)
本質上是先腐蝕再膨脹的過程,公式:,作用是:用來消除小物體、在纖細點處分離物體、平滑較大物體的邊界的同時並不明顯改變其面積
-
閉運算(closing operation)
本質是先膨脹再腐蝕的過程,公式:.作用是:閉運算能夠排除小型黑洞
-
形態學梯度 (Morphological Gradient)
形態學梯度爲膨脹圖與腐蝕圖之差,公式:
作用:將團塊的邊緣突出來,也可以保留物體的邊緣輪廓
-
頂帽(Top Hat)
本質是原圖與開運算的差,公式:
作用:因爲開運算是放大裂縫或者局部低亮度區域,因此,從原圖中減去開運算之後的圖,得到的結果突出了比原圖輪廓周圍的區域更明亮的區域。,應用於分離比臨近點亮一些的斑塊,當一幅圖具有大幅的背景時候,小微物品具有比較規律的情況,可以使用頂帽計算進行背景提取。
- 黑帽(Black Hat)
本質是閉運算的結果與原圖像之差,公式:
黑帽運算效果突出比原圖輪廓周圍的區域更暗的區域,並且這一操作和選擇的核大小有關,所以黑帽運算用來分離比臨近點暗一點的斑塊。
5.OpenCV-空洞填補
同樣這個問題也是解決的仿真問題。在MATLAB中採用imfill可以很容易實現空洞填充操作。但是在OpenCV中沒有這樣的函數。
實現步驟:
- 原圖爲A,A向外延展1到2個像素,將值填充爲背景色(0),標記爲B
- 使用floodFill函數將B的大背景填充,填充值爲前景色(255),種子點爲(0,0)即可(確保(0,0)點位於大背景),標記爲C
- 將填充好的圖像剪裁爲原圖像大小(去掉延展區域),標記爲D
- 將D取反與A相加得到填充的圖像,公式E=A|(~D)
//參考代碼 #include "stdafx.h" #include<opencv2/core/core.hpp> #include<opencv2/highgui/highgui.hpp> #include<opencv2/imgproc/igporc.hpp> using namespace std; using namespace cv; void fillHole(const Mat srcBw, Mat &dstBw) { Size m_size = srcBw.size(); Mat temp = Mat::zeros(m_size.height+2,m_size.width+2,srcBw.type()); //延展圖像 srcBw.copyTo(Temp(Range(1,m_size.height+1),Range(1,m_size.width+1))); cv::floodFill(Temp,Point(0,0),Scalar(255)); Mat cutImg; //剪裁延展的圖像 Temp(Range(1,m_size.height+1),Range(1,m_size.width+1)).copyTo(cutImg); dst = srcBw | (~cutImg); } int main(){ Mat img = cv::imread("23.jpg"); Mat gray; cv::cvtColor(img,gray,CV_RGB2GRAY); Mat bw; cv::threshold(gray,bw,0,255,CV_THRESH_BINARY|CV_THRESH_OTSU); Mat bwFill; fillHole(bw,bwFill); imshow("填充之前",gray); imshow("填充之後",bwFill); waitKey(); return 0; }
參考文獻
CvArr、Mat、CvMat、IplImage、BYTE轉換(總結而來)