1. 程式人生 > >【OpenCV學習筆記】之影象輪廓特徵與影象的矩

【OpenCV學習筆記】之影象輪廓特徵與影象的矩

轉載: https://blog.csdn.net/zhu_hongji/article/details/81699736

 

一、影象的輪廓(Contours of Image)

       輪廓可以說是一個很好的影象目標的外部特徵,這種特徵對於我們進行影象分析,目標識別和理解等更深層次的處理都有很重要的意義。那麼,怎麼取提取輪廓呢?

輪廓提取的基本原理:

        (針對二值化的輪廓提取是這樣的)對於一幅背景為白色、目標為黑色的二值影象,如果在圖中找到一個黑色點,且它的8鄰域(或4鄰域)也均為黑色,則說明該點是目標的內部點,將其置為白色,視覺上就像內部被掏空一樣;否則保持黑色不變,該點是目標的邊界點。

這裡順帶提下邊緣檢測,和輪廓提取的區別:

邊緣檢測主要是通過一些手段檢測數字影象中明暗變化劇烈(即梯度變化比較大)畫素點,偏向於影象中畫素點的變化。如canny邊緣檢測,結果通常儲存在和源圖片一樣尺寸和型別的邊緣圖中。 
輪廓檢測指檢測影象中的物件邊界,更偏向於關注上層語義物件。如OpenCV中的findContours()函式, 它會得到每一個輪廓並以點向量方式儲存,除此也得到一個影象的拓撲資訊,即一個輪廓的後一個輪廓、前一個輪廓、父輪廓和內嵌輪廓的索引編號。 

1.1 發現與繪製輪廓

      在OpenCV裡面利用findContours()函式和drawContours()函式實現這一功能。

API介紹:
 

C++:void findContours(InputOutputArray  image,
                       OutputArrayOfArrays  contours,
                       OutputArray  hierarchy,
                            int  mode,
                            int  method,
                            Point  offset = Point() 
                        )   

引數詳解:

引數一: image,單通道影象矩陣,可以是灰度圖,但更常用的是二值影象,一般是經過Canny、拉普拉斯等邊緣檢測運算元處理過的二值影象;

引數二: contours是一個向量,並且是一個雙重向量,向量內每個元素儲存了一組由連續的Point點構成的點的集合的向量,每一組Point點集就是一個輪廓。有多少輪廓,向量contours就有多少元素。

引數三: hierarchy也是一個向量,向量內每個元素儲存了一個包含4個int整型的陣列。向量hiararchy內的元素和輪廓向量contours內的元素是一一對應的,向量的容量相同。hierarchy向量內每一個元素的4個int型變數——hierarchy[i][0] ~hierarchy[i][3],分別表示第i個輪廓的後一個輪廓、前一個輪廓、父輪廓、內嵌輪廓的索引編號。如果當前輪廓沒有對應的後一個輪廓、前一個輪廓、父輪廓或內嵌輪廓的話,則hierarchy[i][0] ~hierarchy[i][3]的相應位被設定為 預設值-1。

引數四: int型的mode,定義輪廓的檢索模式:

模式一:CV_RETR_EXTERNAL只檢測最外圍輪廓,包含在外圍輪廓內的內圍輪廓被忽略

模式二:CV_RETR_LIST 檢測所有的輪廓,包括內圍、外圍輪廓,但是檢測到的輪廓不建立等級關係,彼此之間獨立,沒有等級關係,這就意味著這個檢索模式下不存在父輪廓或內嵌輪廓,所以hierarchy向量內所有元素的第3、第4個分量都會被置為-1,

模式三:CV_RETR_CCOMP 檢測所有的輪廓,但所有輪廓只建立兩個等級關係,外圍為頂層,若外圍內的內圍輪廓還包含了其他的輪廓資訊,則內圍內的所有輪廓均歸屬於頂層

模式四:CV_RETR_TREE, 檢測所有輪廓,所有輪廓建立一個等級樹結構。外層輪廓包含內層輪廓,內層輪廓還可以繼續包含內嵌輪廓。

引數五: int型的method,定義輪廓的近似方法:

方式一:CV_CHAIN_APPROX_NONE 儲存物體邊界上所有連續的輪廓點到contours向量內

方式二:CV_CHAIN_APPROX_SIMPLE 僅儲存輪廓的拐點資訊,把所有輪廓拐點處的點儲存入contours 向量內,拐點與拐點之間直線段上的資訊點不予保留

方式三和四:CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS使用teh-Chinl chain 近似演算法

引數六: Point偏移量,所有的輪廓資訊相對於原始影象對應點的偏移量,相當於在每一個檢測出的輪廓 點上加上該偏移量,並且Point還可以是負值

drawContours(
		//InputOutputArray  binImg, // 輸出影象
		//OutputArrayOfArrays  contours,//  全部發現的輪廓物件
		//Int contourIdx// 輪廓索引號
		//const Scalar & color,// 繪製時候顏色
		//int  thickness,// 繪製線寬
		//int  lineType,// 線的型別LINE_8
		//InputArray hierarchy,// 拓撲結構圖
		//int maxlevel,// 最大層數, 0只繪製當前的,1表示繪製繪製當前及其內嵌的輪廓
		//Point offset = Point()// 輪廓位移,可選
)

示例程式

//輪廓發現(find contour)
//輪廓發現是基於影象邊緣提取的基礎尋找物件輪廓的方法。所以邊緣提取的閾值選定會影響最終輪廓發現結果
//步驟:輸入影象轉為灰度影象cvtColor
      //使用Canny進行邊緣提取,得到二值影象
      //使用findContours尋找輪廓
      //使用drawContours繪製輪廓
 
#include"stdafx.h"
#include<opencv2/opencv.hpp>
#include<iostream>
#include<math.h>
 
using namespace std;
using namespace cv;
 
Mat src, dst;
char input[] = "gray image";
int threshold_value = 100;
int threshold_max = 255;
void find_contour_demo(int, void*);
int main(int argc, char*argv)
{
	src = imread("C:\\Users\\59235\\Desktop\\imag\\mixed_03.png");
	if (!src.data)
	{
		printf("could not load image...\n");
		return -1;
	}
	namedWindow(input, CV_WINDOW_AUTOSIZE);
	namedWindow("result", CV_WINDOW_AUTOSIZE);
	//輸入影象轉為灰度影象
	cvtColor(src, src, CV_BGR2GRAY);
	imshow(input, src);
 
	const char*thresh = "threshold value";
	createTrackbar(thresh, input, &threshold_value, threshold_max, find_contour_demo);
	find_contour_demo(0, 0);
 
	waitKey(0);
	return 0;
}
 
void find_contour_demo(int, void*)
{
	//使用Canny進行邊緣提取,得到二值影象
	Canny(src, dst, threshold_value, threshold_value * 2, 3, false);
	imshow("canny detection", dst);
 
	//使用findContours尋找輪廓
	vector<vector<Point>> contours;
	vector<Vec4i> hierachy;
	findContours(dst, contours, hierachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
	//findContours(
	//InputOutputArray  binImg, // 輸入影象,非0的畫素被看成1,0的畫素值保持不變,8-bit
	//OutputArrayOfArrays  contours,//  全部發現的輪廓物件
	//OutputArray, hierachy// 圖該的拓撲結構,可選,該輪廓發現演算法正是基於影象拓撲結構實現。
	//int mode, //  輪廓返回的模式
	//int method,// 發現方法
	//Point offset = Point()//  輪廓畫素的位移,預設(0, 0)沒有位移)
 
	//使用drawContours繪製輪廓
	Mat drawImage = Mat::zeros(src.size(), CV_8UC3);
	RNG rng(12345);
	for (int i = 0; i < contours.size(); i++)
	{
		Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
		drawContours(drawImage, contours, i, color, 2, LINE_AA, hierachy, 0, Point(0, 0));
		//drawContours(
		//InputOutputArray  binImg, // 輸出影象
		//OutputArrayOfArrays  contours,//  全部發現的輪廓物件
		//Int contourIdx// 輪廓索引號
		//const Scalar & color,// 繪製時候顏色
		//int  thickness,// 繪製線寬
		//int  lineType,// 線的型別LINE_8
		//InputArray hierarchy,// 拓撲結構圖
		//int maxlevel,// 最大層數, 0只繪製當前的,1表示繪製繪製當前及其內嵌的輪廓
		//Point offset = Point()// 輪廓位移,可選
	}
 
	imshow("result", drawImage);
	return;
}

效果圖:                        (灰度圖)                                                                                                              

(canny邊緣檢測圖)

                                                                    (繪製輪廓圖)

總結:

OpenCV提取輪廓之後,還可以進行許多操作:

ArcLength() 計算輪廓長度 
ContourArea() 計算輪廓區域的面積 
BoundingRect() 輪廓的外包矩形 
ConvexHull() 提取輪廓的凸包 
IsContourConvex() 測試輪廓的凸性 
MinAreaRect() 輪廓的最小外包矩形 
MinEnclosingCircle() 輪廓的最小外包圓
fitEllipse()用橢圓擬合二維點集
approxPolyDP()逼近多邊形曲線
 

二、影象的矩(Image Moments)

矩的概念介紹

       矩函式在影象分析中有著廣泛的應用,如模式識別、目標分類、目標識別與方位估計、影象的編碼與重構等。從一幅影象計算出來的矩集,不僅可以描述影象形狀的全域性特徵,而且可以提供大量關於該影象不同的幾何特徵資訊,如大小,位置、方向和形狀等。影象矩這種描述能力廣泛應用於各種影象處理、計算機視覺和機器人技術領域的目標識別與方位估計中。

一階矩:與形狀有關;

二階矩:顯示曲線圍繞直線平均值的擴充套件程度;

三階矩:關於平均值的對稱性測量;由二階矩和三階矩可以匯出7個不變矩。而不變矩是影象的統計特性,滿足平移、伸縮、旋轉均不變的不變性、在影象識別領域得到廣泛的應用。

1 、空間矩/幾何矩

空間矩的實質為面積或者質量。可以通過一階矩計算質心/重心。

                                                                \texttt{m} _{ji}= \sum _{x,y}  \left ( \texttt{array} (x,y)  \cdot x^j  \cdot y^i \right )

                                                                重心(中心centers):\bar{x} = \frac{\texttt{m}_{10}}{\texttt{m}_{00}} , \; \bar{y} = \frac{\texttt{m}_{01}}{\texttt{m}_{00}}

2、中心矩

        中心矩體現的是影象強度的最大和最小方向(中心矩可以構建影象的協方差矩陣),其只具有平移不變性,所以用中心矩做匹配效果不會很好。

                                                                  \texttt{mu} _{ji}= \sum _{x,y}  \left ( \texttt{array} (x,y)  \cdot (x -  \bar{x} )^j  \cdot (y -  \bar{y} )^i \right )

3、歸一化的中心矩

      歸一化後具有尺度不變性。

                                                                   \texttt{nu} _{ji}= \frac{\texttt{mu}_{ji}}{\texttt{m}_{00}^{(i+j)/2+1}} .

4、Hu矩

Hu矩由於具有尺度、旋轉、平移不變性,可以用來做匹配。

 cv::moments 計算生成資料:

//影象矩:(Image Moments)
//步驟:提取影象邊緣
//發現輪廓
//計算每個輪廓物件的矩
//計算每個物件的中心、弧長、面積
 
#include"stdafx.h"
#include<opencv2/opencv.hpp>
#include<iostream>
#include<math.h>
 
using namespace cv;
using namespace std;
 
Mat src, dst, drawImage;
const char*result = "moments_demo";
int threshold_value = 120;
int threshold_max = 255;
RNG rng(12345);
void Moments_demo(int, void*);
int main(int argc, char*argv)
{
	src = imread("C:\\Users\\59235\\Desktop\\imag\\mixed_01.png");
	if (!src.data)
	{
		printf("could not load image...\n");
		return -1;
	}
	char input[] = "gray image";
	namedWindow(input, CV_WINDOW_AUTOSIZE);
	namedWindow(result, CV_WINDOW_AUTOSIZE);
	//輸入影象轉為灰度影象
	cvtColor(src, dst, CV_BGR2GRAY);
	GaussianBlur(dst, dst, Size(3, 3), 0, 0);
	imshow(input, dst);
 
	const char*thresh = "threshold value";
	createTrackbar(thresh, result, &threshold_value, threshold_max, Moments_demo);
	Moments_demo(0, 0);
 
	waitKey(0);
	return 0;
}
 
void Moments_demo(int, void*)
{
	//提取影象邊緣
	Mat canny_out;
	Canny(dst, canny_out, threshold_value, threshold_value * 2, 3, false);
	//imshow("canny image", canny_out);
 
	//發現輪廓,找到影象輪廓
	vector<vector<Point>> contours;
	vector<Vec4i> hierachy;
	findContours(canny_out, contours, hierachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
 
	//計算每個輪廓物件的矩
	vector<	Moments> contours_moments(contours.size());
	vector<Point2f> centers(contours.size());
	for (size_t i = 0; i < contours.size(); i++)
	{
		//計算矩
		contours_moments[i] = moments(contours[i]);
		//moments(InputArray  array,//輸入資料
		//bool   binaryImage = false // 是否為二值影象
		centers[i] = Point(static_cast<float>(contours_moments[i].m10 / contours_moments[i].m00), static_cast<float>(contours_moments[i].m01 / contours_moments[i].m00));
		//影象中心Center(x0, y0)=(m10/m00,m01/m00)
	}
 
	src.copyTo(drawImage);
	for (size_t i = 0; i < contours.size(); i++)
	{
		printf("centers point x:%.2f,y:%.2f\n", centers[i].x, centers[i].y);
		printf("contours %d Area:%.2f Arc length:%.2f \n", i, contourArea(contours[i]), arcLength(contours[i], true));
		//contourArea(InputArray  contour,//輸入輪廓資料
		//bool   oriented// 預設false、返回絕對值)
		//arcLength(InputArray  curve,//輸入曲線資料
		//bool   closed// 是否是封閉曲線)
 
		//考慮如何把資料顯示在原影象上
		//double A;
		//A=contourArea(contours[i]);
		//ostringstream os;
		//os << A;
		//putText(drawImage,os.str,centers[i], CV_FONT_BLACK, 2.0, Scalar(0,0,255), 2, 8);
		//依次含義:原圖,輸入字的內容,起始位置,字型,字的大小,顏色,線條大小粗 細,連線域
 
		Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
		drawContours(drawImage, contours, i, color, 2, LINE_AA, hierachy, 0, Point(0, 0));//繪製輪廓
		circle(drawImage, centers[i], 2, color, 2, LINE_AA);//繪製圖形中心
	}
	imshow(result, drawImage);
	return;
}

效果圖: