天天看点

【碎片知识(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中实现的。

继续阅读