问题:
为完成玉米颗粒分类任务,现需要处理训练图片,将以下图片中的玉米颗粒进行分割:
目标:
操作步骤(完整代码附在最后,该部分为解释说明)
一、提取通道并进行二值化
# 提取蓝色通道
blue_channel = image[:, :, 0]
# 二值化图像
threshold = 150 # 阈值
_, binary_image = cv2.threshold(blue_channel, threshold, 255, cv2.THRESH_BINARY)
结果:
说明:
1、cv2.threshold()
retval, threshold_image = cv2.threshold(src, thresh, maxval, type[, dst])参数:
src: 输入的单通道图像。这里是提取的蓝色通道图像。
thresh: 阈值,将像素值分为大于等于阈值和小于阈值的两部分。
maxval: 分类后大于等于阈值的像素值,通常设置为最大值255。
type: 阈值化的类型,包括以下几种:
cv2.THRESH_BINARY: 如果像素值大于等于阈值,设置为 maxval,否则设置为0。
cv2.THRESH_BINARY_INV: 如果像素值大于等于阈值,设置为0,否则设置为 maxval。
cv2.THRESH_TRUNC: 如果像素值大于等于阈值,保持原始值,否则设置为阈值。
cv2.THRESH_TOZERO: 如果像素值大于等于阈值,保持原始值,否则设置为0。
cv2.THRESH_TOZERO_INV: 如果像素值大于等于阈值,设置为0,否则保持原始值。
dst(可选):输出图像,与输入图像大小和类型相同,通常不需要指定。返回值:
retval:选择的阈值,通常与输入阈值 thresh 相同,但如果使用自适应阈值化时,可能不同。
threshold_image:阈值化后的图像,是一个与输入图像大小和类型相同的单通道灰度图像。
2、为什么选择提取蓝色通道?
黄色是由红色和绿色混合而成的颜色,因此在RGB颜色模型中,黄色物体在红色通道和绿色通道上都具有较高的强度,而在蓝色通道上的强度较低。提取蓝色通道后,黄色物体通常会变得比背景更暗,从而更容易与背景区分开来,有助于突出玉米颗粒并进行后续的图像处理和分割。
那么怎么判断出这个物体的颜色和哪个通道强度有关呢:可以分别将图像分离成不同的颜色通道(红色、绿色和蓝色),然后可以查看每个通道的图像。
blue_channel = image[:, :, 0] # 蓝色通道
green_channel = image[:, :, 1] # 绿色通道
red_channel = image[:, :, 2] # 红色通道
3、二值化阈值的选择
由于在这个任务中,训练集图像具有一致的背景和物体颜色,因此可以简单地手动选择一个固定的阈值。但如果图片包含了更多场景,可以选择其他的阈值设置方式,如自适应阈值等。
二、反转图像像素值
inverted_image = cv2.bitwise_not(binary_image)
结果:
说明:
1、cv2.bitwise_not()
result = cv2.bitwise_not(src[, dst[, mask]])参数:
src:输入图像,可以是单通道或多通道的图像。
dst:输出图像,与输入图像具有相同的大小和通道数。如果未提供,函数会创建一个新的图像来存储结果。
mask:可选参数,用于指定一个掩模,只有掩模中值为非零的像素才会执行位反转操作。
2、为什么要反转图像像素值?
后续使用到的形态学操作和轮廓查找是基于目标区域是白色的前提下进行的,反转操作是为了提高分割的准确性和可靠性。
三、闭运算
# 定义结构元素
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 9)) # 使用矩形结构元素
# 闭运算
closed_image = cv2.morphologyEx(inverted_image, cv2.MORPH_CLOSE, kernel)
结果:
说明:
1、cv2.getStructuringElement()
element = cv2.getStructuringElement(shape, ksize[, anchor])cv2.getStructuringElement 函数是OpenCV库中用于创建图像处理中的结构元素的函数。
结构元素通常用于形态学操作,如腐蚀、膨胀、开运算、闭运算等。
结构元素可以是不同形状的内核,例如矩形、椭圆、十字形等,
用于定义操作的邻域,例如腐蚀和膨胀操作中的探测窗口。参数:
shape:结构元素的形状,可以是以下之一:
cv2.MORPH_RECT:矩形结构元素
cv2.MORPH_CROSS:十字形结构元素
cv2.MORPH_ELLIPSE:椭圆形结构元素
ksize:结构元素的大小,通常以 (width, height) 或者单个整数值指定。
anchor:可选参数,结构元素的锚点位置,默认为结构元素的中心。
注:结构元素的不同形状和大小可以用于不同的任务和图像处理需求。例如,矩形结构元素通常用于一般形态学操作,椭圆结构元素可以更好地适应曲线或圆形物体的形状,十字形结构元素可用于特定方向的操作,如边缘检测。
2、cv2.morphologyEx()
result = cv2.morphologyEx(src, op, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]])参数:
src:输入图像,通常是二进制图像(黑白图像)。
op:形态学操作的类型,可以是以下之一:
cv2.MORPH_ERODE:腐蚀操作
cv2.MORPH_DILATE:膨胀操作
cv2.MORPH_OPEN:开运算(先腐蚀后膨胀)
cv2.MORPH_CLOSE:闭运算(先膨胀后腐蚀)
其他高级形态学操作如梯度、礼帽、黑帽等。
kernel:形态学操作的内核(结构元素),用于定义操作的邻域。可以使用 cv2.getStructuringElement 函数创建内核。
dst:可选参数,输出图像,用于存储结果。如果不提供,函数会创建一个新的图像。
anchor:可选参数,内核的锚点位置,默认为内核的中心。
iterations:可选参数,操作的迭代次数,默认为1。
borderType:可选参数,边界处理方式,默认为 cv2.BORDER_CONSTANT。
borderValue:可选参数,当边界处理方式为 cv2.BORDER_CONSTANT 时使用的边界值,默认为0。
cv2.morphologyEx()函数根据指定的操作类型 op 和内核 kernel,对输入图像进行操作。
1、腐蚀:缩小白色区域,使物体变小。它将内核滑动到图像上,只有当内核下的所有像素都是白色时,中心像素才保持白色,否则变成黑色。
2、膨胀:扩展白色区域,使物体变大。它将内核滑动到图像上,将内核下的任何白色像素(像素值为255)扩展到中心像素位置,从而使前景区域变大。
3、开运算:先腐蚀后膨胀的组合操作,用于去除小白色噪点或分离物体,平滑物体的边界。
4、闭运算:先膨胀后腐蚀的组合操作,用于关闭物体之间的小孔或连接物体。从结果对比中可以发现小孔面积减小。
四、填充轮廓
# 寻找轮廓
contours, _ = cv2.findContours(closed_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 创建一个空白的二值掩模
mask = np.zeros_like(image[:, :, 0], dtype=np.uint8)
# 填充轮廓
filled_image = cv2.drawContours(mask, contours, -1, (255, 255, 255), thickness=cv2.FILLED)
结果:
说明:
1、cv2.findContours()
contours, hierarchy = cv2.findContours(image, mode, method[, contours[, hierarchy[, offset]]])参数:
image:输入的二值化图像,通常是一个黑白图像(只包含两个像素值,如0和255)。
mode:轮廓检索模式,是一个表示如何检索轮廓的标志。常用的模式包括:
cv2.RETR_EXTERNAL:只检测最外层的轮廓。
cv2.RETR_LIST:检测所有轮廓并将它们存储在列表中,不建立层次关系。
cv2.RETR_CCOMP:检测所有轮廓并将它们存储在两层的层次结构中。
cv2.RETR_TREE:检测所有轮廓并重建一个完整的轮廓层次。
method:轮廓逼近方法,是一个表示如何逼近轮廓的标志。常用的方法包括:
cv2.CHAIN_APPROX_SIMPLE:压缩水平、垂直和对角方向的像素,只保留端点。
cv2.CHAIN_APPROX_TC89_L1:使用 Teh-Chin 近似算法。
cv2.CHAIN_APPROX_TC89_KCOS:使用 Teh-Chin 近似算法的一种改进版本。
contours:返回的轮廓列表,每个轮廓是一个表示对象边界的点的集合。
hierarchy:可选参数,返回的轮廓层次结构信息。在某些情况下,可以用于分析轮廓之间的关系。
offset:可选参数,轮廓点坐标的偏移量。返回值:
contours:包含检测到的轮廓的列表,每个轮廓是一个点的集合。
hierarchy:包含轮廓层次结构信息的列表。
2、二值掩膜
在图像处理中,二值掩膜是一种用于标记图像中某些区域的图像,通常是黑白图像,其中白色像素表示感兴趣的区域,而黑色像素表示不感兴趣的区域。它是一种用于遮罩或选择特定图像区域的工具。
np.zeros_like(image[:, :, 0], dtype=np.uint8)这个操作的目的是创建一个黑色背景的图像:
以输入图像 image 中的一个通道(通常是蓝色通道,即 image[:, :, 0])作为模板,
创建一个与模板图像具有相同形状的全零图像,其中所有像素值都初始化为零。
使用 dtype=np.uint8 指定了像素值的数据类型为无符号8位整数,即像素值的范围在 0 到 255 之间。
这个全零图像用作后续的轮廓填充操作的目标图像,轮廓填充会将感兴趣的区域标记为白色(255),而不感兴趣的区域保持黑色(0)。
3、cv2.drawContours()
cv2.drawContours(image, contours, contourIdx, color, thickness, lineType, hierarchy, maxLevel, offset)参数:
image: 要在其上绘制轮廓的图像。
contours: 要绘制的轮廓,通常是通过 cv2.findContours 函数获得的轮廓列表。
contourIdx: 要绘制的轮廓的索引,如果为负数,将绘制所有轮廓。
color: 绘制轮廓的颜色,通常是一个三元组 (B, G, R),其中 B、G、R 分别表示蓝色、绿色和红色的颜色通道值。可以用 (0, 255, 0) 表示绿色。
thickness: 绘制轮廓线的厚度,如果设置为负数或 cv2.FILLED,则填充轮廓内部。
lineType: 线的类型,通常使用默认值 8。
hierarchy: 轮廓的层次信息,通常不需要手动指定。
maxLevel: 最大层次级别,通常使用默认值 0。
offset: 绘制轮廓时的偏移,通常使用默认值 (0, 0)。
补充:
在以上步骤处理的基础上,我们会遍历得到的轮廓列表,对图片进行剪裁,由于闭运算之后的图片会存在白色噪点,因此它们也会加入到轮廓列表中,因此,我们需要设置面积阈值,筛选掉小面积的轮廓。但可以发现,文章最开头的输出仍包含一张与待测目标物体无关的corn_0.jpg图。
为了解决这个问题,我们需要尽可能地减少白色早点的产生,意味着我们需要做进一步的腐蚀操作,将白色噪点去掉。为了减小腐蚀带来的目标物体的信息丢失,在腐蚀后再进行膨胀操作,扩大目标物体的区域范围。因此我们再次进行开运算。
# 闭运算
closed_image = cv2.morphologyEx(inverted_image, cv2.MORPH_CLOSE, kernel)
#开运算
closed_image2 = cv2.morphologyEx(closed_image, cv2.MORPH_OPEN, kernel)
# 寻找轮廓
contours, _ = cv2.findContours(closed_image2, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
结果:
增加开运算后的分割效果:
完整代码
import os
import timeimport cv2
import numpy as npdef crop_corn():# 确保 "output" 文件夹存在output_folder = "output_p"if not os.path.exists(output_folder):os.makedirs(output_folder)# 加载图像image = cv2.imread("./input_p/image.jpg")# 提取蓝色通道blue_channel = image[:, :, 0]# 二值化图像threshold = 150 # 阈值_, binary_image = cv2.threshold(blue_channel, threshold, 255, cv2.THRESH_BINARY)# 反转图像像素值inverted_image = cv2.bitwise_not(binary_image)# 定义结构元素kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 9)) # 使用矩形结构元素# 闭运算closed_image = cv2.morphologyEx(inverted_image, cv2.MORPH_CLOSE, kernel)#开运算closed_image2 = cv2.morphologyEx(closed_image, cv2.MORPH_OPEN, kernel)# 寻找轮廓contours, _ = cv2.findContours(closed_image2, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)# 创建一个空白的二值掩模mask = np.zeros_like(image[:, :, 0], dtype=np.uint8)# 填充轮廓filled_image = cv2.drawContours(mask, contours, -1, (255, 255, 255), thickness=cv2.FILLED)cv2.imwrite("filled_image.jpg",filled_image)# 寻找填充后的轮廓filled_contours, _ = cv2.findContours(filled_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)# 根据轮廓进行剪裁corn_images = []for contour in filled_contours:area = cv2.contourArea(contour)if area > 1000: # 设置面积阈值,筛选掉小面积的轮廓x, y, w, h = cv2.boundingRect(contour)corn_image = image[y:y+h, x:x+w]corn_images.append(corn_image)# 保存剪裁下来的玉米颗粒for i, corn_image in enumerate(corn_images):# cv2.imwrite(f"corn_{i}.jpg", corn_image) # 保存剪切下来的玉米颗粒图像output_filename = f"corn_{i}.jpg"output_path = os.path.join(output_folder, output_filename)cv2.imwrite(output_path, corn_image)if __name__ == "__main__":crop_corn()