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

如何修复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")

关键改进说明

  1. 替换边缘检测为自适应阈值分割:自适应阈值能更好处理图像光照不均问题,完整保留纤维的连通区域。
  2. 使用分水岭算法分割重叠目标:通过距离变换识别每个纤维的核心区域(种子),再基于种子分割重叠部分,避免将单个纤维拆分成多个轮廓。
  3. 优化过滤条件:提高最小面积阈值过滤噪点,增加最小长度限制确保只保留有效纤维。
  4. 更精准的长度计算:基于分割后的完整轮廓计算最小外接矩形长度,结果更准确。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 04:04:53