You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Otsu阈值分割料堆图像误检测黑点问题及角度求解咨询

问题描述

我尝试通过Otsu阈值分割图像来求解料堆(左右两侧)的角度,原始图像如下:
Original photo

对图像分割后,得到的结果如下:
Segmented photo

视觉上分割后的图像白色背景里没有黑色像素,但用morphology.opening处理后,代码还是检测到了一些黑色像素,结果如下:
检测到异常黑色像素的结果

用其他图像测试时没有这个问题:
正常测试图像结果

请问该如何解决这个问题?有什么思路吗?(下一步要求解料堆左右两侧的角度)

附上代码:

from skimage import io, filters, morphology, measure
import numpy as np
import cv2 as cv
from scipy import ndimage
import math
# Load the image
image = io.imread('mountain3.jpg', as_gray=True)

# Apply Otsu's thresholding to segment the image
segmented_image = image > filters.threshold_otsu(image)

# Perform morphological closing to fill small gaps
structuring_element = morphology.square(1)
closed_image = morphology.closing(segmented_image, structuring_element)

# Apply morphological opening to remove small black regions in the white background
structuring_element = morphology.disk(10)  # Adjust the disk size as needed
opened_image = morphology.opening(closed_image, structuring_element)

# Fill larger gaps using binary_fill_holes
#filled_image = measure.label(opened_image)
#filled_image = filled_image > 0

# Display the segmented image after filling the gaps
io.imshow(opened_image)

io.show()
# Find the first row containing black pixels
first_black_row = None
for row in range(opened_image.shape[0]):
    if np.any(opened_image[row, :] == False):
        first_black_row = row
        break

if first_black_row is not None:
    edge_points = []  # List to store the edge points

    # Iterate over the rows below the first black row
    for row in range(first_black_row, opened_image.shape[0]):
        black_pixel_indices = np.where(opened_image[row, :] == False)[0]

        if len(black_pixel_indices) > 0:
            # Store the first black pixel coordinates on the left and right sides
            left_x = black_pixel_indices[0]
            right_x = black_pixel_indices[-1]
            y = row

            # Append the edge point coordinates
            edge_points.append((left_x, y))
            edge_points.append((right_x, y))

    if len(edge_points) > 0:
        # Plotting the edge points
        import matplotlib.pyplot as plt

        edge_points = np.array(edge_points)

        plt.figure()
        plt.imshow(opened_image, cmap='gray')
        plt.scatter(edge_points[:, 0], edge_points[:, 1], color='red', s=1)
        plt.title('Edge Points')
        plt.show()
    else:
        print("No edge points found.")
else:
    print("No black pixels found in the image.")

解决方案思路
  1. 问题根源:Otsu阈值分割后,背景中存在视觉难以察觉的微小噪点(孤立黑色像素),这些噪点在形态学开运算后仍未被完全清除,导致代码误检测为有效像素。
  2. 优化方向:
    • 先对原始灰度图做高斯模糊,抑制高频噪点,提升Otsu分割的准确性。
    • 调整形态学操作的结构元素尺寸,确保能彻底清除背景噪点。
    • 保留图像中面积最大的连通区域(即料堆主体),直接排除背景中的微小噪点区域。
    • 填充料堆区域的内部孔洞,避免边缘检测时出现断点。
修改后的代码
from skimage import io, filters, morphology, measure
import numpy as np
import matplotlib.pyplot as plt
import math

# 加载图像并预处理:高斯模糊抑制噪点
image = io.imread('mountain3.jpg', as_gray=True)
blurred_image = filters.gaussian(image, sigma=1.5)  # sigma值可根据图像噪点程度调整

# Otsu阈值分割
threshold = filters.threshold_otsu(blurred_image)
segmented_image = blurred_image > threshold

# 形态学闭运算填充料堆内部小缝隙
structuring_element_close = morphology.disk(5)
closed_image = morphology.closing(segmented_image, structuring_element_close)

# 保留最大连通区域(料堆),排除背景噪点
labeled_image = measure.label(closed_image)
region_props = measure.regionprops(labeled_image)
if region_props:
    largest_region = max(region_props, key=lambda x: x.area)
    filled_image = labeled_image == largest_region.label
else:
    filled_image = closed_image

# 形态学开运算清除背景残留微小噪点
structuring_element_open = morphology.disk(10)
opened_image = morphology.opening(filled_image, structuring_element_open)

# 显示处理后的图像
io.imshow(opened_image)
io.show()

# 检测边缘点
first_black_row = None
for row in range(opened_image.shape[0]):
    if np.any(opened_image[row, :] == False):
        first_black_row = row
        break

if first_black_row is not None:
    edge_points = []
    for row in range(first_black_row, opened_image.shape[0]):
        black_pixel_indices = np.where(opened_image[row, :] == False)[0]
        if len(black_pixel_indices) > 0:
            left_x = black_pixel_indices[0]
            right_x = black_pixel_indices[-1]
            edge_points.append((left_x, row))
            edge_points.append((right_x, row))

    if edge_points:
        edge_points = np.array(edge_points)
        plt.figure()
        plt.imshow(opened_image, cmap='gray')
        plt.scatter(edge_points[:, 0], edge_points[:, 1], color='red', s=1)
        plt.title('Edge Points')
        plt.show()

        # 拟合料堆左右侧直线并计算角度
        # 分离左右边缘点
        left_points = edge_points[::2]
        right_points = edge_points[1::2]

        # 线性拟合左侧边缘
        if len(left_points) > 1:
            left_coeffs = np.polyfit(left_points[:, 1], left_points[:, 0], 1)
            left_slope = left_coeffs[0]
            left_angle = math.degrees(math.atan(left_slope))
            print(f"左侧料堆角度: {left_angle:.2f}°")

        # 线性拟合右侧边缘
        if len(right_points) > 1:
            right_coeffs = np.polyfit(right_points[:, 1], right_points[:, 0], 1)
            right_slope = right_coeffs[0]
            right_angle = math.degrees(math.atan(right_slope))
            print(f"右侧料堆角度: {right_angle:.2f}°")
    else:
        print("未检测到边缘点")
else:
    print("图像中未检测到黑色像素")
关键优化点说明
  • 高斯模糊:提前过滤图像中的微小噪点,让Otsu阈值分割更精准,减少背景误分割的情况。
  • 最大连通区域保留:通过measure.label标记所有连通区域,只保留面积最大的区域(料堆),直接排除背景中的噪点区域。
  • 形态学操作调整:改用更大尺寸的圆盘结构元素做闭运算,填充料堆内部缝隙,确保区域完整性。
  • 角度拟合:在代码末尾加入了线性拟合计算料堆两侧角度的逻辑,直接完成后续需求。

内容的提问来源于stack exchange,提问作者Tan Phan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 10:02:14