OpenCV探索之路(十一):輪廓查找和多邊形包圍輪廓


Canny一類的邊緣檢測算法可以根據像素之間的差異,檢測出輪廓邊界的像素,但它沒有將輪廓作為一個整體。所以要將輪廓提起出來,就必須將這些邊緣像素組裝成輪廓。

OpenCV中有一個很強大的函數,它可以從二值圖像中找到輪廓:findContours函數。

有時我們還需要把找到的輪廓畫出來,那就要用到函數drawContours了。

findContours函數和那就要用到函數drawContours函數一般配套使用。

#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;
void main() 
{
	Mat original = imread("test5.jpg");
	namedWindow("My original");
	imshow("My original", original);
	Mat gray = original;
	cvtColor(gray, gray, CV_RGB2GRAY);//灰度化

	int thresh_size = (100 / 4) * 2 + 1; //自適應二值化閾值
	adaptiveThreshold(gray, gray, 255, CV_ADAPTIVE_THRESH_GAUSSIAN_C, CV_THRESH_BINARY_INV, thresh_size, thresh_size / 3);
	//morphologyEx(gray, gray, MORPH_OPEN, Mat());//形態學開運算去噪點

	imshow("gray", gray);

	vector<vector<Point> > contours;
	findContours(gray, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); //找輪廓
	vector<vector<Point>> contours1;
	for (int i = 0; i < contours.size(); ++i)
	{
	    contours1.push_back(contours[i]);
	}

	Mat hole(gray.size(), CV_8U, Scalar(0)); //遮罩圖層
	drawContours(hole, contours1, -1, Scalar(255), CV_FILLED); //在遮罩圖層上,用白色像素填充輪廓,得到MASK
	namedWindow("My hole");
	imshow("My hole", hole);
	Mat crop(original.rows, original.cols, CV_8UC3);
	original.copyTo(crop, hole);//將原圖像拷貝進遮罩圖層
	namedWindow("My warpPerspective");
	imshow("My warpPerspective", crop);
	waitKey(0);
}

右下角的圖就是提取出來的輪廓圖,真的是非常精准。不過精准只是因為原圖的形狀比較簡單,如果遇到復雜圖片,那情況就不太樂觀了。

使用多邊形把輪廓包圍

在實際應用中,常常會有將檢測到的輪廓用多邊形表示出來的需求。比如在一個全家福中,我想用一個矩形框將我自己的頭像框出來,這樣就需要這方面的知識了。

OpenCv這方面的函數總結如下:

  • 返回指定點集最外部矩形邊界:boundingRect()
  • 尋找給定的點集可旋轉的最小包圍矩形:minAreaRect()
  • 尋找最小包圍圓形:minEnclosingCircle()
  • 用橢圓擬合二維點集:fitEllipse()
  • 逼近多邊形曲線:approxPolyDP()

下面給出這些函數用法的綜合案例。

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>
#include <stdlib.h>

using namespace cv;
using namespace std;

Mat src; Mat src_gray;
int thresh = 100;
int max_thresh = 255;
RNG rng(12345);

/// 函數聲明
void thresh_callback(int, void*);

/** @主函數 */
int main(int argc, char** argv)
{
	/// 載入原圖像, 返回3通道圖像
	src = imread("test5.jpg", 1);

	/// 轉化成灰度圖像並進行平滑
	cvtColor(src, src_gray, CV_BGR2GRAY);
	blur(src_gray, src_gray, Size(3, 3));

	/// 創建窗口
	char* source_window = "Source";
	namedWindow(source_window, CV_WINDOW_AUTOSIZE);
	imshow(source_window, src);

	createTrackbar(" Threshold:", "Source", &thresh, max_thresh, thresh_callback);
	thresh_callback(0, 0);

	waitKey(0);
	return(0);
}

/** @thresh_callback 函數 */
void thresh_callback(int, void*)
{
	Mat threshold_output;
	vector<vector<Point> > contours;
	vector<Vec4i> hierarchy;

	/// 使用Threshold檢測邊緣
	threshold(src_gray, threshold_output, thresh, 255, THRESH_BINARY);
	/// 找到輪廓
	findContours(threshold_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));

	/// 多邊形逼近輪廓 + 獲取矩形和圓形邊界框
	vector<vector<Point> > contours_poly(contours.size());
	vector<Rect> boundRect(contours.size());
	vector<Point2f>center(contours.size());
	vector<float>radius(contours.size());

	for (int i = 0; i < contours.size(); i++)
	{
		approxPolyDP(Mat(contours[i]), contours_poly[i], 3, true);
		boundRect[i] = boundingRect(Mat(contours_poly[i]));
		minEnclosingCircle(contours_poly[i], center[i], radius[i]);
	}


	/// 畫多邊形輪廓 + 包圍的矩形框 + 圓形框
	Mat drawing = Mat::zeros(threshold_output.size(), CV_8UC3);
	for (int i = 0; i< contours.size(); i++)
	{
		Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
		drawContours(drawing, contours_poly, i, color, 1, 8, vector<Vec4i>(), 0, Point());
		rectangle(drawing, boundRect[i].tl(), boundRect[i].br(), color, 2, 8, 0);
		circle(drawing, center[i], (int)radius[i], color, 2, 8, 0);
	}

	/// 顯示在一個窗口
	namedWindow("Contours", CV_WINDOW_AUTOSIZE);
	imshow("Contours", drawing);
}

下面兩張不同閾值的效果圖把檢測到的輪廓分別用多邊形、圓形、矩形框出來了。

圖像的矩

圖像的矩到底是什么?

矩是概率與統計中的一個概念,是隨機變量的一種數字特征。

有點抽象,簡而言之,矩就是圖像的特征信息,比如大小、位置、方向等。

OpenCV提供了一些函數來計算圖像的矩:

  • 矩的重心、主軸、面積等特征計算:moments()
  • 計算輪廓面積:contourArea()
  • 計算輪廓長度:arcLength()

下面的程序,使用了兩種方法計算輪廓面積,第一種使用了moments()函數(程序里的mu[i].m00),第二種使用了contourAra()函數進行面積計算,大家可以看一下兩種方法計算出來的面積有沒有差別。

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>
#include <stdlib.h>

using namespace cv;
using namespace std;

Mat src; Mat src_gray;
int thresh = 100;
int max_thresh = 255;
RNG rng(12345);

/// 函數聲明
void thresh_callback(int, void*);

/** @主函數 */
int main()
{
	/// 讀入原圖像, 返回3通道圖像數據
	src = imread("lol10.jpg");

	/// 把原圖像轉化成灰度圖像並進行平滑
	cvtColor(src, src_gray, CV_BGR2GRAY);
	blur(src_gray, src_gray, Size(3, 3));

	/// 創建新窗口
	char* source_window = "Source";
	namedWindow(source_window, CV_WINDOW_AUTOSIZE);
	imshow(source_window, src);

	createTrackbar(" Canny thresh:", "Source", &thresh, max_thresh, thresh_callback);
	thresh_callback(0, 0);

	waitKey(0);
	return(0);
}

/** @thresh_callback 函數 */
void thresh_callback(int, void*)
{
	Mat canny_output;
	vector<vector<Point> > contours;
	vector<Vec4i> hierarchy;

	/// 使用Canndy檢測邊緣
	Canny(src_gray, canny_output, thresh, thresh * 2, 3);
	/// 找到輪廓
	findContours(canny_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));

	/// 計算矩
	vector<Moments> mu(contours.size());
	for (int i = 0; i < contours.size(); i++)
	{
		mu[i] = moments(contours[i], false);
	}

	///  計算中心矩:
	vector<Point2f> mc(contours.size());
	for (int i = 0; i < contours.size(); i++)
	{
		mc[i] = Point2f(mu[i].m10 / mu[i].m00, mu[i].m01 / mu[i].m00);
	}

	/// 繪制輪廓
	Mat drawing = Mat::zeros(canny_output.size(), CV_8UC3);
	for (int i = 0; i< contours.size(); i++)
	{
		Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
		drawContours(drawing, contours, i, color, 2, 8, hierarchy, 0, Point());
		circle(drawing, mc[i], 4, color, -1, 8, 0);
	}

	/// 顯示到窗口中
	namedWindow("Contours", CV_WINDOW_AUTOSIZE);
	imshow("Contours", drawing);

	/// 通過m00計算輪廓面積並且和OpenCV函數比較
	printf("\t Info: Area and Contour Length \n");
	for (int i = 0; i< contours.size(); i++)
	{
		printf(" * Contour[%d] - Area (M_00) = %.2f - Area OpenCV: %.2f - Length: %.2f \n", i, mu[i].m00, contourArea(contours[i]), arcLength(contours[i], true));
		Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
		drawContours(drawing, contours, i, color, 2, 8, hierarchy, 0, Point());
		circle(drawing, mc[i], 4, color, -1, 8, 0);
	}
}

從結果看來,兩種方法計算得到的面積是一樣的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM