本文将通過對一幅米粒圖像的處理,讨論在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();
}
執行個體結果:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIwczLcVmds92czlGZvwVP9EUTDZ0aRJkSwk0LcxGbpZ2LcBDM08CXlpXazRnbvZ2LcRlMMVDT2EWNvwFdu9mZvwFNVRkTzMmaNVTT6hVdsdUZwZlMkZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39zM5ETNwcjMyEDOxIDM4EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
注意:這裡依然在findContour函數中遇到了初始化問題,是以最終結果是在release中實作的。