本文将通过对一幅米粒图像的处理,讨论在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
Ø hierarchy,定义为“vector
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
Ø vector的元素不仅仅可以是int,double,string,还可以是结构体,但是要注意:结构体要定义为全局的,否则会出错。
实例:
Ø vector
Ø vector
基本操作:
Ø 创建vector对象,vector
Ø 尾部插入数字:vec.push_back(a);
Ø 使用下标访问元素,cout<
Ø 使用迭代器访问元素。vector
for(it=vec.begin();it!=vec.end();it++)
cout<<*it<
Ø 插入元素: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> 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 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();
}
实例结果:
注意:这里依然在findContour函数中遇到了初始化问题,所以最终结果是在release中实现的。