OpenCV的瑣碎知識

這篇博客將介紹一些OpenCV的瑣碎的概念知識以及容易出現錯誤的點。可能大家平時看博客感覺OpenCV沒什麼難的,無非是調用一些庫和函數,但是在實際操作過程中很容易出現翻車的現象。好了,廢話不多說開始本章的內容

內容安排

  • OpenCV各個變量之間的轉換關係
  • 採用OpenCV進行連通域分析的原理以及相關函數
  • OpenCV連通域分析的應用-計算歐拉數(euler)
  • 採用OpenCV進行濾波以及形態學處理的相關原理及函數
  • OpenCV輪廓函數的介紹
  • 參考文獻

1. OpenCV各個變量之間的轉換關係

第一個章節的概念來自於一個函數的應用,當時想要根據MATLAB上的閾值提取函數,實現一個C++版本的,然後在OpenCV上找到一個CVThreshold的函數,因爲這個函數提示需要CvArr* 的變量作爲填充進去。但是我之前使用一般都是Mat類型的變量沒見過類似的。後來查了第一篇文獻才知道,這個新的函數變量是什麼。簡而言之,OpenCV各個變量之間的關係就是:

CvArr
CvMat
IplImage

也就是IplImage是由CvMat派生;CvMat由CvArr派生。因此可以得出CvArr作爲函數的參數,無論是傳入CvMat或者IplImage,在函數內部都算是CvMat。

接下來再講講Mat與CvMat和IplImage之間的異同。首先這兩者都能夠顯示和代表圖像。其次Mat側重於計算,矩陣計算能力更好;CvMat和IplImage更側重於圖像,OpenCV對這兩個變量針對圖像的操作(縮放、單通道、圖像閾值操作等)做了優化。

然後對三個變量分別進行介紹:

  1. 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類型

  2. CvMat類型

    CvMat類似於向量,在創建基礎數據類型,比如二維矩陣:

    CvMat* cvCreatMat(int rows ,int cols , int type);

    其中type 可以是任意預定義數據類型,比如RGB或者其他多通道數據。

  3. 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時遇到的,其實不是什麼難題。

中值濾波

中值濾波是一種非線性濾波器,常用於消除圖像中的椒鹽噪聲。與低通濾波不同的是,中值濾波有利於保留邊緣的尖銳度,但是會洗去均勻介質區域中的紋理。

濾波的原理:

輸入圖像x(n1,n2)x(n_1,n_2)中,以任意一個像素爲中心設置一個確定的領域AAAA的邊長爲2N+1,(N=0,1,2,3....)2N+1,(N=0,1,2,3....)。將領域內個像素的強度值按大小順序排列,取位於中間位置的那個值(中值)作爲該像素點的輸出值,濾波公式:A=x(i,j),y=Medx1,x2,x3,,x2N+1A=x(i,j), y=Med {x_1, x_2, x_3,…,x_{2N+1}}

椒鹽噪聲

椒鹽噪聲是由圖像傳感器,傳輸信道,解碼處理等產生的黑白相間的亮暗點噪聲。椒鹽噪聲是指兩種噪聲,一種是鹽噪聲(白色,灰度值=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)

    本質上是先腐蝕再膨脹的過程,公式:dst=open(src,element)=dilate(erode(src,element))dst=open(src,element)=dilate(erode(src,element))​,作用是:用來消除小物體、在纖細點處分離物體、平滑較大物體的邊界的同時並不明顯改變其面積

  • 閉運算(closing operation)

    本質是先膨脹再腐蝕的過程,公式:dst=close(src,element)=erode(dilate(src,element))dst=close(src,element)=erode(dilate(src,element)).作用是:閉運算能夠排除小型黑洞

  • 形態學梯度 (Morphological Gradient)

    形態學梯度爲膨脹圖與腐蝕圖之差,公式:dst=morphgrad(src,element)=dilate(src,element)erode(src,element)dst=morph_grad(src,element)=dilate(src,element)-erode(src,element)

    作用:將團塊的邊緣突出來,也可以保留物體的邊緣輪廓

  • 頂帽(Top Hat)

    本質是原圖與開運算的差,公式:dst=tophat(src,element)=srcopen(src,element)dst=tophat(src,element)=src-open(src,element)

    作用:因爲開運算是放大裂縫或者局部低亮度區域,因此,從原圖中減去開運算之後的圖,得到的結果突出了比原圖輪廓周圍的區域更明亮的區域。,應用於分離比臨近點亮一些的斑塊,當一幅圖具有大幅的背景時候,小微物品具有比較規律的情況,可以使用頂帽計算進行背景提取。

    • 黑帽(Black Hat)

    本質是閉運算的結果與原圖像之差,公式:dst=blackhat(src,element)=close(src,element)srcdst=blackhat(src,element)=close(src,element)-src

    黑帽運算效果突出比原圖輪廓周圍的區域更暗的區域,並且這一操作和選擇的核大小有關,所以黑帽運算用來分離比臨近點暗一點的斑塊。

    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轉換(總結而來)

OpenCV輪廓層次分析實現歐拉數計算

opencv 連通域需要的函數解析

OpenCV—中值濾波

【OpenCV入門教程之十一】 形態學圖像處理(二):開運算、閉運算、形態學梯度、頂帽、黑帽合輯

OpenCV空洞填充算法

【OpenCV3】圖像輪廓查找與繪製——cv::findContours()與cv::drawContours()詳解

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