輪廓(查找和繪製輪廓、輪廓的表達與組織、輪廓的特性)

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矩
12
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(),更加方便。
1
2
使用例子:

//【2】繪製輪廓
//遍歷所有頂層的輪廓,用不同的顏色繪製出來
int index = 0;
for (; index >= 0; index = hierarchy[index][0])
{
	Scalar color(rand() & 255,rand() & 255,rand() & 255);		//隨機數
	drawContours(dstImage,		//outputImage
				contours, 		//輪廓信息
				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編程入門》毛星雲編著_電子工業出版社》。

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