Python中非结构化形状识别:岩石目标计数、像素面积统计及粘连块分割优化求助
Python中非结构化形状识别:岩石目标计数、像素面积统计及粘连块分割优化求助
看起来你现在卡在岩石检测的粘连分割上了——你的现有代码已经尝试了自适应阈值+固定阈值的组合,还做了开运算去噪,但还是没法把连在一起的岩石准确分开对吧?针对你的场景(浅色背景下的深色不规则岩石),我给你几个针对性的优化方案,结合代码调整应该能解决问题:
核心优化方向:用分水岭算法处理粘连目标
分水岭算法是处理这类紧密粘连物体分割的经典方法,它能基于距离变换找到每个目标的“种子点”,然后精准分割开粘连的区域,非常适合你的岩石检测场景。
代码优化实现
我在你的原有代码基础上做了修改,加入了距离变换、种子点标记和分水岭分割的步骤,同时优化了形态学操作和阈值参数:
import cv2 import numpy as np import matplotlib.pyplot as plt image_path = "medium.png" original_image = cv2.imread(image_path) gray_image = cv2.cvtColor(original_image, cv2.COLOR_BGR2GRAY) # 1. 优化阈值组合:适配岩石的灰度特征 _, binary_global = cv2.threshold(gray_image, 220, 255, cv2.THRESH_BINARY_INV) binary_adaptive = cv2.adaptiveThreshold(gray_image, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 15, 3) combined_binary = cv2.bitwise_or(binary_global, binary_adaptive) # 2. 形态学操作:先补空洞再去噪,保证前景完整性 kernel_close = np.ones((3,3), np.uint8) closed = cv2.morphologyEx(combined_binary, cv2.MORPH_CLOSE, kernel_close, iterations=2) kernel_open = np.ones((2,2), np.uint8) cleaned_binary = cv2.morphologyEx(closed, cv2.MORPH_OPEN, kernel_open, iterations=1) # 3. 距离变换+分水岭:分割粘连核心步骤 # 距离变换:计算每个前景像素到最近背景的距离,找到岩石中心区域 dist_transform = cv2.distanceTransform(cleaned_binary, cv2.DIST_L2, 5) # 确定前景种子点:取距离变换峰值区域作为每个岩石的核心 _, sure_fg = cv2.threshold(dist_transform, 0.3*dist_transform.max(), 255, 0) sure_fg = np.uint8(sure_fg) # 确定背景区域:膨胀操作扩大背景范围 sure_bg = cv2.dilate(cleaned_binary, np.ones((3,3), np.uint8), iterations=3) # 找到前景/背景的过渡区域 unknown = cv2.subtract(sure_bg, sure_fg) # 标记种子点:给每个前景核心分配唯一ID _, markers = cv2.connectedComponents(sure_fg) # 调整标记值:背景从0改为1,过渡区域保持0 markers = markers + 1 markers[unknown==255] = 0 # 应用分水岭分割,分割线用红色标记 markers = cv2.watershed(original_image, markers) original_image[markers == -1] = [255,0,0] # 4. 提取分割后的轮廓并统计数据 contours, _ = cv2.findContours(sure_fg, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) output_image = np.ones_like(original_image) * 255 np.random.seed(42) hues = np.linspace(0, 179, len(contours), dtype=np.uint8) np.random.shuffle(hues) colors = [] for hue in hues: hsv = np.array([[[hue, 255, 255]]], dtype=np.uint8) rgb = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)[0][0] colors.append(rgb.tolist()) min_area_threshold = 15 # 可根据岩石实际大小调整 valid_rocks = 0 rock_pixel_areas = [] for i, contour in enumerate(contours): area = cv2.contourArea(contour) if area >= min_area_threshold: valid_rocks += 1 rock_pixel_areas.append(area) # 绘制分割后的岩石区域 cv2.drawContours(output_image, [contour], -1, colors[i % len(colors)], -1) cv2.drawContours(output_image, [contour], -1, (0,0,0), 1) # 可视化结果 plt.figure(figsize=(16, 10)) plt.subplot(131) plt.imshow(cv2.cvtColor(original_image, cv2.COLOR_BGR2RGB)) plt.title('Watershed Segmentation (Red Lines = Boundaries)') plt.axis('off') plt.subplot(132) plt.imshow(cleaned_binary, cmap='gray') plt.title('Preprocessed Binary Image') plt.axis('off') plt.subplot(133) plt.imshow(cv2.cvtColor(output_image, cv2.COLOR_BGR2RGB)) plt.title(f'Total Rocks Detected: {valid_rocks}') plt.axis('off') plt.tight_layout() plt.show() # 输出统计结果 print(f"Total number of rocks detected: {valid_rocks}") print(f"Pixel area of each rock: {[round(area, 2) for area in rock_pixel_areas]}") print(f"Average rock pixel area: {np.mean(rock_pixel_areas):.2f}") # 保存结果 cv2.imwrite('watershed_result.png', original_image) cv2.imwrite('segmented_rocks.png', output_image)
关键步骤解释
- 阈值优化:调整固定阈值到220、自适应阈值块大小到15,让岩石区域更完整地被提取,减少遗漏
- 形态学操作顺序调整:先闭运算填补岩石内部小空洞,再开运算去掉背景噪点,保证前景区域的完整性
- 距离变换+分水岭:
- 距离变换定位每个岩石的中心核心区域(种子点)
- 通过膨胀和减法确定确定的前景、背景和过渡区域
- 基于种子点用分水岭分割粘连区域,红色线条是分割边界
- 数据统计:不仅统计岩石数量,还通过
cv2.contourArea直接获取每个岩石的像素面积,满足你的统计需求
额外调参建议
- 如果出现过度分割/分割不足,可以调整距离变换的阈值(
0.3*dist_transform.max()),比如改成0.2或0.4 - 若背景仍有噪点,可增大开运算的kernel尺寸(如(3,3))或增加迭代次数
- 可加入形状过滤:计算轮廓的圆度(
4*np.pi*area / (perimeter**2)),过滤掉形状过于不规则的非岩石区域
备注:内容来源于stack exchange,提问作者Razark
相关产品推荐
相关产品推荐

