目錄
- 1、輪廓的定義
- 2、如何在圖像中找到輪廓
- opencv自帶的查找輪廓函數:findContours()
- 3、輪廓的表達方式
- 1.頂點的序列
- 2.Freeman鏈碼
- 4、輪廓之間的組織方式
- 5、輪廓的特點(這部分可以展開來詳細探討,這里不做過多解釋)
- 6、輪廓的匹配
- 7、繪制輪廓drawContours()
- 8、例子
- 動態檢測圖形的輪廓
- 步驟
- 理解
- 演示效果
1、輪廓的定義
輪廓是構成任何一個形狀的邊界或外形線。
2、如何在圖像中找到輪廓
利用OpenCV提供的方法cvFindContours()可以很方便的查找輪廓。cvFindContours()方法從二值圖像中尋找輪廓。
因此此方法處理的圖像可以是從cvCanny()函數得到的有邊緣像素的圖像,或者從cvThreshold()及cvAdaptiveThreshold()得到的圖像,這時的邊緣是正和負區域之間的邊界。
opencv自帶的查找輪廓函數:findContours()
參數講解:
【1】檢測輪廓
//定義輪廓和層次結構
vector<vector<Point>> contours; //每個輪廓存儲為一個點向量
vector<Vec4i>hierarchy; // Vec4i
findContours(srcImage,contours, //輪廓數組hierarchy,RETR_EXTERNAL, //獲取外輪廓CHAIN_APPROX_NONE //獲取每個輪廓中的每個像素);
3、輪廓的表達方式
1.頂點的序列
序列是內存存儲器中可以存儲的一種對象。序列是某種結構的鏈表。序列在內存中被實現為一個雙端隊列,因此序列可以實習快速的隨機訪問,以及快速刪除頂端的元素,但是從中間刪除元素則稍慢些。
用多個頂點(或各點間的線段)來表達輪廓。假設要表達一個從(0,0)到(2,2)的矩形,
(1)如果用點來表示,那么依次存儲的可能是:(0,0),(1,0),(2,0),(2,1),(2,2),(1,2),(0,2),(0,1);
(2)如果用點間的線段來表達輪廓,那么依次存儲的可能是:(0,0),(2,0),(2,2),(0,2)。
2.Freeman鏈碼
Freeman鏈碼需要一個起點,以及從起點出發的一系列位移。每個位移有8個方向,從0~7分別指向從正北開始的8個方向。假設要用Freeman鏈碼表達從(0,0)到(2,2)的矩形,可能的表示方法是:起點(0,0),方向鏈2,2,4,4,6,6,0,0。
4、輪廓之間的組織方式
在查找到輪廓之后,不同輪廓是怎么組織的呢?根據不同的選擇,它們可能是:(1)列表;(2)雙層結構;(3)樹型結構。
從縱向上來看,列表只有一層,雙層結構有一或者兩層,樹型結構可能有一層或者多層。
如果要遍歷所有的輪廓,可以使用遞歸的方式。
5、輪廓的特點(這部分可以展開來詳細探討,這里不做過多解釋)
1.輪廓的多邊形逼近
輪廓的多邊形逼近指的是:使用多邊形來近似表示一個輪廓。
多邊形逼近的目的是為了減少輪廓的頂點數目。
多邊形逼近的結果依然是一個輪廓,只是這個輪廓相對要粗曠一些。 可以使用方法cvApproxPoly()2.輪廓的關鍵點
輪廓的關鍵點是:輪廓上包含曲線信息比較多的點。關鍵點是輪廓頂點的子集。
可以使用cvFindDominantPoints函數來獲取輪廓上的關鍵點,該函數返回的結果一個包含 關鍵點在輪廓頂點中索引 的序列。再次強調:是索引,不是具體的點。如果要得到關鍵點的具體坐標,可以用索引到輪廓上去找。
3.輪廓的周長和面積
輪廓的周長可以用cvContourPerimeter或者cvArcLength函數來獲取。
輪廓的面積可以用cvContourArea函數來獲取。4.輪廓的邊界框
有三種常見的邊界框:矩形、圓形、橢圓。
(1)矩形:在圖像處理系統中提供了一種叫Rectangle的矩形,不過它只能表達邊垂直或水平的特例;OpenCv中還有一種叫Box的矩形,它跟數學上的矩形一致,只要4個角是直角即可。
如果要獲取輪廓的Rectangle,可以使用cvBoundingRect函數。
如果要獲取輪廓的Box,可以使用cvMinAreaRect2函數。
(2)圓形
如果要獲取輪廓的圓形邊界框,可以使用cvMinEnclosingCircle函數。
(3)橢圓
如果要獲取輪廓的橢圓邊界框,可以使用cvFitEllipse2函數。
5.輪廓的矩
矩是通過對輪廓上所有點進行積分運算(或者認為是求和運算)而得到的一個粗略特征。
1空間矩
空間矩的實質為面積或者質量。可以通過一階矩計算質心/重心。
重心:
2中心矩
中心矩體現的是圖像強度的最大和最小方向(中心矩可以構建圖像的協方差矩陣),其只具有平移不變性,所以用中心矩做匹配效果不會很好。
3歸一化的中心矩
歸一化后具有尺度不變性。
4Hu矩
6.輪廓的輪廓樹
輪廓樹用來描述某個特定輪廓的內部特征。注意:輪廓樹跟輪廓是一一對應的關系;輪廓樹不用于描述多個輪廓之間的層次關系。
輪廓樹的創建過程:
從一個輪廓創建一個輪廓樹是從底端(葉子節點)到頂端(根節點)的。首先搜索三角形突出或者凹陷的形狀的周邊(輪廓上的每一個點都不是完全和它的相鄰點共線的)每個這樣的三角形被一條線段代替,這條線段通過連接非相鄰點的兩點得到;因此實際上三角形或者被削平或者被填滿。每個這樣的替換都把輪廓的頂點減少,并且給輪廓樹創建一個新節點。如果這樣的一個三角形的兩側有原始邊,那么她就是得到的輪廓樹的葉子;如果一側已是一個三角形,那么它就是那個三角形的父節點。這個過程的迭代最終把物體的外形簡稱一個四邊形,這個四邊形也被剖開;得到的兩個三角形是根節點的兩個子節點。
結果的二分樹最終將原始輪廓的形狀性比編碼。每個節點被它所對應的三角形的信息所注釋。
這樣建立的輪廓樹并不太魯棒,因為輪廓上小的改變也可能會徹底改變結果的樹,同時最初的三角形是任意選取的。為了得到較好的描述需要首先使用函數cvApproxPoly()之后將輪廓排列(運用循環移動)成最初的三角形不怎么收到旋轉影響的狀態。
可以用函數cvCreateContourTree來構造輪廓樹。
7.輪廓的凸包和凸缺陷
這塊內容會在后續詳細探討。
8.輪廓的成對幾何直方圖
成對幾何直方圖(pairwise geometrical histogram PGH)是鏈碼編碼直方圖(chain code histogram CCH)的一個擴展或者延伸。CCH是一種直方圖,用來統計一個輪廓的Freeman鏈碼編碼每一種走法的數字。這種直方圖的一個優良性質為當物體旋轉45度,那么新直方圖是老直方圖的循環平移。這樣就可以不受旋轉影響。
(1)輪廓保存的是一系列的頂點,輪廓是由一系列線段組成的多邊形。對于看起來光滑的輪廓(例如圓),只是線段條數比較多,線段長度比較短而已。實際上,電腦中顯示的任何曲線都由線段組成。
(2)每兩條線段之間都有一定的關系,包括它們(或者它們的延長線)之間的夾角,兩條線段的夾角范圍是:(0,180)。
(3)每兩條線段上的點之間還有距離關系,包括最短(小)距離、最遠(大)距離,以及平均距離。最大距離我用了一個偷懶的計算方法,我把輪廓外界矩形的對角線長度看作了最大距離。
(4)成對幾何直方圖所用的統計數據包括了夾角和距離。
6、輪廓的匹配
1.Hu矩匹配
輪廓的Hu矩對包括縮放、旋轉和鏡像映射在內的變化具有不變性。cvMatchShapes函數可以很方便的實現對2個輪廓間的匹配。
2.輪廓樹匹配
用樹的形式比較兩個輪廓。cvMatchContourTrees函數實現了輪廓樹的對比。
3.成對幾何直方圖匹配
在得到輪廓的成對幾何直方圖之后,可以使用直方圖對比的方法來進行匹配。
7、繪制輪廓drawContours()
輪廓的繪制比較簡單,用上面提到的方法取得輪廓的所有點,然后把這些點連接成一個多邊形即可。
使用函數drawContours(),更加方便。
使用例子:
//【2】繪制輪廓
//遍歷所有頂層的輪廓,用不同的顏色繪制出來
int index = 0;
for (; index >= 0; index = hierarchy[index][0])
{Scalar color(rand() & 255,rand() & 255,rand() & 255); //隨機數drawContours(dstImage, //outputImagecontours, //輪廓信息index, //當前輪廓的索引值color, //輪廓顏色FILLED, //繪制在輪廓內部8, //8連通線型hierarchy); //輪廓間的層次信息
}
8、例子
動態檢測圖形的輪廓
步驟
【1】讀取原圖,轉為灰度圖并高斯模糊
【2】canny檢測圖像邊緣(滑動條控制閾值)
【3】對canny算子掃描后的圖像進行查找輪廓
【4】繪制輪廓
理解
Canny之類的邊緣檢測算法可以根據像素間的差異檢測出輪廓邊界的像素,但是它并沒有將輪廓作為一個整體。
對canny掃描后的圖像查找輪廓是一種較好的選擇。
#include <opencv2/opencv.hpp>
#include <iostream>
#include "windows.h"
#include <stdio.h>
#define WINDOW_NAME "【程序窗口】" //為窗口標題定義的宏using namespace cv;
using namespace std;
//===========================動態檢測圖形的輪廓====================//=================全局變量聲明=================
Mat g_srcImage;
Mat g_grayImage;
int g_nThresh = 80;
int g_nThresh_max = 255;
RNG g_rng(12345);
Mat g_cannyMat_output;
vector<vector<Point>> g_vContours;
vector<Vec4i> g_vHierarchy;//=============全局函數聲明===============
void on_ThreshChange(int,void*);
int main()
{// Read image 讀取圖像SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_GREEN); //字體為綠色//載入原圖g_srcImage = imread("D:\\opencv_picture_test\\lena.jpg",1);//Mat srcImage = imread("D:\\opencv_picture_test\\形態學操作\\孔洞.png", 0); //讀取灰度圖//轉換成灰度并且模糊化降噪cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);blur(g_grayImage, g_grayImage, Size(3, 3));//創建窗口namedWindow("原始圖窗口", WINDOW_AUTOSIZE);imshow("原始圖窗口", g_srcImage);//創建滑動條并初始化createTrackbar("canny 閾值", "原始圖窗口", &g_nThresh,g_nThresh_max, on_ThreshChange);on_ThreshChange(0,0);waitKey(0);return 0;
}
void on_ThreshChange(int, void*)
{//用Canny算子檢測邊緣Canny(g_grayImage, g_cannyMat_output, g_nThresh, g_nThresh * 2, 3);//尋找輪廓.findContours(g_cannyMat_output, g_vContours, g_vHierarchy,RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));//繪出輪廓Mat drawing = Mat::zeros(g_cannyMat_output.size(), CV_8UC3);for (int i = 0; i < g_vContours.size(); i++){Scalar color = Scalar(g_rng.uniform(0, 255),g_rng.uniform(0, 255), g_rng.uniform(0, 255));//任意值drawContours(drawing, g_vContours, i, color, 2, 8, g_vHierarchy,0, Point());}//顯示效果圖imshow("輪廓圖",drawing);
}
演示效果
參考鏈接:
主講輪廓的特性;
輪廓的矩;
輪廓的矩;
以及《《OpenCV3編程入門》毛星云編著_電子工業出版社》。