如何修复Python代码实现图像中重叠样本的准确计数与长度测量?
玻璃纤维重叠样本计数与长度测量修复方案
问题描述
需要对图像中的玻璃纤维样本进行计数并测量每个样本的长度,但当前基于OpenCV的Python代码在样本重叠时无法准确计数——例如实际应为2个样本,识别结果却为4个。
原代码
import cv2 import numpy as np import matplotlib.pyplot as plt # === Load and preprocess image === image = cv2.imread('GF sample/4.jpg') # ⬅️ Load input image gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # Convert to grayscale blur = cv2.GaussianBlur(gray, (11, 11), 0) # Apply Gaussian blur to reduce noise canny = cv2.Canny(blur, 10, 150, 3) # Edge detection using Canny dilated = cv2.dilate(canny, (1, 1), iterations=0) # Slightly enhance edges # === Image dimensions === height, width = gray.shape # === Find external contours === cnts, hierarchy = cv2.findContours( dilated.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) # === Calibration (based on your scale) MICRONS_PER_PIXEL = 1.585 # ⬅️ Adjust based on scale bar (1000 µm = 631 px) # === Filtering thresholds MIN_AREA = 1 # Eliminate tiny specks MIN_WIDTH_HEIGHT = 15 # Filter very small bounding boxes MARGIN = 5 # Avoid counting fibers near the image border # === Output image and result storage rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) font = cv2.FONT_HERSHEY_SIMPLEX filtered_cnt = [] fiber_lengths_um = [] # === Loop through contours and apply filters for c in cnts: area = cv2.contourArea(c) x, y, w, h = cv2.boundingRect(c) if (area > MIN_AREA and w > MIN_WIDTH_HEIGHT and h > MIN_WIDTH_HEIGHT and x > MARGIN and y > MARGIN and (x + w) < (width - MARGIN) and (y + h) < (height - MARGIN)): # Keep only filtered contours filtered_cnt.append(c) # Estimate length using minAreaRect rect = cv2.minAreaRect(c) (w_rect, h_rect) = rect[1] length_px = max(w_rect, h_rect) real_length_um = length_px * MICRONS_PER_PIXEL fiber_lengths_um.append(real_length_um) # === Label and draw only the accepted contours === font = cv2.FONT_HERSHEY_SIMPLEX font_scale = 0.5 thickness = 1 color = (255, 0, 0) # red for i, c in enumerate(filtered_cnt, 1): M = cv2.moments(c) if M["m00"] != 0: cx = int(M["m10"] / M["m00"]) cy = int(M["m01"] / M["m00"]) # Draw contour cv2.drawContours(rgb, [c], -1, (0, 255, 0), 1) # Get label text size label = str(i) (text_width, text_height), baseline = cv2.getTextSize(label, font, font_scale, thickness) # Shift to center text_x = int(cx - text_width / 2) text_y = int(cy + text_height / 2) # Draw label centered cv2.putText(rgb, label, (text_x, text_y), font, font_scale, color, thickness) # === Show results === plt.figure(figsize=(14, 10)) plt.imshow(rgb) plt.title(f" Counted Glass Fibers: {len(filtered_cnt)}") plt.axis('off') plt.show() # === Print lengths of fibers === print("\n📏 List of Detected Fibers (µm):") for idx, length in enumerate(fiber_lengths_um, 1): print(f"{idx}. {length:.2f} µm") # === Summary statistics === print(f"\n📊 Total detected: {len(filtered_cnt)} fibers") print(f"📉 Min: {min(fiber_lengths_um):.2f} µm") print(f"📈 Max: {max(fiber_lengths_um):.2f} µm") print(f"📌 Average: {np.mean(fiber_lengths_um):.2f} µm")
原图

原识别结果

问题原因
原代码依赖Canny边缘检测+外接轮廓提取,重叠样本的边缘会被分割成多个独立轮廓,导致计数错误。针对细长重叠物体,分水岭算法是更合适的解决方案,它能基于距离变换区分重叠目标的核心区域,实现精准分割。
修复后代码
import cv2 import numpy as np import matplotlib.pyplot as plt # === 加载与预处理图像 === image = cv2.imread('GF sample/4.jpg') gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 高斯模糊降噪+自适应阈值处理(适配不均匀光照) blur = cv2.GaussianBlur(gray, (5, 5), 0) thresh = cv2.adaptiveThreshold(blur, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2) # === 形态学操作去除噪点并增强目标 === kernel = np.ones((3, 3), np.uint8) opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2) # 膨胀得到确定的背景区域 sure_bg = cv2.dilate(opening, kernel, iterations=3) # === 距离变换识别确定的前景(种子区域) === dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5) ret, sure_fg = cv2.threshold(dist_transform, 0.2 * dist_transform.max(), 255, 0) sure_fg = np.uint8(sure_fg) # === 提取背景与前景间的未知过渡区域 === unknown = cv2.subtract(sure_bg, sure_fg) # === 标记种子区域 === ret, markers = cv2.connectedComponents(sure_fg) # 所有标记加1,确保背景标记为1而非0 markers = markers + 1 # 未知区域标记为0 markers[unknown == 255] = 0 # === 应用分水岭算法分割重叠目标 === markers = cv2.watershed(image, markers) image[markers == -1] = [255, 0, 0] # 分割线标记为红色 # === 校准参数 === MICRONS_PER_PIXEL = 1.585 # 1000 µm = 631 px # === 过滤阈值 === MIN_AREA = 500 # 调整为适配玻璃纤维的最小面积 MIN_LENGTH = 50 # 最小长度(像素) MARGIN = 5 # === 提取分割后的轮廓并过滤无效目标 === rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) filtered_cnt = [] fiber_lengths_um = [] # 遍历每个标记区域(从2开始,1为背景) for marker in range(2, ret + 1): # 创建掩码提取单个目标 mask = np.zeros(gray.shape, np.uint8) mask[markers == marker] = 255 # 提取目标轮廓 cnts, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if cnts: c = cnts[0] area = cv2.contourArea(c) x, y, w, h = cv2.boundingRect(c) # 过滤条件:面积达标、远离图像边界、长度达标 if (area > MIN_AREA and x > MARGIN and y > MARGIN and (x + w) < (gray.shape[1] - MARGIN) and (y + h) < (gray.shape[0] - MARGIN)): # 计算纤维长度 rect = cv2.minAreaRect(c) length_px = max(rect[1]) if length_px > MIN_LENGTH: filtered_cnt.append(c) real_length_um = length_px * MICRONS_PER_PIXEL fiber_lengths_um.append(real_length_um) # === 绘制轮廓与标记 === font = cv2.FONT_HERSHEY_SIMPLEX font_scale = 0.5 thickness = 1 color = (255, 0, 0) for i, c in enumerate(filtered_cnt, 1): M = cv2.moments(c) if M["m00"] != 0: cx = int(M["m10"] / M["m00"]) cy = int(M["m01"] / M["m00"]) cv2.drawContours(rgb, [c], -1, (0, 255, 0), 1) label = str(i) (text_width, text_height), baseline = cv2.getTextSize(label, font, font_scale, thickness) text_x = int(cx - text_width / 2) text_y = int(cy + text_height / 2) cv2.putText(rgb, label, (text_x, text_y), font, font_scale, color, thickness) # === 展示结果 === plt.figure(figsize=(14, 10)) plt.imshow(rgb) plt.title(f"计数玻璃纤维: {len(filtered_cnt)}") plt.axis('off') plt.show() # === 输出长度统计 === print("\n📏 检测到的纤维长度(µm):") for idx, length in enumerate(fiber_lengths_um, 1): print(f"{idx}. {length:.2f} µm") print(f"\n📊 总计检测: {len(filtered_cnt)} 根纤维") print(f"📉 最小值: {min(fiber_lengths_um):.2f} µm") print(f"📈 最大值: {max(fiber_lengths_um):.2f} µm") print(f"📌 平均值: {np.mean(fiber_lengths_um):.2f} µm")
关键改进说明
- 替换边缘检测为自适应阈值分割:自适应阈值能更好处理图像光照不均问题,完整保留纤维的连通区域。
- 使用分水岭算法分割重叠目标:通过距离变换识别每个纤维的核心区域(种子),再基于种子分割重叠部分,避免将单个纤维拆分成多个轮廓。
- 优化过滤条件:提高最小面积阈值过滤噪点,增加最小长度限制确保只保留有效纤维。
- 更精准的长度计算:基于分割后的完整轮廓计算最小外接矩形长度,结果更准确。
内容的提问来源于stack exchange,提问作者user31726648
相关产品推荐
相关产品推荐

