精确分割请看分水岭算法实现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()函数返回两个值,一个是轮廓本身,还有一个是每条轮廓对应的属性。
2、统计米粒的编号、面积、长度
ares = cv2.contourArea(cont)
计算包围形状的面积,并使用一个for循环来计算面积平均值和长度平均值
3、提取轮廓的水平矩形坐标
# 提取矩形坐标(x,y)
rect = cv2.boundingRect(cont)
# 打印坐标
print("x:{} y:{}".format(rect[0],rect[1]))
参数:
4、绘制矩形
# 绘制矩形
cv2.rectangle(img,rect,(0,0,255),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()