天天看點

【碎片知識(8) · 計算機視覺基礎】對圖檔中Blob的檢測和計數

本文将通過對一幅米粒圖像的處理,讨論在OpenCV中相關函數對Blob的檢測和計數問題。

OpenCV執行個體中的代碼說明:

1)        關于SimpleBlobDetector的說明:

   其目的是對Blob進行偵測,Blob就是在圖檔中連在一起的一團像素集合,該集合中的像素必須擁有共同的特性(比如灰階值等)。

   OpenCV提供了非常便捷的方法去偵測Blob,并可以基于特定的特征(如顔色、尺寸、形狀等)對這些Blob進行篩選,以避免早期的Blob偵測算法中的過度提取。

   具體原理内容在:https://www.learnopencv.com/blob-detection-using-opencv-python-c/

2)        getStructuringElement()函數:

   該函數的第1個參數表示核心的形狀:

矩形(MORPH_RECT);交叉形(MORPH_CORSS);橢圓形(MORPH_ELLIPSE);

   第2和第3個參數分别是核心的尺寸以及錨點的位置,錨點的位置有預設的(-1, -1),即中心點。

   一般在調用腐蝕(erode)以及膨脹(dilate)函數之前,先定義一個Mat類型的變量來獲得getStructuringElement函數的傳回值。

3)        findContours()函數:

   函數原型:findContours(InputOutputArray image,OutputArrayOfArrays contours, OutputArray hierarchy, int mode,  int method, Point offset=Point());

Ø  image,單通道圖像矩陣,可以是灰階圖,但更常用的是二值圖像,一般是經過Canny、拉普拉斯等邊緣檢測算子處理過的二值圖像。

Ø  contours,定義為“vector<vector<Point>>contours”,是一個向量,并且是一個二維向量,向量内每個元素儲存了一組由連續的Point點構成的點的集合的向量,每一組Point點集就是一個輪廓。有多少輪廓,向量contours就有多少元素。

Ø  hierarchy,定義為“vector<Vec4i>hierarchy”, 定義:typedefVec<int, 4> Vec4i; Vec4i是Vec<int,4>的别名,定義了一個“向量内每一個元素包含了4個int型變量”的向量。

4)        OpenCV中的Rect類:

   構造函數:Rect(x, y, width, height)。x, y 為左上角坐标,width, height 則為高和寬。

   常用的成員函數:Size()傳回值為一個Size;area()傳回矩形的面積;contains(Point)用來判斷點是否在矩形内;inside(Rect)函數判斷矩形是否在該矩形内;tl()傳回左上角點坐标;br()傳回右下角點坐标。

5)        vector的使用方法:

   作用:能夠像容器一樣存放各種類型的對象,簡單地說,vector是一個能夠存放任意類型的動态數組,能夠增加和壓縮資料。vector是一個多功能的,能夠操作多種資料結構和算法的模闆類和函數庫。

   使用vector需要注意以下幾點:

Ø  如果你要表示的向量長度較長(需要為向量内部儲存很多數),容易導緻記憶體洩漏,而且效率會很低;

Ø  vector作為函數的參數或者傳回值時,需要注意它的寫法:double Distance(vector<int>&a, vector<int>&b) 其中的“&”絕對不能少。

Ø  vector的元素不僅僅可以是int,double,string,還可以是結構體,但是要注意:結構體要定義為全局的,否則會出錯。

   執行個體:

Ø  vector<int>test; 建立一個vector,int為數組元素的資料類型,test為動态數組名。

Ø  vector<vector<Point2f> > points; 定義一個二維數組。

   基本操作:

Ø  建立vector對象,vector<int> vec;

Ø  尾部插入數字:vec.push_back(a);

Ø  使用下标通路元素,cout<<vec[0]<<endl;記住下标是從0開始的。

Ø  使用疊代器通路元素。vector<int>::iterator it;

for(it=vec.begin();it!=vec.end();it++)

cout<<*it<<endl;

Ø  插入元素:vec.insert(vec.begin()+i,a);在第i+1個元素前面插入a。

Ø  删除元素:vec.erase(vec.begin()+2);删除第3個元素。

Ø  vec.erase(vec.begin()+i,vec.end()+j);删除區間[i, j-1];區間從0開始。

Ø  向量大小:vec.size();

Ø  清空:vec.clear();

/* ***************************************************************************************************
      任務目标:
	                                     檢測圖像中所有面積大于50的米粒。
      
	  任務流程:
                         圖像采集(取到圖像)-> 圖像預處理 -> 基于灰階的門檻值分割
                         -> 圖像特征描述及目标分析 -> 得到最終結果。
*************************************************************************************************** */

#include "opencv2/opencv.hpp"  // 聲明頭檔案

using namespace cv;  // "cv"命名空間涵蓋了OpenCV中大多需求
using namespace std;  // "std"标準C++命名空間

void setDetectParams(SimpleBlobDetector::Params ¶ms);
// 設定利用SimpleBlobDetector檢測Blob的"smart point"

void main()
{
	char *fn = "Rice.jpg";
	Mat image = imread(fn);  // 用imread函數讀取*fn指針所指的圖檔,并放入image中
	if (image.empty())
	{
		cout << "Open the image failure!!!" << endl;
		
		system("pause");
		return;
	}  // 檢測該圖檔是否被打開

	Mat gray, bw;  // gray存放灰階化後的圖像,bw存放二值化後的圖像

	cvtColor(image, gray, COLOR_BGR2GRAY);
	// 為後續的門檻值化做準備,因為threshold函數隻能接受灰階圖像
	threshold(gray, bw, 0, 0xff, CV_THRESH_OTSU);
	// 門檻值化有多重方式,這裡采用的是大津算法,0代表采用預設門檻值,0xff及255(白色)

	Mat element = getStructuringElement(MORPH_CROSS, Size(3, 3));
	// 為形态學操作定義Mat類element核心,此處采用的十字形,尺寸3×3,錨點在中心的核心
	morphologyEx(bw, bw, MORPH_OPEN, element);
	// morphologyEx(bw, bw, MORPH_CLOSE, element);
	// 形态學操作:開運算或閉預算

	Mat seg = bw.clone();  // 将二值化圖像複制一份放入seg當中
	vector<vector<Point>> cnts;  // 資料類型為point的二維數組,第一維區分Blob,第二維區分單一Blob邊緣的點
	// 設定二維向量cnts指代Blob邊緣上的點,而這個點有兩個參數:指向哪個Blob和該點是該Blob邊緣上哪個點
	findContours(seg, cnts, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
	// 目标是seg的邊緣點,此處隻檢測最外圍輪廓,包含在外輪廓内的内輪廓将被忽略

	float area;  // 定義浮點型變量area存放每一個米粒的面積
	Rect rect;  // 定義了Rect型的矩形框變量"rect",用以将識别出的米粒框起來
	int count = 0; // 定義整型變量count計數米粒的數量
	string strCount;  // 字元型變量strCount
	
	for (int i = cnts.size() - 1; i >= 0; i--)  // i指代第一維,即用size傳回一共有多少米粒,對每一個米粒進行循環
	{
		vector<Point> c = cnts[i];  // 定義一個一維vector數組c将米粒集合存放其中
		area = contourArea(c);  // 利用contourArea函數傳回c中第i粒米的邊緣封閉曲線所圍的面積
		if (area < 10)  // 對米粒的面積大小設一個門檻值,如果小于該門檻值則忽略,認為這個Blob是噪聲
		{
			continue;
		}

		// 如果大于該門檻值
		count++;  // 米粒統計數加1
		cout << "blob" << i << ":" << area << endl;  // 輸出第幾粒米和相應面積
		rect = boundingRect(c);  // 對第i粒米調用boundingRect函數生成其外接矩形,并對其編号
		rectangle(image, rect, Scalar(0, 0, 0xff), 1);  // 在image中的rect矩形用藍色寬度為1個像素的矩形框辨別出來

		stringstream ss;  // 定義字元串流将記憶體中的字元串count與輸入輸出流聯系起來
		ss << count;
		ss >> strCount;
		putText(image, strCount, Point (rect.x, rect.y), CV_FONT_HERSHEY_PLAIN, 0.5, Scalar (0, 0xff, 0));
		// 在圖像image的左上角(即Point (rect.x, rect.y))上顯示文本strCount,
		// 字型是(CV_...),字号0.5表示普通字型的一半,顔色采用綠色

		cout << "The Quantity of rice is " << count << endl;
		imshow("Original Image", image);
		imshow("Thresholding Image", bw);
	}

	waitKey();
}
           

執行個體結果:

【碎片知識(8) · 計算機視覺基礎】對圖檔中Blob的檢測和計數

注意:這裡依然在findContour函數中遇到了初始化問題,是以最終結果是在release中實作的。

繼續閱讀