OpenCV For Python识别图片中的米粒个数,并计算米粒的面积平均面积

精确分割请看分水岭算法实现https://blog.csdn.net/qq_36623595/article/details/109273629

使用OpenCV构建图像识别算法,识别图片中的米粒个数,并计算米粒的平均面积和长度

我的开发环境如下

模块版本:OpenCV 4.4.044

版本:Python 3.8.6

编译器:VsCode

下面介绍程序实现:

第一步:图像采集

img = cv2.imread("rice.png")

使用cv2.imread()函数导入图片,括号内输入图片路径,如果图片位于程序所在目录,就可以直接写图片文件名。

第二步:图像预处理

gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #转换为灰度图

主要使用cv2.cvtColor()函数将彩色图片转化为灰度图

第三步:基于灰度的阈值分割

#使用自适应阈值分析进行图像二值化
dst = cv2.adaptiveThreshold(gray,255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY,101, 1)
#形态学去噪
element = cv2.getStructuringElement(cv2.MORPH_CROSS,(3, 3))
dst=cv2.morphologyEx(dst,cv2.MORPH_OPEN,element)  #开运算去噪

使用自适应阈值操作对图像进行二值化,threshold的优点在于可以快速有效的找到类间分割阈值,但其缺点也很明显,就是只能针对单一目标分割,也就是说目标图像的轮廓都属于同一灰度范围,反之可能造成一部分目标探测丢失。

adaptiveThreshold的优点在于可以进行多目标分割,缺点在于基于局部阈值分割出的目标连结性较差,包含噪声。
在这个米粒图片中,下部的图像亮度较暗,如果使用普通阈值操作,下部的米粒将会被认为是背景,而被屏蔽掉。


如图所示:左边是使用局部大津算法,右边是使用全局大津算法,可以看到左边的效果比右边的效果好。

函数官方文档:

   cv2. adaptiveThreshold(InputArray src, OutputArray dst, double maxValue, int adaptiveMethod, int thresholdType, int blockSize, double C)
def adaptiveThreshold(src,maxValue,adaptiveMethod,thresholdType,blockSize,C,dst=None)

参数解释:

src:源图像

maxval:Double类型的,阈值的最大值
adaptiveMethod:Int类型的,这里有两种选择
1 —— ADAPTIVE_THRESH_MEAN_C(通过平均的方法取得平均值)
2 —— ADAPTIVE_THRESH_GAUSSIAN_C(通过高斯取得高斯值)
不过这两种方法最后得到的结果要减掉参数里面的C值

thresholdType:Int类型的,方法如下:
THRESH_BINARY 二进制阈值化 -> 大于阈值为1 小于阈值为0
THRESH_BINARY_INV 反二进制阈值化 -> 大于阈值为0 小于阈值为1
THRESH_TRUNC 截断阈值化 -> 大于阈值为阈值,小于阈值不变
THRESH_TOZERO 阈值化为0 -> 大于阈值的不变,小于阈值的全为0
THRESH_TOZERO_INV 反阈值化为0 -> 大于阈值为0,小于阈值不变
blockSize:Int类型的,这个值来决定像素的邻域块有多大。
注意:这里的blockSize的值要为奇数,否则会给出这样的提示:
Assertion failed (blockSize % 2 == 1 && blockSize > 1) in cv::adaptiveThreshold
C:偏移值调整量,计算adaptiveMethod用到的参数。

然后对图像进行形态学去噪,形态学去噪有一下几种:

  • 开运算:先腐蚀再膨胀,用来消除小物体
  • 闭运算:先膨胀再腐蚀,用于排除小型黑洞
  • 形态学梯度:就是膨胀图与俯视图之差,用于保留物体的边缘轮廓。
  • 顶帽:原图像与开运算图之差,用于分离比邻近点亮一些的斑块。
  • 黑帽:闭运算与原图像之差,用于分离比邻近点暗一些的斑块。

第四步:图像特征描述及目标分析

主要步骤为:
1、检测轮廓- cv2.findContours()函数
2、提取轮廓的水平矩形坐标- rect = cv2.boundingRect( ) 函数
3、绘制矩形- cv2.rectangle()函数
4、 在米粒左上角写上编号-cv2.putText( ) 函

1、检测轮廓(函数模型)

cv2.findContours(image, mode, method[, contours[, hierarchy[, offset ]]])  

参数:

  • image参数是寻找轮廓的图像;

  • mode参数表示轮廓的检索模式,有四种(本文介绍的都是新的cv2接口):

    cv2.RETR_EXTERNAL表示只检测外轮廓(本文使用)
    cv2.RETR_LIST检测的轮廓不建立等级关系
    cv2.RETR_CCOMP建立两个等级的轮廓,上面的一层为外边界,里面的一层为内孔的边界信息。如果内孔内还有一个连通物体,这个物体的边界也在顶层。
    cv2.RETR_TREE建立一个等级树结构的轮廓。

  • method参数method为轮廓的近似办法

    cv2.CHAIN_APPROX_NONE存储所有的轮廓点,相邻的两个点的像素位置差不超过1,即max(abs(x1-x2),abs(y2-y1))==1
    cv2.CHAIN_APPROX_SIMPLE压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标,例如一个矩形轮廓只需4个点来保存轮廓信息
    cv2.CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS使用teh-Chinl chain 近似算法

返回值:

cv2.findContours()函数返回两个值,一个是轮廓本身,还有一个是每条轮廓对应的属性。

  • contour返回值:cv2.findContours()函数首先返回一个list,list中每个元素都是图像中的一个轮廓,用numpy中的ndarray表示。
  • hierarchy返回值:此外,该函数还可返回一个可选的hiararchy结果,这是一个ndarray,其中的元素个数和轮廓个数相同,每个轮廓contours[i]对应4个hierarchy元素hierarchy[i][0]
    ~hierarchy[i][3],分别表示后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号,如果没有对应项,则该值为负数。

2、统计米粒的编号、面积、长度

ares = cv2.contourArea(cont)

计算包围形状的面积,并使用一个for循环来计算面积平均值和长度平均值

3、提取轮廓的水平矩形坐标

    # 提取矩形坐标(x,y)
    rect = cv2.boundingRect(cont) 
    # 打印坐标
    print("x:{} y:{}".format(rect[0],rect[1]))

参数:

  • points:输入信息,可以为包含点的容器(vector)或是Mat。
  • 返回包覆输入信息的最小正矩形的x,y。

4、绘制矩形

    # 绘制矩形
    cv2.rectangle(img,rect,(0,0,255),1)

参数解释:

  • img是原图
  • (x,y)是矩阵的左上点坐标
  • (x+w,y+h)是矩阵的右下点坐标
  • (0,0,255)是画线对应的rgb颜色
  • 1是所画的线的宽度

5、 在米粒左上角写上编号

    # 防止编号到图片之外(上面),因为绘制编号写在左上角,所以让最上面的米粒的y小于10的变为10个像素
    y=10 if rect[1]<10 else rect[1] 
    # 在米粒左上角写上编号
    cv2.putText(img,str(count), (rect[0], y), cv2.FONT_HERSHEY_COMPLEX, 0.4, (0, 255, 0), 1) 

各参数依次是:图片输入/添加的文字/左上角坐标/字体/字体大小/颜色/字体粗细

效果图:


完整代码:

import cv2
import numpy as np
import matplotlib.pyplot as plt

#? 本程序自适应阈值操作,实现计算米粒参数

# 导入图片,图片放在程序所在目录
img = cv2.imread("rice.png")

# 转换为灰度图
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

# 使用局部阈值的自适应阈值操作进行图像二值化
dst = cv2.adaptiveThreshold(gray,255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY,101, 1)
# res ,dst = cv2.threshold(gray,0 ,255, cv2.THRESH_OTSU)
# 形态学去噪
element = cv2.getStructuringElement(cv2.MORPH_CROSS,(3, 3))
# 开运算去噪
dst=cv2.morphologyEx(dst,cv2.MORPH_OPEN,element)
# 轮廓检测函数
contours, hierarchy = cv2.findContours(dst,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
# 绘制轮廓
cv2.drawContours(dst,contours,-1,(120,0,0),2)

count=0 # 米粒总数
ares_avrg=0  # 米粒平均
# 遍历找到的所有米粒
for cont in contours:
    # 计算包围性状的面积
    ares = cv2.contourArea(cont)
    # 过滤面积小于50的形状
    if ares<50:   
        continue
    count+=1
    ares_avrg+=ares
    # 打印出每个米粒的面积
    print("{}-blob:{}".format(count,ares),end="  ") 
    # 提取矩形坐标(x,y)
    rect = cv2.boundingRect(cont) 
    # 打印坐标
    print("x:{} y:{}".format(rect[0],rect[1]))
    # 绘制矩形
    cv2.rectangle(img,rect,(0,0,255),1)
    # 防止编号到图片之外(上面),因为绘制编号写在左上角,所以让最上面的米粒的y小于10的变为10个像素
    y=10 if rect[1]<10 else rect[1] 
    # 在米粒左上角写上编号
    cv2.putText(img,str(count), (rect[0], y), cv2.FONT_HERSHEY_COMPLEX, 0.4, (0, 255, 0), 1) 
    # print('编号坐标:',rect[0],' ', y)
print('个数',count,' 总面积',ares_avrg,' ares',ares)
print("米粒平均面积:{}".format(round(ares_avrg/count,2))) #打印出每个米粒的面积

cv2.namedWindow("imgshow", 2)   #创建一个窗口
cv2.imshow('imgshow', img)    #显示原始图片(添加了外接矩形)

cv2.namedWindow("dst", 2)   #创建一个窗口
cv2.imshow("dst", dst)  #显示灰度图

cv2.waitKey()

 

  • 20
    点赞
  • 160
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Helloworld-睖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值