OpenCV+Python物体计数

《OpenCV计算机视觉开发实践 基于Python朱文伟,李建英 著清华大学出版社 新华书店文轩正版 图书》【摘要 书评 试读】- 京东图书

在机器视觉中,有时需要对产品进行检测和计数。比如,网购的维生素片,有时候忘了有没有吃过,就想对瓶子里的药片计数。物体计数在日常生活和生产活动中应用非常广泛。其难点无非是对于产品的图像分割。本节将介绍一下机器视觉检测和计数的实现。

18.1  基 本 原 理

物体计数使用了基于形态学的基础上衍生出来的、基于距离变换的分水岭算法(Watershed Algorithm),其实现的效果更具普遍性。因此,我们这里所说的物体计数是基于形态学的物体检测和计数。至于什么是形态学,这里就不再赘述了,因为本章是应用章节,不会再讲很多理论。这里,我们以药片为计数对象,来讲一下整体思路:

(1)读取图片。

(2)形态学处理(在二值化前进行适度形态学处理,效果俱佳)。

(3)二值化。

(4)提取轮廓(进行药片分割)。

(5)获取轮廓索引,并筛选所需要的轮廓。

(6)画出轮廓,显示计数。

18.2  相 关 函 数

这里所说的相关函数,不是把稍后物品计数示例中的所有库函数都解释一遍,而是把在本书前面没有讲解过的函数,罗列出来解释一下。在前面章节已经讲解过的函数这里不再解释。

在图像上绘制文字

在OpenCV中,调用cv2.putText函数可添加文字到指定位置,为需要在图片中加入文字的场景提供了一种比较方便的直接方式。注意:OpenCV 不支持显示中文字符,使用 cv2.putText时添加的文本字符串不能包含中文字符(包括中文标点符号)。

该函数原型如下所示:

cv2.putText(img, text, org, fontFace, fontScale, color, thickness=None, lineType=None, bottomLeftOrigin=None)

参数img表示需要绘制文本的图像;text表示需要绘制的文本内容;org表示需要绘制的位置,图像中文本字符串的左下角,可以用一个元组来表示x、y坐标,例如(10, 100)表示x=10,y=100;fontFace表示字体类型,对应的字体类型如下:

  •   cv2.FONT_ITALIC:斜体字体。
  •   cv2.FONT_HERSHEY_PLAIN:小尺寸无衬线字体。
  •   cv2.FONT_HERSHEY_SIMPLEX:正常大小的无衬线字体。
  •   cv2.FONT_HERSHEY_DUPLEX:正常大小的无衬线字体,比FONT_HERSHEY_SIMPLEX更复杂。
  •   cv2.FONT_HERSHEY_COMPLEX:正常大小的衬线字体。
  •   cv2.FONT_HERSHEY_TRIPLEX:正常大小的衬线字体,比FONT_HERSHEY_COMPLEX更复杂。
  •   cv2.FONT_HERSHEY_SCRIPT_SIMPLEX:手写体字体。
  •   cv2.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。

下面我们看几个小例子。

【例18.1】  在现成图片上绘制英文字符

(1)打开PyCharm,新建一个项目,项目名称是pythonProject。

(2)在main.py中输入代码如下:

import cv2
 
img = cv2.imread('test.png')  # 读取彩色图像(BGR)
cv2.putText(img, 'starlight', (100, 40), cv2.FONT_HERSHEY_COMPLEX, 1, (0, 255, 0), 2, cv2.LINE_AA)
cv2.imshow('test', img)  # 显示叠加图像
cv2.waitKey()  # 等待按键命令

图18‑1

(3)运行程序,运行结果如图18-1所示。

我们可以看到,字符串“starligth”已经显示在图片上了。下面我们再看一个实例,在一个自己构造的蓝色背景区域上画文字。

【例18.2】  在定义画布上画文字

(1)打开PyCharm,新建一个项目,项目名称是pythonProject。

(2)在main.py中输入代码如下:

import cv2
import numpy as np
 
bkcolor = (0, 0, 0)                #定义黑色
txtimage = np.zeros((100, 300, 3), dtype=np.uint8) 
txtimage[:] = bkcolor              #画布赋值黑色
cv2.putText(txtimage, "hello world", (20,30), cv2.FONT_HERSHEY_COMPLEX, 1, (255,255,255), 2);                  # 在画布上画字符串hello world
cv2.imshow('result', txtimage)     # 显示图像
cv2.waitKey()                      # 等待按键命令

图18‑2

np.zeros()是NumPy库中一个基础且功能强大的函数,用于创建一个特定形状和类型的新数组,其中所有元素的初始值都为0。该函数在数据处理、科学计算和各种编程任务中都有广泛应用。代码中我们使用np.zeros((100, 300, 3), dtype=np.uint8)创建了高为100、宽为300、具有3个颜色空间(红绿蓝)的画布,并以unin8类型存储。

(3)运行程序,运行结果如图18-2所示。

在灰度图上使用OpenCV库的putText函数时,由于putText函数需要一个颜色参数,如果直接传递一个灰度图作为背景图像,文本可能不会显示任何颜色。这是因为OpenCV中的颜色通常以BGR格式表示,而不是常见的RGB格式。在灰度图中,每个像素只有一个灰度值,没有色彩信息。解决方法是在绘制文本之前,将灰度图转换为BGR图像。可以通过调用cv2.cvtColor函数,将灰度图转换为BGR图像,然后再使用putText函数。

【例18.3】  在灰度图上使用putText函数

(1)打开PyCharm,新建一个项目,项目名称是pythonProject。

(2)在main.py中输入代码如下:

import cv2
import numpy as np
 
# 创建一个灰度图
gray_image = np.zeros((100, 300), dtype=np.uint8)
# 将灰度图转换为BGR图像
bgr_image = cv2.cvtColor(gray_image, cv2.COLOR_GRAY2BGR)
# 设置文本参数
font = cv2.FONT_HERSHEY_SIMPLEX
text = "Hello, World!"
position = (50, 60)  # 文本在图像中的位置
font_scale = 1
font_color = (255, 255, 255)  # 文本颜色,这里是白色
line_type = 2
# 在BGR图像上绘制文本
cv2.putText(bgr_image, text, position, font, font_scale, font_color, line_type)
 
# 显示图像
cv2.imshow('Image with Text', bgr_image)
cv2.waitKey(0)
cv2.destroyAllWindows()

图18‑3

在这个例子中,font_color被设置为(255, 255, 255),这是BGR格式表示的白色。cv2.putText函数将在转换后的BGR图像上绘制文本。注意,在展示图像之前,不需要再次将BGR图像转换为灰度图。

(3)运行程序,运行结果如图18-3所示。

18.3  代码实现药片计数

前面章节讲解了物品计数的基本原理,本节将通过代码来实现药片计数的应用。

【例18.4】  实现药片计数

(1)打开PyCharm,新建一个项目,项目名称是pythonProject。

(2)在main.py中输入代码如下:

import cv2
import numpy as np
 
# 返回一个随机定义的颜色
def random_color():
    color_b =  np.random.randint(100, 255)
    color_g =  np.random.randint(100, 255)
    color_r =  np.random.randint(100, 255)
    return (color_b, color_g, color_r)
# 读取药片图像文件,第二个参数省略,则表示返回彩色图像
src = cv2.imread("med.png")
# 检查图像是否成功读取
if src is None:
    print("Error: 图像未成功读取,请检查文件路径是否正确。")
    exit()
 
cv2.imshow('srcImage', src); # 显示源图像
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (20,20), (-1, -1));
dst = cv2.morphologyEx(src, cv2.MORPH_OPEN, kernel);#执行形态学开运算操作
cv2.imshow('morphology result',dst);# 显示形态学开运算后的图像
dst = cv2.cvtColor(dst, cv2.COLOR_RGB2GRAY); #将RGB格式的图像转换为灰度图像
ret,src_binary=cv2.threshold(dst, 100, 255, cv2.THRESH_OTSU);#进行图像阈值分割
cv2.imshow( "Binarization", src_binary); #显示二值化后图像
pt = (0, 0)
cnts,hierarchy = cv2.findContours(src_binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE,None,None,pt);
#print("contours: {}".format(cnts))# 打印出轮廓列表
 
cv2.setRNGSeed(12345); #为了后续使用随机函数,这里先设置随机数种子
a = len(cnts)
print(a)  #打印药片个数
 
for i in range(0, a, 1):
    area = cv2.contourArea(cnts[i]); #用于计算图像轮廓的面积,参数是图像的轮廓点
    if area <500:  #如果面积小于500
        continue;
 
    contour = cnts[i]
    partial_contour = contour[:1]
    for point in partial_contour:
        x, y = point[0]
 
    color =  random_color(); #得到一个随机颜色值
    cv2.drawContours(src, cnts, i, color, 2, 8);
    cv2.putText(src, str(i), (x,y), cv2.FONT_HERSHEY_COMPLEX, 1,color, 2);
cv2.imshow("count result", src);
cv2.waitKey(0);

形态学操作在处理图像时特别有用,尤其是在去噪、边缘检测、填充孔洞等场景中。在上面代码中,首先,我们读取图像文件med.png并显示。然后,调用函数getStructuringElement用于生成一个结构元素,这个结构元素主要用于形态学操作,如膨胀、腐蚀、开运算和闭运算;我们传给它的第一个参数为cv2.MORPH_RECT,表示矩形结构元素,这是最常见的选择,所有像素的权重都相等。接着,调用函数morphologyEx来执行形态学操作,如腐蚀、膨胀、开运算、闭运算等,这里传给它的第二个参数是cv2.MORPH_OPEN,表示开运算。开运算操作完毕后显示图像。随后调用函数cvtColor,将RGB格式的图像转换为灰度图像。

接着,调用函数thresold进行图像阈值分割,即利用图像中像素的像素值大小的差别,选择一个适当的阈值,将图像分割为目标区域(target_area)与背景区域(background_area),生成一个我们需要的二值图像,主要特点是黑白分明。二值图将为我们裁剪目标区域,进行目标识别与分析,剔除不必要的背景区域,消除不必要区域对于图像处理的干扰。然后显示二值化后的图像。

再接下来,调用findContours函数查找图像轮廓。所谓图像轮廓,就是具有相同颜色或者强度的连续点组成的曲线。轮廓通常用来对图像进行分析,对物体进行识别与检测等。

为了保证图像检测的准确性,首先需要将图像进行二值化或者Canny(边缘检测)处理。图像轮廓是由一系列连续的像素点组成,这些像素点位于物体边界上。轮廓的特点是在物体和背景之间的边界位置,因此可以用来表示物体的形状和结构。轮廓可以是闭合的,也可以是开放的,具体取决于物体的形状。我们把找到的轮廓列表放在cnts中,调用函数len就可以知道有多少个轮廓,那么也就知道药片的数量了。最后我们设计一个for循环,在里面首先调用ContourArea计算整个或部分轮廓的面积,调用函数drawContours绘制图像轮廓,再调用函数putText在每个药片旁绘制一个数字,用来表示药片计数的序号。

(3)运行程序,运行结果如图18-4所示。

由结果图中可以看到,原图在经过形态学处理后,可以去除很多细节,简化后续的药片分割操作。但是,我们在计数结果图上发现,索引17号药片并没有完全分割,实际上修改形态学的结构元素尺寸(改为(22,22)),也可以完全分离这两个药片。也就是把函数getStructuringElement的调用改为:

kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (22,22), (-1, -1));

再运行程序,可以发现17号药片也切割成功了,如图18-5所示。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值