<think>
我们被要求使用Python和OpenCV,基于OTSU和凸包检测来实现粘连大米的分割。根据引用[1]的描述,流程大致包括:使用OTSU进行阈值分割,然后对二值图像进行凸包检测以分离粘连的大米颗粒,最后绘制外接矩形和重心点。
步骤分解:
1. 读取图像并转换为灰度图。
2. 使用OTSU方法进行阈值分割,得到二值图像。
3. 对二值图像进行形态学操作(可选,用于去除噪声或填充小孔)。
4. 查找轮廓,这里由于大米粘连,可能会形成大的轮廓块。
5. 对每个轮廓进行凸包检测,然后利用凸包缺陷(convexity defects)来分离粘连的部分。
6. 分离后,对每个独立的大米颗粒计算外接矩形和重心,并绘制。
但是,引用[1]中并没有给出详细的代码,因此我们需要根据常规方法来实现。
具体步骤:
1. 读取图像,转灰度。
2. 使用大津法(OTSU)二值化,注意可能需要先进行高斯滤波去噪。
3. 形态学操作(例如闭运算,先膨胀后腐蚀,用于填充小孔和连接邻近区域;或者开运算去除小噪声)。
4. 查找轮廓(注意使用合适的参数,比如外部轮廓RETR_EXTERNAL,或者树结构RETR_TREE)。
5. 对于每个轮廓,计算其凸包,然后计算凸包缺陷。
6. 凸包缺陷的深度可以用来判断粘连点。通常,当缺陷深度大于某个阈值时,我们可以认为该处是粘连点,然后根据这些点将轮廓分割。
7. 分割后,每个独立的区域可以视为一个大米颗粒,计算其外接矩形和重心。
然而,凸包缺陷分割轮廓的方法相对复杂。另一种思路是:利用凸包缺陷点来分割轮廓,然后使用分水岭算法或者对每个分割后的区域单独处理。
这里我们采用以下方法:
- 首先,我们通过凸包缺陷找到可能的粘连点(即深度较大的点)。
- 然后,我们根据这些点将轮廓分割成多个部分。
- 但是,分割后的部分可能还不是独立的连通域,因此我们需要根据分割点将原轮廓拆分成多个轮廓,然后分别处理。
由于OpenCV没有直接提供分割轮廓的函数,我们需要自己实现。另一种更简单的方法是使用距离变换和分水岭算法,但题目要求使用凸包检测。
因此,我们考虑使用凸包缺陷来分离粘连的步骤:
1. 计算轮廓的凸包和凸包缺陷。
2. 找出深度超过一定阈值的缺陷点(这些点通常位于粘连的凹陷处)。
3. 将轮廓在这些缺陷点处分割成多个轮廓。
但是,注意凸包缺陷返回的四个值:起始点,终点,凹陷点(即缺陷点),深度(即凹陷点到凸包的最远距离)。我们主要关注凹陷点和深度。
然而,直接根据缺陷点分割轮廓并不简单,因为一个轮廓可能有多个缺陷点,并且这些点可能属于不同的粘连区域。
另一种常见做法:根据凸包缺陷的深度点,在原二值图像上绘制这些点(作为分水岭的标记),然后应用分水岭算法。具体步骤如下:
a. 对二值图像进行距离变换。
b. 在二值图像中,将每个独立的连通域(非粘连的)作为种子区域,同时将粘连区域的凹陷点(即缺陷点)作为背景标记(或者将凹陷点作为分割线)。
c. 但是,这种方法需要调整。
考虑到题目要求使用凸包检测,我们可以这样实现:
- 首先,我们找到轮廓的凸包缺陷,然后根据深度阈值(比如大于轮廓最大内切圆半径的1/5等)找到可能的粘连点。
- 然后,我们在这些粘连点上绘制一条线段(例如,连接两个相邻的凹陷点)来分割粘连区域。但是这种方法需要精确确定分割线。
鉴于复杂性,这里我们采用另一种思路:使用凹点来分割轮廓,然后通过轮廓的层次关系重新建立多个轮廓。
由于实现起来较为复杂,我们可以参考OpenCV关于凸包缺陷分割粘连对象的示例。
然而,根据引用[1]的描述,它可能并没有使用分水岭,而是直接基于凸包缺陷进行分割。因此,我们尝试以下步骤:
步骤1:二值化(OTSU)
步骤2:查找轮廓(只查找最外层轮廓,因为粘连的大米可能形成一个大的轮廓)
步骤3:对每个轮廓计算凸包和凸包缺陷
步骤4:根据凸包缺陷的深度,筛选出可能的粘连点(凹点)
步骤5:将筛选出的凹点作为分割点,将原轮廓分割成多个轮廓(这需要设计算法:例如,从轮廓的起点开始,遇到分割点就新建一个轮廓)
步骤6:对分割后的每个小轮廓计算外接矩形和重心
但是,步骤5的轮廓分割算法并不简单。另一种思路是:根据凹点,我们在二值图像上将这些点连接起来(形成分割线),然后重新查找轮廓。具体如下:
1. 创建一个与原图大小相同的黑色画布。
2. 将原二值图像的所有轮廓(白色)绘制到这个画布上(白色)。
3. 在画布上,将每个凹点用直线连接(例如,连接凸包缺陷返回的起始点和终点,但这样可能并不准确)或者连接相邻的凹点(需要排序)?或者直接在凹点处绘制一个黑色线条(用于断开连接)。
4. 然后重新查找轮廓,此时粘连的区域应该被分割开。
但是,如何绘制分割线?我们可以这样:对于每个轮廓的凸包缺陷,我们取深度较大的那些缺陷,然后获取该缺陷的起始点和终点,然后在二值图像上从凹陷点到起始点和终点的中点(或其他位置)画一条黑色直线?或者直接在凹陷点处画一个黑色小圆点?这样可能效果不好。
更常见的方法是:使用距离变换和分水岭算法,并结合凸包缺陷点作为标记。具体如下:
- 步骤1:二值化(OTSU)并去除小噪声(形态学操作)。
- 步骤2:计算距离变换,并归一化。
- 步骤3:在距离变换图中,我们寻找峰值作为初始标记(这些峰值是每个颗粒的中心)。但是粘连区域可能只有一个峰值。
- 步骤4:同时,我们通过凸包缺陷找到的凹点也可以作为背景标记(或者作为分水岭的标记点)。
- 步骤5:将初始标记(前景:每个颗粒的中心点)和凹点(背景)合并成一个标记图像,然后应用分水岭算法。
但是,题目要求使用凸包检测,我们可以将凸包缺陷检测到的凹点作为额外的背景标记,然后应用分水岭算法。
因此,我们结合凸包缺陷和分水岭算法:
1. 读取图像,转为灰度图。
2. 高斯模糊去噪。
3. OTSU二值化(注意二值化后背景为0,前景为255)。
4. 形态学操作(可选,如开运算去除白噪声,闭运算填充黑洞)。
5. 通过二值图像获取前景区域,使用腐蚀操作获取确定的前景(即每个大米颗粒的中心区域),但腐蚀可能会将粘连区域分开。
6. 计算距离变换,并对距离变换图像进行阈值处理,得到一些峰值点作为可能的前景标记(即每个大米颗粒的中心)。
7. 查找二值图像的轮廓(此时轮廓是粘连的),然后对每个轮廓计算凸包和凸包缺陷,将凸包缺陷的凹点(深度大于一定值)的位置作为背景标记(或者作为分水岭的“背景”标记?注意分水岭需要三种标记:前景、背景、未知区域)。
8. 创建一个标记图像(初始为0),将确定的前景区域标记为正整数(从1开始),将背景标记为另一个整数(例如255),而凹点位置也可以标记为背景(255)。
9. 然后,将剩余的区域标记为未知(0),应用分水岭算法。
10. 分水岭算法后,每个区域会被标记,然后我们就可以为每个区域绘制外接矩形和重心。
但是,这种方法的背景标记除了凹点,还应该包括边界区域。通常,分水岭算法中,我们通过膨胀获取背景,但这里我们使用凹点作为额外的背景标记。
由于引用[1]提到“基于OTSU+凸包检测的粘连大米分割”,因此我们重点在凸包检测部分。我们采用凸包缺陷来获取凹点,然后结合分水岭算法。
具体代码步骤:
1. 读取图像,转灰度,高斯模糊。
2. 二值化(OTSU)。
3. 形态学操作(闭运算,填充内部小孔;开运算去除小噪声)。
4. 确定前景区域:
# 通过腐蚀操作得到每个对象的内部区域(但是腐蚀可能无法完全分离粘连,所以我们用距离变换)
dist = cv2.distanceTransform(binary_image, cv2.DIST_L2, 5)
# 归一化距离变换图像
cv2.normalize(dist, dist, 0, 1.0, cv2.NORM_MINMAX)
# 对距离变换图像进行阈值处理,得到前景标记(即每个对象的中心点)
_, sure_fg = cv2.threshold(dist, 0.5 * dist.max(), 255, cv2.THRESH_BINARY)
sure_fg = np.uint8(sure_fg)
5. 确定背景区域:通常用膨胀操作,但我们这里同时考虑凸包缺陷点。
# 膨胀操作获取背景区域
sure_bg = cv2.dilate(binary_image, kernel, iterations=3)
6. 查找轮廓(整个二值图像),对每个轮廓计算凸包和凸包缺陷,将深度大于一定阈值的凹点收集起来,作为背景标记点。
# 注意:凸包缺陷的凹点是在轮廓内部,所以这些点应该是前景和背景的交界处(即粘连的凹陷处),我们将其视为背景标记。
7. 创建标记图像:初始为0,将sure_fg(前景)标记为正整数(1,2,3...),将sure_bg区域(膨胀后的区域减去sure_fg)标记为背景(255),同时将凹点位置也标记为背景(255)。但是注意,分水岭算法要求标记是整数,背景通常是1,前景从2开始?或者按照常规:未知区域是0,背景是1,前景从2开始。
8. 然后应用分水岭算法。
然而,这种方法比较复杂,而且凹点作为背景标记的可靠性需要验证。
考虑到时间,我们采用另一种更简单的方法:利用凸包缺陷点来分割轮廓,而不使用分水岭。我们可以使用以下步骤(参考OpenCV官方文档中关于凸包缺陷分割手指数量的例子,但这里用于分割粘连物体):
1. 二值化,形态学操作,查找轮廓。
2. 对每个轮廓计算凸包和凸包缺陷。
3. 根据凸包缺陷的深度(需要设定一个合适的阈值,比如轮廓周长的几分之一)来筛选凹点。
4. 将轮廓上的点按照凹点进行分割:将凹点作为分割点,将轮廓拆分成多个子轮廓。
5. 然后,每个子轮廓应该对应一个大米颗粒(注意,非粘连的颗粒可能没有凹点,所以整个轮廓就是一个颗粒)。
如何拆分轮廓?我们可以这样做:
- 首先,轮廓是一个点集,我们按照顺序遍历轮廓点。
- 当我们遇到一个分割点(凹点)时,就将当前收集的点作为一个子轮廓,然后从下一个点开始重新收集,直到遇到下一个分割点。
- 但是,凹点可能不是按顺序的,因此我们需要先对凹点在轮廓中的位置(索引)进行排序。
具体步骤:
a. 获取轮廓的所有点,并记录每个点在轮廓中的索引。
b. 计算凸包缺陷,获取每个缺陷的凹陷点(即凹点)在轮廓中的索引(注意:凸包缺陷返回的凹陷点是轮廓上的点,我们可以通过比较轮廓点的坐标来找到其在轮廓中的索引)。
c. 将所有的凹点索引收集起来,并排序。
d. 然后,将轮廓点按照这些凹点索引进行分割,得到多个子轮廓。
e. 对每个子轮廓计算外接矩形和重心。
但是,这种方法需要确保凹点确实在轮廓上,并且分割后的子轮廓是闭合的吗?注意,我们分割的是同一个轮廓,所以分割后的每个子轮廓应该是原轮廓的一部分,但不闭合。我们需要将分割点作为起点和终点,然后分别形成闭合轮廓?实际上,每个大米颗粒的轮廓应该是闭合的,但分割后可能不闭合,所以我们需要重新构建闭合轮廓。
另一种做法:在凹点处将轮廓断开(在二值图像上),然后重新查找轮廓。具体做法是:在二值图像上,将每个凹点周围的像素设为0(背景),从而断开粘连。然后重新查找轮廓,此时每个颗粒都是独立的连通域。
我们选择这种方法:
1. 在二值图像上,对于每个凹点,我们以该点为中心画一个小圆(半径根据实际情况,比如3-5个像素),颜色为0(背景),从而断开粘连。
2. 然后重新查找轮廓,此时每个大米颗粒就是独立的轮廓。
3. 对每个轮廓计算外接矩形和重心。
代码步骤:
Step 1: 读取图像,转为灰度
Step 2: 高斯模糊
Step 3: OTSU二值化(注意:二值化后,前景为255,背景为0)
Step 4: 形态学操作(闭运算填充孔洞,开运算去除噪声)
Step 5: 查找轮廓(使用RETR_EXTERNAL,只检测外轮廓)
Step 6: 创建一个和二值图同样大小的掩膜(全0),用于绘制轮廓(我们将所有轮廓绘制在这个掩膜上,白色)
Step 7: 对每个轮廓:
a. 计算凸包和凸包缺陷
b. 遍历每个凸包缺陷,如果深度大于某个阈值(比如轮廓周长的1/20,但需要调整),则记录该凹点
c. 在掩膜图像上,在该凹点位置绘制一个小黑色圆(半径为3-5,具体根据图像大小调整)
Step 8: 将掩膜图像作为新的二值图像(此时粘连处已经被黑色圆断开)
Step 9: 在掩膜图像上重新查找轮廓(此时轮廓已经分开)
Step 10: 对每个轮廓计算外接矩形和重心,并在原图上绘制
注意:在Step7中,我们是在一个单独的掩膜图像上操作,这个掩膜图像初始是绘制了所有轮廓(白色),然后我们在凹点处画黑点。这样,粘连的部分就被断开了。
这种方法简单易行。
下面我们编写代码:
注意:凸包缺陷函数cv2.convexityDefects返回的缺陷点集,每个缺陷是一个数组,包含[起点索引,终点索引,凹点索引,深度(距离)]
深度是定点到凸包的最短距离(以8-bit整数表示,实际距离需要除以256?),但实际返回的是浮点数?需要查看OpenCV文档。
在OpenCV中,凸包缺陷返回的深度是定点到凸包边的最远距离,是实际距离(浮点数),但我们需要根据图像大小设定阈值。
我们设定深度阈值为轮廓周长的0.02倍(经验值,可根据实际情况调整)。
代码示例:
由于没有具体的图像,我们按照一般步骤编写,可能需要根据实际图像调整参数。
注意:在绘制凹点处的黑圆时,我们是在一个初始为全0的掩膜上先绘制白色轮廓,然后在凹点处绘制黑圆。但是,我们也可以直接在原二值图像上操作(复制一份),然后在凹点处绘制黑圆。这样更简单。
因此,我们修改:
1. 复制二值图像(我们称为binary_image_copy)。
2. 对每个轮廓,计算凸包缺陷,对于深度大于阈值的凹点,在binary_image_copy上该点坐标处绘制一个黑色实心圆(直径5-10像素,根据图像中大米颗粒大小调整)。
3. 然后在binary_image_copy上重新查找轮廓。
这样,粘连的区域就被黑色圆点断开,形成独立的连通域。
最后,对每个轮廓计算外接矩形和重心。
代码结构:
import cv2
import numpy as np
# 1. 读取图像
image = cv2.imread('rice.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 2. 高斯模糊
blurred = cv2.GaussianBlur(gray, (5,5), 0)
# 3. OTSU二值化
_, binary = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
# 4. 形态学操作:先闭运算填充内部孔洞,再开运算去除小噪声
kernel = np.ones((3,3), np.uint8)
closing = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel, iterations=2)
opening = cv2.morphologyEx(closing, cv2.MORPH_OPEN, kernel, iterations=2)
# 5. 复制一份处理后的二值图像,用于后续断开粘连
binary_with_marks = opening.copy()
# 6. 查找轮廓
contours, hierarchy = cv2.findContours(opening, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 7. 遍历每个轮廓,计算凸包和凸包缺陷
for contour in contours:
# 跳过小轮廓(根据面积过滤)
if cv2.contourArea(contour) < 100:
continue
# 计算凸包
hull = cv2.convexHull(contour, returnPoints=False)
# 计算凸包缺陷
defects = cv2.convexityDefects(contour, hull)
if defects is not None:
for i in range(defects.shape[0]):
s, e, f, d = defects[i,0]
# d是距离,需要除以256得到实际距离(根据文档),但实际测试发现直接使用d即可
# 计算轮廓周长
perimeter = cv2.arcLength(contour, True)
# 设定深度阈值,这里取周长的0.02倍
if d > perimeter * 0.02:
# 获取凹点坐标
concave_point = tuple(contour[f][0])
# 在二值图像上绘制黑色圆点(直径5)
cv2.circle(binary_with_marks, concave_point, 3, (0,0,0), -1)
# 8. 在断开粘连的二值图像上重新查找轮廓
contours, hierarchy = cv2.findContours(binary_with_marks, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 9. 遍历轮廓,计算外接矩形和重心
for contour in contours:
# 跳过小轮廓
if cv2.contourArea(contour) < 100:
continue
# 外接矩形
x, y, w, h = cv2.boundingRect(contour)
cv2.rectangle(image, (x, y), (x+w, y+h), (0,255,0), 2)
# 重心
M = cv2.moments(contour)
if M['m00'] != 0:
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
cv2.circle(image, (cx, cy), 3, (0,0,255), -1)
# 10. 显示结果
cv2.imshow('result', image)
cv2.waitKey(0)
cv2.destroyAllWindows()
注意:二值化时使用了THRESH_BINARY_INV,因为OTSU通常将背景设为0,前景设为255,但有时可能需要反转。具体取决于输入图像。如果大米是亮的,背景是暗的,则用THRESH_BINARY_INV可能不需要?这里我们假设输入图像是大米是暗的,背景是亮的?或者反过来。需要根据实际情况调整。
另外,凸包缺陷的深度d的阈值需要根据实际情况调整。如果断开不够,可以减小阈值;如果断开过多,则增大阈值。
以上代码实现了基于OTSU和凸包检测的粘连大米分割。
注意:由于没有实际图像,可能需要调整参数。
如果图像中大米颗粒之间粘连较少,凸包缺陷可能找不到足够的凹点,那么就不会断开,所以每个颗粒还是独立的。因此,这种方法在粘连较多时有效,非粘连时也不影响。
但是,凸包缺陷的深度阈值(0.02倍周长)可能需要根据实际情况调整。
最后,我们按照引用[1]的描述,绘制外接矩形和重心点。
由于引用[1]中提到“消除粘连后画出所有轮廓外接矩,及中心点”,所以我们的结果应该与引用[1]的图类似。
因此,我们提供上述方法作为解决方案。
注意:如果粘连非常严重,凸包缺陷可能无法检测到所有的凹点,此时可能需要其他方法。
相关问题:
1. 如何调整凸包缺陷的深度阈值?
2. 除了凸包缺陷,还有哪些方法可以分割粘连物体?
3. 如何评估大米分割的准确性?
下面我们生成相关问题。
</think>
### Python+OpenCV基于OTSU和凸包检测的粘连大米分割方法
#### 核心步骤
1. **预处理**
- 灰度化:将RGB图像转为灰度图
- 高斯模糊:减少噪声干扰
- OTSU阈值分割:自动确定最佳阈值进行二值化
```python
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
_, binary = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
```
2. **形态学操作**
- 闭运算填充内部孔洞
- 开运算去除小噪声
```python
kernel = np.ones((3, 3), np.uint8)
closed = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel, iterations=2)
opened = cv2.morphologyEx(closed, cv2.MORPH_OPEN, kernel, iterations=1)
```
3. **轮廓检测与凸包分析**
- 查找所有外轮廓
- 计算凸包和凸包缺陷
- 利用深度缺陷点分离粘连区域
```python
contours, _ = cv2.findContours(opened, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for contour in contours:
hull = cv2.convexHull(contour, returnPoints=False)
defects = cv2.convexityDefects(contour, hull)
if defects is not None:
for i in range(defects.shape[0]):
s, e, f, d = defects[i, 0]
if d > 1000: # 深度阈值根据实际调整
# 在缺陷点位置绘制黑色圆点断开粘连
cv2.circle(opened, tuple(contour[f][0]), 5, (0, 0, 0), -1)
```
4. **重新检测独立轮廓**
```python
separated_contours, _ = cv2.findContours(opened, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
```
5. **绘制外接矩形与重心**
```python
for cnt in separated_contours:
# 外接矩形
x, y, w, h = cv2.boundingRect(cnt)
cv2.rectangle(image, (x, y), (x+w, y+h), (0, 255, 0), 2)
# 重心计算
M = cv2.moments(cnt)
cx = int(M["m10"] / M["m00"])
cy = int(M["m01"] / M["m00"])
cv2.circle(image, (cx, cy), 3, (0, 0, 255), -1)
```
#### 关键参数说明
1. **深度阈值(d值)**:决定是否分离粘连的临界值
- 过大:无法分离紧密粘连
- 过小:过度分割单个米粒
- 经验公式:`d_threshold = contour_area / 50` (需实际调试)
2. **形态学核尺寸**:影响孔洞填充效果
- 过大:导致米粒变形
- 过小:无法填充间隙
#### 完整代码示例
```python
import cv2
import numpy as np
def segment_rice(image_path):
# 1. 图像读取与预处理
image = cv2.imread(image_path)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
# 2. OTSU二值化
_, binary = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
# 3. 形态学操作
kernel = np.ones((3, 3), np.uint8)
closed = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel, iterations=2)
opened = cv2.morphologyEx(closed, cv2.MORPH_OPEN, kernel, iterations=1)
# 4. 凸包缺陷分析
contours, _ = cv2.findContours(opened, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
mask = opened.copy()
for contour in contours:
if cv2.contourArea(contour) < 100:
continue
hull = cv2.convexHull(contour, returnPoints=False)
defects = cv2.convexityDefects(contour, hull)
if defects is not None:
for i in range(defects.shape[0]):
s, e, f, d = defects[i, 0]
if d > 1000: # 调整此阈值
cv2.circle(mask, tuple(contour[f][0]), 5, (0, 0, 0), -1)
# 5. 分割后处理
final_contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 6. 绘制结果
for cnt in final_contours:
x, y, w, h = cv2.boundingRect(cnt)
cv2.rectangle(image, (x, y), (x+w, y+h), (0, 255, 0), 2)
M = cv2.moments(cnt)
if M["m00"] != 0:
cx = int(M["m10"] / M["m00"])
cy = int(M["m01"] / M["m00"])
cv2.circle(image, (cx, cy), 3, (0, 0, 255), -1)
return image
# 使用示例
result = segment_rice("rice_image.jpg")
cv2.imshow("Segmented Rice", result)
cv2.waitKey(0)
```
#### 处理效果说明
最终输出图像将显示:
1. 绿色矩形框:每个米粒的外接矩形
2. 红色圆点:米粒的重心位置
3. 粘连区域通过凸包缺陷点分割为独立个体[^1]
#### 典型应用场景
1. 粮食颗粒计数与分析
2. 生物细胞分割
3. 工业零件检测
4. 材料粒度分析
---