OpenCV物体计数示例

打印 上一主题 下一主题

主题 1802|帖子 1802|积分 5406

OpenCV盘算机视觉开发实践:基于Qt C++ - 商品搜刮 - 京东
在机器视觉中,有时需要对产物进行检测和计数,比如,网购的维生素片,有时候忘了今天有没有吃过,就想对瓶子里的药片计数。物体计数在生存中应用非常广泛。其难点无非是对于产物的图像分割。本节就来介绍一下机器视觉检测和计数的实现。
16.1  基本原理
本次实战在基于形态学的基础上又衍生出基于间隔变换的分水岭算法,使其实现的结果更具广泛性。因此,我们这里所说的物体计数是基于形态学的物体检测和计数。至于什么是形态学,这里就不再赘述了,因为本章是应用章节了,不会再讲许多理论。
这里,我们以药片为计数对象,下面我们来讲一下整体思路:(1)读取图片;(2)形态学处置惩罚(在二值化前进行适度形态学处置惩罚,结果俱佳);(3)二值化;(4)提取轮廓(进行药片分割);(5)获取轮廓索引,并筛选所需要的轮廓;(6)画出轮廓,显示计数。
16.2  相关函数
这里所说的相关函数,不是把稍后物品计数范例中的全部库函数都解释一遍,而是把在本书前面没有出现过的函数,把它们罗列出来解释一下。在前面章节已经出现过的且解释过的函数不再这里解释。
1)在图像上绘制笔墨
在OpenCV中,调用cv2.putText函数可添加笔墨到指定位置,对于需要在图片中参加笔墨的场景提供了一种比较直接方便的方式。注意:OpenCV不支持显示中笔墨符,使用cv2.putText时添加的文本字符串不能包含中笔墨符(包括中文标点符号)。
该函数原型如下所示:
  1. void putText( InputOutputArray img, const String& text, Point org,
  2.                          int fontFace, double fontScale, Scalar color,
  3.                          int thickness = 1, int lineType = LINE_8,
  4.                          bool bottomLeftOrigin = false );
复制代码
参数img表示需要绘制文本的图像;text表示要绘制的文本内容;org表示要绘制的位置,图像中文本字符串的左下角,可以用一个元祖来表示x、y坐标,例如(10, 100)表示x=10,y=100;fontFace表示字体类型,对应的字体类型如下:

  • FONT_ITALIC:斜体字的标记。
  • FONT_HERSHEY_PLAIN:小尺寸无衬线字体。
  • FONT_HERSHEY_SIMPLEX:正常巨细的无衬线字体。
  • FONT_HERSHEY_DUPLEX:正常巨细的无衬线字体(比FONT_HERSHEY_SIMPLEX更复杂)。
  • FONT_HERSHEY_COMPLEX:正常巨细的衬线字体。
  • FONT_HERSHEY_TRIPLEX:正常巨细的衬线字体(比FONT_HERSHEY_COMPLEX更复杂)。
  • FONT_HERSHEY_SCRIPT_SIMPLEX:手写体字体。
  • FONT_HERSHEY_SCRIPT_COMPLEX(比FONT_HERSHEY_SCRIPT_SIMPLEX的更复杂)。
参数fontScale表示字体的巨细,字体比例因子乘以font-specific基本巨细;color表示文本字体颜色,设置三通道的元组BGR,比如(255,0,0),常见颜色取值如下:赤色(0, 0, 255)、绿色(0, 128, 0)、蓝色(255, 0, 0)、黄色(0, 255, 255)、紫色(128, 0, 128)、橙色(0, 165, 255)、白色(255, 255, 255)、黑色(0, 0, 0)、灰色(128, 128, 128)。
参数thickness表示字体粗细,默认为1;参数lineType表示线条类型,默认为cv2.LINE_AA;参数bottomLeftOrigin表示坐标原点,如果为真,则图像数据原点位于左下角,否则它在左上角,其中,org定义了文本起始位置,可以用一个元组来表示x、y坐标,例如(10, 100)表示x=10,y=100。
下面我们看几个小例子。
【例16.1】在现成图片上绘制英笔墨符
(1)打开Qt Creator,新建一个控制台项目,项目名称是test。
(2)在main.cpp中输入代码如下:
  1. #include "opencv2/opencv.hpp"
  2. using namespace cv;
  3. int main()
  4. {
  5.     Mat img = imread("lena.png"); //读取彩色图像(BGR)
  6.     putText(img, "lena", Size(100, 40), FONT_HERSHEY_COMPLEX, 1, (0, 255, 0), 2, LINE_AA);
  7.     imshow("test", img); //显示叠加图像
  8.     waitKey();  //等待按键命令
  9.     return 0;
  10. }
复制代码
(3)运行步伐,运行结果如图16-1所示。

图16-1

我们可以看到,字符串“lena”已经显示在图片上了。下面我们再看一个实例,在一个本身构造的蓝色背景地区上画笔墨。
【例16.2】在定义画布上画笔墨
(1)打开Qt Creator,新建一个控制台项目,项目名称是test。
(2)在main.py中输入代码如下:
  1. #include "opencv2/opencv.hpp"
  2. using namespace cv;
  3. int main()
  4. {
  5.      // 创建一个空的8位单通道灰度图像,大小为100x300
  6.     Mat txtImage(100, 300, CV_8UC1, Scalar(0));
  7.     txtImage.setTo(Scalar(128)); //这句可以设置其它颜色,现在设置灰色
  8.     putText(txtImage, "hello world", Size(20,30), FONT_HERSHEY_COMPLEX, 1, (255,255,255), 2);  //在画布上画字符串hello world
  9.     imshow("result", txtImage);//显示图像
  10.     waitKey();  // 等待按键命令
  11.     return 0;
  12. }
复制代码
代码中我们首先创建一个空的8位单通道灰度图像,巨细为100x300的画布,并用Scalar(0)初始化维黑色。然后通过函数setTo(Scalar(128))来设置画布背景为灰色。
(3)运行步伐,运行结果如图16-2所示。

图16-2

在灰度图上使用putText函数时,由于OpenCV的putText函数需要一个颜色参数,如果直接传递一个灰度图作为背景图像,文本大概不会显示任何颜色。这是因为OpenCV中的颜色通常以BGR格式表示,而不是常见的RGB格式。在灰度图中,每个像素只有一个灰度值,没有色彩信息。解决方法是在绘制文本之前,将灰度图转换为BGR图像。可以通过调用cvtColor函数,将灰度图转换为BGR图像,然后再使用putText函数。看下面的例子。
【例16.3】在灰度图上使用putText函数
(1)打开Qt Creator,新建一个控制台项目,项目名称是test。
(2)在main.cpp中输入代码如下:
  1. #include "opencv2/opencv.hpp"
  2. using namespace cv;
  3. int main() {
  4.     // 创建一个空白的黑色背景图像
  5.     Mat img(100, 300, CV_8UC1, Scalar(0, 0, 0));
  6.     // 设置文本内容
  7.     std::string text = "Hello, OpenCV!";
  8.     Mat bgr_image;
  9.     // 将灰度图转换为BGR图像
  10.     cvtColor(img,bgr_image, COLOR_GRAY2BGR);
  11.     // 设置文本起始坐标(左下角点)
  12.     Point textOrg(10, 50);
  13.     // 设置文本字体
  14.     int fontFace = FONT_HERSHEY_SIMPLEX;
  15.     // 设置字体缩放比例
  16.     double fontScale = 1;
  17.     // 设置文本颜色,使用BGR格式
  18.     Scalar color(255, 0, 255); // 白色文本
  19.     // 设置文本线条类型和粗细
  20.     int thickness = 2;
  21.     // 绘制文本
  22.     putText(bgr_image, text, textOrg, fontFace, fontScale, color, thickness);
  23.     // 显示图像
  24.     imshow("Image with Text", bgr_image);
  25.     waitKey(0);
  26.     return 0;
  27. }
复制代码
在这个例子中,font_color被设置为(255, 255, 255),这是白色在BGR格式下的表示。cv2.putText函数将在转换后的BGR图像上绘制文本。注意,在展示图像之前,不需要再次将BGR图像转换为灰度图。
(3)运行步伐,运行结果如图16-3所示。

图16-3

16.3  代码实现药片计数
前面我们讲解了物品计数的基本原理。下面我们通过代码来实现药片计数的步调。
【例16.4】实现药片计数
(1)打开Qt Creator,新建一个控制台项目,项目名称是test。
(2)在main.cpp中输入代码如下:
  1. #include "opencv2/opencv.hpp"
  2. using namespace cv;
  3. #include <iostream>
  4. using namespace std;
  5. int main(int argc, char** argv)
  6. {
  7.     Mat src, src_binary,dst,src_distance;
  8.     src = imread("med.png");//读取药片图像文件,第二个参数省略,则表示返回彩色图像
  9.     imshow("src", src);        //显示源图像
  10.     Mat kernel = getStructuringElement(MORPH_RECT, Size(20, 20), Point(-1, -1));
  11.     morphologyEx(src, dst, MORPH_OPEN, kernel);        //执行形态学开运算操作
  12.     imshow("morphology",dst);//显示形态学开运算后的图像
  13.     cvtColor(dst, dst, COLOR_RGB2GRAY);                 //将RGB格式的图像转换为灰度图像
  14.     threshold(dst, src_binary, 100, 255, THRESH_OTSU);//进行图像阈值分割
  15.     imshow("binary", src_binary);                                //显示二值化后图像
  16.     vector<vector<Point>> contours;
  17.     findContours(src_binary, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE, Point(0, 0));
  18.     RNG rng(12345);//为了后续使用随机函数,这里先设置随机数种子
  19.     double area;
  20.     Point2i PL;
  21.     for (size_t i = 0; i < contours.size(); i++)
  22.     {
  23.         area = contourArea(contours[i]);//用于计算图像轮廓的面积,参数是图像的轮廓点
  24.         if (area < 500)continue;                  //如果面积小于500
  25.         PL = contours[i].front();
  26.         Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));                                                //得到一个随机颜色值
  27.         drawContours(src, contours, i, color, 2, 8);
  28.         putText(src, to_string(i), PL, FONT_HERSHEY_COMPLEX, 1, color, 2);
  29.     }
  30.     imshow("number of drugs", src);
  31.     waitKey(0);
  32.     return 0;
  33. }
复制代码
形态学操作在处置惩罚图像时特殊有用,尤其是在去噪、边沿检测、添补孔洞等场景中。在代码中,我们首先读取图像文件med.png并显示。然后调用函数getStructuringElement用于天生一个布局元素,这个布局元素主要用于形态学操作,如膨胀、腐蚀、开运算和闭运算,我们传给它的第一个参数为MORPH_RECT,表示矩形布局元素,这是最常见的选择,全部像素的权重都相称。接着,调用函数morphologyEx来实验形态学操作,如腐蚀、膨胀、开运算、闭运算等,这里传给它的第二个参数是MORPH_OPEN,表示开运算。开运算操作完毕后显示图像。随后调用函数cvtColor 将RGB格式的图像转换为灰度图像。
接着,调用函数thresold进行图像阈值分割,即利用图像中像素的像素值巨细的差别,选择一个恰当的阈值,将图像分割为目标地区(target_area)与背景地区(background_area),天生一个我们需要的二值图像,主要特点是优劣分明。二值图将为我们裁剪目标地区,进行目标识别与分析剔除不须要的背景地区,消除不须要地区对于图像处置惩罚的干扰。然后显示二值化后图像。
接下来,调用findContours函数查找图像轮廓,所谓图像轮廓,就是具有相同颜色或者强度的连续点的曲线,轮廓的作用通常可以用来对图像的分析,对物体的识别与检测等。注意:为了检测的准确性,首先需要将图像进行二值化或者Canny(边沿检测)。图像轮廓是由一系列连续的像素点组成,这些像素点位于物体边界上。轮廓的特点是在物体和背景之间的边界位置,因此可以用来表示物体的形状和布局。轮廓可以是闭合的,也可以是开放的,具体取决于物体的形状。我们把找到的轮廓列表放在cnts中,调用函数len就可以知道有多少个轮廓,那么也就知道药片的数量了。末了我们设计一个for循环,在里面首先调用ContourArea盘算整个或部门轮廓的面积,并调用函数drawContours绘制图像轮廓,并调用函数putText在每个药片旁绘制一个数字,来表示序号。
(3)运行步伐,运行结果如图16-4所示。

图16-4

由上图可以看到,原图在颠末形态学处置惩罚后,可以去除许多细节,简化后续的药片分割操作。但是在计数结果图上发现,索引17号药片并没有完全分割,实际上修改形态学的布局元素尺寸(改为20*20),也可以完全分离这两个药片。也就是把函数getStructuringElement的调用改为:
Mat kernel = getStructuringElement(MORPH_RECT, Size(22, 22), Point(-1, -1));
再运行步伐,可以发现17号药片也切割乐成了,如图16-5所示。

图16-5



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

西河刘卡车医

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表