1. 程式人生 > >Opencv之獲取邊緣和畫輪廓

Opencv之獲取邊緣和畫輪廓

有很多時候,我們需要獲得圖形上的某物體輪廓,Opencv提供兩個函式findContours()和drawContours(),一個是尋找輪廓,一個是畫輪廓,下面就來介紹這兩個函式:

一、findContours()

void findContours//提取輪廓,用於提取影象的輪廓
(
InputOutputArray image,//輸入影象,必須是8位單通道影象,並且應該轉化成二值的
OutputArrayOfArrays contours,//檢測到的輪廓,每個輪廓被表示成一個point向量
OutputArray hierarchy,//可選的輸出向量,包含影象的拓撲資訊。其中元素的個數和檢測到的輪廓的數量相等
int mode,//說明需要的輪廓型別和希望的返回值方式
int method,//輪廓近似方法
Point offset = Point()
)

CV_RETR_EXTERNAL 只檢測出最外輪廓即c0。圖2中第一個輪廓指向最外的序列,除此之外沒有別的連線。

    CV_RETR_LIST 檢測出所有的輪廓並將他們儲存到表(list)中,圖2中描繪了這個表,被找到的9條輪廓相互之間由h_prev和h_next連線。這裡並沒有表達出縱向的連線關係,沒有使用v_prev和v_next.

    CV_RETR_COMP 檢測出所有的輪廓並將他們組織成雙層的結構,第一層是外部輪廓邊界,第二層邊界是孔的邊界。從圖2可以看到5個輪廓的邊界,其中3個包含孔。最外層邊界c0有兩個孔,c0之間的所有孔相互間由h_prev和h_next指標連線。

    CV_RETR_TREE 檢測出所有輪廓並且重新建立網狀的輪廓結構。圖2中,根節點是最外層的邊界c0,c0之下是孔h00,在同一層中與另一個孔h01相連線。同理,每個孔都有子節點(相對於c000和c010),這些子節點和父節點被垂直連線起來。這個步驟一直持續到影象最內層的輪廓,這些輪廓會成為樹葉節點。

注意,method 也就是輪廓的近似方法

       CV_CHAIN_CODE 用freeman鏈碼輸出輪廓,其他方法輸出多邊形(頂點的序列)。

       CV_CHAIN_APPROX_NONE將鏈碼編碼中的所有點轉換為點。

       CV_CHAIN_APPROX_SIMPLE壓縮水平,垂直或斜的部分,只儲存最後一個點。

       CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_QPPROX_TC89_KCOS使用Teh-Chin鏈逼近演算法中的一個。

       CV_LINK_RUNS與上述的演算法完全不同,連線所有的水平層次的輪廓。


二、drawContours()

void drawContours//繪製輪廓,用於繪製找到的影象輪廓
(
 InputOutputArray image,//要繪製輪廓的影象
 InputArrayOfArrays contours,//所有輸入的輪廓,每個輪廓被儲存成一個point向量
 int contourIdx,//指定要繪製輪廓的編號,如果是負數,則繪製所有的輪廓
 const Scalar& color,//繪製輪廓所用的顏色
 int thickness = 1, //繪製輪廓的線的粗細,如果是負數,則輪廓內部被填充
 int lineType = 8, /繪製輪廓的線的連通性
 InputArray hierarchy = noArray(),//關於層級的可選引數,只有繪製部分輪廓時才會用到
 int maxLevel = INT_MAX,//繪製輪廓的最高級別,這個引數只有hierarchy有效的時候才有效
                                          //maxLevel=0,繪製與輸入輪廓屬於同一等級的所有輪廓即輸入輪廓和與其相鄰的輪廓
                                          //maxLevel=1, 繪製與輸入輪廓同一等級的所有輪廓與其子節點。
                                          //maxLevel=2,繪製與輸入輪廓同一等級的所有輪廓與其子節點以及子節點的子節點
 Point offset = Point()
)

三、程式實現

#include <opencv2/opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;

#define WINDOWN_1 "原圖"
#define WINDOWN_2 "CANNY圖"
#define WINDOWN_3 "效果圖"

Mat srcImage, grayImage, out_Canny;
int min_Thresh = 50;
int max_Thresh = 250;

vector<vector<Point>> g_vContours;
vector<Vec4i> g_vHierarchy;

RNG G_RNG(1234);
void Find_Draw_COntours(int ,void*);

int main()
{
	srcImage = imread("D://vvoo//BMW_1.jpg");
	namedWindow(WINDOWN_1,1);
	imshow(WINDOWN_1, srcImage);

	cvtColor(srcImage, grayImage, COLOR_BGR2GRAY);
	blur(grayImage, grayImage, Size(3, 3));

	createTrackbar("CANNY 值:", WINDOWN_1, &min_Thresh, max_Thresh, Find_Draw_COntours);
	Find_Draw_COntours(0, 0);
	waitKey(0);
	return 0;
}
void Find_Draw_COntours(int, void*)
{
	Canny(grayImage, out_Canny, min_Thresh, max_Thresh *2, 3);
	imshow(WINDOWN_2, out_Canny);

	findContours(out_Canny, g_vContours, g_vHierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
	Mat Drawing = Mat::zeros(out_Canny.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(WINDOWN_3, Drawing);
}
結果:



三、實際應用

       提取到輪廓後,其實我們更關心的是如果把這些輪廓轉換為可以利用的特徵,也就是涉及到輪廓的描述問題,這時就有多種方法可以選擇,比如向量化為多邊形、矩形、橢圓等。OpenCV裡提供了一些這樣的函式。

// 輪廓表示為一個矩形 
Rect r = boundingRect(Mat(contours[0])); 
rectangle(result, r, Scalar(255), 2); 
// 輪廓表示為一個圓 
float radius; 
Point2f center; 
minEnclosingCircle(Mat(contours[1]), center, radius); 
circle(result, Point(center), static_cast<int>(radius), Scalar(255), 2); 
// 輪廓表示為一個多邊形 
vector<Point> poly; 
approxPolyDP(Mat(contours[2]), poly, 5, true); 
vector<Point>::const_iterator itp = poly.begin(); 
while (itp != (poly.end() - 1)) 
{ 
    line(result, *itp, *(itp + 1), Scalar(255), 2); 
    ++itp; 
} 
line(result, *itp, *(poly.begin()), Scalar(255), 2); 
// 輪廓表示為凸多邊形 
vector<Point> hull; 
convexHull(Mat(contours[3]), hull); 
vector<Point>::const_iterator ith = hull.begin(); 
while (ith != (hull.end() - 1)) 
{ 
    line(result, *ith, *(ith + 1), Scalar(255), 2); 
    ++ith; 
} 
line(result, *ith, *(hull.begin()), Scalar(255), 2);

    程式中我們依次畫了矩形、圓、多邊形和凸多邊形。最終效果如下:


#include<opencv2\opencv.hpp>
#include<iostream>
using namespace cv;
using namespace std;

#define WINDOW_1 "原圖"
#define WINDOW_2 "效果圖"
int min_area = 13;
int max_area = 100;
vector<vector<Point>> g_vContours;
vector<Vec4i> g_vHierarchy;
RNG g_rng(12345);

Mat srcImage,grayImage,b_grayImage,binaryImage;
Mat foreground, d_foreground, c_foreground;

void on_ThreshChange(int, void*);
int main()
{
	srcImage=imread("D://vvoo//soldier.jpg",1);
	cvtColor(srcImage, grayImage, COLOR_BGR2GRAY);
	blur(grayImage, b_grayImage, Size(3, 3));
	threshold(b_grayImage, binaryImage, 100, 255, CV_THRESH_BINARY);

	Mat element_1 = getStructuringElement(MORPH_RECT, Size(3,3));
	Mat element_2 = getStructuringElement(MORPH_RECT, Size(5,5));
	erode(binaryImage, foreground, element_1);
	dilate(foreground, d_foreground, element_2);
	
	namedWindow(WINDOW_1, WINDOW_AUTOSIZE);
	/*imshow(WINDOW_1, srcImage);*/
	createTrackbar("area 值:", WINDOW_1, &min_area, max_area, on_ThreshChange);
	on_ThreshChange(0, 0);
	
	
    /*  imshow("二值化", binaryImage);
	imshow("形態學處理後前景", d_foreground);*/
	/*imshow("Canny", c_foreground);*/
	
	waitKey(0);
	return 0;
}
void on_ThreshChange(int, void*)
{
	Canny(d_foreground, c_foreground, 100, 200,3);
	findContours(c_foreground, g_vContours, g_vHierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE, Point(0, 0));
	Mat drawing = Mat::zeros(srcImage.size(), CV_8UC3);

	for (int i = 0; i< g_vContours.size(); i++)
	{
		drawContours(drawing, g_vContours, i, Scalar(255,255,255), 3, 18, g_vHierarchy, 0, Point());
		double area = contourArea(Mat(g_vContours[i]));//計算輪廓面積
		if (area>min_area && area < 100)
		{
			Scalar color = Scalar(g_rng.uniform(0, 255), g_rng.uniform(0, 255), g_rng.uniform(0, 255));//任意值
			Rect rect = boundingRect(Mat(g_vContours[i]));//計算點集的最外面矩形邊界
			rectangle(drawing, rect,color, 2);
			rectangle(srcImage, rect, /*Scalar(0,0,255)*/color, 2);
			rectangle(c_foreground, rect, color, 2);
			
		}
	}
	imshow(WINDOW_1, srcImage);
	imshow(WINDOW_2, drawing);
	imshow("Canny", c_foreground);

}

不明白哪裡錯了,為什麼在原圖srcImage上畫的矩形基本上不隨著滑動按鈕而變化呢?求解!


按理說三幅圖的矩形框應該是一樣的,但最後一張和前兩張不一樣,why?


五、參考資料