摘要 :本文介绍基于 OpenCV 的物体计数实现方法。首先讲解物体计数的基本原理------基于形态学与距离变换的分水岭算法,并给出整体实现思路;随后详细说明如何在图像上绘制文字,包括
cv2.putText函数的基本用法、在定义画布上绘制文字,以及解决灰度图无法直接显示彩色文本的方法;最后通过一个完整的药片计数示例,演示从图像读取、形态学处理、二值化、轮廓提取到计数标注的完整流程。
在机器视觉中,有时需要对产品进行检测和计数。物体计数在生活中的应用非常广泛,例如每天都要吃维生素片,但有时候忘了今天有没有吃过,就想对瓶子里的药片计
数。本章就来介绍一下机器视觉检测和计数的实现。
1.物体计数基本原理
本章所说的物体计数是基于形态学的物体检测和计数,是在形态学的基础上衍生出的基于距离变换的分水岭算法,效果更具普遍性。至于什么是形态学,这里不再赘述,因为本章是应用章节,不会讲很多理论。物体计数的整体实现思路如下:
(1)读取图片。
(2)形态学处理(在二值化前进行适度形态学处理,效果俱佳)。
(3)二值化。
(4)提取轮廓(进行药片分割)
(5)获取轮廓索引,并筛选所需要的轮廓。
(6)画出轮廓,显示计数。
2.在图像上绘制文字
为了方便统计物品,我们在物品计数的结果上通常要写上该物品的序号,这就需要在图像上绘制字符文字。在OpenCV中,调用cv2.putText函数可添加文字到指定位置。它对于需要在图片中加入文字的场景,提供了一种比较直接、方便的方式。注意:OpenCV不支持显示中文字符,使用cv2.putText添加的文本字符串不能包含中文字符(包括中文标点
符号)。
【例16.1】在现成图片上绘制英文字符
cpp
#include "opencv2/opencv.hpp"
using namespace cv;
int main()
{
Mat img = imread("lena.png"); //读取彩色图像(BGR)
putText(img, "lena", Size(100, 40), FONT_HERSHEY_COMPLEX, 1, (0, 255, 0), 2, LINE_AA);
imshow("test", img); //显示叠加图像
waitKey(); //等待按键命令
return 0;
}

【例16.2】在定义画布上画文字
cpp
#include "opencv2/opencv.hpp"
using namespace cv;
int main()
{
// 创建一个空的8位单通道灰度图像,大小为100x300
Mat txtImage(100, 300, CV_8UC1, Scalar(0));
txtImage.setTo(Scalar(128)); //这句可以设置其它颜色,现在设置灰色
putText(txtImage, "hello world", Size(20, 30), FONT_HERSHEY_COMPLEX, 1, (255, 255, 255), 2); //在画布上画字符串hello world
imshow("result", txtImage);//显示图像
waitKey(); // 等待按键命令
return 0;
}

OpenCV的putText函数需要一个颜色参数,如果直接传递一个灰度图作为背景图像,
文本可能不会显示任何颜色。这是因为OpenCV中的颜色通常以BGR格式表示,而不是常
见的RGB格式。在灰度图中,每个像素只有一个灰度值,没有色彩信息。解决方法是在
绘制文本之前,将灰度图转换为BGR图像。我们可以先调用cvtColor函数将灰度图转换为
BGR图像,再使用putText函数。看下面的例子。
【例16.3】在灰度图上使用putText函数
cpp
#include "opencv2/opencv.hpp"
using namespace cv;
#include <opencv2/opencv.hpp>
int main() {
// 创建一个空白的黑色背景图像
Mat img(100, 300, CV_8UC1, Scalar(0, 0, 0));
// 设置文本内容
std::string text = "Hello, OpenCV!";
Mat bgr_image;
// 将灰度图转换为BGR图像
cvtColor(img, bgr_image, COLOR_GRAY2BGR);
// 设置文本起始坐标(左下角点)
Point textOrg(10, 50);
// 设置文本字体
int fontFace = FONT_HERSHEY_SIMPLEX;
// 设置字体缩放比例
double fontScale = 1;
// 设置文本颜色,使用BGR格式
Scalar color(255, 0, 255); // 白色文本
// 设置文本线条类型和粗细
int thickness = 2;
// 绘制文本
putText(bgr_image, text, textOrg, fontFace, fontScale, color, thickness);
// 显示图像
imshow("Image with Text", bgr_image);
waitKey(0);
return 0;
}

3.代码实现药片计数
【例16.4】实现药片计数
cpp
#include "opencv2/opencv.hpp"
using namespace cv;
#include <iostream>
using namespace std;
int main(int argc, char** argv)
{
Mat src, src_binary, dst, src_distance;
src = imread("med.png");//读取药片图像文件,第二个参数省略,则表示返回彩色图像
imshow("src", src);//显示源图像
Mat kernel = getStructuringElement(MORPH_RECT, Size(22, 22), Point(-1, -1));
morphologyEx(src, dst, MORPH_OPEN, kernel);//执行形态学开运算操作
imshow("morphology", dst);//显示形态学开运算后的图像
cvtColor(dst, dst, COLOR_RGB2GRAY); //将RGB格式的图像转换为灰度图像
threshold(dst, src_binary, 100, 255, THRESH_OTSU);//进行图像阈值分割
imshow("binary", src_binary);//显示二值化后图像
vector<vector<Point>> contours;
findContours(src_binary, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE, Point(0, 0));
RNG rng(12345);//为了后续使用随机函数,这里先设置随机数种子
double area;
Point2i PL;
for (size_t i = 0; i < contours.size(); i++)
{
area = contourArea(contours[i]);//用于计算图像轮廓的面积,参数是图像的轮廓点
if (area < 500)continue; //如果面积小于500
PL = contours[i].front();
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));//得到一个随机颜色值
drawContours(src, contours, i, color, 2, 8);
putText(src, to_string(i), PL, FONT_HERSHEY_COMPLEX, 1, color, 2);
}
imshow("number of drugs", src);
waitKey(0);
return 0;
}

